uber-go/zap · critical

underlying error from the wrapped (*Logger, error) call

Error message

underlying error from the wrapped (*Logger, error) call

What it means

zap.Must panics with the error returned by a (*Logger, error) constructor (e.g. zap.NewProduction or a Config.Build) if that error is non-nil. The panic value is the underlying error itself, so the runtime message shows the wrapped cause of logger construction failure.

Source

Thrown at logger.go:119

	return NewProductionConfig().Build(options...)
}

// NewDevelopment builds a development Logger that writes DebugLevel and above
// logs to standard error in a human-friendly format.
//
// It's a shortcut for NewDevelopmentConfig().Build(...Option).
func NewDevelopment(options ...Option) (*Logger, error) {
	return NewDevelopmentConfig().Build(options...)
}

// Must is a helper that wraps a call to a function returning (*Logger, error)
// and panics if the error is non-nil. It is intended for use in variable
// initialization such as:
//
//	var logger = zap.Must(zap.NewProduction())
func Must(logger *Logger, err error) *Logger {
	if err != nil {
		panic(err)
	}

	return logger
}

// NewExample builds a Logger that's designed for use in zap's testable
// examples. It writes DebugLevel and above logs to standard out as JSON, but
// omits the timestamp and calling function to keep example output
// short and deterministic.
func NewExample(options ...Option) *Logger {
	encoderCfg := zapcore.EncoderConfig{
		MessageKey:     "msg",
		LevelKey:       "level",
		NameKey:        "logger",
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.StringDurationEncoder,
	}

View on GitHub (pinned to bbd4ecbd87)

Solutions

  1. Inspect the panic message for the wrapped cause (e.g. "open sink ...: permission denied") and fix the config/path.
  2. If construction may legitimately fail, use the (logger, err) form instead of Must and handle the error, falling back to zap.NewNop().
  3. Ensure output directories exist and are writable before the logger is constructed.
  4. Validate the Config (level names, encodings, paths) before calling Build.

Example fix

// before
var logger = zap.Must(zap.NewProduction()) // panics on failure
// after
logger, err := zap.NewProduction()
if err != nil {
    logger = zap.NewNop()
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate config before Must-style construction:
for _, p := range cfg.OutputPaths {
    if p != "stderr" && p != "stdout" {
        if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { return err }
    }
}
if _, err := zapcore.ParseLevel(cfg.Level.Level().String()); err != nil { return err }

Try / catch

func initLogger() *zap.Logger {
    l, err := zap.NewProduction()
    if err != nil {
        fmt.Fprintf(os.Stderr, "logger init failed: %v\n", err)
        return zap.NewNop()
    }
    return l
}
var logger = initLogger()

Prevention

When it happens

Trigger: Package-level var logger = zap.Must(zap.NewProduction()) (or zap.Must(cfg.Build())) when construction fails: invalid config paths, unreadable output files, invalid encoding names, unparseable level strings in the config.

Common situations: Program startup crashing at var-initialization time because the log file path is unwritable in a container, or a config with an invalid OutputPaths/Level value; commonly seen when deploying to restricted environments where /var/log isn't writable.

Related errors


AI-assisted analysis of uber-go/zap@bbd4ecbd87 (2026-08-31). Data as JSON: /api/errors/a4c17417c721a874. Report an issue: GitHub.