+4
-3
cmd/appview/main.go
+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
+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
+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
+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
+1
-1
internal/ping/keys.go
+74
internal/server/middleware.go
+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
+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
}