Skip to main content

Log levels

Levels aren't one filter, they're two: a runtime one that lives per logger (and optionally per sink), and a compile-time one that deletes the call site entirely before the compiler even sees it as a function call. Confusing the two is the most common reason "I set the level but nothing changed."

The six levels

LevelenumIntended for
tracespdlog::level::traceExtremely verbose, per-iteration detail — off by default even in dev
debugspdlog::level::debugDevelopment diagnostics, disabled in production
infospdlog::level::infoNormal operational messages — the default level
warnspdlog::level::warnSomething unexpected but recoverable
errspdlog::level::errAn operation failed
criticalspdlog::level::criticalThe process can't continue meaningfully
offspdlog::level::offSuppresses everything, including critical

Runtime filtering

logger->set_level(spdlog::level::debug) changes one logger; spdlog::set_level(...) changes every registered logger at once. Independently, each sink can also have its own level via sink->set_level(...).

A message must pass both the logger level and the sink level
auto sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
sink->set_level(spdlog::level::warn); // sink drops anything below warn

auto log = std::make_shared<spdlog::logger>("app", sink);
log->set_level(spdlog::level::debug); // logger itself is happy to pass debug+

log->debug("this is silently dropped by the sink, not the logger");
log->warn("this gets through — it passes both filters");

Whichever filter is stricter wins. If a message vanishes, check the sink level before assuming the logger level is wrong.

Compile-time filtering

SPDLOG_ACTIVE_LEVEL and the matching SPDLOG_TRACE/SPDLOG_DEBUG/SPDLOG_INFO/... macros remove disabled calls at compile time — arguments included, so an expensive-to-format argument at a disabled level costs nothing at runtime.

target_compile_definitions(app PRIVATE SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG)

See Compile-time log level for the full mechanism, including the ODR hazard of mixing values across translation units.

Level from environment

spdlog::cfg::load_env_levels() reads SPDLOG_LEVEL and applies it to loggers by name:

SPDLOG_LEVEL=info,app=debug ./myapp

That sets the global default to info and overrides the "app" logger specifically to debug.

Env-var levels are the cheapest runtime config you'll ever add

One call at startup (spdlog::cfg::load_env_levels()) gets you per-deployment, per-logger level control with zero configuration-file machinery.

Which mechanism when

Runtime levelSink levelCompile-time macro
Cost when disabledOne comparisonOne comparison, after formatting startsZero — code doesn't exist
GranularityPer loggerPer sinkWhole binary (or per-TU if you're not careful)
Changeable without rebuildYesYesNo

See also