uber-go/zap · error
missing Level
Error message
missing Level
What it means
Config.Build validates that a non-zero Level is configured before constructing the logger. Because AtomicLevel's zero value is not usable, zap rejects configurations where cfg.Level was never set, returning errors.New("missing Level"). This prevents silently building a logger with an uninitialized level.
Source
Thrown at config.go:251
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}
}
// Build constructs a logger from the Config and Options.
func (cfg Config) Build(opts ...Option) (*Logger, error) {
enc, err := cfg.buildEncoder()
if err != nil {
return nil, err
}
sink, errSink, err := cfg.openSinks()
if err != nil {
return nil, err
}
if cfg.Level == (AtomicLevel{}) {
return nil, errors.New("missing Level")
}
log := New(
zapcore.NewCore(enc, sink, cfg.Level),
cfg.buildOptions(errSink)...,
)
if len(opts) > 0 {
log = log.WithOptions(opts...)
}
return log, nil
}
func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option {
opts := []Option{ErrorOutput(errSink)}
if cfg.Development {
opts = append(opts, Development())
}View on GitHub (pinned to bbd4ecbd87)
Solutions
- Set cfg.Level = zap.NewAtomicLevel() (or zap.NewAtomicLevelAt(zapcore.InfoLevel)) before calling Build
- Start from zap.NewProductionConfig() or zap.NewDevelopmentConfig() and override fields instead of building a zero-value Config
- If using an AtomicLevel shared with dynamic config, ensure it is initialized via zap.NewAtomicLevel before Build
Example fix
// before
cfg := zap.Config{Encoding: "json", OutputPaths: []string{"stderr"}}
logger, err := cfg.Build()
// after
cfg := zap.Config{Encoding: "json", OutputPaths: []string{"stderr"}}
cfg.Level = zap.NewAtomicLevelAt(zapcore.InfoLevel)
logger, err := cfg.Build() Defensive patterns
Strategy: validation
Validate before calling
if cfg.Level == (zap.AtomicLevel{}) {
cfg.Level = zap.NewAtomicLevelAt(zapcore.InfoLevel)
}
logger, err := cfg.Build() Type guard
func hasLevel(c zap.Config) bool {
return c.Level != (zap.AtomicLevel{})
} Prevention
- Always start from zap.NewProductionConfig()/NewDevelopmentConfig() and mutate
- Set cfg.Level immediately when constructing any zap.Config literal
- Use zap.New(core, opts) directly when you need full control instead of zero-value Config
When it happens
Trigger: Calling cfg.Build() (or zap.New(...)-style construction via Config) on a zap.Config literal where the Level field was left as the zero-value AtomicLevel{} — e.g. zap.Config{Encoding:"json"} without cfg.Level = zap.NewAtomicLevel().
Common situations: Hand-rolled zap.Config structs instead of zap.NewProductionConfig()/NewDevelopmentConfig; copying a config but forgetting to re-set Level; dynamic config loading that omits the level key.
Related errors
- missing EncodeTime in EncoderConfig
- no encoder registered for name %q
- no encoder name specified
- must specify logging level
- can't register a sink factory for empty string
AI-assisted analysis of uber-go/zap@bbd4ecbd87 (2026-08-31).
Data as JSON: /api/errors/15345385457b19f3.
Report an issue: GitHub.