or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-types.mdcontextual-logging.mdcore-logging.mddiode-writer.mdfield-methods.mdglobal-configuration.mdglobal-logger.mdhooks.mdhttp-middleware.mdindex.mdjournald-integration.mdpkgerrors-integration.mdsampling.mdwriters-and-output.md
tile.json

pkgerrors-integration.mddocs/

Package Errors Integration

The pkgerrors subpackage provides integration with github.com/pkg/errors for extracting and logging stack traces from wrapped errors. This allows you to capture detailed error context including file names, line numbers, and function names.

Package Imports

import (
    "github.com/rs/zerolog"
    "github.com/rs/zerolog/pkgerrors"
)

Note: You must also have github.com/pkg/errors installed:

go get github.com/pkg/errors

Overview

The pkg/errors package provides error wrapping with stack traces. The pkgerrors subpackage extracts these stack traces and formats them for zerolog.

Features:

  • Extract stack traces from wrapped errors
  • Format stack with file, line, and function information
  • Configurable field names
  • Works with error wrapping chains
  • Zero overhead when not used

Setup

Configure zerolog to use the pkgerrors marshaler:

import (
    "github.com/rs/zerolog"
    "github.com/rs/zerolog/pkgerrors"
)

func init() {
    // Set the error stack marshaler
    zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
}

After this configuration, the Stack() method on events will extract and log stack traces from errors created with pkg/errors.

MarshalStack Function

// Extract and marshal stack trace from error
func MarshalStack(err error) interface{}

This function extracts the stack trace from errors that implement the StackTrace() method (like pkg/errors). It returns nil if no stack trace is found.

Example:

func init() {
    zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
}

err := errors.New("something went wrong")
logger.Error().Stack().Err(err).Msg("operation failed")
// Includes full stack trace

Field Name Configuration

Customize the field names used in stack trace output:

// Field names for stack frames (defaults shown)
var StackSourceFileName = "source"      // File path field name
var StackSourceLineName = "line"        // Line number field name
var StackSourceFunctionName = "func"    // Function name field name

Example:

func init() {
    // Customize field names
    pkgerrors.StackSourceFileName = "file"
    pkgerrors.StackSourceLineName = "line_number"
    pkgerrors.StackSourceFunctionName = "function"

    zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
}

Usage Examples

Basic Error with Stack Trace

package main

import (
    "os"

    "github.com/pkg/errors"
    "github.com/rs/zerolog"
    "github.com/rs/zerolog/pkgerrors"
)

func init() {
    zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
}

func main() {
    logger := zerolog.New(os.Stderr).With().Timestamp().Logger()

    err := errors.New("database connection failed")

    logger.Error().Stack().Err(err).Msg("startup failed")
}

Output:

{
  "level": "error",
  "stack": [
    {
      "source": "/path/to/main.go",
      "line": "15",
      "func": "main"
    },
    {
      "source": "/path/to/runtime/proc.go",
      "line": "250",
      "func": "main"
    }
  ],
  "error": "database connection failed",
  "time": "2024-01-15T10:30:00Z",
  "message": "startup failed"
}

Wrapped Errors

func connectDB() error {
    err := tryConnect()
    if err != nil {
        return errors.Wrap(err, "failed to connect to database")
    }
    return nil
}

func tryConnect() error {
    return errors.New("connection timeout")
}

func main() {
    logger := zerolog.New(os.Stderr).With().Timestamp().Logger()

    err := connectDB()
    if err != nil {
        logger.Error().Stack().Err(err).Msg("database error")
    }
}

Output includes stack trace from where error was created:

{
  "level": "error",
  "stack": [
    {
      "source": "/path/to/main.go",
      "line": "8",
      "func": "tryConnect"
    },
    {
      "source": "/path/to/main.go",
      "line": "3",
      "func": "connectDB"
    },
    {
      "source": "/path/to/main.go",
      "line": "15",
      "func": "main"
    }
  ],
  "error": "failed to connect to database: connection timeout",
  "message": "database error"
}

Conditional Stack Traces

Only log stack traces for certain error types or conditions:

func logError(logger zerolog.Logger, err error, msg string) {
    event := logger.Error()

    // Only add stack for unexpected errors
    if isUnexpected(err) {
        event = event.Stack()
    }

    event.Err(err).Msg(msg)
}

func isUnexpected(err error) bool {
    // Don't include stack for known error types
    switch err.(type) {
    case *ValidationError, *NotFoundError:
        return false
    default:
        return true
    }
}

Stack Traces in Hooks

Automatically add stack traces to error logs:

func init() {
    zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
}

func main() {
    hook := zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, msg string) {
        if level == zerolog.ErrorLevel {
            e.Stack()
        }
    })

    logger := zerolog.New(os.Stderr).Hook(hook)

    // Stack trace automatically added to errors
    err := errors.New("unexpected error")
    logger.Error().Err(err).Msg("operation failed")
}

Multiple Error Types

Handle both pkg/errors and standard errors:

import (
    "errors"
    "fmt"

    pkgerrors "github.com/pkg/errors"
)

func processData() error {
    // Standard error (no stack trace)
    if badInput {
        return errors.New("invalid input")
    }

    // pkg/errors error (with stack trace)
    if systemError {
        return pkgerrors.New("system failure")
    }

    return nil
}

func main() {
    err := processData()
    if err != nil {
        logger.Error().Stack().Err(err).Msg("processing failed")
        // Stack trace only included for pkg/errors
    }
}

Custom Field Names

func init() {
    // Use shorter field names
    pkgerrors.StackSourceFileName = "file"
    pkgerrors.StackSourceLineName = "line"
    pkgerrors.StackSourceFunctionName = "fn"

    zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
}

// Output format:
// "stack": [{"file": "main.go", "line": "42", "fn": "main"}]

Stack Traces in Context

Add stack traces to contextual loggers:

func handler(w http.ResponseWriter, r *http.Request) {
    logger := hlog.FromRequest(r)

    err := processRequest(r)
    if err != nil {
        logger.Error().Stack().Err(err).Msg("request failed")
        // Stack trace helps debug production issues
    }
}

Error Creation Patterns

Creating Errors with Stack Traces

import "github.com/pkg/errors"

// New error with stack trace
err := errors.New("something went wrong")

// Wrap existing error
err := errors.Wrap(originalErr, "additional context")

// Wrap with formatted message
err := errors.Wrapf(originalErr, "failed to process %s", filename)

// Create without stack trace (when not needed)
err := fmt.Errorf("validation failed: %w", validationErr)

Error Wrapping Best Practices

func readFile(path string) ([]byte, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        // Wrap with context
        return nil, errors.Wrapf(err, "failed to read %s", path)
    }
    return data, nil
}

func processConfig() error {
    data, err := readFile("config.yaml")
    if err != nil {
        // Add more context
        return errors.Wrap(err, "failed to load configuration")
    }
    // ...
    return nil
}

func main() {
    err := processConfig()
    if err != nil {
        logger.Error().Stack().Err(err).Msg("startup failed")
        // Full error chain and stack trace logged
    }
}

Stack Trace Format

The stack trace is logged as an array of objects, each containing:

{
  "stack": [
    {
      "source": "/full/path/to/file.go",
      "line": "42",
      "func": "functionName"
    },
    {
      "source": "/full/path/to/other.go",
      "line": "123",
      "func": "callerFunction"
    }
  ]
}

Field descriptions:

  • source - Full path to source file
  • line - Line number in source file
  • func - Function name (short name, not full path)

Best Practices

1. Enable Early

Configure the marshaler during initialization:

func init() {
    zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
}

2. Use Stack Selectively

Stack traces are verbose. Use them for errors, not warnings:

// Good
logger.Error().Stack().Err(err).Msg("critical failure")

// Avoid
logger.Warn().Stack().Err(err).Msg("minor issue")

3. Wrap Errors with Context

Add context when wrapping errors:

// Good - adds context
return errors.Wrapf(err, "failed to connect to %s", hostname)

// Less useful
return errors.Wrap(err, "error occurred")

4. Don't Double Stack

Don't call Stack() multiple times on the same error:

// Good
logger.Error().Stack().Err(err).Msg("failed")

// Avoid - redundant
logger.Error().Stack().Stack().Err(err).Msg("failed")

5. Consider Performance

Stack traces add overhead. For high-volume errors, consider sampling:

var errorCount int64

func logError(err error) {
    count := atomic.AddInt64(&errorCount, 1)

    event := logger.Error()

    // Only include stack for every 100th error
    if count%100 == 0 {
        event = event.Stack()
    }

    event.Err(err).Msg("error occurred")
}

6. Use for Debugging, Not Always

Enable stack traces in development, consider disabling in production:

func init() {
    if os.Getenv("ENV") == "development" {
        zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
    }
}

Performance Considerations

  • Stack trace extraction has minimal overhead
  • Stack traces increase log size significantly
  • Consider using only for critical errors
  • No overhead when Stack() is not called
  • Marshaling happens only when event is logged

Compatibility

Works with:

  • github.com/pkg/errors (primary use case)
  • Any error type that implements StackTrace() errors.StackTrace
  • Error wrapping chains

Does not work with:

  • Standard library errors (errors.New())
  • fmt.Errorf() errors
  • Custom errors without stack trace interface

Migration from pkg/errors

If you're using pkg/errors but not yet logging stack traces:

// Before
logger.Error().Err(err).Msg("failed")
// Error message logged but no stack trace

// After
func init() {
    zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
}

logger.Error().Stack().Err(err).Msg("failed")
// Error message and full stack trace logged

See Also

  • Core Logging - Error logging with Err()
  • Field Methods - Stack() and error methods
  • Global Configuration - ErrorStackMarshaler configuration
  • Hooks - Automatically add stack traces with hooks