Logging Best Practices
Overview
Section titled “Overview”This guide covers best practices for efficient logging in ESPHome components and platforms. It’s important to understand that logging has performance implications, especially in networked environments. Following these best practices will minimize CPU, RAM, flash, and network overhead.
Declaring the log tag
Section titled “Declaring the log tag”Declare each source file’s tag with ESPHOME_LOG_TAG, at namespace scope in the .cpp file:
#include "esphome/core/log.h"
namespace esphome::neat_temp_sensor {
ESPHOME_LOG_TAG(TAG, "neat_temp_sensor.sensor");On ESP8266 this keeps the tag string in flash instead of RAM; on other platforms it is a plain constant pointer. The
macro is available from ESPHome 2026.10.0, and components in the ESPHome repository must use it: CI rejects a plain
static const char *const TAG = "...";.
Because the tag may live in flash, pass TAG only to the logging macros. Do not use it as a scheduler name
(set_timeout, set_interval, defer and friends), pass it to C string functions such as strcmp or strlen, or build
a std::string from it. CI checks this as well.
External components that also support releases before 2026.10.0 can define a fallback that gives the old declaration:
#ifndef ESPHOME_LOG_TAG#define ESPHOME_LOG_TAG(name, tag) static const char *const name = tag#endifUnderstanding Logger Overhead
Section titled “Understanding Logger Overhead”Network Impact
Section titled “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
Section titled “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
Section titled “Minimizing Impact”- Do not use the component/platform name in log messages — it’s redundant because
TAGalready 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
Section titled “Examples”CAUTION
Bad
ESPHOME_LOG_TAG(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
TIP
Good
ESPHOME_LOG_TAG(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
Logging State Changes: Don’t
Section titled “Logging State Changes: Don’t”Component code should not include any log calls for entity state changes. This applies to every entity type that
publishes its state through the API (sensor, binary_sensor, switch, text_sensor, light, cover, etc.), not
only sensors:
- The entity base classes already log each published state at the
VERBOSElevel. - Client-side logging tools based on aioesphomeapi (including
esphome logs) receive state changes over the API and insert them into the log output regardless of the device’s log level (this can be disabled with the--no-statesflag).
An extra log call in a component’s publish path duplicates this output, adds format strings to flash and costs CPU time
and network traffic on every state update, which may happen many times per second. Transient internal states that are
not published to the API (for example, intermediate steps of a multi-step transition) may still warrant a VERBOSE log
when they are useful for debugging.
Configuration Logging (ESP_LOGCONFIG)
Section titled “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
Section titled “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
Section titled “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
Section titled “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
\nadds only one byte - Consider the length of substituted values (for example,
%smight expand to 20+ characters/bytes for long strings) - The log header (timestamp, level, tag) uses approximately 30 bytes
- Most combined
ESP_LOGCONFIGcalls 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 relatedESP_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 togetherESP_LOGCONFIG(TAG,"Sensor '%s'\n"" State Class: '%s'\n"" Unit: '%s'",name, state_class, unit);// Optional field as separate callif (!icon.empty()) {ESP_LOGCONFIG(TAG, " Icon: '%s'", icon);} -
Maintain visual hierarchy:
// Preserve indentation in the outputESP_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
Section titled “What NOT to Optimize”Avoid complex string building or conditional formatting:
// Bad - creates complexity and uses more flashstd::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 clearESP_LOGCONFIG(TAG, "Settings:");if (setting1) ESP_LOGCONFIG(TAG, " Setting1: %d", value1);if (setting2) ESP_LOGCONFIG(TAG, " Setting2: %d", value2);Runtime Logging Best Practices
Section titled “Runtime Logging Best Practices”Runtime logging affects the ongoing operation of your component:
1. Use Appropriate Log Levels
Section titled “1. Use Appropriate Log Levels”We have macros to log messages at the following log levels:
ESP_LOGVV(TAG, "Detailed trace info"); // VERY_VERBOSE - Usually compiled outESP_LOGV(TAG, "Verbose information"); // VERBOSE - Detailed logging for troubleshooting/commissioningESP_LOGD(TAG, "Debug information"); // DEBUG - For developmentESP_LOGI(TAG, "Informational message"); // INFO - Important user-facing eventsESP_LOGW(TAG, "Warning condition"); // WARNING - Potential issuesESP_LOGE(TAG, "Error occurred"); // ERROR - Definite problems/unexpected behaviorIn general, use:
ESP_LOGVVto log detailed technical information, such as the content of data packets/messages being processed and/or processing state/status.ESP_LOGVto log messages that don’t normally need to be seen but may add value when troubleshooting or preparing/commissioning a new device/configuration.ESP_LOGDto log messages that are necessary for normal use; we try to use it sparingly as, when it’s too noisy, it becomes difficult to spot these and/or other important messages. Note that, as a project, we abuse this log level a bit; it’s normally more verbose than “very verbose” but it is instead ESPHome’s default log level and we treat it accordingly.ESP_LOGIto log information that a non-tech-savvy user might want to see; this log level should not contain any technical detail that a normal, non-developer human would not be able to make sense of at a glance.ESP_LOGWwhen something potentially bad happened, but we can likely continue without any real issues.ESP_LOGEwhen something really bad happened and we cannot continue and/or unexpected behavior is likely to be the result of the failure.
2. Avoid Logging in Tight Loops
Section titled “2. Avoid Logging in Tight Loops”// Bad - logs every iterationvoid loop() { float value = read_sensor(); ESP_LOGD(TAG, "Sensor value: %.2f", value); // Don't do this!}
// Good - log only on change or periodicallyvoid 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
Section titled “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());Copyright © 2026 ESPHome - A project from the Open Home Foundation