Skip to main content

Echo Logging

Logging is an essential aspect of any web application, providing visibility into what's happening in your system during runtime. Echo provides a powerful and flexible logging system that helps developers track application events, debug issues, and monitor performance. In this guide, we'll explore Echo's logging features and how to use them effectively in your projects.

Introduction to Echo Logging

Echo's logging system allows you to record events, errors, and information about the application's state. The framework provides a default logger but also allows you to integrate custom loggers to fit your specific needs.

Logging in Echo serves several important purposes:

  • Debugging application issues
  • Tracking user activity
  • Monitoring application performance
  • Recording errors and exceptions
  • Providing an audit trail of system events

Basic Logging with Echo

The Logger Interface

Echo defines a simple Logger interface that any logging implementation must satisfy:

go
type Logger interface {
Debug(...interface{})
Debugf(string, ...interface{})
Info(...interface{})
Infof(string, ...interface{})
Warn(...interface{})
Warnf(string, ...interface{})
Error(...interface{})
Errorf(string, ...interface{})
}

Default Logger

Echo comes with a default logger that writes to standard output. Here's a basic example of how to use it:

go
package main

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

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

// Using the default logger
e.Logger.Info("Server starting up")
e.Logger.Debugf("Debug mode: %v", e.Debug)

// Handle requests
e.GET("/", func(c echo.Context) error {
e.Logger.Info("Handling root request")
return c.String(200, "Hello, World!")
})

e.Logger.Fatal(e.Start(":8080"))
}

The output will look something like:

{"time":"2023-07-20T10:15:30Z","level":"INFO","prefix":"echo","message":"Server starting up"}
{"time":"2023-07-20T10:15:30Z","level":"DEBUG","prefix":"echo","message":"Debug mode: false"}

Log Levels

Echo's logger supports different log levels, allowing you to control the verbosity of your logs:

  • Debug: Detailed information for debugging
  • Info: Normal operational information
  • Warn: Warning events that might need attention
  • Error: Error events that might still allow the application to continue
  • Fatal: Critical errors that stop the application

Customizing the Logger

Setting Log Level

You can control which logs are displayed by setting the log level:

go
e := echo.New()

// Set the logger to only show info, warn, error, and fatal logs
e.Logger.SetLevel(log.INFO)

Custom Logger Implementation

Echo allows you to replace the default logger with your own implementation. This is useful when you want to integrate with logging frameworks like Zap, Logrus, or others.

Here's an example of integrating Logrus with Echo:

go
package main

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

// LogrusLogger adapts logrus to Echo's logger interface
type LogrusLogger struct {
*logrus.Logger
}

func (l LogrusLogger) Debug(i ...interface{}) {
l.Logger.Debug(i...)
}

func (l LogrusLogger) Debugf(format string, args ...interface{}) {
l.Logger.Debugf(format, args...)
}

func (l LogrusLogger) Info(i ...interface{}) {
l.Logger.Info(i...)
}

func (l LogrusLogger) Infof(format string, args ...interface{}) {
l.Logger.Infof(format, args...)
}

func (l LogrusLogger) Warn(i ...interface{}) {
l.Logger.Warn(i...)
}

func (l LogrusLogger) Warnf(format string, args ...interface{}) {
l.Logger.Warnf(format, args...)
}

func (l LogrusLogger) Error(i ...interface{}) {
l.Logger.Error(i...)
}

func (l LogrusLogger) Errorf(format string, args ...interface{}) {
l.Logger.Errorf(format, args...)
}

func (l LogrusLogger) SetLevel(level log.Lvl) {
switch level {
case log.DEBUG:
l.Logger.SetLevel(logrus.DebugLevel)
case log.INFO:
l.Logger.SetLevel(logrus.InfoLevel)
case log.WARN:
l.Logger.SetLevel(logrus.WarnLevel)
case log.ERROR:
l.Logger.SetLevel(logrus.ErrorLevel)
}
}

func (l LogrusLogger) Level() log.Lvl {
switch l.Logger.GetLevel() {
case logrus.DebugLevel:
return log.DEBUG
case logrus.InfoLevel:
return log.INFO
case logrus.WarnLevel:
return log.WARN
case logrus.ErrorLevel:
return log.ERROR
default:
return log.OFF
}
}

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

// Create a new logrus logger
logrusLogger := logrus.New()
logrusLogger.SetFormatter(&logrus.JSONFormatter{})

// Set logrus as Echo's logger
e.Logger = LogrusLogger{Logger: logrusLogger}
e.Logger.SetLevel(log.INFO)

e.Logger.Info("Server starting with custom logger")

// Routes
e.GET("/", func(c echo.Context) error {
e.Logger.Info("Handling root request")
return c.String(200, "Hello, World!")
})

e.Logger.Fatal(e.Start(":8080"))
}

HTTP Request Logging Middleware

Echo provides middleware for logging HTTP requests, which is essential for tracking API usage, performance, and troubleshooting issues.

Basic Request Logging

The simplest way to enable request logging is to use Echo's built-in middleware:

go
package main

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

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

// Add logger middleware
e.Use(middleware.Logger())

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

e.Logger.Fatal(e.Start(":8080"))
}

When you make a request to http://localhost:8080/, you'll see a log entry like this:

{"time":"2023-07-20T10:30:45.1234Z","id":"","remote_ip":"127.0.0.1","host":"localhost:8080","method":"GET","uri":"/","user_agent":"Mozilla/5.0","status":200,"error":"","latency":0.001,"latency_human":"1ms","bytes_in":0,"bytes_out":12}

Customizing Request Logs

You can configure the logger middleware to customize what information is logged and how it's formatted:

go
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "method=${method}, uri=${uri}, status=${status}\n",
}))

With this configuration, logs will appear as:

method=GET, uri=/, status=200

Common Log Formats

Echo supports various log formats, including predefined ones like:

go
// Apache Common Log Format
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "${remote_ip} - - [${time_rfc3339}] \"${method} ${uri} ${protocol}\" ${status} ${bytes_out}\n",
}))

This will output logs like:

127.0.0.1 - - [2023-07-20T10:30:45Z] "GET / HTTP/1.1" 200 12

Advanced Logging Techniques

Contextual Logging

You can add context-specific information to your logs by using Echo's context:

go
e.GET("/users/:id", func(c echo.Context) error {
userID := c.Param("id")
e.Logger.Infof("Processing request for user ID: %s", userID)

// Simulate user lookup
if userID == "1" {
e.Logger.Info("User found")
return c.String(200, "User details for ID 1")
}

e.Logger.Warn("User not found")
return c.String(404, "User not found")
})

Structured Logging

Structured logging makes it easier to parse and analyze logs. Here's how to do structured logging with a custom logger like zerolog:

go
package main

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

type ZeroLogger struct {
logger zerolog.Logger
}

// Implement Echo's Logger interface with zerolog
// (implementation details similar to the Logrus example above)

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

// Create zerolog logger
zl := zerolog.New(os.Stdout).With().Timestamp().Logger()

// Set as Echo's logger
e.Logger = &ZeroLogger{logger: zl}

// Example of structured logging
e.GET("/order/:id", func(c echo.Context) error {
orderID := c.Param("id")

// Log with context fields
e.Logger.Info().
Str("order_id", orderID).
Str("user_ip", c.RealIP()).
Msg("Processing order")

return c.String(200, "Order processed")
})

e.Logger.Fatal(e.Start(":8080"))
}

Practical Example: Complete Logging Setup

Here's a more complete example that shows how to set up logging for a production application:

go
package main

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

// ZeroLogger adapts zerolog to Echo's logger interface
type ZeroLogger struct {
logger zerolog.Logger
}

// Implement all required Logger interface methods

func main() {
// Initialize Echo
e := echo.New()

// Set up zerolog
output := zerolog.ConsoleWriter{
Out: os.Stdout,
TimeFormat: time.RFC3339,
NoColor: false,
}

logger := zerolog.New(output).
Level(zerolog.InfoLevel).
With().
Timestamp().
Str("service", "api-server").
Logger()

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

// Configure HTTP request logging middleware
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "${time_rfc3339} ${remote_ip} ${method} ${uri} ${protocol} ${status} ${latency_human} ${bytes_in} ${bytes_out}\n",
CustomTimeFormat: time.RFC3339,
}))

// Add recovery middleware to log panics
e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{
LogLevel: 1, // ERROR level
LogErrorFunc: func(c echo.Context, err error, stack []byte) error {
e.Logger.Errorf("Panic recovered: %v\n%s", err, stack)
return err
},
}))

// Routes
e.GET("/", func(c echo.Context) error {
e.Logger.Info("Handling home request")
return c.String(200, "Welcome to the API")
})

e.GET("/error", func(c echo.Context) error {
e.Logger.Error("This is a simulated error")
return c.String(500, "Internal Server Error")
})

e.GET("/panic", func(c echo.Context) error {
panic("This is a simulated panic")
})

// Start server
e.Logger.Info("Starting server on port 8080")
e.Logger.Fatal(e.Start(":8080"))
}

Logging Best Practices

  1. Choose appropriate log levels - Use DEBUG for detailed troubleshooting, INFO for general application flow, WARN for potentially problematic situations, and ERROR for actual errors.

  2. Include context in logs - Add relevant details such as request IDs, user IDs, and transaction IDs to make troubleshooting easier.

  3. Use structured logging - Structured logs are easier to parse, filter, and analyze.

  4. Log sensitive data carefully - Never log passwords, tokens, or personal information. Consider masking sensitive data.

  5. Configure log rotation - For production systems, ensure logs are rotated to prevent disk space issues.

  6. Consider log aggregation - In distributed systems, use tools like ELK (Elasticsearch, Logstash, Kibana) or a cloud logging service to aggregate logs from multiple instances.

Summary

Echo provides a flexible and powerful logging system that can be tailored to your application's needs. From the simple default logger to custom integrations with popular logging libraries, Echo makes it easy to implement comprehensive logging for your web applications.

Effective logging is crucial for debugging, monitoring, and maintaining web applications. By following the examples and best practices in this guide, you can set up a robust logging system that will help you keep your Echo applications running smoothly.

Additional Resources

Exercises

  1. Basic Logger Implementation: Create a simple Echo application that logs different types of events using the default logger.

  2. Custom Logger: Implement a custom logger for Echo using a logging library not covered in this guide (like slog or zap).

  3. Enhanced Request Logging: Configure the logger middleware to include additional information such as request headers and query parameters.

  4. Log Rotation: Set up log rotation for an Echo application using a library like lumberjack.

  5. Structured Logging: Create an application that uses structured logging with contextual information for different API endpoints.



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