A high-performance Zerolog wrapper for Echo web framework that provides structured logging with minimal overhead.
- π High Performance - Built on top of zerolog, one of the fastest structured loggers for Go
 - π Structured Logging - JSON formatted logs with rich contextual information
 - π Request Correlation - Automatic request ID tracking and context propagation
 - β‘ Low Latency - Minimal overhead request/response logging
 - π Highly Configurable - Extensive middleware configuration options
 - π Request Enrichment - Add custom fields based on request context
 - π Performance Monitoring - Built-in slow request detection and alerting
 
Install lecho based on your Echo version:
For Echo v4 (recommended):
go get github.com/ziflex/lecho/v3For Echo v3 (legacy):
go get github.com/ziflex/lechoReplace Echo's default logger with lecho for structured logging:
package main
import (
	"os"
	"github.com/labstack/echo/v4"
	"github.com/ziflex/lecho/v3"
)
func main() {
	e := echo.New()
	e.Logger = lecho.New(os.Stdout)
	
	// Your routes and middleware here
	e.Start(":8080")
}If you already have a zerolog logger configured, you can wrap it with lecho:
package main
import (
	"os"
	"github.com/labstack/echo/v4"
	"github.com/rs/zerolog"
	"github.com/ziflex/lecho/v3"
)
func main() {
	// Configure your zerolog instance
	log := zerolog.New(os.Stdout).With().Timestamp().Logger()
	
	e := echo.New()
	e.Logger = lecho.From(log)
	
	e.Start(":8080")
}Lecho provides several configuration options to customize logging behavior:
package main
import (
	"os"
	"github.com/labstack/echo/v4"
	"github.com/labstack/gommon/log"
	"github.com/ziflex/lecho/v3"
)
func main() {
	e := echo.New()
	e.Logger = lecho.New(
		os.Stdout,
		lecho.WithLevel(log.DEBUG),                                    // Set log level
		lecho.WithFields(map[string]interface{}{"service": "api"}),    // Add default fields
		lecho.WithTimestamp(),                                         // Add timestamp to logs
		lecho.WithCaller(),                                           // Add caller information
		lecho.WithPrefix("MyApp"),                                    // Add a prefix to logs
		// lecho.WithHook(myHook),                                    // Add custom hooks
		// lecho.WithHookFunc(myHookFunc),                            // Add hook functions
	)
	
	e.Start(":8080")
}WithLevel(level log.Lvl)- Set the minimum log level (DEBUG, INFO, WARN, ERROR, OFF)WithFields(fields map[string]interface{})- Add default fields to all log entriesWithField(key string, value interface{})- Add a single default fieldWithTimestamp()- Include timestamp in log entriesWithCaller()- Include caller file and line informationWithCallerWithSkipFrameCount(count int)- Include caller info with custom skip frame countWithPrefix(prefix string)- Add a prefix field to all log entriesWithHook(hook zerolog.Hook)- Add a custom zerolog hookWithHookFunc(hookFunc zerolog.HookFunc)- Add a custom hook function
The lecho middleware provides automatic request logging with rich contextual information. It integrates seamlessly with Echo's request lifecycle and supports various customization options.
package main
import (
	"net/http"
	"os"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"github.com/labstack/gommon/log"
	"github.com/rs/zerolog"
	"github.com/ziflex/lecho/v3"
)
func main() {
	e := echo.New()
	
	// Create and configure logger
	logger := lecho.New(
		os.Stdout,
		lecho.WithLevel(log.DEBUG),
		lecho.WithTimestamp(),
		lecho.WithCaller(),
	)
	e.Logger = logger
	
	// Add request ID middleware (optional but recommended)
	e.Use(middleware.RequestID())
	
	// Add lecho middleware for request logging
	e.Use(lecho.Middleware(lecho.Config{
		Logger: logger,
	}))
	
	// Example route
	e.GET("/", func(c echo.Context) error {
		// Log using Echo's logger interface
		c.Logger().Info("Processing request")
		
		// Or use zerolog directly from context
		zerolog.Ctx(c.Request().Context()).Info().Msg("Using zerolog interface")
		
		return c.String(http.StatusOK, "Hello, World!")
	})
	
	e.Start(":8080")
}Sample output:
{"level":"info","id":"123e4567-e89b-12d3-a456-426614174000","remote_ip":"127.0.0.1","host":"localhost:8080","method":"GET","uri":"/","user_agent":"curl/7.68.0","status":200,"referer":"","latency":1.234,"latency_human":"1.234ms","bytes_in":"0","bytes_out":"13","time":"2023-10-15T10:30:00Z"}Monitor and highlight slow requests by logging them at a higher level when they exceed a specified duration:
package main
import (
	"os"
	"time"
	"github.com/labstack/echo/v4"
	"github.com/rs/zerolog"
	"github.com/ziflex/lecho/v3"
)
func main() {
	e := echo.New()
	logger := lecho.New(os.Stdout, lecho.WithTimestamp())
	
	e.Use(lecho.Middleware(lecho.Config{
		Logger:              logger,
		RequestLatencyLevel: zerolog.WarnLevel,        // Log level for slow requests
		RequestLatencyLimit: 500 * time.Millisecond,  // Threshold for slow requests
	}))
	
	// Requests taking longer than 500ms will be logged at WARN level
	// instead of the default INFO level
}Output for slow request:
{"level":"warn","remote_ip":"127.0.0.1","method":"GET","uri":"/slow","status":200,"latency":750.123,"latency_human":"750.123ms","time":"2023-10-15T10:30:00Z"}Organize request information under a nested key for better log structure:
package main
import (
	"os"
	"github.com/labstack/echo/v4"
	"github.com/ziflex/lecho/v3"
)
func main() {
	e := echo.New()
	logger := lecho.New(os.Stdout, lecho.WithTimestamp())
	
	e.Use(lecho.Middleware(lecho.Config{
		Logger:  logger,
		NestKey: "request", // Nest all request info under this key
	}))
	
	// All request-related fields will be nested under "request"
}Sample output:
{"level":"info","request":{"remote_ip":"127.0.0.1","method":"GET","uri":"/api/users","status":200,"latency":15.234,"latency_human":"15.234ms"},"time":"2023-10-15T10:30:00Z"}The Enricher function allows you to add custom fields to log entries based on request context. This is useful for adding user IDs, trace IDs, or other contextual information:
package main
import (
	"os"
	"github.com/labstack/echo/v4"
	"github.com/rs/zerolog"
	"github.com/ziflex/lecho/v3"
)
func main() {
	e := echo.New()
	logger := lecho.New(os.Stdout, lecho.WithTimestamp())
	
	e.Use(lecho.Middleware(lecho.Config{
		Logger: logger,
		Enricher: func(c echo.Context, logger zerolog.Context) zerolog.Context {
			// Add user ID if available in context
			if userID := c.Get("user_id"); userID != nil {
				logger = logger.Str("user_id", userID.(string))
			}
			
			// Add trace ID from header
			if traceID := c.Request().Header.Get("X-Trace-ID"); traceID != "" {
				logger = logger.Str("trace_id", traceID)
			}
			
			return logger
		},
	}))
	
	// Set up routes that use user context
	e.GET("/api/profile", func(c echo.Context) error {
		c.Set("user_id", "user123") // This will be logged
		return c.JSON(200, map[string]string{"status": "ok"})
	})
}Sample output:
{"level":"info","user_id":"user123","trace_id":"abc-def-123","remote_ip":"127.0.0.1","method":"GET","uri":"/api/profile","status":200,"time":"2023-10-15T10:30:00Z"}Control how errors are handled in the middleware chain. By default, lecho logs errors but doesn't propagate them to Echo's error handler:
package main
import (
	"errors"
	"net/http"
	"os"
	"github.com/labstack/echo/v4"
	"github.com/ziflex/lecho/v3"
)
func main() {
	e := echo.New()
	logger := lecho.New(os.Stdout, lecho.WithTimestamp())
	
	// Configure error handling
	e.Use(lecho.Middleware(lecho.Config{
		Logger:      logger,
		HandleError: true, // Propagate errors to Echo's error handler
	}))
	
	// Custom error handler
	e.HTTPErrorHandler = func(err error, c echo.Context) {
		code := http.StatusInternalServerError
		if he, ok := err.(*echo.HTTPError); ok {
			code = he.Code
		}
		c.JSON(code, map[string]string{"error": err.Error()})
	}
	
	// Route that may return an error
	e.GET("/error", func(c echo.Context) error {
		return errors.New("something went wrong")
	})
}With HandleError: false (default): Errors are logged but not propagated to Echo's error handler.
With HandleError: true: Errors are both logged and passed to Echo's error handler for proper HTTP response handling.
The lecho.Config struct provides extensive customization options:
| Option | Type | Description | Default | 
|---|---|---|---|
Logger | 
*lecho.Logger | 
Custom logger instance | lecho.New(os.Stdout, WithTimestamp()) | 
Skipper | 
middleware.Skipper | 
Function to skip middleware | middleware.DefaultSkipper | 
AfterNextSkipper | 
middleware.Skipper | 
Skip logging after handler execution | middleware.DefaultSkipper | 
BeforeNext | 
middleware.BeforeFunc | 
Function executed before next handler | nil | 
Enricher | 
lecho.Enricher | 
Function to add custom fields | nil | 
RequestIDHeader | 
string | 
Header name for request ID | "X-Request-ID" | 
RequestIDKey | 
string | 
JSON key for request ID in logs | "id" | 
NestKey | 
string | 
Key for nesting request fields | "" (no nesting) | 
HandleError | 
bool | 
Propagate errors to error handler | false | 
RequestLatencyLimit | 
time.Duration | 
Threshold for slow request detection | 0 (disabled) | 
RequestLatencyLevel | 
zerolog.Level | 
Log level for slow requests | zerolog.InfoLevel | 
Lecho provides utilities to convert between Echo and Zerolog log levels:
package main
import (
	"fmt"
	"github.com/labstack/gommon/log"
	"github.com/rs/zerolog"
	"github.com/ziflex/lecho/v3"
)
func main() {
	// Convert Echo log level to Zerolog level
	zeroLevel, echoLevel := lecho.MatchEchoLevel(log.WARN)
	fmt.Printf("Echo WARN -> Zerolog: %s, Echo: %s\n", zeroLevel, echoLevel)
	
	// Convert Zerolog level to Echo level
	echoLevel2, zeroLevel2 := lecho.MatchZeroLevel(zerolog.InfoLevel)
	fmt.Printf("Zerolog INFO -> Echo: %s, Zerolog: %s\n", echoLevel2, zeroLevel2)
}Access the logger from Echo context in your handlers:
e.GET("/api/users", func(c echo.Context) error {
	// Method 1: Using Echo's logger interface
	c.Logger().Info("Fetching users")
	
	// Method 2: Using zerolog directly from request context
	zerolog.Ctx(c.Request().Context()).Info().Str("action", "fetch_users").Msg("Processing request")
	
	// Method 3: If using lecho.Context wrapper
	if lechoCtx, ok := c.(*lecho.Context); ok {
		lechoCtx.Logger().Info("Using lecho context")
	}
	
	return c.JSON(200, []string{"user1", "user2"})
})package main
import (
	"net/http"
	"os"
	"time"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"github.com/labstack/gommon/log"
	"github.com/rs/zerolog"
	"github.com/ziflex/lecho/v3"
)
func main() {
	e := echo.New()
	
	// Configure logger with all options
	logger := lecho.New(
		os.Stdout,
		lecho.WithLevel(log.INFO),
		lecho.WithTimestamp(),
		lecho.WithCaller(),
		lecho.WithFields(map[string]interface{}{
			"service": "api",
			"version": "1.0.0",
		}),
	)
	e.Logger = logger
	
	// Add middleware stack
	e.Use(middleware.RequestID())
	e.Use(lecho.Middleware(lecho.Config{
		Logger:              logger,
		HandleError:         true,
		RequestLatencyLevel: zerolog.WarnLevel,
		RequestLatencyLimit: 200 * time.Millisecond,
		NestKey:            "http",
		Enricher: func(c echo.Context, logger zerolog.Context) zerolog.Context {
			if userID := c.Get("user_id"); userID != nil {
				logger = logger.Str("user_id", userID.(string))
			}
			return logger
		},
		Skipper: func(c echo.Context) bool {
			// Skip logging for health check endpoints
			return c.Request().URL.Path == "/health"
		},
	}))
	
	e.GET("/health", func(c echo.Context) error {
		return c.String(http.StatusOK, "OK")
	})
	
	e.GET("/api/slow", func(c echo.Context) error {
		time.Sleep(300 * time.Millisecond) // Simulates slow operation
		return c.JSON(200, map[string]string{"result": "completed"})
	})
	
	e.Start(":8080")
}