# README
slog: net/http middleware
net/http handler to log HTTP requests using slog.
Sponsored by:
Cloud-native search engine for observability - An OSS alternative to Splunk, Elasticsearch, Loki, and Tempo.
See also:
- slog-multi:
slog.Handler
chaining, fanout, routing, failover, load balancing... - slog-formatter:
slog
attribute formatting - slog-sampling:
slog
sampling policy - slog-mock:
slog.Handler
for test purposes
HTTP middlewares:
- slog-gin: Gin middleware for
slog
logger - slog-echo: Echo middleware for
slog
logger - slog-fiber: Fiber middleware for
slog
logger - slog-chi: Chi middleware for
slog
logger - slog-http:
net/http
middleware forslog
logger
Loggers:
- slog-zap: A
slog
handler forZap
- slog-zerolog: A
slog
handler forZerolog
- slog-logrus: A
slog
handler forLogrus
Log sinks:
- slog-datadog: A
slog
handler forDatadog
- slog-betterstack: A
slog
handler forBetterstack
- slog-rollbar: A
slog
handler forRollbar
- slog-loki: A
slog
handler forLoki
- slog-sentry: A
slog
handler forSentry
- slog-syslog: A
slog
handler forSyslog
- slog-logstash: A
slog
handler forLogstash
- slog-fluentd: A
slog
handler forFluentd
- slog-graylog: A
slog
handler forGraylog
- slog-quickwit: A
slog
handler forQuickwit
- slog-slack: A
slog
handler forSlack
- slog-telegram: A
slog
handler forTelegram
- slog-mattermost: A
slog
handler forMattermost
- slog-microsoft-teams: A
slog
handler forMicrosoft Teams
- slog-webhook: A
slog
handler forWebhook
- slog-kafka: A
slog
handler forKafka
- slog-nats: A
slog
handler forNATS
- slog-parquet: A
slog
handler forParquet
+Object Storage
- slog-channel: A
slog
handler for Go channels
🚀 Install
go get github.com/samber/slog-http
Compatibility: go >= 1.21
No breaking changes will be made to exported APIs before v2.0.0.
💡 Usage
Handler options
type Config struct {
DefaultLevel slog.Level
ClientErrorLevel slog.Level
ServerErrorLevel slog.Level
WithUserAgent bool
WithRequestID bool
WithRequestBody bool
WithRequestHeader bool
WithResponseBody bool
WithResponseHeader bool
WithSpanID bool
WithTraceID bool
Filters []Filter
}
Attributes will be injected in log payload.
Other global parameters:
sloghttp.TraceIDKey = "trace_id"
sloghttp.SpanIDKey = "span_id"
sloghttp.RequestBodyMaxSize = 64 * 1024 // 64KB
sloghttp.ResponseBodyMaxSize = 64 * 1024 // 64KB
sloghttp.HiddenRequestHeaders = map[string]struct{}{ ... }
sloghttp.HiddenResponseHeaders = map[string]struct{}{ ... }
sloghttp.RequestIDHeaderKey = "X-Request-Id"
Minimal
import (
"net/http"
"os"
"time"
sloghttp "github.com/samber/slog-http"
"log/slog"
)
// Create a slog logger, which:
// - Logs to stdout.
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
// mux router
mux := http.NewServeMux()
// Routes
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
}))
mux.Handle("/error", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "I'm angry" http.StatusInternalServerError)
}))
// Middleware
handler := sloghttp.Recovery(mux)
handler = sloghttp.New(logger)(handler)
// Start server
http.ListenAndServe(":4242", handler)
// output:
// time=2023-10-15T20:32:58.926+02:00 level=INFO msg="Success" env=production request.time=2023-10-15T20:32:58.626+02:00 request.method=GET request.path=/ request.route="" request.ip=127.0.0.1:63932 request.length=0 response.time=2023-10-15T20:32:58.926+02:00 response.latency=100ms response.status=200 response.length=7 id=229c7fc8-64f5-4467-bc4a-940700503b0d
OTEL
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
config := sloghttp.Config{
WithSpanID: true,
WithTraceID: true,
}
mux := http.NewServeMux()
// Middleware
handler := sloghttp.Recovery(mux)
handler = sloghttp.NewWithConfig(logger, config)(handler)
// Start server
http.ListenAndServe(":4242", handler)
Custom log levels
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
config := sloghttp.Config{
DefaultLevel: slog.LevelInfo,
ClientErrorLevel: slog.LevelWarn,
ServerErrorLevel: slog.LevelError,
}
mux := http.NewServeMux()
// Middleware
handler := sloghttp.Recovery(mux)
handler = sloghttp.NewWithConfig(logger, config)(handler)
// Start server
http.ListenAndServe(":4242", handler)
Verbose
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
config := sloghttp.Config{
WithRequestBody: true,
WithResponseBody: true,
WithRequestHeader: true,
WithResponseHeader: true,
}
mux := http.NewServeMux()
// Middleware
handler := sloghttp.Recovery(mux)
handler = sloghttp.NewWithConfig(logger, config)(handler)
// Start server
http.ListenAndServe(":4242", handler)
Filters
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
mux := http.NewServeMux()
// Middleware
handler := sloghttp.Recovery(mux)
handler = sloghttp.NewWithFilters(
logger,
sloghttp.Accept(func (w sloghttp.WrapResponseWriter, r *http.Request) bool {
return xxx
}),
sloghttp.IgnoreStatus(401, 404),
)(handler)
// Start server
http.ListenAndServe(":4242", handler)
Available filters:
- Accept / Ignore
- AcceptMethod / IgnoreMethod
- AcceptStatus / IgnoreStatus
- AcceptStatusGreaterThan / IgnoreStatusLessThan
- AcceptStatusGreaterThanOrEqual / IgnoreStatusLessThanOrEqual
- AcceptPath / IgnorePath
- AcceptPathContains / IgnorePathContains
- AcceptPathPrefix / IgnorePathPrefix
- AcceptPathSuffix / IgnorePathSuffix
- AcceptPathMatch / IgnorePathMatch
- AcceptHost / IgnoreHost
- AcceptHostContains / IgnoreHostContains
- AcceptHostPrefix / IgnoreHostPrefix
- AcceptHostSuffix / IgnoreHostSuffix
- AcceptHostMatch / IgnoreHostMatch
Using custom time formatters
import (
"net/http"
"log/slog"
sloghttp "github.com/samber/slog-http"
slogformatter "github.com/samber/slog-formatter"
)
// Create a slog logger, which:
// - Logs to stdout.
// - RFC3339 with UTC time format.
logger := slog.New(
slogformatter.NewFormatterHandler(
slogformatter.TimezoneConverter(time.UTC),
slogformatter.TimeFormatter(time.DateTime, nil),
)(
slog.NewTextHandler(os.Stdout, nil),
),
)
// mux router
mux := http.NewServeMux()
// Routes
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
}))
mux.Handle("/error", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "I'm angry" http.StatusInternalServerError)
}))
// Middleware
handler := sloghttp.Recovery(mux)
handler = sloghttp.New(logger)(handler)
// Start server
http.ListenAndServe(":4242", handler)
// output:
// time=2023-10-15T20:32:58.926+02:00 level=INFO msg="Success" env=production request.time=2023-10-15T20:32:58Z request.method=GET request.path=/ request.route="" request.ip=127.0.0.1:63932 request.length=0 response.time=2023-10-15T20:32:58Z response.latency=100ms response.status=200 response.length=7 id=229c7fc8-64f5-4467-bc4a-940700503b0d
Using custom logger sub-group
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
// mux router
mux := http.NewServeMux()
// Routes
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
}))
mux.Handle("/error", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "I'm angry" http.StatusInternalServerError)
}))
// Middleware
handler := sloghttp.Recovery(mux)
handler = sloghttp.New(logger.WithGroup("http"))(handler)
// Start server
http.ListenAndServe(":4242", handler)
// output:
// time=2023-10-15T20:32:58.926+02:00 level=INFO msg="Success" env=production http.request.time=2023-10-15T20:32:58.626+02:00 http.request.method=GET http.request.path=/ http.request.route="" http.request.ip=127.0.0.1:63932 http.request.length=0 http.response.time=2023-10-15T20:32:58.926+02:00 http.response.latency=100ms http.response.status=200 http.response.length=7 http.id=229c7fc8-64f5-4467-bc4a-940700503b0d
Add logger to a single route
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
// mux router
mux := http.NewServeMux()
// Routes
mux.Handler("/", sloghttp.New(logger)(
http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
return c.String(http.StatusOK, "Hello, World!")
},
),
sloghttp.New(logger),
))
// Start server
http.ListenAndServe(":4242", handler)
Adding custom attributes
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
// Add an attribute to all log entries made through this logger.
logger = logger.With("env", "production")
// mux router
mux := http.NewServeMux()
// Routes
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sloghttp.AddCustomAttributes(r, slog.String("foo", "bar"))
w.Write([]byte("Hello, World!"))
}))
// Middleware
handler := sloghttp.Recovery(mux)
handler = sloghttp.New(logger)(handler)
// Start server
http.ListenAndServe(":4242", handler)
// output:
// time=2023-10-15T20:32:58.926+02:00 level=INFO msg="Success" env=production request.time=2023-10-15T20:32:58.626+02:00 request.method=GET request.path=/ request.route="" request.ip=127.0.0.1:63932 request.length=0 response.time=2023-10-15T20:32:58.926+02:00 response.latency=100ms response.status=200 response.length=7 id=229c7fc8-64f5-4467-bc4a-940700503b0d foo=bar
JSON output
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// mux router
mux := http.NewServeMux()
// Routes
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
}))
// Middleware
handler := sloghttp.Recovery(mux)
handler = sloghttp.New(logger)(handler)
// Start server
http.ListenAndServe(":4242", handler)
// output:
// {"time":"2023-10-15T20:32:58.926+02:00","level":"INFO","msg":"Success","env":"production","http":{"request":{"time":"2023-10-15T20:32:58.626+02:00","method":"GET","path":"/","route":"","ip":"127.0.0.1:55296","length":0},"response":{"time":"2023-10-15T20:32:58.926+02:00","latency":100000,"status":200,"length":7},"id":"04201917-d7ba-4b20-a3bb-2fffba5f2bd9"}}
🤝 Contributing
- Ping me on twitter @samuelberthe (DMs, mentions, whatever :))
- Fork the project
- Fix open issues or request new features
Don't hesitate ;)
# Install some dev dependencies
make tools
# Run tests
make test
# or
make watch-test
👤 Contributors
💫 Show your support
Give a ⭐️ if this project helped you!
📝 License
Copyright © 2023 Samuel Berthe.
This project is MIT licensed.
# Functions
Basic.
Host.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
Method.
Path.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
Status.
No description provided by the author
No description provided by the author
AddCustomAttributes adds custom attributes to the request context.
GetRequestID returns the request identifier.
GetRequestIDFromContext returns the request identifier from the context.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
New returns a `func(http.Handler) http.Handler` (middleware) that logs requests using slog.
NewWithConfig returns a `func(http.Handler) http.Handler` (middleware) that logs requests using slog.
NewWithFilters returns a `func(http.Handler) http.Handler` (middleware) that logs requests using slog.
No description provided by the author
# Variables
No description provided by the author
No description provided by the author
64KB.
Formatted with http.CanonicalHeaderKey.
No description provided by the author
64KB.
No description provided by the author
No description provided by the author
# Interfaces
No description provided by the author
# Type aliases
No description provided by the author