# README
slog: Echo middleware
Echo middleware 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
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
# echo v4 (current)
go get github.com/samber/slog-echo
# echo v5 (alpha)
go get github.com/samber/slog-echo@echo-v5
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:
slogecho.TraceIDKey = "trace_id"
slogecho.SpanIDKey = "span_id"
slogecho.RequestBodyMaxSize = 64 * 1024 // 64KB
slogecho.ResponseBodyMaxSize = 64 * 1024 // 64KB
slogecho.HiddenRequestHeaders = map[string]struct{}{ ... }
slogecho.HiddenResponseHeaders = map[string]struct{}{ ... }
Minimal
import (
"net/http"
"os"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
slogecho "github.com/samber/slog-echo"
"log/slog"
)
// Create a slog logger, which:
// - Logs to stdout.
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
// Echo instance
e := echo.New()
// Middleware
e.Use(slogecho.New(logger))
e.Use(middleware.Recover())
// Routes
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.GET("/error", func(c echo.Context) error {
return echo.
NewHTTPError(http.StatusInternalServerError, "I'm angry").
WithInternal(errors.New("I'm angry internally"))
})
// Start server
e.Logger.Fatal(e.Start(":4242"))
// 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 http.error="map[code:500 internal:I'm angry internally message:I'm angry]" http.internal="I'm angry internally"
OTEL
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
config := slogecho.Config{
WithSpanID: true,
WithTraceID: true,
}
e := echo.New()
e.Use(slogecho.NewWithConfig(logger, config))
e.Use(middleware.Recover())
Custom log levels
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
config := slogecho.Config{
DefaultLevel: slog.LevelInfo,
ClientErrorLevel: slog.LevelWarn,
ServerErrorLevel: slog.LevelError,
}
e := echo.New()
e.Use(slogecho.NewWithConfig(logger, config))
e.Use(middleware.Recover())
Verbose
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
config := slogecho.Config{
WithRequestBody: true,
WithResponseBody: true,
WithRequestHeader: true,
WithResponseHeader: true,
}
e := echo.New()
e.Use(slogecho.NewWithConfig(logger, config))
e.Use(middleware.Recover())
Filters
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
e := echo.New()
e.Use(
slogecho.NewWithFilters(
logger,
slogecho.Accept(func (c echo.Context) bool {
return xxx
}),
slogecho.IgnoreStatus(401, 404),
),
)
e.Use(middleware.Recover())
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 (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
slogecho "github.com/samber/slog-echo"
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),
),
)
// Echo instance
e := echo.New()
// Middleware
e.Use(slogecho.New(logger))
e.Use(middleware.Recover())
// Routes
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.GET("/error", func(c echo.Context) error {
return echo.
NewHTTPError(http.StatusInternalServerError, "I'm angry").
WithInternal(errors.New("I'm angry internally"))
})
// Start server
e.Logger.Fatal(e.Start(":4242"))
// 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 error="map[code:500 internal:I'm angry internally message:I'm angry]" internal="I'm angry internally"
Using custom logger sub-group
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
// Echo instance
e := echo.New()
// Middleware
e.Use(slogecho.New(logger.WithGroup("http")))
e.Use(middleware.Recover())
// Routes
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.GET("/error", func(c echo.Context) error {
return echo.
NewHTTPError(http.StatusInternalServerError, "I'm angry").
WithInternal(errors.New("I'm angry internally"))
})
// Start server
e.Logger.Fatal(e.Start(":4242"))
// 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 http.error="map[code:500 internal:I'm angry internally message:I'm angry]" http.internal="I'm angry internally"
Add logger to a single route
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
// Echo instance
e := echo.New()
// Middleware
e.Use(middleware.Recover())
// Routes
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}, slogecho.New(logger))
// Start server
e.Logger.Fatal(e.Start(":4242"))
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")
// Echo instance
e := echo.New()
// Middleware
e.Use(slogecho.New(logger))
e.Use(middleware.Recover())
// Routes
e.GET("/", func(c echo.Context) error {
// Add an attribute to a single log entry.
slogecho.AddCustomAttributes(c, slog.String("foo", "bar"))
return c.String(http.StatusOK, "Hello, World!")
})
// Start server
e.Logger.Fatal(e.Start(":4242"))
// 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 error="map[code:500 internal:I'm angry internally message:I'm angry]" internal="I'm angry internally"
JSON output
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// Echo instance
e := echo.New()
// Middleware
e.Use(slogecho.New(logger))
e.Use(middleware.Recover())
// Routes
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
// Start server
e.Logger.Fatal(e.Start(":4242"))
// 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"}, "error": {"code":500, "internal":"I'm angry internally", "message":"I'm angry"}, "internal": "I'm angry internally"}
🤝 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.
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 echo.MiddlewareFunc (middleware) that logs requests using slog.
NewWithConfig returns a echo.HandlerFunc (middleware) that logs requests using slog.
NewWithFilters returns a echo.MiddlewareFunc (middleware) that logs requests using slog.
# Variables
No description provided by the author
No description provided by the author
64KB.
No description provided by the author
64KB.
No description provided by the author
No description provided by the author
# Type aliases
No description provided by the author