Logger lifecycle
Loggers are shared_ptr-owned. The registry holds one reference, you typically hold another (or
several), and if you're using async logging, the thread pool needs to stay alive as long as anything
might still log through it. Getting shutdown order wrong is the most common way spdlog "loses" the
last few log lines before a crash.
Shared ownership
class Server {
std::shared_ptr<spdlog::logger> log_ = spdlog::get("server");
public:
void handle_request() {
log_->info("handling request"); // no lookup on every call
}
};
spdlog::get walks the registry under a lock every time it's called. Look a logger up once —
typically at construction — and keep the shared_ptr, rather than calling spdlog::get on every log
statement in a hot path.
Sink sharing
Several loggers can share one sink instance — that's how two independent components can both write to the same file without interleaving corrupted output, because the sink (not the logger) owns the mutex that serializes writes. See Multi-sink loggers for the construction pattern.
Shutdown
spdlog::shutdown() drops every registered logger (equivalent to drop_all()) and, if async logging
is in use, joins the background thread pool so it doesn't outlive the loggers using it.
Once spdlog::shutdown() has run, logging through a dangling reference to a dropped logger — or from
a static object's destructor that races with spdlog's own static teardown — is undefined behavior.
Call spdlog::shutdown() as close to the end of main() as you can, after every other subsystem that
might still log has already stopped.
Static initialization order
The classic trap: a file-scope static auto logger = spdlog::stdout_color_mt("lib"); in a library.
Static initialization order across translation units is unspecified, so this can run before or after
spdlog's own internal statics are ready, depending on link order and luck.
The fix is either a function-local static (guaranteed to initialize on first use, not at program start):
spdlog::logger& lib_logger() {
static auto logger = spdlog::stdout_color_mt("lib");
return *logger;
}
or an explicit init() call the application invokes at a known point in startup.
Lifetime with async loggers
An async logger's thread pool must be alive whenever the logger might flush — which means the pool
has to outlive every async logger that uses it. spdlog::shutdown() handles the ordering for you if
you let it own the teardown; see
Thread pool and async logger for what happens
if you tear the pool down manually and get the order wrong.
See also
- The registry — where the shared ownership starts.
- Creating loggers — factory vs explicit construction.
- Thread pool and async logger — the extra lifetime constraint async adds.
- spdlog overview.