A modern C++20 logging library with modular architecture, rich formatting, visual widgets, and zero-overhead performance.
Echo is a high-performance, header-mostly logging library designed for modern C++20 applications. It combines powerful logging capabilities with rich terminal UI widgets, providing everything from simple console output to sophisticated multi-sink logging with custom formatters, category filtering, and visual progress indicators.
Key Features:
- Zero overhead - Compile-time log filtering eliminates runtime cost (literally 0ns!)
- Modular architecture - Enable only the sinks you need via compile-time flags
- Performance first - 17M+ ops/sec for filtered logs, 3-4M ops/sec for active logging
- Rich formatting - Pattern formatters, custom formatters, color support
- Visual widgets - Progress bars, spinners, banners, boxes with themes
- Developer friendly - Fluent API, category filtering, thread-safe
Echo is built for applications that demand both flexibility and performance, from embedded systems to high-throughput servers.
#include <echo/echo.hpp>
int main() {
// Simple printing (no log levels)
echo("Hello, world!");
echo("Colored text").red().bold();
// Logging with levels
echo::info("Application started");
echo::debug("Value: ", 42);
echo::error("Something went wrong!").red();
// Fluent interface with colors
echo::warn("Warning!").yellow().bold();
echo::info("Custom color").hex("#FF1493");
echo::error("RGB color").rgb(255, 87, 51);
// Print only once (useful in loops)
for (int i = 0; i < 1000; i++) {
echo::info("This prints only once!").once();
}
return 0;
}┌─────────────────────────────────────────────────────────────────────────┐
│ ECHO LIBRARY │
├──────────────┬──────────────┬──────────────┬──────────────┬─────────────┤
│ Core │ Sinks │ Formatters │ Filters │ Utils │
│ │ │ │ │ │
│ ┌────────┐ │ ┌────────┐ │ ┌────────┐ │ ┌────────┐ │ ┌────────┐ │
│ │ Proxy │ │ │Console │ │ │Pattern │ │ │Category│ │ │ Color │ │
│ │ Level │ │ │ File │ │ │ Custom │ │ │ Level │ │ │Terminal│ │
│ │ Mutex │ │ │Syslog* │ │ │Default │ │ │ │ │ │ Hash │ │
│ │ Once │ │ │Network*│ │ │ │ │ │ │ │ │ │ │
│ │ Time │ │ │ Null* │ │ │ │ │ │ │ │ │ │ │
│ └────────┘ │ └────────┘ │ └────────┘ │ └────────┘ │ └────────┘ │
│ │ │ │ │ │
└──────────────┴──────────────┴──────────────┴──────────────┴─────────────┘
│ │ │ │ │
└───────────────┴───────────────┴──────────────┴──────────────┘
│
┌───────▼────────┐
│ Your App │
└────────────────┘
* Optional sinks (compile-time flags)
Data Flow:
Log Call → Compile-Time Filter → Runtime Filter → Category Filter
↓
Format Message → Formatter (Pattern/Custom/Default)
↓
Sink Registry → [ConsoleSink, FileSink*, SyslogSink*, NetworkSink*, NullSink*]
↓
Output (stdout/stderr, file, syslog, network, /dev/null)
include(FetchContent)
FetchContent_Declare(
echo
GIT_REPOSITORY https://github.com/robolibs/echo.git
GIT_TAG v0.0.21
)
FetchContent_MakeAvailable(echo)
target_link_libraries(your_target PRIVATE echo::echo)add_requires("echo")
target("your_target")
set_kind("binary")
add_packages("echo")
add_files("src/*.cpp")git clone https://github.com/robolibs/echo.git
cd echo
# Using Make (auto-detects cmake/xmake)
make config
make build
make test
# Or directly with CMake
mkdir build && cd build
cmake -DECHO_BUILD_EXAMPLES=ON -DECHO_ENABLE_TESTS=ON ..
make -j$(nproc)
ctest
# Or with XMake
xmake f --examples=y --tests=y
xmake
xmake testSix log levels with compile-time and runtime filtering:
echo::trace("Detailed trace information");
echo::debug("Debug information");
echo::info("Informational message");
echo::warn("Warning message");
echo::error("Error message");
echo::critical("Critical error");Compile-time filtering (zero overhead):
# CMake
cmake -DLOGLEVEL=Error ..
# XMake
xmake f -c -DLOGLEVEL=Error
# Result: trace/debug/info/warn calls are completely removed from binaryRuntime filtering:
echo::set_level(echo::Level::Warn); // Only Warn and aboveChain colors, styles, and modifiers:
// Standard colors
echo::info("Message").red().bold();
echo::warn("Warning").yellow().italic();
// Custom colors
echo::info("HEX color").hex("#FF1493");
echo::error("RGB color").rgb(255, 87, 51);
// Multiple modifiers
echo::critical("Alert!").red().bold().underline();Available colors: red, green, yellow, blue, magenta, cyan, white, gray
Available styles: bold, italic, underline
// Print only once (useful in loops)
for (int i = 0; i < 1000000; i++) {
echo::warn("This prints only once!").once();
}
// Rate-limited printing (every N milliseconds)
for (int i = 0; i < 1000000; i++) {
echo::info("Prints every 1 second").every(1000);
}
// Conditional printing
bool debug_mode = true;
echo::debug("Debug info").when(debug_mode);Perfect for progress indicators and status updates:
for (int i = 0; i <= 100; i++) {
echo("Progress: ", i, "%").inplace();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
echo("\n"); // Move to next line when doneRoute logs to multiple destinations simultaneously:
#define ECHO_ENABLE_FILE_SINK
#define ECHO_ENABLE_SYSLOG_SINK
#include <echo/echo.hpp>
int main() {
// Clear default console sink
echo::clear_sinks();
// Add console sink with custom level
auto console = std::make_shared<echo::ConsoleSink>();
console->set_level(echo::Level::Info);
echo::add_sink(console);
// Add file sink with rotation
auto file = std::make_shared<echo::FileSink>("app.log");
file->enable_rotation(5 * 1024 * 1024, 3); // 5MB, keep 3 files
echo::add_sink(file);
// Add syslog sink (Unix only)
auto syslog = std::make_shared<echo::SyslogSink>("myapp");
echo::add_sink(syslog);
// Logs go to all sinks
echo::info("Application started");
echo::error("Error logged to console, file, and syslog");
return 0;
}Available sinks:
- ConsoleSink - Always available (stdout/stderr)
- FileSink - File logging with rotation (
-DECHO_ENABLE_FILE_SINK) - SyslogSink - Unix syslog integration (
-DECHO_ENABLE_SYSLOG_SINK) - NetworkSink - TCP/UDP logging (
-DECHO_ENABLE_NETWORK_SINK) - NullSink - Discard output (
-DECHO_ENABLE_NULL_SINK)
Three formatter types for maximum flexibility:
DefaultFormatter - Simple timestamp + level:
auto formatter = std::make_shared<echo::DefaultFormatter>(true, true);
// Output: [14:30:45][info] MessagePatternFormatter - Customizable patterns:
auto formatter = std::make_shared<echo::PatternFormatter>(
"[{timestamp}] [{level}] {file}:{line} - {message}"
);
echo::set_global_formatter(formatter);
// Output: [2026-01-08 14:30:45] [INFO] main.cpp:42 - MessagePattern placeholders:
{timestamp}or{time}- Timestamp{level}- Log level{message}or{msg}- Log message{file}- Source file{line}- Line number{function}or{func}- Function name{thread}- Thread ID
CustomFormatter - Lambda-based:
auto json_formatter = std::make_shared<echo::CustomFormatter>(
[](const echo::LogRecord& rec) {
return "{\"time\":\"" + rec.timestamp +
"\",\"level\":\"" + std::string(echo::detail::level_name(rec.level)) +
"\",\"msg\":\"" + rec.message + "\"}";
}
);Hierarchical category system with wildcard support:
// Set category-specific log levels
echo::set_category_level("network", echo::Level::Debug);
echo::set_category_level("database", echo::Level::Warn);
echo::set_category_level("app.*", echo::Level::Info); // Wildcard
// Log with categories
echo::category("network").debug("TCP connection established");
echo::category("network").info("Received 1024 bytes");
echo::category("database").debug("Query executed"); // Filtered (< Warn)
echo::category("database").warn("Slow query detected"); // Logged
echo::category("app.auth").info("User logged in"); // Matches app.*
echo::category("app.api").error("Request failed");Hierarchical matching:
Category: "app.network.tcp"
Search order:
1. "app.network.tcp" (exact match)
2. "app.network.*" (parent wildcard)
3. "app.*" (grandparent wildcard)
4. Global level (fallback)
Rich progress bars with themes, time tracking, and byte formatting:
#include <echo/widget.hpp>
// Basic progress bar
echo::progress_bar bar(100);
for (int i = 0; i <= 100; ++i) {
bar.set_progress(i);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
bar.finish();
// Themed progress bar with time and speed
echo::progress_bar download(1024 * 1024 * 100); // 100MB
download.set_theme(echo::BarTheme::ocean());
download.set_show_elapsed(true);
download.set_show_remaining(true);
download.set_show_bytes(true, 1024); // Show as KB/MB/GB
download.set_show_speed(true);
for (size_t i = 0; i <= 1024 * 1024 * 100; i += 1024 * 100) {
download.set_progress(i);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
download.finish();6 Bar Styles:
- Classic:
[===> ] - Blocks:
[███▓▒░ ] - Smooth:
[████▌ ] - Arrows:
[→→→→ ] - Dots:
[●●●●○○○○] - ASCII:
[###>... ]
5 Pre-configured Themes:
- fire() - Red-orange-yellow gradient
- ocean() - Dark blue to cyan
- forest() - Dark green to light green
- sunset() - Orange-red-gold
- neon() - Magenta-cyan-yellow
15 animated spinner styles:
echo::spinner spin(echo::SpinnerStyle::dots_scrolling);
spin.set_message("Loading...");
spin.set_color("#00FF00");
for (int i = 0; i < 50; ++i) {
spin.tick();
std::this_thread::sleep_for(std::chrono::milliseconds(
spin.get_interval_ms()
));
}
spin.stop("Done!");Spinner styles: line, pipe, simple_dots, dots_scrolling, flip, toggle, layer, point, dqpb, bouncing_bar, bouncing_ball, aesthetic, binary, grow_vertical, grow_horizontal
Track multi-step workflows:
echo::steps workflow({"Initialize", "Process", "Validate", "Complete"});
workflow.set_color("#00FFFF");
workflow.next(); // Initialize
std::this_thread::sleep_for(std::chrono::seconds(1));
workflow.complete();
workflow.next(); // Process
std::this_thread::sleep_for(std::chrono::seconds(1));
workflow.complete();
workflow.next(); // Validate
std::this_thread::sleep_for(std::chrono::seconds(1));
workflow.fail(); // Mark as failed
workflow.next(); // Complete// Separator
echo::separator("Section Title", '=');
echo::separator("Gradient", '─', {"#FF0000", "#00FF00", "#0000FF"});
// Box (6 styles: Single, Double, Rounded, Heavy, Dashed, ASCII)
echo::box("Important Message", echo::BoxStyle::Double);
echo::box("Colored Box", echo::BoxStyle::Rounded, "#FF00FF");
// Header
echo::header("Application Name");
echo::header("Colored Header", "#00FFFF");
// Title
echo::title("Chapter 1", '=');
// Banner
echo::banner("WELCOME", echo::BoxStyle::Heavy);Comprehensive color manipulation library:
#include <echo/utils/color.hpp>
// Color conversions
auto rgb = echo::hex_to_rgb("#FF1493");
auto hex = echo::to_hex(255, 20, 147);
// Color mixing (7 modes)
auto mixed = echo::mix(color1, color2, 0.5);
auto added = echo::add(color1, color2);
auto multiplied = echo::multiply(color1, color2);
auto screened = echo::screen(color1, color2);
auto overlayed = echo::overlay(color1, color2);
// Color adjustments (8 operations)
auto lighter = echo::lighten(rgb, 0.2);
auto darker = echo::darken(rgb, 0.2);
auto brighter = echo::brighten(rgb, 50);
auto dimmer = echo::dim(rgb, 50);
auto saturated = echo::saturate(rgb, 0.3);
auto desaturated = echo::desaturate(rgb, 0.3);
auto inverted = echo::invert(rgb);
auto gray = echo::grayscale(rgb);
// Color analysis
auto lum = echo::luminance(rgb);
bool dark = echo::is_dark(rgb);
bool light = echo::is_light(rgb);
auto dist = echo::distance(color1, color2);
auto contrast = echo::contrast_ratio(color1, color2);
// Color schemes (7 generators)
auto comp = echo::complementary(rgb);
auto analog = echo::analogous(rgb, 5, 30);
auto triadic = echo::triadic(rgb);
auto tints = echo::tints(rgb, 5);
auto shades = echo::shades(rgb, 5);
auto tones = echo::tones(rgb, 5);
auto temp = echo::from_temperature(0.5); // Warm colorPowerful chainable string formatting with 60+ methods:
#include <echo/format.hpp>
// Colors and styles
auto str = echo::format::String("Hello")
.red().bold().underline();
// RGB/HEX colors
auto custom = echo::format::String("Custom")
.fg(255, 100, 50)
.bg("#1E1E1E");
// Text alignment
auto aligned = echo::format::String("Text")
.center(20)
.border('*');
// Text transformation
auto transformed = echo::format::String("hello world")
.title_case()
.bold();
// Truncation and wrapping
auto truncated = echo::format::String("Long text...")
.ellipsis(10);
auto wrapped = echo::format::String("Very long text that needs wrapping")
.wrap(20);
// Numeric formatting
auto bytes = echo::format::String("1048576")
.format_bytes(); // "1.00 MB"
auto duration = echo::format::String("3665")
.format_duration(); // "1h 1m 5s"
// Special formatting
auto progress = echo::format::String("50")
.progress(50); // Progress bar representation
auto badge = echo::format::String("value")
.badge("label"); // [label: value]Echo is designed for invisible overhead in production code.
| Scenario | Latency | Throughput | Notes |
|---|---|---|---|
| Compile-time filtered | 0 ns | ∞ | Code doesn't exist in binary |
| Runtime filtered | 56-60 ns | 16.7-17.7M ops/s | Just integer comparison |
| Active logging (NullSink) | 260-290 ns | 3.4-3.8M ops/s | Formatting only |
| Active logging (file) | 350 ns | 2.9M ops/s | Includes file I/O |
| Active logging (console) | 650 ns | 1.5M ops/s | Includes terminal I/O |
.once() overhead |
+3-5 ns | - | After first call |
| Category filtering | +30-40 ns | - | Hash lookup |
When you compile with -DLOGLEVEL=Error, filtered log statements are completely removed from the binary:
// Compiled with -DLOGLEVEL=Error
echo::debug("expensive", calculate()); // ← Doesn't exist in binary (0 ns)
echo::info("expensive", calculate()); // ← Doesn't exist in binary (0 ns)
echo::error("This runs", calculate()); // ← Exists in binaryThe calculate() function is NEVER called for filtered logs!
✅ DO: Use compile-time levels in production
if(CMAKE_BUILD_TYPE STREQUAL "Release")
add_compile_definitions(LOGLEVEL=Error)
endif()✅ DO: Use .once() in loops
for (int i = 0; i < 1000000; i++) {
echo::warn("Warning").once(); // Prints once, then ~60ns overhead
}✅ DO: Enable only needed sinks
#define ECHO_ENABLE_FILE_SINK // Only enable what you need
#include <echo/echo.hpp>❌ DON'T: Use echo() in performance-critical loops
// BAD - echo() always prints (no filtering)
for (int i = 0; i < 1000000; i++) {
echo("iteration"); // Prints 1M times
}
// GOOD - Use log levels with compile-time filtering
for (int i = 0; i < 1000000; i++) {
echo::debug("iteration").once(); // Compile-time filtered
}See misc/PERFORMANCE.md for detailed benchmarks and optimization guide.
# Run all tests
make test
# Run specific test
make test TEST=test_category
# Run with sanitizers
make test-asan # AddressSanitizer
make test-tsan # ThreadSanitizer
make test-ubsan # UndefinedBehaviorSanitizer
make test-sanitizers # All sanitizers
# Build and run examples
make build
./build/color_demo
./build/advanced_progress_demoTest Coverage:
- 17 test files (150+ test cases)
- Core features: levels, sinks, formatters, categories
- Edge cases: concurrency, invalid inputs, long messages, Unicode
- Integration: multi-sink, real-world scenarios
- Thread safety: up to 20 concurrent threads
Echo supports three build systems with automatic detection:
make build # Build project (auto-detects cmake/xmake)
make config # Configure build
make reconfig # Full reconfigure (cleans cache)
make test # Run tests
make clean # Clean build directory
make help # Show all targetsmkdir build && cd build
cmake -DECHO_BUILD_EXAMPLES=ON -DECHO_ENABLE_TESTS=ON ..
make -j$(nproc)
ctestCMake Options:
-DECHO_BUILD_EXAMPLES=ON- Build examples-DECHO_ENABLE_TESTS=ON- Enable tests-DECHO_BUILD_BENCHMARKS=ON- Build benchmarks-DCOMPILER=gcc|clang- Compiler selection-DECHO_ENABLE_SIMD=ON- SIMD optimizations (default: ON)
xmake f --examples=y --tests=y
xmake
xmake testXMake Options:
--examples=y- Build examples--tests=y- Enable tests--toolchain=gcc|clang- Compiler selection--simd=true- SIMD optimizations (default: true)
- C++20 or later (required)
- GCC 10+ (recommended)
- Clang 12+ (recommended)
- MSVC 19.29+ (Visual Studio 2019 16.11+)
SIMD Support:
- x86_64: AVX, AVX2, FMA
- ARM64: NEON (enabled by default)
- ARM32: NEON with hard float
- C++20 compiler
- Standard library only (no external dependencies)
- Unix/Linux/macOS for full terminal features
- Optional: doctest for tests
Generate API documentation:
make docs TYPE=doxygen
# Open docs/doxygen/html/index.htmlThe project includes 36 comprehensive examples:
Core Logging:
- main.cpp - Basic logging
- performance_demo.cpp - Performance characteristics
- test_once.cpp, test_every_when.cpp - Rate limiting
Colors:
- color_demo.cpp - HEX color support
- color_manipulation_demo.cpp - RGB utilities
- format_demo.cpp - format::String colors
- format_text_demo.cpp - Text manipulation
Formatters:
- formatter_demo.cpp - Custom formatters
- formatter_basic_demo.cpp - Direct formatter usage
- global_formatter_demo.cpp - Global formatter API
Sinks:
- sink_demo.cpp - Sink system
- null_sink_demo.cpp - NullSink for testing
- file_logging_demo.cpp - File logging
Widgets:
- wait_demo.cpp - Progress bars, spinners, steps
- advanced_progress_demo.cpp - Advanced progress features
- separator_demo.cpp - Banners and separators
- fullwidth_demo.cpp - Auto-sizing progress bars
Benchmarks (11 files):
- bench_basic.cpp, bench_levels.cpp, bench_sinks.cpp
- bench_formatters.cpp, bench_categories.cpp
- bench_compile_time.cpp, bench_once.cpp
- bench_threading.cpp, bench_memory.cpp
- bench_latency.cpp, bench_vs_spdlog.cpp
MIT License - see LICENSE for details.
Made possible thanks to these amazing projects.
Contributions are welcome! Please feel free to submit a Pull Request.
See CHANGELOG.md for version history.