Skip to main content

Echo Logging Practices

Introduction

Logging is a critical aspect of any web application. It provides visibility into application behavior, helps with debugging, and offers insights into performance issues. In the Echo framework, logging is well-integrated and highly customizable to suit various application needs.

This guide will walk you through the best practices for implementing logging in your Echo applications, from basic setup to advanced configurations. Whether you're building a small API or a complex web service, proper logging can save you countless hours when troubleshooting issues in development or production environments.

Understanding Echo's Logging System

Echo comes with a built-in logging interface that allows you to:

  1. Log HTTP requests and responses
  2. Track application events
  3. Report errors and exceptions
  4. Monitor performance metrics

The default logger in Echo is minimal, but you can easily extend it or replace it with third-party logging libraries for more advanced features.

Basic Logging Setup

Let's start with a simple example of setting up logging in an Echo application:

go
package main

import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

func main() {
// Create a new Echo instance
e := echo.New()

// Enable logging middleware with default configuration
e.Use(middleware.Logger())

// Define a route
e.GET("/", func(c echo.Context) error {
return c.String(200, "Hello, World!")
})

// Start the server
e.Logger.Fatal(e.Start(":8080"))
}

Output: When you run this application and make a request, you'll see output similar to:

2023/09/30 10:45:23 GET / 200 2.157µs 192.168.1.1 Mozilla/5.0 (Windows NT 10.0; Win64; x64)

This log entry includes the timestamp, HTTP method, path, status code, response time, client IP, and user agent.

Customizing the Logger

Echo's default logger is basic, but you can customize it to fit your needs:

go
// Custom logger configuration
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "time=${time_rfc3339}, method=${method}, uri=${uri}, status=${status}, latency=${latency_human}\n",
}))

Output:

time=2023-09-30T10:50:45Z, method=GET, uri=/, status=200, latency=3.14ms

Structured Logging with JSON Format

For better log parsing in production systems, you might want to use JSON format:

go
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: `{"time":"${time_rfc3339}","remote_ip":"${remote_ip}",` +
`"method":"${method}","uri":"${uri}","status":${status},` +
`"latency":${latency},"latency_human":"${latency_human}"}` + "\n",
}))

Output:

json
{"time":"2023-09-30T11:05:23Z","remote_ip":"192.168.1.1","method":"GET","uri":"/","status":200,"latency":0.000002157,"latency_human":"2.157µs"}

Custom Log Templates

Echo's logger middleware accepts a variety of template variables that you can use in your log format:

VariableDescription
${time_rfc3339}Current time in RFC3339 format
${remote_ip}Client's IP address
${host}Request host
${method}HTTP method
${uri}Request URI
${status}Response status
${latency}Response latency
${latency_human}Human-readable response latency
${bytes_in}Bytes received
${bytes_out}Bytes sent

Logging Specific Routes or Skip Certain Paths

Sometimes you want to exclude certain routes from logging, such as health checks or static assets:

go
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Skipper: func(c echo.Context) bool {
// Skip logging for health check endpoint
return c.Path() == "/health" || c.Path() == "/metrics"
},
}))

Using Custom Logger Implementation

For more advanced use cases, you might want to use a third-party logger like Zap or Logrus:

go
package main

import (
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/log"
)

type CustomLogger struct {
// Your custom logger implementation
}

// Implement the Echo Logger interface
func (cl *CustomLogger) Output() io.Writer {
// Implementation
}

func (cl *CustomLogger) SetOutput(w io.Writer) {
// Implementation
}

func (cl *CustomLogger) Prefix() string {
// Implementation
}

func (cl *CustomLogger) SetPrefix(p string) {
// Implementation
}

// Other methods...

func main() {
e := echo.New()

// Set custom logger
customLogger := &CustomLogger{}
e.Logger = customLogger

// Routes and middleware...

e.Start(":8080")
}

Contextual Logging

Adding context to your logs makes them more informative and easier to troubleshoot:

go
e.GET("/users/:id", func(c echo.Context) error {
// Extract parameters
userID := c.Param("id")

// Log with context
c.Logger().Infof("Processing request for user ID: %s", userID)

// Your handler logic...

return c.JSON(200, map[string]string{"message": "User processed"})
})

Logging Middleware for Error Handling

Combine logging with error handling for better visibility into application failures:

go
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
// Your logger config
}))

e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{
StackSize: 1 << 10, // 1 KB
LogLevel: log.ERROR,
LogErrorFunc: func(c echo.Context, err error, stack []byte) error {
c.Logger().Errorf("Error: %s\n%s", err, stack)
return err
},
}))

Rotating Log Files

For production applications, you'll want to implement log rotation to manage file sizes:

go
package main

import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"gopkg.in/natefinch/lumberjack.v2"
"io"
"os"
)

func main() {
e := echo.New()

// Configure logging middleware
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Output: getLogWriter(),
}))

// Routes...

e.Start(":8080")
}

func getLogWriter() io.Writer {
// For production, use a log rotation system
return &lumberjack.Logger{
Filename: "/var/log/myapp/app.log",
MaxSize: 10, // megabytes
MaxBackups: 3,
MaxAge: 28, // days
Compress: true,
}

// For development, you might want to log to both file and console
// return io.MultiWriter(os.Stdout, &lumberjack.Logger{...})
}

Environment-Based Logging Configuration

Adjust your logging based on the environment:

go
package main

import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"os"
)

func main() {
e := echo.New()

env := os.Getenv("APP_ENV")

switch env {
case "production":
// Minimal logs with JSON format for production
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: `{"time":"${time_rfc3339}","status":${status},"latency":${latency}}` + "\n",
Output: getProductionLogWriter(),
}))
e.Logger.SetLevel(log.INFO)

case "development":
// Verbose and colorful logs for development
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "${time_rfc3339_nano} ${color}method=${method}, uri=${uri}, status=${status}, latency=${latency_human}${color:reset}\n",
Output: os.Stdout,
}))
e.Logger.SetLevel(log.DEBUG)

default:
// Default configuration
e.Use(middleware.Logger())
e.Logger.SetLevel(log.INFO)
}

// Routes and other middleware...

e.Start(":8080")
}

Real-World Application: API Request Logging with Correlation IDs

Here's a more comprehensive example that demonstrates logging in a real-world API:

go
package main

import (
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

// Define a constant for the correlation ID context key
const CorrelationIDKey = "correlation_id"

// Middleware to add a correlation ID to each request
func AddCorrelationID(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Generate a unique correlation ID
correlationID := uuid.New().String()

// Add it to the context
c.Set(CorrelationIDKey, correlationID)

// Add it as a response header
c.Response().Header().Set("X-Correlation-ID", correlationID)

// Continue with the next handler
return next(c)
}
}

func main() {
e := echo.New()

// Add correlation ID middleware
e.Use(AddCorrelationID)

// Configure logging with correlation ID
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: `{"time":"${time_rfc3339}","correlation_id":"${header:X-Correlation-ID}","remote_ip":"${remote_ip}","method":"${method}","uri":"${uri}","status":${status},"latency":${latency},"user_agent":"${user_agent}"}` + "\n",
}))

// Sample API endpoint
e.GET("/api/products/:id", func(c echo.Context) error {
productID := c.Param("id")
correlationID := c.Get(CorrelationIDKey).(string)

// Log the action with context
c.Logger().Infof("Fetching product %s [correlation_id=%s]", productID, correlationID)

// Logic to get product...
product := map[string]interface{}{
"id": productID,
"name": "Sample Product",
"price": 29.99,
}

return c.JSON(200, product)
})

e.Start(":8080")
}

Output:

json
{"time":"2023-09-30T12:15:45Z","correlation_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","remote_ip":"192.168.1.1","method":"GET","uri":"/api/products/123","status":200,"latency":0.00451,"user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}

Summary

Effective logging is essential for monitoring, debugging, and understanding your Echo applications. Here are the key takeaways:

  1. Start Simple: Use Echo's built-in logger middleware for basic needs
  2. Customize Format: Adjust the log format based on your requirements
  3. Structure Your Logs: Use JSON formatting for better parsing in production
  4. Add Context: Include correlation IDs and request-specific information
  5. Configure by Environment: Use different logging configurations for development and production
  6. Rotate Logs: Implement log rotation for production applications
  7. Monitor Performance: Track latency and response times through logs

By following these best practices, you'll create a logging system that provides valuable insights into your application's behavior and helps you quickly identify and resolve issues.

Additional Resources

Exercises

  1. Implement a custom logger that sends critical errors to a notification service
  2. Create a middleware that logs all database queries with their execution time
  3. Set up different logging configurations for development, staging, and production environments
  4. Implement a system that aggregates logs from multiple Echo microservices
  5. Create a dashboard to visualize your application logs and identify performance bottlenecks


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)