Introduction
Embedded development often feels like flying blind—especially when things go wrong and there's no GUI or operating system to help diagnose the issue. In these situations, logs become your eyes. If you're building firmware for ESP32, ESP32-S3, or other Espressif chips using the ESP-IDF framework, the esp_log
module is your most powerful ally.
The ESP-IDF logging system is not just about printing messages—it's a fully-featured, configurable framework that helps you monitor the behavior of your firmware, identify and isolate bugs, and keep track of system performance. Whether you're new to ESP32 development or a seasoned engineer, mastering the logging system will supercharge your debugging workflow.
This guide dives deep into how to use ESP-IDF’s logging module for effective debugging, offering examples, best practices, configuration tips, and more.
What is esp_log
and Why Use It?
The esp_log
module is a logging utility provided by ESP-IDF. It outputs messages to the UART console (by default), and supports various severity levels (from errors to verbose messages).
Key Features:
- Granular log levels: ERROR, WARNING, INFO, DEBUG, VERBOSE
- Per-module log configuration
- Runtime control of log levels
- Output redirection and formatting options
This logging capability is especially useful for:
- Debugging initialization sequences
- Tracking sensor values or state changes
- Diagnosing memory leaks or task crashes
- Monitoring hardware communication (I2C, SPI, UART)
- Verifying real-time behavior in multitasking apps
Setting Up the Logging Module
1. Include the Logging Header
To use logging, include the following in your .c
or .cpp
file:
#include "esp_log.h"
This gives you access to all the logging macros and helper functions.
2. Define a Log Tag
A tag helps identify which component or module the log message is coming from. It can be the filename, a feature name, or a custom identifier.
static const char *TAG = "SensorModule";
This tag will be used in all log macros so you can filter or identify them in logs.
3. Use Logging Macros
ESP-IDF provides several macros to log at different levels:
Macro | Purpose | Sample Output |
---|---|---|
ESP_LOGE |
Error messages | E (1234) SensorModule: Sensor read failed |
ESP_LOGW |
Warning messages | W (1250) SensorModule: High temperature |
ESP_LOGI |
Informational messages | I (1300) SensorModule: Sensor initialized |
ESP_LOGD |
Debug messages | D (1320) SensorModule: Raw data = 123 |
ESP_LOGV |
Verbose (very detailed) logs | V (1350) SensorModule: Entered state 2 |
Example usage:
ESP_LOGI(TAG, "Sensor initialized successfully");
ESP_LOGW(TAG, "High temperature detected: %d", temp);
ESP_LOGE(TAG, "Sensor read failed with error code: %d", err_code);
These macros work like printf()
and support format specifiers.
Configuring Log Levels
Logging verbosity can be controlled both globally and per-module. This is crucial because debug and verbose logs can significantly slow down your system or flood the serial monitor.
1. Global Log Level via menuconfig
Run this in your project directory:
idf.py menuconfig
Navigate to:
Component config → Log output → Default log verbosity
Choose a level:
- None: disables logging
- Error: logs only critical issues
- Warning: includes non-fatal issues
- Info: logs operational messages
- Debug: includes low-level details
- Verbose: logs everything (useful during deep debugging)
2. Set Log Level at Runtime
You can dynamically change log levels during runtime:
esp_log_level_set("*", ESP_LOG_VERBOSE); // Enable all logs
esp_log_level_set("SensorModule", ESP_LOG_DEBUG); // Only for this tag
This is particularly useful if you're debugging only one subsystem and want to avoid unnecessary noise from others.
Real-World Debugging Example
Here’s a full example showing how logging can be used to debug a temperature sensor:
#include "esp_log.h"
#include "driver/adc.h"
static const char *TAG = "TempSensor";
void read_temperature() {
ESP_LOGI(TAG, "Starting temperature read");
int raw = adc1_get_raw(ADC1_CHANNEL_6);
if (raw < 0) {
ESP_LOGE(TAG, "Failed to read ADC channel");
return;
}
float voltage = (raw / 4095.0) * 3.3;
float temperature = (voltage - 0.5) * 100;
ESP_LOGD(TAG, "Raw ADC: %d, Voltage: %.2fV", raw, voltage);
if (temperature > 60) {
ESP_LOGW(TAG, "High temperature detected: %.2f°C", temperature);
}
ESP_LOGI(TAG, "Temperature: %.2f°C", temperature);
}
Sample Output on Serial Monitor:
I (1234) TempSensor: Starting temperature read
D (1240) TempSensor: Raw ADC: 2048, Voltage: 1.65V
I (1250) TempSensor: Temperature: 28.75°C
If an error occurs:
E (1234) TempSensor: Failed to read ADC channel
Advanced Logging Tips
Redirect Logs to UART1 or External Display
By default, logs go to UART0. You can change the output UART using esp_log_set_vprintf()
to redirect logs (for example, to UART1 or even to an LCD, SD card, or network).
Filter Logs with idf.py monitor
Use filter options in the monitor:
idf.py monitor | grep SensorModule
Or filter by log level:
idf.py monitor | grep "E ("
Reduce Noise with Custom Log Levels in Production
Before releasing your firmware, reduce logs to Error or Warning to avoid performance overhead and leaking internal system behavior.
Best Practices
- Use meaningful, consistent tags per module (e.g.,
BLEManager
,WiFiConn
,SensorReader
). - Avoid placing logs inside time-critical code such as ISRs or tight loops.
- Use
ESP_LOGE()
beforeassert()
to capture debug info before halting. - Use
esp_err_to_name(err)
oresp_err_to_name_r()
for readable error messages. - Limit log verbosity in production builds for performance and security.
Conclusion
The ESP-IDF logging system is an indispensable tool for every ESP32 developer. With minimal setup and powerful flexibility, it can help you trace bugs, monitor application behavior, and ensure your firmware runs as expected.
By understanding the different logging levels, how to configure them, and where to place logs effectively, you'll be well-equipped to debug even the most complex embedded systems.
So the next time your ESP32 app isn’t behaving as expected, remember: logs don’t lie—if you're listening.