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.
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/errorsThe pkg/errors package provides error wrapping with stack traces. The pkgerrors subpackage extracts these stack traces and formats them for zerolog.
Features:
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.
// 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 traceCustomize 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 nameExample:
func init() {
// Customize field names
pkgerrors.StackSourceFileName = "file"
pkgerrors.StackSourceLineName = "line_number"
pkgerrors.StackSourceFunctionName = "function"
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
}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"
}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"
}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
}
}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")
}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
}
}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"}]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
}
}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)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
}
}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 fileline - Line number in source filefunc - Function name (short name, not full path)Configure the marshaler during initialization:
func init() {
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
}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")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")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")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")
}Enable stack traces in development, consider disabling in production:
func init() {
if os.Getenv("ENV") == "development" {
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
}
}Stack() is not calledWorks with:
github.com/pkg/errors (primary use case)StackTrace() errors.StackTraceDoes not work with:
errors.New())fmt.Errorf() errorsIf 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