Introduction: Why Proper Logging in C++ Matters
Logging is not just about writing messages to a file. In a production environment, it is your primary tool for monitoring, debugging, and auditing. Poorly configured logging can lead to loss of critical data, performance degradation (I/O-bound), or, conversely, to "noisy" disk clogging. In this article, we will cover best practices for logging in C++ that will help you avoid common mistakes and build a reliable log collection system.
1. Choosing a Library: spdlog vs glog vs std::clog
The C++ standard library only offers basic streams std::clog and std::cerr, which are unsuitable for serious projects. Let's consider three popular approaches:
1.1. spdlog — The Modern Standard
spdlog is a header-only library with support for asynchronous logging, file rotation, formatting (à la fmtlib), and multiple sinks (file, console, syslog, Kafka).
Key advantages:
- High performance (due to asynchronous threads)
- Flexible configuration of levels and formats
- Support for structured logging (JSON)
#include <spdlog/spdlog.h>#include <spdlog/sinks/rotating_file_sink.h>
int main() { // Create a rotating file logger (5 files of 10 MB each) auto logger = spdlog::rotating_logger_mt("file_logger", "logs/app.log", 1048576 * 10, 5); logger->set_level(spdlog::level::info); logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%t] %v");
logger->info("Application started"); logger->warn("Low memory: {} MB", free_memory); logger->error("Database connection error: {}", err.what()); return 0;}1.2. Google glog — A Proven Solution
The glog library from Google is simple to use and has built-in support for conditional logging and stack dumps. The downside is less flexible formatting and lack of asynchronicity.
#include <glog/logging.h>
int main(int argc, char* argv[]) { google::InitGoogleLogging(argv[0]); FLAGS_log_dir = "./logs"; FLAGS_stderrthreshold = google::WARNING; // Duplicate WARNING+ to stderr
LOG(INFO) << "Server started on port " << port; LOG_IF(WARNING, memory_usage > 0.9) << "Memory almost exhausted"; LOG(FATAL) << "Critical error, terminating"; return 0;}1.3. Custom Logger Using std::clog
Use std::clog only for debugging or very small utilities. It does not support rotation, levels, or multithreading.
#define LOG(level, msg) std::clog << "[" << level << "] " << msg << std::endl;LOG("INFO", "Starting");LOG("ERROR", "Error: " + std::to_string(errno));2. Best Practices for Configuration and Architecture
Even the best library won't help if the logging architecture is poorly designed. Follow these principles:
2.1. Use Logging Levels
Define 5-6 levels and strictly adhere to them:
- TRACE — detailed debugging (function entry/exit)
- DEBUG — debugging information for developers
- INFO — normal application operation (startup, shutdown)
- WARN — potential issues (low disk space, high load)
- ERROR — errors that do not interrupt operation (exceptions)
- FATAL — critical error after which the application terminates
// In production, set to WARN to avoid cluttering logs with INFO messageslogger->set_level(spdlog::level::warn);2.2. Structured Logging (JSON)
For