caddyserver/caddy · error
invalid log level: %v
Error message
invalid log level: %v
What it means
parseLevel first runs the level string through the replacer (placeholders like {$ENV_VAR}); if replacement itself errors — unset env var without a default, or malformed placeholder syntax — the level is invalid before it is even matched against the known level names.
Source
Thrown at logging.go:755
if IsWriterStandardStream(wo) && term.IsTerminal(int(os.Stderr.Fd())) {
// if interactive terminal, make output more human-readable by default
encCfg.EncodeTime = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString(ts.UTC().Format("2006/01/02 15:04:05.000"))
}
if coloringEnabled {
encCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
}
return zapcore.NewConsoleEncoder(encCfg)
}
return zapcore.NewJSONEncoder(encCfg)
}
func parseLevel(levelInput string) (zapcore.LevelEnabler, error) {
repl := NewReplacer()
level, err := repl.ReplaceOrErr(levelInput, true, true)
if err != nil {
return nil, fmt.Errorf("invalid log level: %v", err)
}
level = strings.ToLower(level)
// set up the log level
switch level {
case "debug":
return zapcore.DebugLevel, nil
case "", "info":
return zapcore.InfoLevel, nil
case "warn":
return zapcore.WarnLevel, nil
case "error":
return zapcore.ErrorLevel, nil
case "panic":
return zapcore.PanicLevel, nil
case "fatal":
return zapcore.FatalLevel, nil
default:View on GitHub (pinned to 50e54ee279)
Solutions
- Export the referenced environment variable in the environment Caddy runs in.
- Provide an inline default: level {$LOG_LEVEL:INFO}.
- Hardcode the level while debugging to isolate the placeholder problem.
Example fix
# before
export LOG_LEVEL= # unset/empty in env
global { log { level {$LOG_LEVEL} } }
# after
global { log { level {$LOG_LEVEL:INFO} } } Defensive patterns
Strategy: validation
Validate before calling
// resolve level placeholders yourself before loading
func resolvedLevel(envLookup func(string) (string, bool), raw string) (string, error) {
if v, ok := envLookup(strings.Trim(raw, "{}$")); ok && v != "" {
return v, nil
}
if def, ok := strings.CutPrefix(strings.Trim(raw, "{}$"), ""); ok && def != "" {
return def, nil
}
return "", fmt.Errorf("level placeholder %s unresolvable", raw)
} Try / catch
if err := caddy.Validate(cfg); err != nil {
if strings.Contains(err.Error(), "invalid log level") {
// placeholder failed to resolve — check env vars and defaults
}
return err
} Prevention
- Always add defaults to placeholders: {$LOG_LEVEL:INFO}.
- Run 'caddy validate' in the same environment (env vars) as production.
- Keep an env-var manifest per deployment and diff it during releases.
When it happens
Trigger: level (or stacktrace level) set to {$LOG_LEVEL} where LOG_LEVEL is unset and no default suffix is provided; placeholder braces unbalanced; placeholder containing an invalid default after the colon.
Common situations: Docker/systemd deployments where the env var is injected in one environment but missing in another; CI running caddy validate without the production env; copy-pasted configs referencing variables that were never exported.
Related errors
- invalid action type
- include and exclude must not intersect, but found %s in both
- when both include and exclude are populated, each element mu
- unrecognized log level: %s
- server %s: logger name must not have a port: %s
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/cb0f7ae5ae3f567f.
Report an issue: GitHub.