Skip to main content

Echo Recover Middleware

Introduction

When developing web applications with Go and the Echo framework, handling unexpected errors is crucial for maintaining a stable and reliable application. In Go, a panic occurs when your program encounters an unrecoverable error during execution. Unhandled panics can crash your entire application, which is particularly problematic in production environments.

The Echo Recover middleware provides a safety net for your application by catching panics during request processing, converting them into HTTP 500 Internal Server Error responses, and logging the error details. This allows your server to continue running even when unexpected errors occur.

How Recover Middleware Works

The Recover middleware works by utilizing Go's built-in recover function within a deferred function call. When a panic occurs inside your request handler, the middleware catches it, logs the details, and returns an appropriate error response to the client.

Basic Implementation

Here's how the Echo Recover middleware is implemented internally (simplified):

go
func Recover() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
defer func() {
if r := recover(); r != nil {
// Log the error
// Return an HTTP 500 error
c.Error(echo.NewHTTPError(http.StatusInternalServerError, "Internal Server Error"))
}
}()
return next(c)
}
}
}

Adding Recover Middleware to Your Echo Application

Basic Usage

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()

// Register the Recover middleware
e.Use(middleware.Recover())

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

e.GET("/panic", func(c echo.Context) error {
// This will cause a panic
panic("Something went terribly wrong!")
return nil
})

// Start server
e.Start(":8080")
}

In this example, when you access /panic, the handler will panic. Without the Recover middleware, the entire application would crash. With the middleware, the panic is caught, the error is logged, and a 500 Internal Server Error is returned to the client while the server continues to run.

Customizing the Recover Middleware

Echo allows you to customize how the Recover middleware behaves through configuration options.

Custom Configuration

go
// Custom Recover middleware configuration
e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{
// Skip middleware for specific routes
Skipper: func(c echo.Context) bool {
return c.Path() == "/skip-recover"
},
// Use a custom log function
LogErrorFunc: func(c echo.Context, err error, stack []byte) error {
fmt.Printf("Error: %v\n", err)
fmt.Printf("Stack trace: %s\n", stack)
return err
},
// Disable stack tracing
DisableStackAll: false,
// Limit the number of stack trace lines
StackSize: 4 * 1024, // 4 KB
// Don't log recovery
DisablePrintStack: false,
}))

Configuration Options Explained

  1. Skipper: Function to determine which requests to skip this middleware for
  2. LogErrorFunc: Custom function to handle error logging
  3. DisableStackAll: When set to true, disables formatting stack traces of all other goroutines
  4. StackSize: Limits the size of the stack trace (in bytes)
  5. DisablePrintStack: When set to true, prevents printing stack traces to the logs

Real-World Example: API with Graceful Error Handling

Let's look at a more complex example of an API that uses the Recover middleware to handle panics gracefully:

go
package main

import (
"database/sql"
"fmt"
"log"
"net/http"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
_ "github.com/mattn/go-sqlite3"
)

type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}

var db *sql.DB

func main() {
// Initialize database connection
var err error
db, err = sql.Open("sqlite3", ":memory:")
if err != nil {
log.Fatal(err)
}
defer db.Close()

// Create tables
_, err = db.Exec(`CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
)`)
if err != nil {
log.Fatal(err)
}

// Insert test data
_, err = db.Exec(`INSERT INTO users (name, email) VALUES (?, ?)`, "John Doe", "[email protected]")
if err != nil {
log.Fatal(err)
}

// Create Echo instance
e := echo.New()

// Middleware
e.Use(middleware.Logger())
e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{
LogErrorFunc: func(c echo.Context, err error, stack []byte) error {
log.Printf("ERROR: %s\n", err)
log.Printf("STACK: %s\n", stack)

// You could also send the error to a monitoring service like Sentry here

return err
},
}))

// Routes
e.GET("/users/:id", getUserHandler)
e.POST("/users", createUserHandler)
e.GET("/dangerous", dangerousHandler)

// Start server
e.Start(":8080")
}

func getUserHandler(c echo.Context) error {
id := c.Param("id")

var user User
err := db.QueryRow("SELECT id, name, email FROM users WHERE id = ?", id).Scan(&user.ID, &user.Name, &user.Email)

if err != nil {
if err == sql.ErrNoRows {
return echo.NewHTTPError(http.StatusNotFound, "User not found")
}
return echo.NewHTTPError(http.StatusInternalServerError, "Database error")
}

return c.JSON(http.StatusOK, user)
}

func createUserHandler(c echo.Context) error {
user := new(User)
if err := c.Bind(user); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid request payload")
}

result, err := db.Exec("INSERT INTO users (name, email) VALUES (?, ?)", user.Name, user.Email)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Could not create user")
}

id, _ := result.LastInsertId()
user.ID = int(id)

return c.JSON(http.StatusCreated, user)
}

func dangerousHandler(c echo.Context) error {
// This handler will panic
var nullMap map[string]string = nil
nullMap["key"] = "value" // This will cause a panic: assignment to nil map

return c.String(http.StatusOK, "You'll never see this")
}

In this example:

  1. We set up a simple REST API with user management
  2. We include custom logging for panics via RecoverWithConfig
  3. We have a deliberately dangerous handler to test panic recovery
  4. When a panic occurs in dangerousHandler, the middleware catches it and returns a 500 response

Benefits of Using Recover Middleware

  1. Improved Stability: Your application continues running even when unexpected errors occur
  2. Better User Experience: Users receive proper error responses instead of connection errors
  3. Enhanced Debugging: Detailed stack traces help identify the root cause of panics
  4. Centralized Error Handling: All panic recoveries are managed in one place

When to Use Recover Middleware

The Recover middleware should be used in virtually all Echo applications, especially in production environments. However, during development, you might want to consider allowing panics to be more visible to help with debugging.

Summary

The Echo Recover middleware is an essential component for building robust web applications with the Echo framework. It:

  • Catches panics that occur during request processing
  • Prevents your application from crashing
  • Logs error details for debugging purposes
  • Returns appropriate error responses to clients
  • Can be customized to handle errors according to your needs

By implementing this middleware, you add a crucial safety net to your application, ensuring it can gracefully handle unexpected errors and continue serving users even when things go wrong.

Additional Resources

Exercises

  1. Create a simple Echo application that uses the Recover middleware and has a route that deliberately causes a panic.
  2. Customize the Recover middleware to log errors to a file instead of standard output.
  3. Implement a custom LogErrorFunc that includes additional context about the request in the error log.
  4. Create a middleware setup that skips the Recover middleware for a specific route or user agent.
  5. Extend the real-world example to include sending error notifications to an external service.


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