Easylogging++
High-performance, extensible, lightweight C++ logging library. Provides customizable format, logging support for classes/third-party libraries/STL containers, and thread-safe design. Characterized by balance of ease of use and functionality.
Library
Easylogging++
Overview
Easylogging++ is a "high-performance, extensible, lightweight C++ logging library" developed with a balance of ease of use and functionality as its key feature. Conceptualized with "customizable format, logging support for classes/third-party libraries/STL containers, and thread-safe design," it provides easy introduction through header-only design and a rich feature set. Achieves comprehensive log management in C++ projects with a single header file, supporting medium to large-scale applications through the balance of configuration flexibility and performance.
Details
Easylogging++ 2025 edition maintains its solid position as a balanced C++ logging library with stable adoption in medium-scale projects. Provides comprehensive features including easy introduction through single header file (easylogging++.h), flexible customization via configuration files or programmatic settings, multi-threading support, rotating/daily log files, color console output, and performance measurement functionality. While not offering the ultra-high speed of spdlog, it's valued as a choice that achieves full-scale log management while keeping learning costs low through ease of configuration and rich functionality.
Key Features
- Header-only Design: Easy introduction through single header file
- Rich Configuration Options: Support for both file configuration and programmatic settings
- Thread-safe: Safe operation in multi-threaded environments
- Performance Measurement: Execution time measurement via TIMED_FUNC and TIMED_SCOPE macros
- Custom Class Support: Support for logging custom classes
- Crash Handling: Automatic log output on exception occurrence
Pros and Cons
Pros
- Simplified introduction and build through header-only design
- Detailed customization through rich configuration options
- Optimal for medium-scale projects with comprehensive feature set
- Runtime configuration changes possible through configuration files
- Direct log output support for STL containers and custom classes
- Low learning cost with comprehensive documentation
Cons
- Inferior performance compared to spdlog
- Performance constraints in large-scale, high-load systems
- Maintenance difficulties as configuration complexity increases
- Slower adoption of latest C++ features compared to other libraries
- Potential compilation time increase due to header-only design
- Other choices tend to be prioritized in modern C++ projects
Reference Pages
Usage Examples
Installation and Basic Setup
# Download source from Git
git clone https://github.com/abumq/easyloggingpp.git
# Copy header file only
cp easyloggingpp/src/easylogging++.h ./include/
cp easyloggingpp/src/easylogging++.cc ./src/
# Using vcpkg
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install easyloggingpp
# Using Conan
# Add to conanfile.txt
echo "[requires]" > conanfile.txt
echo "easyloggingpp/9.97.1" >> conanfile.txt
# Build with CMake
mkdir build && cd build
cmake -Dtest=ON ../
make
make install
Basic Log Output
#include "easylogging++.h"
// Required initialization macro (once in main file only)
INITIALIZE_EASYLOGGINGPP
int main(int argc, char* argv[]) {
// Initialization supporting startup arguments
START_EASYLOGGINGPP(argc, argv);
// Basic log output
LOG(INFO) << "Application started";
LOG(DEBUG) << "Debug info: Configuration loaded";
LOG(WARNING) << "Warning: Configuration file not found";
LOG(ERROR) << "Error: Database connection failed";
// Data output
int userId = 123;
std::string userName = "John Doe";
LOG(INFO) << "User processing: ID=" << userId << ", Name=" << userName;
// STL container output
std::vector<int> numbers = {1, 2, 3, 4, 5};
LOG(INFO) << "Number array: " << numbers;
std::map<std::string, int> scores = {
{"Math", 85}, {"English", 92}, {"Literature", 78}
};
LOG(INFO) << "Scores: " << scores;
return 0;
}
// Compilation example
// g++ main.cpp easylogging++.cc -o app -std=c++11 -DELPP_THREAD_SAFE
Customization via Configuration Files
// logging.conf
/*
* GLOBAL:
FORMAT = "%datetime %level %msg"
FILENAME = "/tmp/logs/myapp.log"
ENABLED = true
TO_FILE = true
TO_STANDARD_OUTPUT = true
SUBSECOND_PRECISION = 6
PERFORMANCE_TRACKING = true
MAX_LOG_FILE_SIZE = 2097152 ## 2MB
LOG_FLUSH_THRESHOLD = 100 ## Flush every 100 logs
* DEBUG:
FORMAT = "%datetime{%d/%M} %func %msg"
FILENAME = "/tmp/logs/debug.log"
* ERROR:
FORMAT = "%datetime %level [%loc] %msg"
FILENAME = "/tmp/logs/error.log"
*/
#include "easylogging++.h"
INITIALIZE_EASYLOGGINGPP
int main(void) {
// Load from configuration file
el::Configurations conf("/path/to/logging.conf");
// Reconfigure default logger
el::Loggers::reconfigureLogger("default", conf);
// Or reconfigure all loggers
el::Loggers::reconfigureAllLoggers(conf);
// Using global configuration file
el::Loggers::configureFromGlobal("global.conf");
LOG(INFO) << "Configuration from file completed";
LOG(DEBUG) << "Debug logs output to debug.log";
LOG(ERROR) << "Error logs output to error.log";
return 0;
}
Programmatic Configuration and Customization
#include "easylogging++.h"
INITIALIZE_EASYLOGGINGPP
int main(int argc, const char** argv) {
// Programmatic configuration
el::Configurations defaultConf;
defaultConf.setToDefault();
// Basic settings
defaultConf.set(el::Level::Info,
el::ConfigurationType::Format, "%datetime %level %msg");
defaultConf.set(el::Level::Info,
el::ConfigurationType::Filename, "./logs/info.log");
defaultConf.set(el::Level::Info,
el::ConfigurationType::Enabled, "true");
// Global settings
defaultConf.setGlobally(
el::ConfigurationType::Format, "%datetime [%level] %msg");
defaultConf.setGlobally(
el::ConfigurationType::ToFile, "true");
defaultConf.setGlobally(
el::ConfigurationType::ToStandardOutput, "true");
// Apply configuration
el::Loggers::reconfigureLogger("default", defaultConf);
// Create and configure new logger
el::Logger* businessLogger = el::Loggers::getLogger("business");
el::Configurations businessConf;
businessConf.setToDefault();
businessConf.set(el::Level::Info,
el::ConfigurationType::Format, "%datetime [BUSINESS] %msg");
businessConf.set(el::Level::Info,
el::ConfigurationType::Filename, "./logs/business.log");
el::Loggers::reconfigureLogger("business", businessConf);
// Usage examples
LOG(INFO) << "Default logger log";
CLOG(INFO, "business") << "Business logger log";
// Inline configuration parsing
el::Configurations c;
c.setToDefault();
c.parseFromText("*GLOBAL:\n FORMAT = %level %msg");
el::Loggers::reconfigureLogger("default", c);
return 0;
}
Conditional Logging and Performance Features
#include "easylogging++.h"
INITIALIZE_EASYLOGGINGPP
// Heavy processing example
void performHeavyTask(int iter) {
// Measure execution time of entire function
TIMED_FUNC(timerObj);
LOG(INFO) << "Starting heavy task: " << iter << " iterations";
// Initialization process
usleep(5000);
while (iter-- > 0) {
// Block-level execution time measurement
TIMED_SCOPE(timerBlkObj, "heavy-iter");
// Conditional logging
LOG_IF(iter % 10 == 0, INFO) << "Progress: " << iter << " remaining";
// Log every N times
LOG_EVERY_N(5, DEBUG) << "Debug log every 5 times";
// Log only first N times
LOG_N_TIMES(3, INFO) << "First 3 times only: " << iter;
// Log after N times
LOG_AFTER_N(10, WARNING) << "Warning after 10 occurrences";
// Heavy processing simulation
usleep(iter * 1000);
// Performance checkpoint
if (iter % 3 == 0) {
PERFORMANCE_CHECKPOINT(timerBlkObj);
}
}
LOG(INFO) << "Heavy task completed";
}
int main() {
// Enable performance tracking
el::Loggers::addFlag(el::LoggingFlag::PerformanceTracking);
LOG(INFO) << "Performance test started";
performHeavyTask(20);
// Simultaneous output with multiple loggers
CLOG(INFO, "default", "performance") << "Output with multiple loggers";
return 0;
}
Custom Classes and Formatters
#include "easylogging++.h"
INITIALIZE_EASYLOGGINGPP
// Custom class inheriting el::Loggable
class User : public el::Loggable {
public:
User(int id, const std::string& name, int age)
: m_id(id), m_name(name), m_age(age) {}
// Implementation of log output method
virtual void log(el::base::type::ostream_t& os) const {
os << "User{id=" << m_id << ", name='" << m_name
<< "', age=" << m_age << "}";
}
private:
int m_id;
std::string m_name;
int m_age;
};
// Class using MAKE_LOGGABLE macro
class Product {
public:
Product(int id, const std::string& name, double price)
: m_id(id), m_name(name), m_price(price) {}
int getId() const { return m_id; }
const std::string& getName() const { return m_name; }
double getPrice() const { return m_price; }
private:
int m_id;
std::string m_name;
double m_price;
};
// Custom formatter implementation
inline MAKE_LOGGABLE(Product, product, os) {
os << "Product{id=" << product.getId()
<< ", name='" << product.getName()
<< "', price=" << product.getPrice() << "}";
return os;
}
// Custom formatter for time type
inline MAKE_LOGGABLE(std::chrono::system_clock::time_point, when, os) {
time_t t = std::chrono::system_clock::to_time_t(when);
auto tm = std::localtime(&t);
char buf[1024];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S (%Z)", tm);
os << buf;
return os;
}
// Custom format specifier
const char* getClientIP(const el::LogMessage*) {
return "192.168.1.100"; // Actually retrieve from request
}
int main(void) {
// Install custom format specifier
el::Helpers::installCustomFormatSpecifier(
el::CustomFormatSpecifier("%client_ip", getClientIP));
// Reconfigure format
el::Loggers::reconfigureAllLoggers(
el::ConfigurationType::Format,
"%datetime %level %client_ip : %msg");
// Custom class log output
User user(1, "John Doe", 30);
LOG(INFO) << "User info: " << user;
Product product(101, "Laptop", 899.00);
LOG(INFO) << "Product info: " << product;
// Time type log output
auto now = std::chrono::system_clock::now();
LOG(INFO) << "Current time: " << now;
// Nested STL container logs
std::vector<Product> products = {
Product(101, "Laptop", 899.00),
Product(102, "Mouse", 29.80),
Product(103, "Keyboard", 54.80)
};
LOG(INFO) << "Product list: " << products;
return 0;
}
Error Handling and Crash Support
#include "easylogging++.h"
INITIALIZE_EASYLOGGINGPP
// Custom crash handler
void myCrashHandler(int sig) {
LOG(FATAL) << "Application crash! Signal: " << sig;
LOG(FATAL) << "Stack trace information logged";
// Required: application termination
el::Helpers::crashAbort(sig);
}
// Custom configuration validation
bool validateLogConfiguration(const el::Configurations& conf) {
// Validate log file path
std::string filename = conf.get(el::Level::Info, el::ConfigurationType::Filename)->value();
if (filename.empty()) {
LOG(ERROR) << "Log filename not configured";
return false;
}
// Check directory existence
std::string logDir = filename.substr(0, filename.find_last_of("/"));
struct stat info;
if (stat(logDir.c_str(), &info) != 0 || !(info.st_mode & S_IFDIR)) {
LOG(ERROR) << "Log directory does not exist: " << logDir;
return false;
}
return true;
}
class SafeLogger {
public:
SafeLogger() {
try {
// Safe configuration loading
el::Configurations conf;
conf.setToDefault();
// Basic configuration
conf.setGlobally(el::ConfigurationType::Format,
"%datetime{%Y-%M-%d %H:%m:%s.%g} [%level] %msg");
conf.setGlobally(el::ConfigurationType::ToFile, "true");
conf.setGlobally(el::ConfigurationType::ToStandardOutput, "true");
conf.setGlobally(el::ConfigurationType::MaxLogFileSize, "5242880"); // 5MB
if (validateLogConfiguration(conf)) {
el::Loggers::reconfigureAllLoggers(conf);
LOG(INFO) << "Logger configuration completed";
} else {
// Fallback configuration
el::Configurations fallback;
fallback.setToDefault();
fallback.setGlobally(el::ConfigurationType::ToStandardOutput, "true");
fallback.setGlobally(el::ConfigurationType::ToFile, "false");
el::Loggers::reconfigureAllLoggers(fallback);
LOG(WARNING) << "Using fallback configuration";
}
} catch (const std::exception& e) {
std::cerr << "Logger initialization error: " << e.what() << std::endl;
}
}
template<typename T>
void safeLog(el::Level level, const T& message) {
try {
switch (level) {
case el::Level::Info:
LOG(INFO) << message;
break;
case el::Level::Debug:
LOG(DEBUG) << message;
break;
case el::Level::Warning:
LOG(WARNING) << message;
break;
case el::Level::Error:
LOG(ERROR) << message;
break;
case el::Level::Fatal:
LOG(FATAL) << message;
break;
default:
LOG(INFO) << message;
}
} catch (...) {
std::cerr << "Log output error" << std::endl;
}
}
};
int main(void) {
// Configure crash handler
el::Helpers::setCrashHandler(myCrashHandler);
SafeLogger logger;
logger.safeLog(el::Level::Info, "Safe logger test started");
try {
// Processing that may cause exceptions
throw std::runtime_error("Test exception");
} catch (const std::exception& e) {
logger.safeLog(el::Level::Error, std::string("Exception caught: ") + e.what());
}
// Intentional crash test (remove in actual use)
// int* p = nullptr;
// *p = 42; // Segmentation fault
logger.safeLog(el::Level::Info, "Program terminated normally");
return 0;
}
Usage in Multi-threaded Environments
#include "easylogging++.h"
#include <thread>
#include <vector>
#include <mutex>
#include <chrono>
INITIALIZE_EASYLOGGINGPP
class ThreadSafeCounter {
private:
int count = 0;
std::mutex mtx;
public:
void increment(int threadId) {
std::lock_guard<std::mutex> lock(mtx);
count++;
// Thread-safe log output
LOG(INFO) << "Thread " << threadId << ": count = " << count;
}
int getCount() const {
std::lock_guard<std::mutex> lock(mtx);
return count;
}
};
void workerFunction(int threadId, ThreadSafeCounter& counter) {
// Thread-specific logger
el::Logger* threadLogger = el::Loggers::getLogger("thread_" + std::to_string(threadId));
CLOG(INFO, ("thread_" + std::to_string(threadId)).c_str())
<< "Worker thread started: ID=" << threadId;
for (int i = 0; i < 10; ++i) {
counter.increment(threadId);
// Simulate processing time
std::this_thread::sleep_for(std::chrono::milliseconds(100));
CLOG(DEBUG, ("thread_" + std::to_string(threadId)).c_str())
<< "Process " << i + 1 << "/10 completed";
}
CLOG(INFO, ("thread_" + std::to_string(threadId)).c_str())
<< "Worker thread finished: ID=" << threadId;
}
int main() {
// Enable thread-safe flag
el::Loggers::addFlag(el::LoggingFlag::MultiLoggerSupport);
// Configure loggers for each thread
for (int i = 0; i < 5; ++i) {
std::string loggerId = "thread_" + std::to_string(i);
el::Logger* logger = el::Loggers::getLogger(loggerId);
el::Configurations conf;
conf.setToDefault();
conf.set(el::Level::Global, el::ConfigurationType::Format,
"%datetime [THREAD-" + std::to_string(i) + "] %level %msg");
conf.set(el::Level::Global, el::ConfigurationType::Filename,
"./logs/thread_" + std::to_string(i) + ".log");
el::Loggers::reconfigureLogger(loggerId, conf);
}
LOG(INFO) << "Multi-thread test started";
ThreadSafeCounter counter;
std::vector<std::thread> threads;
// Create worker threads
for (int i = 0; i < 5; ++i) {
threads.emplace_back(workerFunction, i, std::ref(counter));
}
// Wait for all threads to complete
for (auto& thread : threads) {
thread.join();
}
LOG(INFO) << "All threads completed. Final count: " << counter.getCount();
return 0;
}
// Compilation example (thread-safe enabled)
// g++ main.cpp easylogging++.cc -o app -std=c++11 -pthread -DELPP_THREAD_SAFE