uber-go/zap · error

unrecognized level: %q

Error message

unrecognized level: %q

What it means

Level.UnmarshalText parses textual level names ("debug", "info", "warn", "error", "dpanic", "panic", "fatal", case-insensitively). If the text (and its lowercase form) matches none of them, it returns "unrecognized level: %q". The nil-receiver case is reported separately as errUnmarshalNilLevel.

Source

Thrown at zapcore/level.go:175

// MarshalText marshals the Level to text. Note that the text representation
// drops the -Level suffix (see example).
func (l Level) MarshalText() ([]byte, error) {
	return []byte(l.String()), nil
}

// UnmarshalText unmarshals text to a level. Like MarshalText, UnmarshalText
// expects the text representation of a Level to drop the -Level suffix (see
// example).
//
// In particular, this makes it easy to configure logging levels using YAML,
// TOML, or JSON files.
func (l *Level) UnmarshalText(text []byte) error {
	if l == nil {
		return errUnmarshalNilLevel
	}
	if !l.unmarshalText(text) && !l.unmarshalText(bytes.ToLower(text)) {
		return fmt.Errorf("unrecognized level: %q", text)
	}
	return nil
}

func (l *Level) unmarshalText(text []byte) bool {
	switch string(text) {
	case "debug":
		*l = DebugLevel
	case "info", "": // make the zero value useful
		*l = InfoLevel
	case "warn", "warning":
		*l = WarnLevel
	case "error":
		*l = ErrorLevel
	case "dpanic":
		*l = DPanicLevel
	case "panic":
		*l = PanicLevel

View on GitHub (pinned to bbd4ecbd87)

Solutions

  1. Use one of the exact level names: debug, info, warn, error, dpanic, panic, fatal (case-insensitive).
  2. Trim and validate the level string before parsing, e.g. strings.TrimSpace(strings.ToLower(os.Getenv("LOG_LEVEL"))).
  3. Prefer zapcore.ParseLevel with a fallback: on error, log a warning and default to info.
  4. If you need a custom name like "trace", map it in your config layer to a real zap level.

Example fix

// before
level, _ := zapcore.ParseLevel(cfg.Level) // "verbose" -> unrecognized level
// after
level, err := zapcore.ParseLevel(cfg.Level)
if err != nil {
    level = zapcore.InfoLevel
}
Defensive patterns

Strategy: validation

Validate before calling

var validLevels = map[string]bool{
    "debug": true, "info": true, "warn": true,
    "error": true, "dpanic": true, "panic": true, "fatal": true,
}
lvl := strings.TrimSpace(strings.ToLower(os.Getenv("LOG_LEVEL")))
if !validLevels[lvl] {
    lvl = "info"
}
level, err := zapcore.ParseLevel(lvl)

Try / catch

level, err := zapcore.ParseLevel(raw)
if err != nil {
    log.Printf("invalid LOG_LEVEL %q, defaulting to info", raw)
    level = zapcore.InfoLevel
}

Prevention

When it happens

Trigger: Unmarshaling a config value (YAML/JSON/TOML) or calling zapcore.ParseLevel/Set with a string that is not a valid level name, e.g. ParseLevel("verbose"), "WARNING" typo variants like "warnning", or empty string.

Common situations: Config files with misspelled or nonstandard level names ("trace", "verbose", "WARN "+trailing whitespace), environment-variable-driven levels where users supply arbitrary strings, older zap versions lacking "notice"/custom levels.

Related errors


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