From c2f42a9b8752ca924eb44d28ef326ff28d99e392 Mon Sep 17 00:00:00 2001 From: Stig Otnes Kolstad Date: Mon, 18 Dec 2023 11:52:27 +0100 Subject: [PATCH] feat: use zap for http access logging Closes #54 --- pkg/registry/registry.go | 56 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 706e4e1..58e2b83 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -10,6 +10,7 @@ import ( "net/http" "strings" "sync" + "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" @@ -83,7 +84,7 @@ func (reg *Registry) SetAuthTokens(authTokens map[string]string) { // setupRoutes initialises and configures the HTTP router. Must be called before starting the server (`ServeHTTP`). func (reg *Registry) setupRoutes() { reg.router = chi.NewRouter() - reg.router.Use(middleware.Logger) + reg.router.Use(reg.RequestLogger()) reg.router.NotFound(reg.NotFound()) reg.router.MethodNotAllowed(reg.MethodNotAllowed()) reg.router.Get("/", reg.Index()) @@ -98,6 +99,59 @@ func (reg *Registry) setupRoutes() { }) } +// Request logger for Chi using Zap as the logger. +func (reg *Registry) RequestLogger() func(next http.Handler) http.Handler { + // This method is _adapted_ from https://github.com/moul/chizap/blob/0ebf11a6a5535e3c6bb26f1236b2833ae7825675/chizap.go + // written by Manfred Touron, licensed under the MIT license. + // + // + // Copyright (c) 2021 Manfred Touron (manfred.life) + // + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + // + // The above copyright notice and this permission notice shall be included in all + // copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + // SOFTWARE. + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + wr := middleware.NewWrapResponseWriter(w, r.ProtoMajor) + t1 := time.Now() + defer func() { + ua := wr.Header().Get("User-Agent") + if ua == "" { + ua = r.Header.Get("User-Agent") + } + + reqLogger := reg.logger.With( + zap.String("proto", r.Proto), + zap.String("method", r.Method), + zap.String("path", r.URL.Path), + zap.Int("size", wr.BytesWritten()), + zap.Int("status", wr.Status()), + zap.String("reqId", middleware.GetReqID(r.Context())), + zap.Duration("responseTime", time.Since(t1)), + zap.String("userAgent", ua), + ) + + reqLogger.Info("HTTP request") + }() + next.ServeHTTP(wr, r) + }) + } +} + func (reg *Registry) ServeHTTP(w http.ResponseWriter, r *http.Request) { reg.router.ServeHTTP(w, r) }