Logging Best Practices
Overview
Logging is an essential part of ESPHome for both debugging and monitoring. It's important to understand that logging has performance implications, especially in networked environments.
This guide covers best practices for efficient logging in ESPHome components and platforms.
Understanding Logger Overhead
Network Impact
Each ESP_LOG*
call in ESPHome results in:
- Format string processing - The printf-style formatting is processed
- Memory allocation - For the formatted message
- Serial output - Written to the console/UART
- Network packet creation - The log message is packaged for transmission
- Network transmission - Sent over WiFi/Ethernet to connected clients
For devices with many sensors or components, this can result in thousands of network packets each time a client connects, causing:
- Network congestion
- Delayed log streaming
- Potential timeouts in Home Assistant entity discovery
- Device loop blocking in extreme cases (100+ sensors)
Flash Memory Impact
Each logging call also consumes flash memory:
- Each unique (format) string will consume dedicated space in flash
- Each function call adds to binary size
Embedded devices have limited flash memory available; inefficient use of logging results in significant amounts of wasted space and time.
Minimizing Impact
- Do not use the component/platform name in log messages -- it's redundant because
TAG
already identifies the running component/platform. - Keep messages short and concise; avoid extra words which do not ease debugging.
- Do not:
- repeat similar strings.
- explain troubleshooting steps or ask questions.
- include punctuation unless necessary; each message appears on a new line. For example, a period (
.
) or exclamation point (!
) at the end of every message does not help with debugging and only wastes space.
Examples
Bad
static const char *const TAG = "neat_temp_sensor.sensor";
// ...
ESP_LOGD(TAG, "Enabling neat_temp_sensor communication.");
// ...
ESP_LOGD(TAG, "Disabling neat_temp_sensor communication.");
// ...
ESP_LOGE(TAG, "I2C error during reading of neat_temp_sensor values! Is the sensor connected?");
- Redundant platform name in messages
- Long, repeating strings with only minor differences
- Unnecessary text/characters and punctuation
Good
static const char *const TAG = "neat_temp_sensor.sensor";
// ...
ESP_LOGD(TAG, "Enabling");
// ...
ESP_LOGD(TAG, "Disabling");
// ...
ESP_LOGE(TAG, "Communication failed");
- Short messages which may be shared by many components/platforms
- TAG identifies the component/platform
Configuration Logging (ESP_LOGCONFIG
)
Configuration logging dumps the current component/platform configuration. This is particularly important to optimize
as it runs every time an API client connects (for example: Home Assistant, ESPHome Device Builder or esphome logs
).
The Problem
Consider a typical sensor configuration dump:
void MyComponent::dump_config() {
ESP_LOGCONFIG(TAG, "My Component:");
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
ESP_LOGCONFIG(TAG, " Update Interval: %ums", this->update_interval_);
ESP_LOGCONFIG(TAG, " Samples: %d", this->samples_);
ESP_LOGCONFIG(TAG, " Mode: %s", this->get_mode_str());
}
This generates five separate network packets for a single component. With 100 sensors, this becomes 500+ packets every time a client connects.
The Solution: Combine Related Log Messages
Combine related configuration fields into single log calls using newline characters:
void MyComponent::dump_config() {
ESP_LOGCONFIG(TAG,
"My Component:\n"
" Address: 0x%02X\n"
" Update Interval: %ums\n"
" Samples: %d\n"
" Mode: %s",
this->address_,
this->update_interval_,
this->samples_,
this->get_mode_str());
}
This reduces five packets to one, an 80% reduction!
Best Practices for Combined Logging
Important
The default log buffer is 512 bytes.
This limit applies to the total formatted message size, not the number of lines.
When combining log messages:
- Each
\n
adds only one byte - Consider the length of substituted values (for example,
%s
might expand to 20+ characters/bytes for long strings) - The log header (timestamp, level, tag) uses approximately 30 bytes
-
Most combined ESP_LOGCONFIG calls stay well under this limit, even with 8-10 lines
-
Use string literal concatenation for readability:
ESP_LOGCONFIG(TAG, "Component Name: %s\n" " Setting 1: %d\n" " Setting 2: %s", name, value1, value2);
-
Group related fields that are always logged together:
// Good - these settings are related ESP_LOGCONFIG(TAG, "UART Configuration:\n" " Baud Rate: %u\n" " Data Bits: %u\n" " Parity: %s\n" " Stop Bits: %u", baud_rate_, data_bits_, parity_str, stop_bits_);
-
Keep optional fields separate:
// Always log these together ESP_LOGCONFIG(TAG, "Sensor '%s'\n" " State Class: '%s'\n" " Unit: '%s'", name, state_class, unit); // Optional field as separate call if (!icon.empty()) { ESP_LOGCONFIG(TAG, " Icon: '%s'", icon); }
-
Maintain visual hierarchy:
// Preserve indentation in the output ESP_LOGCONFIG(TAG, "Parent Component:\n" " Child Setting 1: %d\n" " Child Setting 2: %s\n" " Sub-setting: %d", value1, value2, value3);
What NOT to Optimize
Avoid complex string building or conditional formatting:
// Bad - creates complexity and uses more flash
std::string config_str = "Settings:";
if (setting1) config_str += str_sprintf("\n Setting1: %d", value1);
if (setting2) config_str += str_sprintf("\n Setting2: %d", value2);
ESP_LOGCONFIG(TAG, "%s", config_str.c_str());
// Good - simple and clear
ESP_LOGCONFIG(TAG, "Settings:");
if (setting1) ESP_LOGCONFIG(TAG, " Setting1: %d", value1);
if (setting2) ESP_LOGCONFIG(TAG, " Setting2: %d", value2);
Runtime Logging Best Practices
Runtime logging affects the ongoing operation of your component:
1. Use Appropriate Log Levels
ESP_LOGVV(TAG, "Detailed trace info"); // VERY_VERBOSE - Usually compiled out
ESP_LOGV(TAG, "Verbose information"); // VERBOSE - Detailed logging
ESP_LOGD(TAG, "Debug information"); // DEBUG - For development
ESP_LOGI(TAG, "Informational message"); // INFO - Important events
ESP_LOGW(TAG, "Warning condition"); // WARNING - Potential issues
ESP_LOGE(TAG, "Error occurred"); // ERROR - Definite problems
2. Avoid Logging in Tight Loops
// Bad - logs every iteration
void loop() {
float value = read_sensor();
ESP_LOGD(TAG, "Sensor value: %.2f", value); // Don't do this!
}
// Good - log only on change or periodically
void loop() {
float value = read_sensor();
if (abs(value - last_value_) > 0.1) {
ESP_LOGD(TAG, "Sensor value changed: %.2f", value);
last_value_ = value;
}
}
3. Combine Related Runtime Messages
When multiple related events occur together:
// Instead of:
ESP_LOGI(TAG, "Connection established");
ESP_LOGI(TAG, "IP Address: %s", ip.c_str());
ESP_LOGI(TAG, "Subnet: %s", subnet.c_str());
ESP_LOGI(TAG, "Gateway: %s", gateway.c_str());
// Use:
ESP_LOGI(TAG,
"Connection established\n"
" IP Address: %s\n"
" Subnet: %s\n"
" Gateway: %s",
ip.c_str(), subnet.c_str(), gateway.c_str());