· Esp32  · 4 min read

Mastering ESP-IDF Logging: A Complete Guide to Debugging with ESP_LOG

Learn how to harness the full power of ESP-IDF's built-in logging module to debug, monitor, and trace issues in your ESP32 projects. This guide walks you through everything from setup to real-world examples of using ESP_LOGI, ESP_LOGD, and more.

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:

MacroPurposeSample Output
ESP_LOGEError messagesE (1234) SensorModule: Sensor read failed
ESP_LOGWWarning messagesW (1250) SensorModule: High temperature
ESP_LOGIInformational messagesI (1300) SensorModule: Sensor initialized
ESP_LOGDDebug messagesD (1320) SensorModule: Raw data = 123
ESP_LOGVVerbose (very detailed) logsV (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() before assert() to capture debug info before halting.
  • Use esp_err_to_name(err) or esp_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.

    Related articles

    View All Articles »

    Why OTA for ESP32 Is Still Too Hard — and What I'm Building to Fix It

    Over-the-air (OTA) firmware updates are critical for modern embedded devices, but implementing a secure, automated OTA system on the ESP32 is still more complex than it should be. In this post, I break down the current state of OTA for ESP32, common pain points with existing solutions, and why I’m building an open-source, developer-friendly OTA update system with CI/CD support, firmware signing, and a lightweight OTA agent.

    Task Synchronization with FreeRTOS Event Groups on ESP32 (ESP-WROVER-KIT Example)

    FreeRTOS event groups provide an efficient way to synchronize tasks based on multiple conditions using binary flags. In this post, we explore how to use event groups on the ESP-WROVER-KIT with ESP-IDF. Through a practical example involving sensor and network initialization, we demonstrate how event bits can signal system readiness to a processing task. This approach enables clean, scalable task coordination in real-time embedded applications using the ESP32.

    Using FreeRTOS Mutex with ESP-WROVER-KIT for Resource Protection

    FreeRTOS mutexes are essential for managing shared resources in embedded systems, ensuring that only one task accesses a resource at a time. In this post, we explore how mutexes work in FreeRTOS and demonstrate their use on the ESP-WROVER-KIT by protecting UART output shared between two tasks. This practical example shows how to prevent race conditions and output conflicts, while highlighting the importance of mutual exclusion in real-time applications on the ESP32 platform.