perf(api): build the access log with a single record

This commit is contained in:
Julien Neuhart
2026-09-08 18:35:03 +02:00
parent b39b8c76aa
commit fcfd590169

View File

@@ -309,33 +309,47 @@ func telemetryMiddleware(logger *slog.Logger, serverName, correlationIdHeader st
WriteBytes: c.Response().Size, WriteBytes: c.Response().Size,
})...) })...)
accessLogger := logger. // Pick the level and message before building the record: err.Error
With(slog.String("log_type", "access")). // walks a joined error chain, and the nil-error branch has no use
With(slog.String("correlation_id", correlationId)). // for it.
With(slog.String("remote_ip", c.RealIP())). level := slog.LevelInfo
With(slog.String("host", c.Request().Host)). msg := "request handled"
With(slog.String("uri", c.Request().RequestURI)).
With(slog.String("method", c.Request().Method)).
With(slog.String("path", routePath)).
With(slog.String("referer", c.Request().Referer())).
With(slog.String("user_agent", c.Request().UserAgent())).
With(slog.Int("status", c.Response().Status)).
With(slog.Int64("latency", int64(finishTime.Sub(startTime)))).
With(slog.String("latency_human", finishTime.Sub(startTime).String())).
With(slog.Int64("bytes_in", c.Request().ContentLength)).
With(slog.Int64("bytes_out", c.Response().Size))
switch { switch {
case err == nil: case err == nil:
accessLogger.InfoContext(ctx, "request handled")
case canceled: case canceled:
// A client abort is expected, not a server failure; keep it // A client abort is expected, not a server failure; keep it
// visible but out of the error stream. // visible but out of the error stream.
accessLogger.InfoContext(ctx, err.Error()) msg = err.Error()
default: default:
accessLogger.ErrorContext(ctx, err.Error()) level = slog.LevelError
msg = err.Error()
} }
// One record rather than a chain of With calls. Each With clones
// the whole handler chain, and this logger fans out to a JSON
// handler and an OpenTelemetry bridge that is wired in even when no
// exporter is configured, so a 14-deep chain clones both sub-chains
// 14 times to emit a single line.
latency := finishTime.Sub(startTime)
logger.LogAttrs(ctx, level, msg,
slog.String("log_type", "access"),
slog.String("correlation_id", correlationId),
slog.String("remote_ip", c.RealIP()),
slog.String("host", c.Request().Host),
slog.String("uri", c.Request().RequestURI),
slog.String("method", c.Request().Method),
slog.String("path", routePath),
slog.String("referer", c.Request().Referer()),
slog.String("user_agent", c.Request().UserAgent()),
slog.Int("status", c.Response().Status),
slog.Int64("latency", int64(latency)),
slog.String("latency_human", latency.String()),
slog.Int64("bytes_in", c.Request().ContentLength),
slog.Int64("bytes_out", c.Response().Size),
)
additionalAttributes := []attribute.KeyValue{ additionalAttributes := []attribute.KeyValue{
semconvSrv.Route(routePath), semconvSrv.Route(routePath),
} }