Files
ASF_01_sys_sw_arch/draft- to be removed SW/components/utils/logger
2026-02-01 19:47:53 +01:00
..
2026-02-01 19:47:53 +01:00
2026-02-01 19:47:53 +01:00
2026-02-01 19:47:53 +01:00
2026-02-01 19:47:53 +01:00
2026-02-01 19:47:53 +01:00
2026-02-01 19:47:53 +01:00
2026-02-01 19:47:53 +01:00
2026-02-01 19:47:53 +01:00

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

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:

  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:

// 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:

  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:
#include "logger.hpp"
  1. 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);
  1. 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.