Discover books, shows, and movies at your level. Track your progress by filling your Shelf with what you find, and share with other language learners. *No dusting required. shlf.space
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add logger

Signed-off-by: brookjeynes <me@brookjeynes.dev>

authored by

brookjeynes and committed by
Tangled
670d5775 a062f41b

+164 -23
+8 -8
cmd/server/main.go
··· 2 2 3 3 import ( 4 4 "context" 5 - "log" 6 - "log/slog" 7 5 "net/http" 8 6 "os" 9 7 10 8 "shlf.space/internal/config" 9 + "shlf.space/internal/log" 11 10 "shlf.space/internal/server" 12 11 ) 13 12 14 13 func main() { 15 14 ctx := context.Background() 15 + logger := log.FromContext(ctx) 16 16 17 17 config, err := config.LoadConfig(ctx) 18 18 if err != nil { 19 - slog.Error("failed to load config", "err", err) 19 + logger.Error("failed to load config", "err", err) 20 20 } 21 21 22 22 state, err := server.Make(ctx, config) 23 23 defer func() { 24 24 if err := state.Close(); err != nil { 25 - slog.Error("failed to close state", "err", err) 25 + logger.Error("failed to close state", "err", err) 26 26 } 27 27 }() 28 28 if err != nil { 29 - log.Fatalf("failed to start server: %v", err) 29 + logger.Error("failed to start server", "err", err) 30 30 os.Exit(-1) 31 31 } 32 32 33 - slog.Info("Starting server", "addr", config.Core.ListenAddr) 33 + logger.Info("Starting server", "addr", config.Core.ListenAddr) 34 34 35 35 err = http.ListenAndServe(config.Core.ListenAddr, state.Router()) 36 36 if err != nil { 37 - slog.Error("failed to start server", "err", err) 37 + logger.Error("failed to start server", "err", err) 38 38 } 39 39 40 - slog.Info("Shutdown complete.") 40 + logger.Info("Shutdown complete.") 41 41 }
+14
go.mod
··· 15 15 require ( 16 16 github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect 17 17 github.com/andybalholm/brotli v1.1.0 // indirect 18 + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 18 19 github.com/beorn7/perks v1.0.1 // indirect 19 20 github.com/cenkalti/backoff/v4 v4.3.0 // indirect 20 21 github.com/cespare/xxhash/v2 v2.3.0 // indirect 22 + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect 23 + github.com/charmbracelet/lipgloss v1.1.0 // indirect 24 + github.com/charmbracelet/log v1.0.0 // indirect 25 + github.com/charmbracelet/x/ansi v0.8.0 // indirect 26 + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect 27 + github.com/charmbracelet/x/term v0.2.1 // indirect 21 28 github.com/cli/browser v1.3.0 // indirect 22 29 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 23 30 github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect 24 31 github.com/fatih/color v1.16.0 // indirect 25 32 github.com/fsnotify/fsnotify v1.9.0 // indirect 33 + github.com/go-logfmt/logfmt v0.6.1 // indirect 26 34 github.com/golang-jwt/jwt/v5 v5.2.2 // indirect 27 35 github.com/google/go-querystring v1.1.0 // indirect 28 36 github.com/gorilla/securecookie v1.1.2 // indirect 29 37 github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 38 + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 30 39 github.com/mattn/go-colorable v0.1.13 // indirect 40 + github.com/mattn/go-runewidth v0.0.16 // indirect 31 41 github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 42 + github.com/muesli/termenv v0.16.0 // indirect 32 43 github.com/natefinch/atomic v1.0.1 // indirect 33 44 github.com/prometheus/client_golang v1.17.0 // indirect 34 45 github.com/prometheus/client_model v0.5.0 // indirect 35 46 github.com/prometheus/common v0.45.0 // indirect 36 47 github.com/prometheus/procfs v0.12.0 // indirect 48 + github.com/rivo/uniseg v0.4.7 // indirect 49 + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 37 50 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 38 51 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 52 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect 39 53 golang.org/x/net v0.42.0 // indirect 40 54 golang.org/x/sync v0.16.0 // indirect 41 55 golang.org/x/time v0.3.0 // indirect
+27
go.sum
··· 5 5 github.com/a-h/templ v0.3.1001/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= 6 6 github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= 7 7 github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= 8 + github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 9 + github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 8 10 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 9 11 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 10 12 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= ··· 20 22 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 21 23 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 22 24 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 25 + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= 26 + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= 27 + github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 28 + github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 29 + github.com/charmbracelet/log v1.0.0 h1:HVVVMmfOorfj3BA9i8X8UL69Hoz9lI0PYwXfJvOdRc4= 30 + github.com/charmbracelet/log v1.0.0/go.mod h1:uYgY3SmLpwJWxmlrPwXvzVYujxis1vAKRV/0VQB7yWA= 31 + github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= 32 + github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= 33 + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= 34 + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= 35 + github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= 36 + github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= 23 37 github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= 24 38 github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= 25 39 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= ··· 38 52 github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 39 53 github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= 40 54 github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 55 + github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= 56 + github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= 41 57 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 42 58 github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= 43 59 github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= ··· 122 138 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 123 139 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 124 140 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 141 + github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 142 + github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 125 143 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 126 144 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 127 145 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 128 146 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 129 147 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 130 148 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 149 + github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 150 + github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 131 151 github.com/mattn/go-sqlite3 v1.14.40 h1:f7+saIsbq4EF86mUqe0uiecQOJYMOdfi5uATADmUG94= 132 152 github.com/mattn/go-sqlite3 v1.14.40/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= 133 153 github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= ··· 136 156 github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 137 157 github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 138 158 github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 159 + github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 160 + github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 139 161 github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= 140 162 github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= 141 163 github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= ··· 169 191 github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 170 192 github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= 171 193 github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= 194 + github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 195 + github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 196 + github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 172 197 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 173 198 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 174 199 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= ··· 196 221 github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11/go.mod h1:Wlo/SzPmxVp6vXpGt/zaXhHH0fn4IxgqZc82aKg6bpQ= 197 222 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4= 198 223 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 224 + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 225 + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 199 226 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 200 227 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 201 228 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+8 -1
internal/db/db.go
··· 4 4 "context" 5 5 "database/sql" 6 6 "fmt" 7 + "log/slog" 7 8 "strings" 8 9 9 10 _ "github.com/mattn/go-sqlite3" 11 + "shlf.space/internal/log" 10 12 ) 11 13 12 14 type DB struct { 13 15 *sql.DB 16 + logger *slog.Logger 14 17 } 15 18 16 19 type Execer interface { ··· 32 35 "_auto_vacuum=incremental", 33 36 } 34 37 38 + logger := log.FromContext(ctx) 39 + logger = log.SubLogger(logger, "db") 40 + 35 41 db, err := sql.Open("sqlite3", dbPath+"?"+strings.Join(opts, "&")) 36 42 if err != nil { 37 43 return nil, fmt.Errorf("failed to open db: %w", err) ··· 43 49 } 44 50 defer conn.Close() 45 51 52 + logger.Debug("creating tables") 46 53 _, err = conn.ExecContext(ctx, ` 47 54 create table if not exists profiles ( 48 55 did text primary key, ··· 81 88 return nil, fmt.Errorf("failed to execute db create statement: %w", err) 82 89 } 83 90 84 - return &DB{db}, nil 91 + return &DB{db, logger}, nil 85 92 }
+65
internal/log/log.go
··· 1 + package log 2 + 3 + import ( 4 + "context" 5 + "log/slog" 6 + "os" 7 + 8 + "github.com/charmbracelet/log" 9 + ) 10 + 11 + func NewHandler(name string) slog.Handler { 12 + return log.NewWithOptions(os.Stderr, log.Options{ 13 + ReportTimestamp: true, 14 + Prefix: name, 15 + Level: log.DebugLevel, 16 + }) 17 + } 18 + 19 + func New(name string) *slog.Logger { 20 + return slog.New(NewHandler(name)) 21 + } 22 + 23 + func NewContext(ctx context.Context, name string) context.Context { 24 + return IntoContext(ctx, New(name)) 25 + } 26 + 27 + type ctxKey struct{} 28 + 29 + // IntoContext adds a logger to a context. Use FromContext to pull the logger 30 + // out. 31 + func IntoContext(ctx context.Context, l *slog.Logger) context.Context { 32 + return context.WithValue(ctx, ctxKey{}, l) 33 + } 34 + 35 + // FromContext returns a logger from a context.Context; if the passed context 36 + // is nil, return the default slog logger. 37 + func FromContext(ctx context.Context) *slog.Logger { 38 + if ctx != nil { 39 + v := ctx.Value(ctxKey{}) 40 + if v == nil { 41 + return slog.Default() 42 + } 43 + return v.(*slog.Logger) 44 + } 45 + 46 + return slog.Default() 47 + } 48 + 49 + // Sublogger derives a new logger from an existing one by appending a suffix to 50 + // its prefix. 51 + func SubLogger(base *slog.Logger, suffix string) *slog.Logger { 52 + // Try to get the underlying charmbracelet logger. 53 + if cl, ok := base.Handler().(*log.Logger); ok { 54 + prefix := cl.GetPrefix() 55 + if prefix != "" { 56 + prefix = prefix + "/" + suffix 57 + } else { 58 + prefix = suffix 59 + } 60 + return slog.New(NewHandler(prefix)) 61 + } 62 + 63 + // Fallback to no known handler type. 64 + return slog.New(NewHandler(suffix)) 65 + }
+5 -2
internal/server/login.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 - "log/slog" 6 5 "net/http" 7 6 "strings" 8 7 ··· 11 10 ) 12 11 13 12 func (s *Server) Login(w http.ResponseWriter, r *http.Request) { 13 + logger := s.logger.With("handler", "Login") 14 + 14 15 switch r.Method { 15 16 case http.MethodGet: 16 17 returnURL := r.URL.Query().Get("return_url") ··· 37 38 38 39 // Basic handle validation 39 40 if !strings.Contains(handle, ".") { 41 + logger.Error("invalid handle format", "raw", handle) 40 42 htmx.HxNotice(w, "login-msg", fmt.Sprintf("'%s' is an invalid handle. Did you mean %s.bsky.social?", handle, handle)) 41 43 return 42 44 } 43 45 44 46 if err := s.oauth.SetAuthReturn(w, r, returnURL); err != nil { 45 - slog.Error("failed to set auth return", "err", err) 47 + logger.Error("failed to set auth return", "err", err) 46 48 } 47 49 48 50 redirectURL, err := s.oauth.ClientApp.StartAuthFlow(r.Context(), handle) 49 51 if err != nil { 52 + logger.Error("failed to start auth", "err", err) 50 53 htmx.HxNotice(w, "login-msg", fmt.Sprintf("Failed to start auth flow: %v", err)) 51 54 return 52 55 }
+3 -2
internal/server/logout.go
··· 1 1 package server 2 2 3 3 import ( 4 - "log/slog" 5 4 "net/http" 6 5 7 6 "shlf.space/internal/server/htmx" 8 7 ) 9 8 10 9 func (s *Server) Logout(w http.ResponseWriter, r *http.Request) { 10 + logger := s.logger.With("handler", "Logout") 11 + 11 12 err := s.oauth.DeleteSession(w, r) 12 13 if err != nil { 13 - slog.Error("failed to logout", "err", err) 14 + logger.Error("failed to logout", "err", err) 14 15 } 15 16 16 17 htmx.HxRedirect(w, http.StatusOK, "/login")
+5 -2
internal/server/middleware/middleware.go
··· 20 20 type Middleware struct { 21 21 oauth *oauth.OAuth 22 22 idResolver *atproto.Resolver 23 + logger *slog.Logger 23 24 } 24 25 25 - func New(oauth *oauth.OAuth, idResolver *atproto.Resolver) Middleware { 26 + func New(oauth *oauth.OAuth, idResolver *atproto.Resolver, logger *slog.Logger) Middleware { 26 27 return Middleware{ 27 28 oauth: oauth, 28 29 idResolver: idResolver, 30 + logger: logger, 29 31 } 30 32 } 31 33 32 34 type middlewareFunc func(http.Handler) http.Handler 33 35 34 36 func (mw Middleware) ResolveIdent() middlewareFunc { 37 + logger := mw.logger.With("middleware", "ResolveIdent") 35 38 excluded := []string{"favicon.ico", "favicon.svg"} 36 39 37 40 return func(next http.Handler) http.Handler { ··· 46 49 47 50 id, err := mw.idResolver.ResolveIdent(r.Context(), didOrHandle) 48 51 if err != nil { 49 - slog.Error("failed to resolve did/handle", "err", err) 52 + logger.Error("failed to resolve did/handle", "err", err) 50 53 w.WriteHeader(http.StatusNotFound) 51 54 notfound.NotFoundPage(notfound.NotFoundPageParams{}).Render(r.Context(), w) 52 55 return
+1 -1
internal/server/oauth/accounts.go
··· 47 47 48 48 session, err := o.SessionStore.Get(r, AccountsName) 49 49 if err != nil { 50 - return err 50 + o.Logger.Warn("failed to decode existing accounts cookie, will create new", "err", err) 51 51 } 52 52 53 53 data, err := json.Marshal(account)
+5
internal/server/oauth/handler.go
··· 47 47 48 48 func (o *OAuth) callback(w http.ResponseWriter, r *http.Request) { 49 49 ctx := r.Context() 50 + logger := o.Logger.With("query", r.URL.Query()) 50 51 51 52 authReturn := o.GetAuthReturn(r) 52 53 _ = o.ClearAuthReturn(w, r) ··· 55 56 if err != nil { 56 57 var callbackErr *oauth.AuthRequestCallbackError 57 58 if errors.As(err, &callbackErr) { 59 + logger.Debug("callback error", "err", callbackErr) 58 60 http.Redirect(w, r, fmt.Sprintf("/login?error=%s", callbackErr.ErrorCode), http.StatusFound) 59 61 return 60 62 } 63 + logger.Error("failed to process callback", "err", err) 61 64 http.Redirect(w, r, "/login?error=oauth", http.StatusFound) 62 65 return 63 66 } 64 67 65 68 if err := o.SaveSession(w, r, sessData); err != nil { 69 + logger.Error("failed to save session", "data", sessData, "err", err) 66 70 http.Redirect(w, r, "/login?error=session", http.StatusFound) 67 71 return 68 72 } 73 + o.Logger.Debug("session saved successfully") 69 74 70 75 redirectURL := "/" 71 76 if authReturn.ReturnURL != "" {
+5 -1
internal/server/oauth/oauth.go
··· 3 3 import ( 4 4 "errors" 5 5 "fmt" 6 + "log/slog" 6 7 "net/http" 7 8 "time" 8 9 ··· 23 24 Config *config.Config 24 25 JwksUri string 25 26 IdResolver *idresolver.Resolver 27 + Logger *slog.Logger 26 28 } 27 29 28 - func New(config *config.Config, res *idresolver.Resolver) (*OAuth, error) { 30 + func New(config *config.Config, res *idresolver.Resolver, logger *slog.Logger) (*OAuth, error) { 29 31 var oauthConfig oauth.ClientConfig 30 32 var clientUri string 31 33 scope := []string{ ··· 73 75 clientApp.Resolver.Client.Transport = http.DefaultTransport 74 76 } 75 77 78 + logger.Info("oauth setup successfully", "IsConfidential", clientApp.Config.IsConfidential()) 76 79 return &OAuth{ 77 80 ClientApp: clientApp, 78 81 Config: config, 79 82 SessionStore: sessionStore, 80 83 JwksUri: jwksUri, 81 84 IdResolver: res, 85 + Logger: logger, 82 86 }, nil 83 87 } 84 88
+2
internal/server/router.go
··· 6 6 7 7 "github.com/go-chi/chi/v5" 8 8 "shlf.space/internal/atproto" 9 + "shlf.space/internal/log" 9 10 "shlf.space/internal/server/middleware" 10 11 notfound "shlf.space/internal/ui/views/not-found" 11 12 ) ··· 15 16 middleware := middleware.New( 16 17 s.oauth, 17 18 s.idResolver, 19 + log.SubLogger(s.logger, "middleware"), 18 20 ) 19 21 20 22 userRouter := s.UserRouter(&middleware)
+6 -1
internal/server/server.go
··· 3 3 import ( 4 4 "context" 5 5 "fmt" 6 + "log/slog" 6 7 7 8 "github.com/bluesky-social/indigo/atproto/identity" 8 9 "shlf.space/internal/atproto" 9 10 "shlf.space/internal/config" 10 11 "shlf.space/internal/db" 12 + "shlf.space/internal/log" 11 13 "shlf.space/internal/server/oauth" 12 14 ) 13 15 ··· 16 18 config *config.Config 17 19 idResolver *atproto.Resolver 18 20 database *db.DB 21 + logger *slog.Logger 19 22 } 20 23 21 24 func Make(ctx context.Context, config *config.Config) (*Server, error) { 25 + logger := log.FromContext(ctx) 22 26 idResolver := atproto.DefaultResolver() 23 27 24 28 database, err := db.Make(ctx, config.Core.DbPath) ··· 26 30 return nil, err 27 31 } 28 32 29 - oauth, err := oauth.New(config, idResolver) 33 + oauth, err := oauth.New(config, idResolver, log.SubLogger(logger, "oauth")) 30 34 if err != nil { 31 35 return nil, fmt.Errorf("failed to start oauth handler: %w", err) 32 36 } ··· 36 40 oauth: oauth, 37 41 config: config, 38 42 idResolver: idResolver, 43 + logger: logger, 39 44 }, nil 40 45 } 41 46
+7 -2
internal/server/shelf.go
··· 11 11 ) 12 12 13 13 func (s *Server) Shelf(w http.ResponseWriter, r *http.Request) { 14 + logger := s.logger.With("handler", "Shelf") 14 15 user := s.oauth.GetAccountUser(r) 15 16 16 17 didOrHandle := chi.URLParam(r, "user") ··· 21 22 22 23 profileIdentity, err := s.resolveIdentity(didOrHandle) 23 24 if err != nil { 25 + logger.Error("failed to find profile", "didOrHandle", didOrHandle, "err", err) 24 26 http.Error(w, "Profile not found", http.StatusNotFound) 25 27 return 26 28 } 29 + did := profileIdentity.DID.String() 27 30 28 - catalog, err := db.GetBooksByDID(s.database, profileIdentity.DID.String()) 31 + catalog, err := db.GetBooksByDID(s.database, did) 29 32 if err != nil { 33 + logger.Error("failed to get catalog", "did", did, "err", err) 30 34 htmx.HxError(w, http.StatusInternalServerError, "Failed to get books, try again later.") 31 35 return 32 36 } ··· 35 39 catalogView[i] = b.View() 36 40 } 37 41 38 - shelfItems, err := db.GetShelf(s.database, profileIdentity.DID.String()) 42 + shelfItems, err := db.GetShelf(s.database, did) 39 43 if err != nil { 44 + logger.Error("failed to get shelf items", "did", did, "err", err) 40 45 htmx.HxError(w, http.StatusInternalServerError, "Failed to get shelf, try again later.") 41 46 return 42 47 }
+2 -2
internal/ui/components/header/header.templ
··· 17 17 </summary> 18 18 <div class="absolute flex flex-col right-0 mt-2 p-1 gap-1 w-48 bg-white drop-shadow-sm"> 19 19 <a 20 - href={ templ.SafeURL(fmt.Sprintf("/%s/books", params.User.Account.Did)) } 20 + href={ templ.SafeURL(fmt.Sprintf("/%s/shelf", params.User.Account.Did)) } 21 21 class="button border-none text-sm w-full" 22 22 > 23 - My books 23 + My shelf 24 24 </a> 25 25 <hr/> 26 26 <button type="button" hx-post="/logout" hx-swap="none" class="button border-none text-sm w-full">
+1 -1
internal/ui/views/shelf/shelf.templ
··· 11 11 serializedCatalog, _ := templ.JSONString(params.Catalog) 12 12 serializedItems, _ := templ.JSONString(params.Items) 13 13 }} 14 - @layouts.Base(layouts.BaseParams{Title: fmt.Sprintf("%s's books", params.ProfileHandle)}) { 14 + @layouts.Base(layouts.BaseParams{Title: fmt.Sprintf("%s's shelf", params.ProfileHandle)}) { 15 15 @header.Header(header.HeaderParams{User: params.User}) 16 16 <div 17 17 class="container"