slogecho

package module
v1.23.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 29, 2026 License: MIT Imports: 15 Imported by: 101

README

slog: Echo middleware

tag Go Version GoDoc Build Status Go report Coverage Contributors License

Echo middleware to log http requests using slog.

See also:

HTTP middlewares:

Loggers:

Log sinks:

🚀 Install

# echo v5 (current)
go get github.com/samber/slog-echo

# echo v4 (legacy)
go get github.com/samber/slog-echo@echo-v4

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(c echo.Context, err error) string

	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:

Filters
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))

router := gin.New()
router.Use(
	sloggin.NewWithFilters(
		logger,
		sloggin.Accept(func (c *gin.Context) bool {
			return xxx
		}),
		sloggin.IgnoreStatus(401, 404),
	),
)

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/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

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.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	TraceIDKey   = "trace_id"
	SpanIDKey    = "span_id"
	RequestIDKey = "id"

	RequestBodyMaxSize  = 64 * 1024 // 64KB
	ResponseBodyMaxSize = 64 * 1024 // 64KB

	HiddenRequestHeaders = map[string]struct{}{
		"authorization": {},
		"cookie":        {},
		"set-cookie":    {},
		"x-auth-token":  {},
		"x-csrf-token":  {},
		"x-xsrf-token":  {},
	}
	HiddenResponseHeaders = map[string]struct{}{
		"set-cookie": {},
	}
)

Functions

func AddCustomAttributes added in v1.5.0

func AddCustomAttributes(c echo.Context, attrs ...slog.Attr)

AddCustomAttributes adds custom attributes to the request context.

func New

func New(logger *slog.Logger) echo.MiddlewareFunc

New returns a echo.MiddlewareFunc (middleware) that logs requests using slog.

Requests with errors are logged using slog.Error(). Requests without errors are logged using slog.Info().

func NewWithConfig

func NewWithConfig(logger *slog.Logger, config Config) echo.MiddlewareFunc

NewWithConfig returns a echo.HandlerFunc (middleware) that logs requests using slog.

func NewWithFilters added in v1.3.0

func NewWithFilters(logger *slog.Logger, filters ...Filter) echo.MiddlewareFunc

NewWithFilters returns a echo.MiddlewareFunc (middleware) that logs requests using slog.

Requests with errors are logged using slog.Error(). Requests without errors are logged using slog.Info().

Types

type Config

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(c echo.Context, err error) string

	Filters []Filter
}

func DefaultConfig added in v1.19.0

func DefaultConfig() Config

DefaultConfig returns the default configuration for the request logger.

type Filter added in v1.3.0

type Filter func(ctx echo.Context) bool

func Accept added in v1.3.0

func Accept(filter Filter) Filter

Basic

func AcceptHost added in v1.3.0

func AcceptHost(hosts ...string) Filter

Host

func AcceptHostContains added in v1.3.0

func AcceptHostContains(parts ...string) Filter

func AcceptHostMatch added in v1.3.0

func AcceptHostMatch(regs ...regexp.Regexp) Filter

func AcceptHostPrefix added in v1.3.0

func AcceptHostPrefix(prefixs ...string) Filter

func AcceptHostSuffix added in v1.3.0

func AcceptHostSuffix(prefixs ...string) Filter

func AcceptMethod added in v1.3.0

func AcceptMethod(methods ...string) Filter

Method

func AcceptPath added in v1.3.0

func AcceptPath(urls ...string) Filter

Path

func AcceptPathContains added in v1.3.0

func AcceptPathContains(parts ...string) Filter

func AcceptPathMatch added in v1.3.0

func AcceptPathMatch(regs ...regexp.Regexp) Filter

func AcceptPathPrefix added in v1.3.0

func AcceptPathPrefix(prefixs ...string) Filter

func AcceptPathSuffix added in v1.3.0

func AcceptPathSuffix(prefixs ...string) Filter

func AcceptStatus added in v1.3.0

func AcceptStatus(statuses ...int) Filter

Status

func AcceptStatusGreaterThan added in v1.3.0

func AcceptStatusGreaterThan(status int) Filter

func AcceptStatusGreaterThanOrEqual added in v1.3.0

func AcceptStatusGreaterThanOrEqual(status int) Filter

func AcceptStatusLessThan added in v1.15.1

func AcceptStatusLessThan(status int) Filter

func AcceptStatusLessThanOrEqual added in v1.15.1

func AcceptStatusLessThanOrEqual(status int) Filter

func Ignore added in v1.3.0

func Ignore(filter Filter) Filter

func IgnoreHost added in v1.3.0

func IgnoreHost(hosts ...string) Filter

func IgnoreHostContains added in v1.3.0

func IgnoreHostContains(parts ...string) Filter

func IgnoreHostMatch added in v1.3.0

func IgnoreHostMatch(regs ...regexp.Regexp) Filter

func IgnoreHostPrefix added in v1.3.0

func IgnoreHostPrefix(prefixs ...string) Filter

func IgnoreHostSuffix added in v1.3.0

func IgnoreHostSuffix(suffixs ...string) Filter

func IgnoreMethod added in v1.3.0

func IgnoreMethod(methods ...string) Filter

func IgnorePath added in v1.3.0

func IgnorePath(urls ...string) Filter

func IgnorePathContains added in v1.3.0

func IgnorePathContains(parts ...string) Filter

func IgnorePathMatch added in v1.3.0

func IgnorePathMatch(regs ...regexp.Regexp) Filter

func IgnorePathPrefix added in v1.3.0

func IgnorePathPrefix(prefixs ...string) Filter

func IgnorePathSuffix added in v1.3.0

func IgnorePathSuffix(suffixs ...string) Filter

func IgnoreStatus added in v1.3.0

func IgnoreStatus(statuses ...int) Filter

func IgnoreStatusGreaterThan added in v1.15.1

func IgnoreStatusGreaterThan(status int) Filter

func IgnoreStatusGreaterThanOrEqual added in v1.15.1

func IgnoreStatusGreaterThanOrEqual(status int) Filter

func IgnoreStatusLessThan added in v1.3.0

func IgnoreStatusLessThan(status int) Filter

func IgnoreStatusLessThanOrEqual added in v1.3.0

func IgnoreStatusLessThanOrEqual(status int) Filter

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL