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.

Logging LibraryC++Header-onlyHigh PerformanceThread-safe

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