http/server/logging/logging.go (82 lines of code) (raw):
package logging
import (
"context"
"log/slog"
"net/http"
"time"
"github.com/Azure/aks-middleware/http/common/logging"
"github.com/gorilla/mux"
"google.golang.org/grpc/metadata"
)
// TODO (Tom): Add a logger wrapper in its own package
// https://medium.com/@ansujain/building-a-logger-wrapper-in-go-with-support-for-multiple-logging-libraries-48092b826bee
// more info about http handler here: https://pkg.go.dev/net/http#Handler
func NewLogging(logger *slog.Logger) mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return &loggingMiddleware{
next: next,
now: time.Now,
logger: logger,
}
}
}
// enforcing that loggingMiddleware implements the http.Handler interface to ensure safety at compile time
var _ http.Handler = &loggingMiddleware{}
type loggingMiddleware struct {
next http.Handler
now func() time.Time
logger *slog.Logger
}
type RequestLogData struct {
Code int
Duration time.Duration
Error string
}
func (l *loggingMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
customWriter := logging.NewResponseWriter(w)
startTime := l.now()
ctx := r.Context()
l.LogRequestStart(ctx, r, "RequestStart")
l.next.ServeHTTP(customWriter, r)
endTime := l.now()
latency := endTime.Sub(startTime)
errorMsg := customWriter.Buf.String()
data := RequestLogData{
Code: customWriter.StatusCode,
Duration: latency,
Error: errorMsg,
}
// TODO (tomabraham): move RequestStart and RequestEnd to a different interceptor
// ApiRequestLog should only get "finished call" logs
l.LogRequestEnd(ctx, r, "RequestEnd", data)
l.LogRequestEnd(ctx, r, "finished call", data)
}
func BuildAttributes(ctx context.Context, r *http.Request, extra ...interface{}) []interface{} {
md, ok := metadata.FromIncomingContext(ctx)
attributes := []interface{}{
"source", "ApiRequestLog",
"protocol", "HTTP",
"method_type", "unary",
"component", "server",
"method", r.Method,
"service", r.Host,
"url", r.URL.String(),
}
headers := make(map[string]string)
if ok {
for key, values := range md {
if len(values) > 0 {
headers[key] = values[0]
}
}
}
attributes = append(attributes, "headers", headers)
attributes = append(attributes, extra...)
return attributes
}
func (l *loggingMiddleware) LogRequestStart(ctx context.Context, r *http.Request, msg string) {
attributes := BuildAttributes(ctx, r)
l.logger.InfoContext(ctx, msg, attributes...)
}
func (l *loggingMiddleware) LogRequestEnd(ctx context.Context, r *http.Request, msg string, data RequestLogData) {
attributes := BuildAttributes(ctx, r, "code", data.Code, "time_ms", data.Duration.Milliseconds(), "error", data.Error)
if data.Code >= http.StatusBadRequest {
l.logger.ErrorContext(ctx, msg, attributes...)
} else {
l.logger.InfoContext(ctx, msg, attributes...)
}
}