Skip to main content
Logger middleware for Fiber logs HTTP request and response details. It provides flexible formatting, custom tags, and integration with popular logging libraries.

Installation

go get -u github.com/gofiber/fiber/v3
go get -u github.com/gofiber/fiber/v3/middleware/logger

Signatures

func New(config ...Config) fiber.Handler

Usage

Basic Usage

package main

import (
    "github.com/gofiber/fiber/v3"
    "github.com/gofiber/fiber/v3/middleware/logger"
)

func main() {
    app := fiber.New()

    // Default logger
    app.Use(logger.New())

    app.Get("/", func(c fiber.Ctx) error {
        return c.SendString("Hello, World!")
    })

    app.Listen(":3000")
}

Custom Format

app.Use(logger.New(logger.Config{
    Format: "[${ip}]:${port} ${status} - ${method} ${path}\n",
}))

Log to File

accessLog, err := os.OpenFile("./access.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
    log.Fatalf("error opening file: %v", err)
}
defer accessLog.Close()

app.Use(logger.New(logger.Config{
    Stream: accessLog,
}))

Custom Tags

import "github.com/gofiber/fiber/v3/middleware/requestid"

app.Use(requestid.New())
app.Use(logger.New(logger.Config{
    CustomTags: map[string]logger.LogFunc{
        "requestid": func(output logger.Buffer, c fiber.Ctx, data *logger.Data, extraParam string) (int, error) {
            return output.WriteString(requestid.FromContext(c))
        },
    },
    Format: "${pid} ${requestid} ${status} - ${method} ${path}\n",
}))

Predefined Formats

// Common Log Format
app.Use(logger.New(logger.Config{
    Format: logger.CommonFormat,
}))

// Combined Log Format
app.Use(logger.New(logger.Config{
    Format: logger.CombinedFormat,
}))

// JSON Format
app.Use(logger.New(logger.Config{
    Format: logger.JSONFormat,
}))

// Elastic Common Schema (ECS)
app.Use(logger.New(logger.Config{
    Format: logger.ECSFormat,
}))

Time Zone and Format

app.Use(logger.New(logger.Config{
    Format:     "${pid} ${status} - ${method} ${path}\n",
    TimeFormat: "02-Jan-2006",
    TimeZone:   "America/New_York",
}))

Integration with Zap Logger

import (
    "github.com/gofiber/contrib/fiberzap/v2"
    "github.com/gofiber/fiber/v3/log"
    "github.com/gofiber/fiber/v3/middleware/logger"
)

zapLogger := fiberzap.NewLogger(fiberzap.LoggerConfig{
    ExtraKeys: []string{"request_id"},
})

app.Use(logger.New(logger.Config{
    Stream: logger.LoggerToWriter(zapLogger, log.LevelDebug),
}))

Disable Colors

app.Use(logger.New(logger.Config{
    DisableColors: true,
}))

Force Colors

app.Use(logger.New(logger.Config{
    ForceColors: true,
}))

Configuration

Next
func(fiber.Ctx) bool
default:"nil"
Function to skip this middleware when it returns true.
Skip
func(fiber.Ctx) bool
default:"nil"
Function to determine if logging is skipped or written to Stream.
Done
func(fiber.Ctx, []byte)
default:"nil"
Callback function executed after the log string is written to Stream.
CustomTags
map[string]LogFunc
default:"map[string]LogFunc"
Map of custom tag names to functions that generate their values.
Format
string
default:"DefaultFormat"
Logging format string. See Tags for available placeholders.
TimeFormat
string
Time format for the ${time} tag. See Go’s time.Format documentation.
TimeZone
string
Time zone for timestamps (e.g., “UTC”, “America/New_York”, “Asia/Shanghai”).
TimeInterval
time.Duration
default:"500 * time.Millisecond"
Delay before the timestamp is updated.
Stream
io.Writer
default:"os.Stdout"
Output destination for logs.
LoggerFunc
func(c fiber.Ctx, data *Data, cfg *Config) error
default:"defaultLoggerInstance"
Custom logger function for integration with logging libraries.
DisableColors
bool
default:"false"
Disables colorized output.
ForceColors
bool
default:"false"
Forces colorized output even when not outputting to a terminal.

Default Configuration

var ConfigDefault = Config{
    Next:              nil,
    Skip:              nil,
    Done:              nil,
    Format:            DefaultFormat,
    TimeFormat:        "15:04:05",
    TimeZone:          "Local",
    TimeInterval:      500 * time.Millisecond,
    Stream:            os.Stdout,
    BeforeHandlerFunc: beforeHandlerFunc,
    LoggerFunc:        defaultLoggerInstance,
    enableColors:      true,
}

Predefined Formats

FormatDescription
DefaultFormat[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n
CommonFormat${ip} - - [${time}] "${method} ${url} ${protocol}" ${status} ${bytesSent}\n
CombinedFormatCommon format + "${referer}" "${ua}"\n
JSONFormat{time: ${time}, ip: ${ip}, method: ${method}, url: ${url}, status: ${status}, bytesSent: ${bytesSent}}\n
ECSFormatElastic Common Schema format

Tags

TagDescription
${pid}Process ID
${time}Timestamp
${referer}Referer header
${protocol}HTTP protocol
${port}Port
${ip}Client IP
${ips}IP addresses from X-Forwarded-For
${host}Host header
${method}HTTP method
${path}Request path
${url}Full URL
${ua}User-Agent
${latency}Request latency
${status}Response status code
${resBody}Response body
${reqHeaders}Request headers
${queryParams}Query parameters
${body}Request body
${bytesSent}Bytes sent
${bytesReceived}Bytes received
${route}Matched route
${error}Error if any
${reqHeader:name}Specific request header
${respHeader:name}Specific response header
${query:name}Specific query parameter
${form:name}Specific form field
${cookie:name}Specific cookie
${locals:name}Specific local variable

Color Tags

TagColor
${black}, ${red}, ${green}, ${yellow}Colors
${blue}, ${magenta}, ${cyan}, ${white}Colors
${reset}Reset color

Best Practices

Register Early

// Register logger BEFORE routes to log all requests
app.Use(logger.New())

// Now register routes
app.Get("/", handler)

Skip Health Checks

app.Use(logger.New(logger.Config{
    Next: func(c fiber.Ctx) bool {
        // Don't log health check endpoints
        return c.Path() == "/health" || c.Path() == "/metrics"
    },
}))

Conditional Logging

app.Use(logger.New(logger.Config{
    Skip: func(c fiber.Ctx) bool {
        // Only log errors
        return c.Response().StatusCode() < 400
    },
}))

Post-Log Actions

app.Use(logger.New(logger.Config{
    Done: func(c fiber.Ctx, logString []byte) {
        // Send errors to external service
        if c.Response().StatusCode() >= 500 {
            sendToSlack(logString)
        }
    },
}))

Common Patterns

Development vs Production

config := logger.Config{}

if os.Getenv("ENV") == "production" {
    config.Format = logger.JSONFormat
    config.DisableColors = true
} else {
    config.Format = logger.DefaultFormat
}

app.Use(logger.New(config))

Separate Access and Error Logs

accessLog, _ := os.OpenFile("./access.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
errorLog, _ := os.OpenFile("./error.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)

app.Use(logger.New(logger.Config{
    Stream: accessLog,
    Skip: func(c fiber.Ctx) bool {
        return c.Response().StatusCode() >= 400
    },
}))

app.Use(logger.New(logger.Config{
    Stream: errorLog,
    Skip: func(c fiber.Ctx) bool {
        return c.Response().StatusCode() < 400
    },
}))

Log Request Body

app.Use(logger.New(logger.Config{
    Format: "${method} ${path} - ${body}\n",
}))

Log Specific Headers

app.Use(logger.New(logger.Config{
    Format: "${reqHeader:X-Request-ID} ${method} ${path} ${status}\n",
}))

Custom Tag Example

app.Use(logger.New(logger.Config{
    CustomTags: map[string]logger.LogFunc{
        "user": func(output logger.Buffer, c fiber.Ctx, data *logger.Data, extraParam string) (int, error) {
            user := c.Locals("user")
            if user != nil {
                return output.WriteString(user.(string))
            }
            return output.WriteString("anonymous")
        },
    },
    Format: "[${user}] ${method} ${path} ${status}\n",
}))

Notes

  • Registration order matters: only routes added after the logger are logged
  • Writing to os.File is goroutine-safe
  • Custom streams may require locking for concurrent writes
  • The TimeInterval reduces timestamp update overhead

Build docs developers (and LLMs) love