π¨ net/http middleware for slog logger
See the codenet/http handler to log HTTP requests using slog.
See also:
slog.Handler chaining, fanout, routing, failover, load balancing...slog attribute formattingslog sampling policyslog.Handler for test purposesHTTP middlewares:
slog loggerslog loggerslog loggerslog loggernet/http middleware for slog loggerLoggers:
slog handler for Zapslog handler for Zerologslog handler for LogrusLog sinks:
slog handler for Datadogslog handler for Betterstackslog handler for Rollbarslog handler for Lokislog handler for Sentryslog handler for Syslogslog handler for Logstashslog handler for Fluentdslog handler for Graylogslog handler for Quickwitslog handler for Slackslog handler for Telegramslog handler for Mattermostslog handler for Microsoft Teamsslog handler for Webhookslog handler for Kafkaslog handler for NATSslog handler for Parquet + Object Storageslog handler for Go channelsgo get github.com/samber/slog-http
Compatibility: go >= 1.21
No breaking changes will be made to exported APIs before v2.0.0.
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
WithClientIP bool
WithCustomMessage func(w http.ResponseWriter, r *http.Request) string
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"
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
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)
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)
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)
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:
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
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
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)
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
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"}}
Don't hesitate ;)
# Install some dev dependencies
make tools
# Run tests
make test
# or
make watch-test
Give a βοΈ if this project helped you!
Copyright Β© 2023 Samuel Berthe.
This project is MIT licensed.
258 followers Β· starred Apr 2025
127 followers Β· starred Dec 2024
Go
95.6%
Makefile
4.4%
π¨ net/http middleware for slog logger
See the codenet/http handler to log HTTP requests using slog.
See also:
slog.Handler chaining, fanout, routing, failover, load balancing...slog attribute formattingslog sampling policyslog.Handler for test purposesHTTP middlewares:
slog loggerslog loggerslog loggerslog loggernet/http middleware for slog loggerLoggers:
slog handler for Zapslog handler for Zerologslog handler for LogrusLog sinks:
slog handler for Datadogslog handler for Betterstackslog handler for Rollbarslog handler for Lokislog handler for Sentryslog handler for Syslogslog handler for Logstashslog handler for Fluentdslog handler for Graylogslog handler for Quickwitslog handler for Slackslog handler for Telegramslog handler for Mattermostslog handler for Microsoft Teamsslog handler for Webhookslog handler for Kafkaslog handler for NATSslog handler for Parquet + Object Storageslog handler for Go channelsgo get github.com/samber/slog-http
Compatibility: go >= 1.21
No breaking changes will be made to exported APIs before v2.0.0.
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
WithClientIP bool
WithCustomMessage func(w http.ResponseWriter, r *http.Request) string
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"
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
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)
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)
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)
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:
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
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
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)
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
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"}}
Don't hesitate ;)
# Install some dev dependencies
make tools
# Run tests
make test
# or
make watch-test
Give a βοΈ if this project helped you!
Copyright Β© 2023 Samuel Berthe.
This project is MIT licensed.
258 followers Β· starred Apr 2025
127 followers Β· starred Dec 2024
Go
95.6%
Makefile
4.4%