# 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: 1. **Zero Overhead**: No object instantiation or virtual function calls 2. **Compile-Time Efficiency**: Header-only interface with minimal includes 3. **Easy Integration**: Simple include without complex initialization 4. **Memory Efficient**: No per-instance memory overhead 5. **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 ```cpp 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 ```cpp #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 ```cpp #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 ```cpp #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 ```cpp #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 ```cpp // 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 ```cpp // 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: 1. **Level Filtering**: Messages below the minimum level are filtered at runtime 2. **Macro Efficiency**: Macros provide zero-overhead abstraction 3. **String Formatting**: Only performed when message will be output 4. **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: ```cpp // 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 ```cpp 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 ```bash # 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: 1. **ESP-IDF Integration**: Uses ESP-IDF's thread-safe logging system 2. **Minimal Shared State**: Only configuration is shared between threads 3. **Atomic Operations**: Configuration changes are atomic 4. **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 1. Include the ASF logger header: ```cpp #include "logger.hpp" ``` 2. Replace ESP-IDF logging calls: ```cpp // Old ESP_LOGI(TAG, "Message"); ESP_LOGE(TAG, "Error: %d", error); // New ASF_LOGI(TAG, 1001, "Message"); ASF_LOGE(TAG, 1002, "Error: %d", error); ``` 3. Initialize the logger in your main function: ```cpp 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: ```cpp // 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.