Anytime you are developing an embedded project you will need to do some level of debugging, be it variables, pointer addresses, strings, states etc. Now, there are various ways to debug a program, at least for embedded:
- Using LEDs
- Using Buzzers
- Measuring output with scopes
- Measuring GPIO levels with multimeters,
- Printing to console ,among others
I have to admit that the most common method I have used is printing to consoles and LEDs. Am sure that most people also have used this approach the most. Setting up a multimeter is sometimes time consuming, especially when you have several GPIOs to monitor, or when debugging a value that cannot be measured using a meter or a scope.
In this article I am going to show you a basic way to improve your debugging messages when using ESP32. I will compare 2 methods, one is printing directly using the Serial print commands, the other is using the inbuilt logger library.
The problem
Now let's say you are writing some firmware and you have subsystems that you need to monitor. Assume you have a KEYPAD that does some data reading from the user and you have a STATE_MACHINE that runs the whole system. The first method is to use direct Serial print commands. They typically look like below:
// in setup
void setup() {
Serial.begin(115200);
delay(50); // allow some time for the UART to initialise
Serial.println("Serial started Ok"); // raw serial print commands
}
In keypad module, you could do something like this using Serial print commands:
void read_keypad() {
uint8_t key = 4;
Serial.print("[KEYPAD]");
Serial.print("Keypad called with value ");
Serial.println(key);
}
Which will work just as fine because it prints the output as shown below:
[KEYPAD]Keypad called with value 4
Now the problem is that when debugging a real-world application, we typically need to know a little bit more info than the one shown above. For example, what file did it come from? What line? What module/function is running? What is the debug level? Is this an error, info, or a warning?
Enter ESP logger library
Looking at the output above, there is some super essential information we are missing. We can write our own library to do this but why reinvent the wheel. ESP32 comes with an inbuilt logging library that am going to show you how to use for most common cases.
First this article assumes you are developing using platformio (https://platformio.org/) using the Arduino framework. It also works if you are using the native ESP-IDF framework. Using it in the ESP-IDF framework will come in a later article.
Step 1: Include the “esp_log.h” header
This is the header that contains the library declarations
At the top of your file:
extern "C" {
#include "esp_log.h"
}
This exposes the functions prototypes we will be calling.
Step 2: Define the TAG
The tag is like a label, or a name, used generally to show the source of the debug message:
So for example you can define the tag as a global tag or for a specific subsystem: For our example, we will define 2 tags:
static const char* TAG = "ESP32-LOG";
static const char* KEYPAD_TAG = "KEYPAD-TAG";
This will help us show the source of the debug message
Step 3: Initialize serial monitor and set the debug level
In the setup function, We initialize the serial monitor just like before, and set the debug level to INFO. The general log levels are:
- Verbose (Trace)
- Debug
- Info
- Warn
- Error
- Fatal
- None
You can refer to the esp_logger library docs for more info (https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/log.html)
void setup() {
Serial.begin(115200);
delay(50); // allow some time for the UART to initialise
esp_log_level_set(TAG, ESP_LOG_INFO);
}
Step 4: Debug
To debug a message with a given level, the logging library provides several essential MACROS that do this job. E.g to debug an INFO level, use ESPLOGI() as below:
This MACRO takes 2 parameters: The TAG and the formatted message. See usage below:
void setup() {
Serial.begin(115200);
delay(50); // allow some time for the UART to initialise
Serial.println("Serial started Ok");
esp_log_level_set(TAG, ESP_LOG_INFO);
ESP_LOGI(TAG, "ESP32 logging initialized");
ESP_LOGD(TAG, "starting up with value: %d", 123);
ESP_LOGE(TAG, "Failed. Could not start subsystem");
}
Other MACROS you can use are:
- Verbose: ESP_LOGV, ESP_EARLY_LOGV, ESP_DRAM_LOGV.
- Debug: ESP_LOGD, ESP_EARLY_LOGD, ESP_DRAM_LOGD.
- Info: ESP_LOGI, ESP_EARLY_LOGI, ESP_DRAM_LOGI.
- Warning: ESP_LOGW, ESP_EARLY_LOGW, ESP_DRAM_LOGW.
- Error: ESP_LOGE, ESP_EARLY_LOGE, ESP_DRAM_LOGE.
See the logging library docs for more info.
Final thing is to add the following build flag to your platformio.ini to set the verbosity level:
;in platformio.ini
build_flags =
-DCORE_DEBUG_LEVEL=5 ;enable verbose logging
When we run this code, this is the output:
[ 141][I][main.cpp:19] setup(): [ESP32-LOG] ESP32 logging initialized
[ 148][D][main.cpp:20] setup(): [ESP32-LOG] starting up with value: 123
[ 154][E][main.cpp:21] setup(): [ESP32-LOG] Failed. Could not start subsystem
Clearly, this output is entirely different from the one we saw above using raw Serial print commands. As you can see, it shows the LOG_LEVEL, FILE_NAME, LINE_NUMBER, FUNCTION_NAME, SOURCE(TAG) and the MESSAGE. Which is all the info we need.
Step 4: Separating module tags
Now finally, the most common use case I apply is to know the source of the debug message, especially when you have a huge codebase.
Suppose we have a function that reads the keypad, we could easily print debug messages and specify the source as shown in the function below:
void read_keypad() {
// read you button matriX here
ESP_LOGD(KEYPAD_TAG, "Keypad called with value %d", 4); // assume user pressed 4
}
Output:
[ 3181][D][main.cpp:26] read_keypad(): [KEYPAD] Keypad called with value 4
Conclusion
Now there is a lot more that could be said and done for this logging library, e.g printing with console colors etc, but this article provides a high level basic usage for the library for your next embedded ESP32 project .
Thank you for reading this far. See you around!

