Experimenting with AT Protocol to hit up your friends

Setup some http logging and custom colorized logs

Changed files
+150 -28
cmd
appview
internal
+4 -3
cmd/appview/main.go
··· 5 5 "net/http" 6 6 7 7 "atyo.app/internal/logging" 8 - appview "atyo.app/internal/server" 8 + "atyo.app/internal/server" 9 9 ) 10 10 11 11 func main() { 12 + logging.Initialize() 12 13 logging.Level.Set(slog.LevelDebug) // for active development 13 14 14 - server := appview.NewServer() 15 - mux := server.InitializeRoutes() 15 + router := server.NewRouter() 16 + mux := router.InitializeRoutes() 16 17 17 18 // TODO configurable listen address, TLS, etc 18 19 listenAddr := "127.0.0.1:3000"
+2 -3
go.mod
··· 7 7 require ( 8 8 github.com/bluesky-social/indigo v0.0.0-20250621010046-488d1b91889b 9 9 github.com/ipfs/go-cid v0.4.1 10 + github.com/lmittmann/tint v1.1.2 10 11 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e 11 12 golang.org/x/crypto v0.31.0 12 13 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 ··· 37 38 github.com/ipfs/go-log v1.0.5 // indirect 38 39 github.com/ipfs/go-log/v2 v2.5.1 // indirect 39 40 github.com/ipfs/go-metrics-interface v0.0.1 // indirect 40 - github.com/jba/slog v0.2.0 // indirect 41 41 github.com/jbenet/goprocess v0.1.4 // indirect 42 42 github.com/klauspost/cpuid/v2 v2.2.7 // indirect 43 43 github.com/mattn/go-isatty v0.0.20 // indirect ··· 50 50 github.com/multiformats/go-multihash v0.2.3 // indirect 51 51 github.com/multiformats/go-varint v0.0.7 // indirect 52 52 github.com/opentracing/opentracing-go v1.2.0 // indirect 53 - github.com/phsym/console-slog v0.3.1 // indirect 54 53 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 55 54 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 56 55 github.com/prometheus/client_golang v1.17.0 // indirect ··· 68 67 go.uber.org/atomic v1.11.0 // indirect 69 68 go.uber.org/multierr v1.11.0 // indirect 70 69 go.uber.org/zap v1.26.0 // indirect 71 - golang.org/x/sys v0.28.0 // indirect 70 + golang.org/x/sys v0.32.0 // indirect 72 71 golang.org/x/time v0.8.0 // indirect 73 72 google.golang.org/protobuf v1.33.0 // indirect 74 73 lukechampine.com/blake3 v1.2.1 // indirect
+4 -6
go.sum
··· 67 67 github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= 68 68 github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= 69 69 github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= 70 - github.com/jba/slog v0.2.0 h1:jI0U5NRR3EJKGsbeEVpItJNogk0c4RMeCl7vJmogCJI= 71 - github.com/jba/slog v0.2.0/go.mod h1:0Dh7Vyz3Td68Z1OwzadfincHwr7v+PpzadrS2Jua338= 72 70 github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= 73 71 github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= 74 72 github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= ··· 85 83 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 86 84 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 87 85 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 86 + github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= 87 + github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= 88 88 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 89 89 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 90 90 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= ··· 106 106 github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= 107 107 github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 108 108 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 109 - github.com/phsym/console-slog v0.3.1 h1:Fuzcrjr40xTc004S9Kni8XfNsk+qrptQmyR+wZw9/7A= 110 - github.com/phsym/console-slog v0.3.1/go.mod h1:oJskjp/X6e6c0mGpfP8ELkfKUsrkDifYRAqJQgmdDS0= 111 109 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 112 110 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 113 111 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= ··· 206 204 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 207 205 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 208 206 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 209 - golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 210 - golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 207 + golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 208 + golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 211 209 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 212 210 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 213 211 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+59 -9
internal/logging/logging.go
··· 1 - // Package log provides a simple facade for [log/slog] to write text logs to 2 - // stderr. TODO: access logs to stdout. 1 + // Simple setup for logging to stderr. TODO: HTTP access logs to stdout? 3 2 4 3 package logging 5 4 ··· 7 6 "log/slog" 8 7 "os" 9 8 10 - "github.com/phsym/console-slog" 9 + "github.com/lmittmann/tint" 11 10 ) 12 11 13 - const rfc3339Millis = "2006-01-02 15:04:05.000Z07:00" 12 + // Approximately RFC3339 with millisecond precision 13 + const timeFormat = "2006-01-02 15:04:05.000 -07:00" 14 14 15 15 var Level = new(slog.LevelVar) // Info by default 16 16 17 - func init() { 18 - logger := slog.New(console.NewHandler(os.Stderr, &console.HandlerOptions{ 17 + const ( 18 + ansiBlack = 0 19 + ansiRed = 1 20 + ansiGreen = 2 21 + ansiYellow = 3 22 + ansiBlue = 4 23 + ansiMagenta = 5 24 + ansiCyan = 6 25 + ansiWhite = 7 26 + ansiBrightBlack = 8 // gray 27 + ansiBrightRed = 9 28 + ansiBrightGreen = 10 29 + ansiBrightYellow = 11 30 + ansiBrightBlue = 12 31 + ansiBrightMagenta = 13 32 + ansiBrightCyan = 14 33 + ansiBrightWhite = 15 34 + ) 35 + 36 + func Initialize() { 37 + handler := tint.NewHandler(os.Stderr, &tint.Options{ 19 38 AddSource: true, 20 - TimeFormat: rfc3339Millis, 21 - })) 39 + Level: Level, 40 + TimeFormat: timeFormat, 41 + ReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr { 42 + if len(groups) != 0 { 43 + return attr 44 + } 45 + 46 + if attr.Key == slog.TimeKey { 47 + return tint.Attr(ansiBrightBlack, attr) 48 + } 22 49 23 - slog.SetDefault(logger) 50 + if attr.Key == slog.SourceKey { 51 + return tint.Attr(ansiCyan, attr) 52 + } 53 + 54 + if attr.Key == slog.LevelKey { 55 + level := attr.Value.Any().(slog.Level) 56 + 57 + switch level { 58 + case slog.LevelDebug: 59 + return tint.Attr(ansiMagenta, attr) 60 + case slog.LevelError: 61 + return tint.Attr(ansiRed, attr) 62 + case slog.LevelWarn: 63 + return tint.Attr(ansiYellow, attr) 64 + case slog.LevelInfo: 65 + return tint.Attr(ansiWhite, attr) 66 + } 67 + } 68 + 69 + return attr 70 + }, 71 + }) 72 + 73 + slog.SetDefault(slog.New(handler)) 24 74 }
+1 -1
internal/ping/keys.go
··· 138 138 return err 139 139 } 140 140 141 - slog.Info("created pubkey record", "public_key", hex.EncodeToString(selfPubKey[:])) 141 + slog.Info("created pubkey record", "pubkey", hex.EncodeToString(selfPubKey[:])) 142 142 143 143 k.selfPrivateKey = selfPrivKey 144 144 return nil
+74
internal/server/middleware.go
··· 1 + package server 2 + 3 + import ( 4 + "log/slog" 5 + "net/http" 6 + "slices" 7 + ) 8 + 9 + type handlerFunc = func(http.ResponseWriter, *http.Request) 10 + 11 + type Middleware func(next handlerFunc) handlerFunc 12 + 13 + var defaultMiddleware Middleware = ChainMiddleware( 14 + LogMiddleware(slog.LevelInfo), 15 + ) 16 + 17 + func ChainMiddleware(middleware ...Middleware) Middleware { 18 + return func(next handlerFunc) handlerFunc { 19 + for _, adapt := range slices.Backward(middleware) { 20 + next = adapt(next) 21 + } 22 + 23 + return next 24 + } 25 + } 26 + 27 + func LogMiddleware(level slog.Level) Middleware { 28 + return func(next handlerFunc) handlerFunc { 29 + return func(w http.ResponseWriter, req *http.Request) { 30 + logger := &responseLogger{ 31 + Code: http.StatusOK, // default response writer behavior 32 + Writer: w, 33 + } 34 + 35 + next(logger, req) 36 + 37 + slog.Log( 38 + req.Context(), 39 + level, 40 + req.Method+" "+req.URL.Path, 41 + "remote", req.RemoteAddr, 42 + "code", logger.Code, 43 + "status", http.StatusText(logger.Code), 44 + "len", logger.Len, 45 + ) 46 + } 47 + } 48 + } 49 + 50 + type responseLogger struct { 51 + Code int 52 + Len int 53 + Writer http.ResponseWriter 54 + } 55 + 56 + // Header implements [http.ResponseWriter]. 57 + func (s *responseLogger) Header() http.Header { 58 + return s.Writer.Header() 59 + } 60 + 61 + // Write implements [http.ResponseWriter]. 62 + func (s *responseLogger) Write(data []byte) (int, error) { 63 + length, err := s.Writer.Write(data) 64 + if err == nil { 65 + s.Len = length 66 + } 67 + return length, err 68 + } 69 + 70 + // WriteHeader implements [http.ResponseWriter]. 71 + func (s *responseLogger) WriteHeader(statusCode int) { 72 + s.Code = statusCode 73 + s.Writer.WriteHeader(statusCode) 74 + }
+6 -6
internal/server/router.go
··· 1 - package appview 1 + package server 2 2 3 3 import ( 4 4 "encoding/json" ··· 20 20 pings *ping.Client 21 21 } 22 22 23 - func NewServer() *Server { 23 + func NewRouter() *Server { 24 24 dir := lookup.NewDirectory() 25 25 client := auth.NewClient(dir) 26 26 keys := ping.NewKeyManager(client) ··· 37 37 mux := http.NewServeMux() 38 38 39 39 // Static web app/client 40 - mux.HandleFunc("GET /", s.handleStatic) 40 + mux.HandleFunc("GET /", defaultMiddleware(s.handleStatic)) 41 41 42 42 // App functionality 43 - mux.HandleFunc("POST /ping/{target}", s.sendPing) 44 - mux.HandleFunc("GET /ping", s.fetchPings) 43 + mux.HandleFunc("POST /ping/{target}", defaultMiddleware(s.sendPing)) 44 + mux.HandleFunc("GET /ping", defaultMiddleware(s.fetchPings)) 45 45 46 46 // Auth 47 - mux.HandleFunc("POST /login", s.login) 47 + mux.HandleFunc("POST /login", defaultMiddleware(s.login)) 48 48 49 49 return mux 50 50 }