The hlog subpackage provides HTTP middleware for zerolog, making it easy to add request logging with automatic metadata capture. All middleware functions follow the standard Go http.Handler pattern.
import (
"net/http"
"github.com/rs/zerolog/hlog"
)Inject a logger into the request context.
// Inject logger into request context
func NewHandler(log zerolog.Logger) func(http.Handler) http.HandlerExample:
logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
handler := hlog.NewHandler(logger)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
hlog.FromRequest(r).Info().Msg("handling request")
}))
http.ListenAndServe(":8080", handler)Retrieve the logger from the request context.
// Get logger from request context
func FromRequest(r *http.Request) *zerolog.LoggerExample:
func myHandler(w http.ResponseWriter, r *http.Request) {
logger := hlog.FromRequest(r)
logger.Info().Msg("processing request")
}This is a convenience wrapper around zerolog.Ctx(r.Context()).
These handlers add request metadata to the logger's context.
Generate and track unique request IDs.
// Add request ID to logger context and optionally to response header
func RequestIDHandler(fieldKey, headerName string) func(next http.Handler) http.HandlerParameters:
fieldKey - Field name for logger (empty to skip logging)headerName - Response header name (empty to skip header)Example:
chain := hlog.NewHandler(logger)
chain = hlog.RequestIDHandler("request_id", "X-Request-ID")(chain)
http.ListenAndServe(":8080", chain)
// Each request gets unique ID logged and sent in headerRetrieving request ID:
// Get request ID from request
func IDFromRequest(r *http.Request) (id xid.ID, ok bool)
// Get request ID from context
func IDFromCtx(ctx context.Context) (id xid.ID, ok bool)
// Add request ID to context
func CtxWithID(ctx context.Context, id xid.ID) context.ContextExample:
func handler(w http.ResponseWriter, r *http.Request) {
if id, ok := hlog.IDFromRequest(r); ok {
fmt.Fprintf(w, "Request ID: %s", id)
}
}Add the request URL to the logger context.
// Add request URL to logger context
func URLHandler(fieldKey string) func(next http.Handler) http.HandlerExample:
chain := hlog.NewHandler(logger)
chain = hlog.URLHandler("url")(chain)
// Logs include: "url":"/api/users?page=1"Add the HTTP method to the logger context.
// Add HTTP method to logger context
func MethodHandler(fieldKey string) func(next http.Handler) http.HandlerExample:
chain := hlog.MethodHandler("method")(chain)
// Logs include: "method":"GET"Add both method and URL to the logger context.
// Add "METHOD URL" to logger context
func RequestHandler(fieldKey string) func(next http.Handler) http.HandlerExample:
chain := hlog.RequestHandler("request")(chain)
// Logs include: "request":"GET /api/users"Add the remote address (IP and port) to the logger context.
// Add remote address to logger context
func RemoteAddrHandler(fieldKey string) func(next http.Handler) http.HandlerExample:
chain := hlog.RemoteAddrHandler("remote")(chain)
// Logs include: "remote":"192.168.1.100:54321"Add only the remote IP (without port) to the logger context.
// Add remote IP only to logger context
func RemoteIPHandler(fieldKey string) func(next http.Handler) http.HandlerExample:
chain := hlog.RemoteIPHandler("ip")(chain)
// Logs include: "ip":"192.168.1.100"Add the User-Agent header to the logger context.
// Add User-Agent to logger context
func UserAgentHandler(fieldKey string) func(next http.Handler) http.HandlerExample:
chain := hlog.UserAgentHandler("user_agent")(chain)
// Logs include: "user_agent":"Mozilla/5.0..."Add the Referer header to the logger context.
// Add Referer to logger context
func RefererHandler(fieldKey string) func(next http.Handler) http.HandlerExample:
chain := hlog.RefererHandler("referer")(chain)
// Logs include: "referer":"https://example.com/page"Add the request host to the logger context.
// Add host to logger context (optionally trim port)
func HostHandler(fieldKey string, trimPort ...bool) func(next http.Handler) http.HandlerExample:
// With port
chain := hlog.HostHandler("host")(chain)
// Logs include: "host":"example.com:8080"
// Without port
chain := hlog.HostHandler("host", true)(chain)
// Logs include: "host":"example.com"Add the protocol version to the logger context.
// Add protocol version (e.g., "HTTP/1.1") to logger context
func ProtoHandler(fieldKey string) func(next http.Handler) http.HandlerExample:
chain := hlog.ProtoHandler("protocol")(chain)
// Logs include: "protocol":"HTTP/1.1"Add the HTTP version without the "HTTP/" prefix.
// Add HTTP version (e.g., "1.1") to logger context
func HTTPVersionHandler(fieldKey string) func(next http.Handler) http.HandlerExample:
chain := hlog.HTTPVersionHandler("http_version")(chain)
// Logs include: "http_version":"1.1"Add any custom request header to the logger context.
// Add custom request header to logger context
func CustomHeaderHandler(fieldKey, header string) func(next http.Handler) http.HandlerExample:
chain := hlog.CustomHeaderHandler("correlation_id", "X-Correlation-ID")(chain)
// Logs include: "correlation_id":"abc-123"Add the Etag from the response headers to the logger context.
// Add Etag from response to logger context
func EtagHandler(fieldKey string) func(next http.Handler) http.HandlerExample:
chain := hlog.EtagHandler("etag")(chain)
// Logs include: "etag":"33a64df551425fcc55e4d42a148795d9f25f89d4"Add any custom response header to the logger context.
// Add custom response header to logger context
func ResponseHeaderHandler(fieldKey, headerName string) func(next http.Handler) http.HandlerExample:
chain := hlog.ResponseHeaderHandler("content_type", "Content-Type")(chain)
// Logs include: "content_type":"application/json"Log HTTP access information after each request.
// Call function after each request with access information
func AccessHandler(f func(r *http.Request, status, size int, duration time.Duration)) func(next http.Handler) http.HandlerExample:
chain := hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) {
hlog.FromRequest(r).Info().
Int("status", status).
Int("size", size).
Dur("duration", duration).
Msg("request completed")
})(chain)Complete access logging:
accessLogger := func(r *http.Request, status, size int, duration time.Duration) {
logger := hlog.FromRequest(r)
var event *zerolog.Event
if status >= 500 {
event = logger.Error()
} else if status >= 400 {
event = logger.Warn()
} else {
event = logger.Info()
}
event.
Int("status", status).
Int("size", size).
Dur("duration", duration).
Msg("access")
}
handler := hlog.AccessHandler(accessLogger)(myHandler)package main
import (
"net/http"
"os"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/hlog"
)
func main() {
// Create logger
logger := zerolog.New(os.Stdout).With().
Timestamp().
Str("service", "api").
Logger()
// Define handler
myHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get logger from request
hlog.FromRequest(r).Info().Msg("handling request")
// Your handler logic
w.WriteHeader(http.StatusOK)
w.Write([]byte("Hello World"))
})
// Chain middleware
handler := hlog.NewHandler(logger)(myHandler)
handler = hlog.RequestIDHandler("request_id", "X-Request-ID")(handler)
handler = hlog.MethodHandler("method")(handler)
handler = hlog.URLHandler("url")(handler)
handler = hlog.RemoteIPHandler("ip")(handler)
handler = hlog.UserAgentHandler("user_agent")(handler)
handler = hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) {
hlog.FromRequest(r).Info().
Int("status", status).
Int("size", size).
Dur("duration", duration).
Msg("access")
})(handler)
// Start server
http.ListenAndServe(":8080", handler)
}Output example:
{
"level":"info",
"service":"api",
"request_id":"c0umghhqb5jnvr5jq9kg",
"method":"GET",
"url":"/api/users",
"ip":"192.168.1.100",
"user_agent":"Mozilla/5.0...",
"time":"2024-01-15T10:30:00Z",
"message":"handling request"
}
{
"level":"info",
"service":"api",
"request_id":"c0umghhqb5jnvr5jq9kg",
"method":"GET",
"url":"/api/users",
"ip":"192.168.1.100",
"user_agent":"Mozilla/5.0...",
"status":200,
"size":11,
"duration":5.123,
"time":"2024-01-15T10:30:00Z",
"message":"access"
}Order matters when chaining middleware. Generally:
handler := myHandler
handler = hlog.AccessHandler(accessLog)(handler)
handler = hlog.UserAgentHandler("user_agent")(handler)
handler = hlog.RemoteIPHandler("ip")(handler)
handler = hlog.URLHandler("url")(handler)
handler = hlog.MethodHandler("method")(handler)
handler = hlog.RequestIDHandler("request_id", "X-Request-ID")(handler)
handler = hlog.NewHandler(logger)(handler)Note: Middleware wraps from bottom to top in the chain above.
func setupRoutes(logger zerolog.Logger) http.Handler {
mux := http.NewServeMux()
// Apply middleware to all routes
handler := hlog.NewHandler(logger)(mux)
handler = hlog.RequestIDHandler("request_id", "X-Request-ID")(handler)
handler = hlog.AccessHandler(accessLog)(handler)
// Add routes
mux.HandleFunc("/api/users", usersHandler)
mux.HandleFunc("/api/posts", postsHandler)
return handler
}mux := http.NewServeMux()
// Global middleware
handler := hlog.NewHandler(logger)(mux)
handler = hlog.RequestIDHandler("request_id", "X-Request-ID")(handler)
// Per-route middleware
adminHandler := http.HandlerFunc(adminFunc)
adminHandler = hlog.CustomHeaderHandler("admin_id", "X-Admin-ID")(adminHandler)
mux.Handle("/admin", adminHandler)
mux.HandleFunc("/public", publicFunc)var handler http.Handler = myHandler
if production {
// Production: minimal logging
handler = hlog.NewHandler(logger)(handler)
handler = hlog.RequestIDHandler("request_id", "X-Request-ID")(handler)
handler = hlog.AccessHandler(accessLog)(handler)
} else {
// Development: detailed logging
handler = hlog.NewHandler(logger)(handler)
handler = hlog.RequestIDHandler("request_id", "X-Request-ID")(handler)
handler = hlog.MethodHandler("method")(handler)
handler = hlog.URLHandler("url")(handler)
handler = hlog.RemoteIPHandler("ip")(handler)
handler = hlog.UserAgentHandler("user_agent")(handler)
handler = hlog.RefererHandler("referer")(handler)
handler = hlog.AccessHandler(verboseAccessLog)(handler)
}Chi router:
import "github.com/go-chi/chi/v5"
r := chi.NewRouter()
r.Use(hlog.NewHandler(logger))
r.Use(hlog.RequestIDHandler("request_id", "X-Request-ID"))
r.Use(hlog.AccessHandler(accessLog))
r.Get("/", homeHandler)Gorilla mux:
import "github.com/gorilla/mux"
r := mux.NewRouter()
r.Use(hlog.NewHandler(logger))
r.Use(hlog.RequestIDHandler("request_id", "X-Request-ID"))
r.Use(hlog.AccessHandler(accessLog))
r.HandleFunc("/", homeHandler)NewHandler must be first in the chain to inject the logger:
// Good
handler := hlog.NewHandler(logger)(myHandler)
handler = hlog.RequestIDHandler("request_id", "X-Request-ID")(handler)
// Bad - RequestIDHandler won't find logger
handler := hlog.RequestIDHandler("request_id", "X-Request-ID")(myHandler)
handler = hlog.NewHandler(logger)(handler)Generate request IDs early so they're available in all logs:
handler := hlog.NewHandler(logger)(myHandler)
handler = hlog.RequestIDHandler("request_id", "X-Request-ID")(handler)
// RequestID now available to all subsequent middleware and handlersCombine access logging with metrics:
accessHandler := func(r *http.Request, status, size int, duration time.Duration) {
// Log
hlog.FromRequest(r).Info().
Int("status", status).
Dur("duration", duration).
Msg("access")
// Metrics
metrics.RequestDuration.Observe(duration.Seconds())
metrics.RequestCount.WithLabelValues(strconv.Itoa(status)).Inc()
}
handler = hlog.AccessHandler(accessHandler)(handler)Use consistent field names across your application:
const (
RequestIDField = "request_id"
MethodField = "method"
URLField = "url"
StatusField = "status"
DurationField = "duration"
)
handler = hlog.RequestIDHandler(RequestIDField, "X-Request-ID")(handler)
handler = hlog.MethodHandler(MethodField)(handler)Avoid logging headers that may contain sensitive data:
// Good
handler = hlog.UserAgentHandler("user_agent")(handler)
handler = hlog.CustomHeaderHandler("api_version", "X-API-Version")(handler)
// Avoid
handler = hlog.CustomHeaderHandler("auth", "Authorization")(handler) // BAD
handler = hlog.CustomHeaderHandler("cookie", "Cookie")(handler) // BADUpdateContext is used internally (not thread-safe, but safe here)github.com/rs/xid (fast, unique)All hlog middleware is thread-safe and can be used with concurrent requests. The logger is copied per-request to avoid data races.