diff options
Diffstat (limited to 'logging')
-rw-r--r-- | logging/logger.go | 43 | ||||
-rw-r--r-- | logging/middleware.go | 107 |
2 files changed, 150 insertions, 0 deletions
diff --git a/logging/logger.go b/logging/logger.go new file mode 100644 index 0000000..f80a1ae --- /dev/null +++ b/logging/logger.go @@ -0,0 +1,43 @@ +package logging + +import ( + "os" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/go-kit/log/term" +) + +// Logger records log lines to an output. +type Logger interface { + Log(keyvals ...any) error +} + +// DefaultLoggers produces helpful base loggers for each level. +// +// They write logfmt to standard out, annotated with ANSI colors depending on the level. +func DefaultLoggers() (debug, info, warn, error Logger) { + base := term.NewLogger(os.Stdout, log.NewLogfmtLogger, func(keyvals ...any) term.FgBgColor { + for i := 0; i < len(keyvals)-1; i += 2 { + if keyvals[i] != "level" { + continue + } + + switch keyvals[i+1] { + case level.DebugValue(): + return term.FgBgColor{Fg: term.DarkGray} + case level.InfoValue(): + return term.FgBgColor{Fg: term.Green} + case level.WarnValue(): + return term.FgBgColor{Fg: term.Yellow} + case level.ErrorValue(): + return term.FgBgColor{Fg: term.Red} + } + } + + return term.FgBgColor{} + }) + base = log.NewSyncLogger(base) + + return level.Debug(base), level.Info(base), level.Warn(base), level.Error(base) +} diff --git a/logging/middleware.go b/logging/middleware.go new file mode 100644 index 0000000..cbb345a --- /dev/null +++ b/logging/middleware.go @@ -0,0 +1,107 @@ +package logging + +import ( + "context" + "errors" + "io" + "time" + + "tildegit.org/tjp/gus" +) + +func LogRequests(logger Logger) gus.Middleware { + return func(inner gus.Handler) gus.Handler { + return func(ctx context.Context, request *gus.Request) *gus.Response { + start := time.Now() + + response := inner(ctx, request) + if response != nil { + body := loggedResponseBody{ + request: request, + response: response, + body: response.Body, + start: start, + logger: logger, + } + response.Body = &body + } + + return response + } + } +} + +type loggedResponseBody struct { + request *gus.Request + response *gus.Response + body io.Reader + + start time.Time + + written int + logger Logger +} + +func loggingBody(logger Logger, request *gus.Request, response *gus.Response) io.Reader { + body := &loggedResponseBody{ + request: request, + response: response, + body: response.Body, + start: time.Now(), + written: 0, + logger: logger, + } + + if _, ok := response.Body.(io.WriterTo); ok { + return loggedWriteToResponseBody{body} + } + + return body +} + +func (lr *loggedResponseBody) log() { + end := time.Now() + _ = lr.logger.Log( + "msg", "request", + "ts", end, + "dur", end.Sub(lr.start), + "url", lr.request.URL, + "status", lr.response.Status, + "bodylen", lr.written, + ) +} + +func (lr *loggedResponseBody) Read(b []byte) (int, error) { + if lr.body == nil { + return 0, io.EOF + } + + wr, err := lr.body.Read(b) + lr.written += wr + + if errors.Is(err, io.EOF) { + lr.log() + } + + return wr, err +} + +func (lr *loggedResponseBody) Close() error { + if cl, ok := lr.body.(io.Closer); ok { + return cl.Close() + } + return nil +} + +type loggedWriteToResponseBody struct { + *loggedResponseBody +} + +func (lwtr loggedWriteToResponseBody) WriteTo(dst io.Writer) (int64, error) { + n, err := lwtr.body.(io.WriterTo).WriteTo(dst) + if err == nil { + lwtr.written += int(n) + lwtr.log() + } + return n, err +} |