Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

samber/slog-chi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

89 Commits

Repository files navigation

slog: Chi middleware

tag Go Version GoDoc Build Status Go report Coverage Contributors License

Chi middleware to log http requests using slog.

See also:

HTTP middlewares:

Loggers:

Log sinks:

πŸš€ Install

go get github.com/samber/slog-chi

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
	WithClientIP bool
	WithCustomMessage func(w http.ResponseWriter, r *http.Request) string
	Filters []Filter
}

Attributes will be injected in log payload.

Other global parameters:

slogchi.TraceIDKey = "trace_id"
slogchi.SpanIDKey = "span_id"
slogchi.RequestIDKey = "id"
slogchi.RequestBodyMaxSize = 64 * 1024 // 64KB
slogchi.ResponseBodyMaxSize = 64 * 1024 // 64KB
slogchi.HiddenRequestHeaders = map[string]struct{}{ ... }
slogchi.HiddenResponseHeaders = map[string]struct{}{ ... }

Minimal

import (
	"net/http"
	"os"
	"time"
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	slogchi "github.com/samber/slog-chi"
	"log/slog"
)
// Create a slog logger, which:
// - Logs to stdout.
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
// Chi instance
router := chi.NewRouter()
// Middleware
router.Use(slogchi.New(logger))
router.Use(middleware.Recoverer)
// Routes
router.GET("/", func(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, World!"))
})
router.GET("/error", func(w http.ResponseWriter, r *http.Request) {
	http.Error(w, http.StatusText(400), 400)
})
// Start server
err := http.ListenAndServe(":4242", router)
// output:
// time=2023εΉ΄10月15ζ—₯T20:32:58.926+02:00 level=INFO msg="200: OK" env=production request.time=2023εΉ΄10月15ζ—₯T20: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月15ζ—₯T20:32:58.926+02:00 response.latency=100ms response.status=200 response.length=7 id=""

OTEL

logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
config := slogchi.Config{
	WithSpanID: true,
	WithTraceID: true,
}
router := chi.NewRouter()
router.Use(slogchi.NewWithConfig(logger, config))
router.Use(middleware.Recoverer)

Custom log levels

logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
config := slogchi.Config{
	DefaultLevel: slog.LevelInfo,
	ClientErrorLevel: slog.LevelWarn,
	ServerErrorLevel: slog.LevelError,
}
router := chi.NewRouter()
router.Use(slogchi.NewWithConfig(logger, config))
router.Use(middleware.Recoverer)

Verbose

logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
config := slogchi.Config{
	WithRequestBody: true,
	WithResponseBody: true,
	WithRequestHeader: true,
	WithResponseHeader: true,
}
router := chi.NewRouter()
router.Use(slogchi.NewWithConfig(logger, config))
router.Use(middleware.Recoverer)

Filters

logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
router := chi.NewRouter()
router.Use(
	slogchi.NewWithFilters(
		logger,
		slogchi.Accept(func (ww middleware.WrapResponseWriter, r *http.Request) bool {
			return xxx
		}),
		slogchi.IgnoreStatus(401, 404),
	),
)
router.Use(middleware.Recoverer)

Available filters:

  • Accept / Ignore
  • AcceptMethod / IgnoreMethod
  • AcceptStatus / IgnoreStatus
  • AcceptStatusGreaterThan / IgnoreStatusGreaterThan
  • AcceptStatusLessThan / IgnoreStatusLessThan
  • AcceptStatusGreaterThanOrEqual / IgnoreStatusGreaterThanOrEqual
  • AcceptStatusLessThanOrEqual / 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 (
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	slogchi "github.com/samber/slog-chi"
	slogformatter "github.com/samber/slog-formatter"
	"log/slog"
)
// 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),
	),
)
// Chi instance
router := chi.NewRouter()
// Middleware
router.Use(slogchi.New(logger))
router.Use(middleware.Recoverer)
// Routes
router.GET("/", func(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, World!"))
})
router.GET("/error", func(w http.ResponseWriter, r *http.Request) {
	http.Error(w, http.StatusText(400), 400)
})
// Start server
err := http.ListenAndServe(":4242", router)
// output:
// time=2023εΉ΄10月15ζ—₯T20:32:58.926+02:00 level=INFO msg="200: OK" env=production request.time=2023εΉ΄10月15ζ—₯T20:32:58.626Z request.method=GET request.path=/ request.route="" request.ip=127.0.0.1:63932 request.length=0 response.time=2023εΉ΄10月15ζ—₯T20:32:58Z response.latency=100ms response.status=200 response.length=7 id=""

Using custom logger sub-group

logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
// Chi instance
router := chi.NewRouter()
// Middleware
router.Use(slogchi.New(logger.WithGroup("http")))
router.Use(middleware.Recoverer)
// Routes
router.GET("/", func(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, World!"))
})
router.GET("/error", func(w http.ResponseWriter, r *http.Request) {
	http.Error(w, http.StatusText(400), 400)
})
// Start server
err := http.ListenAndServe(":4242", router)
// output:
// time=2023εΉ΄10月15ζ—₯T20:32:58.926+02:00 level=INFO msg="200: OK" env=production http.request.time=2023εΉ΄10月15ζ—₯T20: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月15ζ—₯T20:32:58.926+02:00 http.response.latency=100ms http.response.status=200 http.response.length=7 http.id=""

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")
// Chi instance
router := chi.NewRouter()
// Middleware
router.Use(slogchi.New(logger))
router.Use(middleware.Recoverer)
// Routes
router.GET("/", func(w http.ResponseWriter, r *http.Request) {
	// Add an attribute to a single log entry.
	slogchi.AddCustomAttributes(r, slog.String("foo", "bar"))
	w.Write([]byte("Hello, World!"))
})
// Start server
err := http.ListenAndServe(":4242", router)
// output:
// time=2023εΉ΄10月15ζ—₯T20:32:58.926+02:00 level=INFO msg="200: OK" env=production request.time=2023εΉ΄10月15ζ—₯T20: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月15ζ—₯T20:32:58.926+02:00 response.latency=100ms response.status=200 response.length=7 id="" foo=bar

JSON output

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// Chi instance
router := chi.NewRouter()
// Middleware
router.Use(slogchi.New(logger))
router.Use(middleware.Recoverer)
// Routes
router.GET("/", func(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, World!"))
})
// Start server
err := http.ListenAndServe(":4242", router)
// output:
// {"time":"2023-10-15T20:32:58.926+02:00","level":"INFO","msg":"200: OK","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":""}}

🀝 Contributing

Don't hesitate ;)

# Install some dev dependencies
make tools
# Run tests
make test
# or
make watch-test

πŸ‘€ Contributors

Contributors

πŸ’« Show your support

Give a ⭐️ if this project helped you!

GitHub Sponsors

πŸ“ License

Copyright Β© 2023 Samuel Berthe.

This project is MIT licensed.

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /