Add logging middleware
This commit is contained in:
parent
c47eae8afa
commit
55e3a68db6
@ -15,6 +15,7 @@ var DefaultConfig = &AppConfig{
|
|||||||
},
|
},
|
||||||
HTTP: &HTTPConfig{
|
HTTP: &HTTPConfig{
|
||||||
Listen: "127.0.0.1:8080",
|
Listen: "127.0.0.1:8080",
|
||||||
|
LogRequests: false,
|
||||||
ReadTimeout: "10s",
|
ReadTimeout: "10s",
|
||||||
WriteTimeout: "10s",
|
WriteTimeout: "10s",
|
||||||
IdleTimeout: "1m",
|
IdleTimeout: "1m",
|
||||||
@ -76,6 +77,7 @@ const (
|
|||||||
// HTTP Configuration
|
// HTTP Configuration
|
||||||
type HTTPConfig struct {
|
type HTTPConfig struct {
|
||||||
Listen string `yaml:"listen,omitempty" env:"APP_HTTP_LISTEN"`
|
Listen string `yaml:"listen,omitempty" env:"APP_HTTP_LISTEN"`
|
||||||
|
LogRequests bool `yaml:"logRequests" env:"APP_HTTP_LOG_REQUESTS"`
|
||||||
ReadTimeout string `yaml:"readTimeout" env:"APP_HTTP_READ_TIMEOUT"` // Go duration (e.g. 10s)
|
ReadTimeout string `yaml:"readTimeout" env:"APP_HTTP_READ_TIMEOUT"` // Go duration (e.g. 10s)
|
||||||
WriteTimeout string `yaml:"writeTimeout" env:"APP_HTTP_WRITE_TIMEOUT"` // Go duration (e.g. 10s)
|
WriteTimeout string `yaml:"writeTimeout" env:"APP_HTTP_WRITE_TIMEOUT"` // Go duration (e.g. 10s)
|
||||||
IdleTimeout string `yaml:"idleTimeout" env:"APP_HTTP_IDLE_TIMEOUT"` // Go duration (e.g. 10s)
|
IdleTimeout string `yaml:"idleTimeout" env:"APP_HTTP_IDLE_TIMEOUT"` // Go duration (e.g. 10s)
|
||||||
|
@ -89,6 +89,11 @@ func prepHTTPServer(ctx context.Context, handleFuncs []HTTPFunc, hcFuncs ...Heal
|
|||||||
idleTimeout = *iT
|
idleTimeout = *iT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inject logging middleware
|
||||||
|
if cfg.HTTP.LogRequests {
|
||||||
|
handler = loggingMiddleware(ctx, handler)
|
||||||
|
}
|
||||||
|
|
||||||
return &http.Server{
|
return &http.Server{
|
||||||
Addr: cfg.HTTP.Listen,
|
Addr: cfg.HTTP.Listen,
|
||||||
ReadTimeout: readTimeout,
|
ReadTimeout: readTimeout,
|
||||||
|
87
pkg/srv/http_log.go
Normal file
87
pkg/srv/http_log.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package srv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ExcludeFromLogging = regexp.MustCompile(`\/(ready|live|metrics)$`)
|
||||||
|
|
||||||
|
type LoggingResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
statusCode int
|
||||||
|
body *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func loggingMiddleware(appCtx context.Context, next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if ExcludeFromLogging.Match([]byte(r.URL.Path)) {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log := zerolog.Ctx(appCtx)
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
lrr := newLoggingResponseWriter(w)
|
||||||
|
next.ServeHTTP(lrr, r)
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("path", r.URL.Path).
|
||||||
|
Any("query", r.URL.Query()).
|
||||||
|
Int("statusCode", lrr.statusCode).
|
||||||
|
Str("protocol", r.Proto).
|
||||||
|
Str("remote", r.RemoteAddr).
|
||||||
|
Dur("duration", time.Since(start)).
|
||||||
|
Msg("http request served")
|
||||||
|
|
||||||
|
// Log response body
|
||||||
|
trcLog := log.Trace().
|
||||||
|
Str("path", r.URL.Path).
|
||||||
|
Int("statusCode", lrr.statusCode)
|
||||||
|
|
||||||
|
// Check if it's JSON
|
||||||
|
firstByte, err := lrr.body.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
trcLog.Err(errors.New("invalid response body")).Send()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lrr.body.UnreadByte()
|
||||||
|
|
||||||
|
if firstByte == '{' {
|
||||||
|
trcLog = trcLog.RawJSON("response", lrr.body.Bytes())
|
||||||
|
} else {
|
||||||
|
trcLog = trcLog.Bytes("response", lrr.body.Bytes())
|
||||||
|
}
|
||||||
|
trcLog.Msg("response payload")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement Flush to support the http.Flusher interface
|
||||||
|
func (w *LoggingResponseWriter) Flush() {
|
||||||
|
if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LoggingResponseWriter) WriteHeader(code int) {
|
||||||
|
w.statusCode = code
|
||||||
|
w.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LoggingResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
w.body.Write(b)
|
||||||
|
return w.ResponseWriter.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLoggingResponseWriter(w http.ResponseWriter) *LoggingResponseWriter {
|
||||||
|
return &LoggingResponseWriter{
|
||||||
|
ResponseWriter: w, statusCode: http.StatusOK, body: bytes.NewBuffer(nil),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user