labstack/echo · error

panic: err from config.ToMiddleware() in RequestLoggerWithCo

Error message

panic: err from config.ToMiddleware() in RequestLoggerWithConfig

What it means

This panic is raised by RequestLoggerWithConfig when config.ToMiddleware() returns a non-nil error. The only validation ToMiddleware performs is at request_logger.go:255-257: it requires config.LogValuesFunc to be non-nil (the field doc at line 131 explicitly marks it 'Mandatory'). Because RequestLoggerWithConfig has no way to surface the error to the caller, it panics with the underlying error value rather than propagating it through the Echo middleware registration path.

Source

Thrown at middleware/request_logger.go:240

	ResponseSize int64
	// Headers are list of headers from request. Note: request can contain more than one header with same value so slice
	// of values is what will be returned/logged for each given header.
	// Note: header values are converted to canonical form with http.CanonicalHeaderKey as this how request parser converts header
	// names to. For example, the canonical key for "accept-encoding" is "Accept-Encoding".
	Headers map[string][]string
	// QueryParams are list of query parameters from request URI. Note: request can contain more than one query parameter
	// with same name so slice of values is what will be returned/logged for each given query param name.
	QueryParams map[string][]string
	// FormValues are list of form values from request body+URI. Note: request can contain more than one form value with
	// same name so slice of values is what will be returned/logged for each given form value name.
	FormValues map[string][]string
}

// RequestLoggerWithConfig returns a RequestLogger middleware with config.
func RequestLoggerWithConfig(config RequestLoggerConfig) echo.MiddlewareFunc {
	mw, err := config.ToMiddleware()
	if err != nil {
		panic(err)
	}
	return mw
}

// ToMiddleware converts RequestLoggerConfig into middleware or returns an error for invalid configuration.
func (config RequestLoggerConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
	if config.Skipper == nil {
		config.Skipper = DefaultSkipper
	}
	now := time.Now
	if config.timeNow != nil {
		now = config.timeNow
	}

	if config.LogValuesFunc == nil {
		return nil, errors.New("missing LogValuesFunc callback function for request logger middleware")
	}

View on GitHub (pinned to 05489dc173)

Solutions

  1. Set the LogValuesFunc field in your RequestLoggerConfig — it is mandatory and must be a non-nil func(c *echo.Context, v middleware.RequestLoggerValues) error.
  2. If you want default behavior with no custom sink, use the zero-argument middleware.RequestLogger() constructor (request_logger.go:395) which wires its own LogValuesFunc to c.Logger() for you.
  3. If configuration is built dynamically or from external input, call config.ToMiddleware() directly so you receive the error instead of panicking, then handle or log it before registering the middleware.
  4. Add a nil check before e.Use(): if cfg.LogValuesFunc == nil { return fmt.Errorf("LogValuesFunc is required") }.

Example fix

// before
e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
    LogStatus: true,
    LogURI:    true,
}))

// after
e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
    LogStatus: true,
    LogURI:    true,
    LogValuesFunc: func(c *echo.Context, v middleware.RequestLoggerValues) error {
        fmt.Printf("uri=%s status=%d\n", v.URI, v.Status)
        return nil
    },
}))
Defensive patterns

Strategy: validation

Validate before calling

// Validate before registering the middleware
func buildRequestLogger(cfg middleware.RequestLoggerConfig) (echo.MiddlewareFunc, error) {
    if cfg.LogValuesFunc == nil {
        return nil, errors.New("RequestLoggerConfig.LogValuesFunc is mandatory")
    }
    return cfg.ToMiddleware() // returns (mw, err) instead of panicking
}

// usage
mw, err := buildRequestLogger(cfg)
if err != nil {
    log.Fatal(err)
}
e.Use(mw)

Type guard

// Compile-time guarantee that the config carries a non-nil callback.
// (Go has no structural type guard; use a constructor that enforces it.)
func NewRequestLogger(logValuesFunc func(c *echo.Context, v middleware.RequestLoggerValues) error, opts ...func(*middleware.RequestLoggerConfig)) echo.MiddlewareFunc {
    if logValuesFunc == nil {
        panic("NewRequestLogger: logValuesFunc must not be nil")
    }
    cfg := middleware.RequestLoggerConfig{LogValuesFunc: logValuesFunc}
    for _, opt := range opts {
        opt(&cfg)
    }
    mw, err := cfg.ToMiddleware()
    if err != nil {
        panic(err)
    }
    return mw
}

Try / catch

// Go has no try/catch; recover in main() only as a last-resort guard during
// application bootstrap. Prefer validation. If you must isolate panics during
// wiring, wrap e.Use calls:
func safeUse(e *echo.Echo, name string, build func() echo.MiddlewareFunc) {
    defer func() {
        if r := recover(); r != nil {
            log.Fatalf("failed to register %s: %v", name, r)
        }
    }()
    e.Use(build())
}

safeUse(e, "request-logger", func() echo.MiddlewareFunc {
    return middleware.RequestLoggerWithConfig(cfg)
})

Prevention

When it happens

Trigger: Calling middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{...}) with a struct literal that omits the LogValuesFunc field, or sets it to nil. This commonly happens when a developer copies a Logger() config (which needs no callback) and forgets that the RequestLogger variant is callback-driven, or when LogValuesFunc is conditionally assigned (e.g. behind an env flag) and the branch that leaves it unset executes at startup.

Common situations: Migrating from the older Logger()/LoggerWithConfig middleware to the newer RequestLogger API and assuming it has sane defaults; refactoring that accidentally drops the LogValuesFunc assignment; wiring the logger conditionally (dev vs prod) where one path leaves it nil; initializing the config in one place and assigning the callback in another that hasn't run yet by the time e.Use() is called.

Related errors


AI-assisted analysis of labstack/echo@05489dc173 (2026-08-04). Data as JSON: /data/errors/c7a7d5f231e9cea2.json. Report an issue: GitHub.