summaryrefslogtreecommitdiff
path: root/logging
diff options
context:
space:
mode:
Diffstat (limited to 'logging')
-rw-r--r--logging/logger.go43
-rw-r--r--logging/middleware.go107
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
+}