13 KiB
ASF Logger Module
Overview
The ASF Logger module provides a comprehensive C++ wrapper for ESP-IDF logging functionality. It offers a clean, efficient, and feature-rich logging interface that abstracts the underlying ESP-IDF logging mechanism while providing enhanced formatting, filtering, and configuration options.
Features
- Multiple Log Levels: Verbose, Debug, Info, Warning, Error
- ISO 8601 Timestamps: Precise timestamping with millisecond accuracy
- Unique Message IDs: Track and identify specific log messages
- Color-Coded Output: Visual distinction between log levels
- Configurable Filtering: Runtime log level adjustment
- Low Overhead: Minimal performance impact with compile-time optimizations
- Thread-Safe: Built on ESP-IDF's thread-safe logging system
- Easy Integration: Simple namespace-based API with convenient macros
Architecture
Design Philosophy
The logger is implemented as a namespace with free functions rather than a class-based approach for several reasons:
- Zero Overhead: No object instantiation or virtual function calls
- Compile-Time Efficiency: Header-only interface with minimal includes
- Easy Integration: Simple include without complex initialization
- Memory Efficient: No per-instance memory overhead
- Thread-Safe: Stateless design with minimal shared state
Output Format
The logger produces output in the following format:
ISO_TIMESTAMP : TAG[LEVEL] : ID : Message
Example:
2025-01-21T10:30:45.123Z : GPIO_WRAPPER[INFO] : 1001 : GPIO wrapper initialized
2025-01-21T10:30:45.124Z : UART_WRAPPER[ERROR] : 2001 : Failed to configure UART port 0
Class Diagram
┌─────────────────────────────────────┐
│ asf::logger │
│ (namespace) │
├─────────────────────────────────────┤
│ + initialize(config): void │
│ + setLogLevel(level): void │
│ + getLogLevel(): LogLevel │
│ + enableTimestamp(enable): void │
│ + enableColor(enable): void │
│ + enableId(enable): void │
│ + getIsoTimestamp(buf, size): char* │
│ + log(tag, id, level, fmt, ...): void│
│ + logVerbose(tag, id, fmt, ...): void│
│ + logDebug(tag, id, fmt, ...): void │
│ + logInfo(tag, id, fmt, ...): void │
│ + logWarning(tag, id, fmt, ...): void│
│ + logError(tag, id, fmt, ...): void │
│ + getDefaultConfig(): LoggerConfig │
│ + logLevelToString(level): char* │
│ + logLevelToColor(level): char* │
└─────────────────────────────────────┘
Enumerations
LogLevel
VERBOSE: Most detailed logging (level 0)DEBUG: Debug information (level 1)INFO: General information (level 2)WARNING: Warning messages (level 3)ERROR: Error messages (level 4)NONE: No logging (level 5)
Configuration Structure
struct LoggerConfig {
LogLevel minLevel; // Minimum log level to display
bool enableTimestamp; // Enable ISO timestamp in output
bool enableColor; // Enable color output
bool enableId; // Enable unique ID in output
uint32_t maxMessageLength; // Maximum message length
};
Usage Examples
Basic Usage
#include "logger.hpp"
static const char* TAG = "MY_MODULE";
void myFunction() {
// Initialize logger with default configuration
asf::logger::LoggerConfig config = asf::logger::getDefaultConfig();
asf::logger::initialize(config);
// Log messages with different levels
asf::logger::logInfo(TAG, 1001, "Module initialized successfully");
asf::logger::logWarning(TAG, 1002, "Configuration parameter missing, using default: %d", 42);
asf::logger::logError(TAG, 1003, "Failed to connect to server: %s", "timeout");
}
Using Convenience Macros
#include "logger.hpp"
static const char* TAG = "SENSOR_MODULE";
void sensorTask() {
// Short form macros for easier usage
ASF_LOGI(TAG, 2001, "Sensor task started");
ASF_LOGD(TAG, 2002, "Reading sensor value: %d", sensorValue);
ASF_LOGW(TAG, 2003, "Sensor value out of range: %d", sensorValue);
ASF_LOGE(TAG, 2004, "Sensor communication failed");
// Long form macros
ASF_LOG_INFO(TAG, 2005, "Sensor calibration complete");
ASF_LOG_ERROR(TAG, 2006, "Critical sensor failure detected");
}
Custom Configuration
#include "logger.hpp"
void setupCustomLogger() {
asf::logger::LoggerConfig config = {};
config.minLevel = asf::logger::LogLevel::DEBUG;
config.enableTimestamp = true;
config.enableColor = false; // Disable colors for file output
config.enableId = true;
config.maxMessageLength = 512; // Longer messages
asf::logger::initialize(config);
// Runtime configuration changes
asf::logger::setLogLevel(asf::logger::LogLevel::WARNING);
asf::logger::enableColor(true);
}
Performance-Critical Code
#include "logger.hpp"
static const char* TAG = "PERFORMANCE";
void performanceCriticalFunction() {
// Check log level before expensive operations
if (asf::logger::getLogLevel() <= asf::logger::LogLevel::DEBUG) {
// Only format expensive debug info if debug logging is enabled
char debugInfo[256];
formatExpensiveDebugInfo(debugInfo, sizeof(debugInfo));
ASF_LOGD(TAG, 3001, "Debug info: %s", debugInfo);
}
// Error logging is always fast
ASF_LOGE(TAG, 3002, "Critical error occurred");
}
Module-Specific Logging
// gpio_wrapper.cpp
#include "logger.hpp"
static const char* TAG = "GPIO_WRAPPER";
class Gpio {
public:
bool configure(uint32_t pin, GpioMode mode) {
ASF_LOGI(TAG, 4001, "Configuring GPIO pin %lu", pin);
if (!isValidPin(pin)) {
ASF_LOGE(TAG, 4002, "Invalid GPIO pin: %lu", pin);
return false;
}
ASF_LOGD(TAG, 4003, "GPIO pin %lu configured successfully", pin);
return true;
}
};
API Reference
Initialization Functions
- initialize(config): Initialize logger with configuration
- getDefaultConfig(): Get default logger configuration
Configuration Functions
- setLogLevel(level): Set minimum log level
- getLogLevel(): Get current minimum log level
- enableTimestamp(enable): Enable/disable timestamp
- enableColor(enable): Enable/disable color output
- enableId(enable): Enable/disable message ID
Logging Functions
- log(tag, id, level, format, ...): Main logging function
- logVerbose(tag, id, format, ...): Log verbose message
- logDebug(tag, id, format, ...): Log debug message
- logInfo(tag, id, format, ...): Log info message
- logWarning(tag, id, format, ...): Log warning message
- logError(tag, id, format, ...): Log error message
Utility Functions
- getIsoTimestamp(buffer, size): Get ISO 8601 timestamp
- logLevelToString(level): Convert log level to string
- logLevelToColor(level): Convert log level to color code
Convenience Macros
Long Form Macros
ASF_LOG_VERBOSE(tag, id, format, ...)ASF_LOG_DEBUG(tag, id, format, ...)ASF_LOG_INFO(tag, id, format, ...)ASF_LOG_WARNING(tag, id, format, ...)ASF_LOG_ERROR(tag, id, format, ...)
Short Form Macros
ASF_LOGV(tag, id, format, ...)ASF_LOGD(tag, id, format, ...)ASF_LOGI(tag, id, format, ...)ASF_LOGW(tag, id, format, ...)ASF_LOGE(tag, id, format, ...)
Message ID Guidelines
To maintain consistency and avoid conflicts, follow these guidelines for message IDs:
ID Ranges by Module
- 1000-1999: Core system modules
- 2000-2999: Hardware abstraction layer (HAL)
- 3000-3999: Application layer
- 4000-4999: Communication modules
- 5000-5999: User interface modules
- 6000-6999: Test and debug modules
ID Ranges by Severity
Within each module range:
- x001-x199: Info messages
- x200-x399: Debug messages
- x400-x599: Warning messages
- x600-x799: Error messages
- x800-x999: Verbose messages
Example ID Assignment
// GPIO Wrapper (HAL module, range 2000-2999)
static const uint32_t GPIO_INIT_SUCCESS = 2001; // Info
static const uint32_t GPIO_PIN_CONFIGURED = 2002; // Info
static const uint32_t GPIO_DEBUG_STATE = 2201; // Debug
static const uint32_t GPIO_INVALID_PIN = 2601; // Error
static const uint32_t GPIO_CONFIG_FAILED = 2602; // Error
Performance Considerations
Compile-Time Optimizations
The logger is designed for minimal overhead:
- Level Filtering: Messages below the minimum level are filtered at runtime
- Macro Efficiency: Macros provide zero-overhead abstraction
- String Formatting: Only performed when message will be output
- Memory Usage: Fixed buffer sizes prevent dynamic allocation
Runtime Performance
- Fast Level Check: O(1) log level comparison
- Efficient Formatting: Uses stack-based buffers
- Minimal Function Calls: Direct ESP-IDF integration
- Thread-Safe: No locking overhead (ESP-IDF handles synchronization)
Memory Usage
- Static Configuration: ~20 bytes of static memory
- Stack Usage: ~400 bytes per log call (configurable)
- No Heap Allocation: All operations use stack memory
- Flash Usage: ~2KB for complete implementation
Integration with ESP-IDF Wrappers
Replacing ESP-IDF Logging
Replace ESP-IDF logging calls in your wrappers:
// Before (ESP-IDF logging)
ESP_LOGI(TAG, "GPIO wrapper initialized");
ESP_LOGE(TAG, "Failed to configure GPIO pin %lu: %s", pin, esp_err_to_name(ret));
// After (ASF logging)
ASF_LOGI(TAG, 1001, "GPIO wrapper initialized");
ASF_LOGE(TAG, 1002, "Failed to configure GPIO pin %lu: %s", pin, esp_err_to_name(ret));
Consistent Error Reporting
bool Gpio::configure(uint32_t pin, GpioMode mode) {
ASF_LOGI(TAG, 2001, "Configuring GPIO pin %lu", pin);
if (!isValidPin(pin)) {
ASF_LOGE(TAG, 2002, "Invalid GPIO pin: %lu", pin);
return false;
}
esp_err_t ret = gpio_config(&config);
if (ret != ESP_OK) {
ASF_LOGE(TAG, 2003, "Failed to configure GPIO pin %lu: %s", pin, esp_err_to_name(ret));
return false;
}
ASF_LOGI(TAG, 2004, "GPIO pin %lu configured successfully", pin);
return true;
}
Testing
The logger includes comprehensive unit tests covering:
- Initialization and Configuration: Default and custom configurations
- Log Level Management: Setting and getting log levels
- Message Formatting: Timestamp, color, and ID formatting
- Null Parameter Handling: Graceful handling of invalid inputs
- Performance Testing: High-frequency logging scenarios
- Macro Functionality: All convenience macros
- Edge Cases: Long messages, buffer limits, etc.
Running Tests
# Build and run logger tests
idf.py build
idf.py flash monitor
Dependencies
- ESP-IDF logging system (
esp_log.h) - ESP-IDF timer (
esp_timer.h) - Standard C library (
cstdio,cstring,ctime) - POSIX time functions (
sys/time.h)
Thread Safety
The ASF Logger is thread-safe because:
- ESP-IDF Integration: Uses ESP-IDF's thread-safe logging system
- Minimal Shared State: Only configuration is shared between threads
- Atomic Operations: Configuration changes are atomic
- Stack-Based Buffers: Each thread uses its own stack space
Limitations
- Message Length: Limited by configured maximum message length (default: 256 characters)
- ID Range: 32-bit unsigned integer range (0 to 4,294,967,295)
- Color Support: Depends on terminal/console color support
- Timestamp Accuracy: Limited by system clock resolution
Migration Guide
From ESP-IDF Logging
- Include the ASF logger header:
#include "logger.hpp"
- Replace ESP-IDF logging calls:
// Old
ESP_LOGI(TAG, "Message");
ESP_LOGE(TAG, "Error: %d", error);
// New
ASF_LOGI(TAG, 1001, "Message");
ASF_LOGE(TAG, 1002, "Error: %d", error);
- Initialize the logger in your main function:
void app_main() {
asf::logger::LoggerConfig config = asf::logger::getDefaultConfig();
asf::logger::initialize(config);
// Your application code
}
Message ID Assignment
Create a header file for your module's message IDs:
// module_log_ids.hpp
#pragma once
namespace MyModule {
namespace LogIds {
// Info messages (1001-1199)
static const uint32_t INIT_SUCCESS = 1001;
static const uint32_t CONFIG_LOADED = 1002;
// Error messages (1600-1799)
static const uint32_t INIT_FAILED = 1601;
static const uint32_t CONFIG_ERROR = 1602;
}
}
This ASF Logger provides a robust, efficient, and feature-rich logging solution that enhances the ESP-IDF logging system while maintaining compatibility and performance.