nsqio/nsq · error

invalid log level '%s' (debug, info, warn, error, fatal)

Error message

invalid log level '%s' (debug, info, warn, error, fatal)

What it means

internal/lg/loglevel.go's LevelFromString (internal/lg/lg.go) parses log level strings for every nsq binary's --log-level flag; it accepts only debug, info, warn, error and fatal after lowercasing. Any other value returns 'invalid log level \'%s\' ...' listing the valid set, and the binary exits during flag validation before doing any work.

Source

Thrown at internal/lg/lg.go:73

		return "FATAL"
	}
	return "invalid"
}

func ParseLogLevel(levelstr string) (LogLevel, error) {
	switch strings.ToLower(levelstr) {
	case "debug":
		return DEBUG, nil
	case "info":
		return INFO, nil
	case "warn":
		return WARN, nil
	case "error":
		return ERROR, nil
	case "fatal":
		return FATAL, nil
	}
	return 0, fmt.Errorf("invalid log level '%s' (debug, info, warn, error, fatal)", levelstr)
}

func Logf(logger Logger, cfgLevel LogLevel, msgLevel LogLevel, f string, args ...interface{}) {
	if cfgLevel > msgLevel {
		return
	}
	_ = logger.Output(3, fmt.Sprintf(msgLevel.String()+": "+f, args...))
}

func LogFatal(prefix string, f string, args ...interface{}) {
	logger := log.New(os.Stderr, prefix, log.Ldate|log.Ltime|log.Lmicroseconds)
	Logf(logger, FATAL, FATAL, f, args...)
	os.Exit(1)
}

View on GitHub (pinned to 85cf10c09c)

Solutions

  1. Use exactly one of: debug, info, warn, error, fatal (case-insensitive).
  2. If the level comes from an environment variable that may be unset, default it in the wrapper: --log-level=${LOG_LEVEL:-info}.
  3. Fix 'warning' -> 'warn' and 'err' -> 'error' in configs; these are the two most common near-misses.

Example fix

# before
nsqd --log-level=warning
# invalid log level 'warning' (debug, info, warn, error, fatal)

# after
nsqd --log-level=warn
Defensive patterns

Strategy: validation

Validate before calling

func isValidLogLevel(s string) bool {
    switch strings.ToLower(s) {
    case "debug", "info", "warn", "error", "fatal":
        return true
    }
    return false
}

if lvl := os.Getenv("NSQ_LOG_LEVEL"); lvl != "" && !isValidLogLevel(lvl) {
    return fmt.Errorf("NSQ_LOG_LEVEL=%q invalid; use debug|info|warn|error|fatal", lvl)
}

Type guard

func isValidLogLevel(s string) bool {
    switch strings.ToLower(s) {
    case "debug", "info", "warn", "error", "fatal":
        return true
    }
    return false
}

Try / catch

// wrapper scripts: catch flag errors and hint the accepted set
if err := runNsqd(args); err != nil && strings.Contains(err.Error(), "invalid log level") {
    return errors.New("set --log-level to one of: debug, info, warn, error, fatal (note: 'warning' is not valid)")
}

Prevention

When it happens

Trigger: Passing --log-level=warning, --log-level=WARNING is fine (lowercased) but --log-level=err, notice, trace, INFO2, or a value with whitespace/typo (e.g. 'in fo', 'info ') fails. It applies to nsqd, nsqlookupd, nsqadmin, and the apps since they all use internal/lg via their options.

Common situations: Copy-pasting log level names from other ecosystems (python 'warning', syslog 'notice', logrus 'trace'); config templating that substitutes an empty or unset variable producing '--log-level=' (empty string is NOT in the switch, so it fails); upgrading scripts where 'warning' was tolerated by an older wrapper.

Related errors


AI-assisted analysis of nsqio/nsq@85cf10c09c (2026-08-16). Data as JSON: /api/errors/8f88b5fe71a93569. Report an issue: GitHub.