[esp32] Use ESP-IDF Log V2 to reduce flash usage

Switch from ESP-IDF Log V1 to V2, which centralizes log formatting
inside esp_log() instead of expanding esp_log_timestamp(), color codes,
and LOG_FORMAT() at every ESP_LOGx macro call site.

This saves ~9KB of flash by eliminating ~500 per-site macro expansions
in ESP-IDF library code (gpio, ethernet, mdns, uart, wifi, etc.).

Override esp_log_format() to skip ESP-IDF's own formatting after the
ESPHome logger hook is installed, since ESPHome does its own formatting.
For early boot and constrained environments (ISR, cache disabled),
format messages in ESPHome style with colors using a stack buffer.
This commit is contained in:
J. Nick Koston
2026-03-13 15:38:43 -10:00
parent 5e3c44d48f
commit 3a491722b2
2 changed files with 75 additions and 0 deletions
+5
View File
@@ -1614,6 +1614,11 @@ async def to_code(config):
# This saves ~250 bytes of RAM (tag cache) and associated code
add_idf_sdkconfig_option("CONFIG_LOG_TAG_LEVEL_IMPL_NONE", True)
# Use ESP-IDF Log V2 to eliminate per-site esp_log_timestamp() macro expansions
# V2 centralizes formatting inside esp_log(), reducing flash usage
add_idf_sdkconfig_option("CONFIG_LOG_VERSION_1", False)
add_idf_sdkconfig_option("CONFIG_LOG_VERSION_2", True)
# Reduce PHY TX power in the event of a brownout
add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True)
+70
View File
@@ -86,3 +86,73 @@ int HOT esp_idf_log_vprintf_(const char *format, va_list args) { // NOLINT
#endif
} // namespace esphome
#if defined(USE_ESP32) && !defined(BOOTLOADER_BUILD)
// Override esp_log_format to disable ESP-IDF's own log formatting so that
// the vprintf hook receives a single call per message (just the user's format
// string + args). Without this, Log V2 makes 3 vprintf calls per message
// (header, body, newline) which fragments the output in ESPHome's logger.
// This strong definition overrides the archive symbol from ESP-IDF's liblog.
// It affects all callers including precompiled blobs (e.g. wifi).
//
// Before the ESPHome logger hook is installed (early boot), we fall through
// to the original ESP-IDF formatting so boot messages have proper formatting.
#include <esp_private/log_message.h>
#include <esp_private/log_print.h>
#include <esp_private/log_format.h>
#include <esp_log_write.h>
// Outlined cold path for early boot / constrained environment logging.
// Uses esp_log_printf/esp_log_vprintf which dispatch to esp_rom_vprintf
// for constrained environments (same as ESP-IDF's original esp_log_format).
// Must be in IRAM since it's called from the IRAM esp_log_format during
// early boot when the scheduler isn't running (constrained_env=1).
static void IRAM_ATTR __attribute__((noinline)) esp_log_format_early_(esp_log_msg_t *message) {
// ESP-IDF levels: NONE=0 ERROR=1 WARN=2 INFO=3 DEBUG=4 VERBOSE=5
// Color digits: E=1(red) W=3(yellow) I=2(green) D=6(cyan) V=7(gray)
// DRAM_ATTR required since this function is in IRAM and can't access flash constants
static DRAM_ATTR const char color_digit[] = {'\0', '1', '3', '2', '6', '7'};
static DRAM_ATTR const char lvl[] = {'\0', 'E', 'W', 'I', 'D', 'V'};
uint8_t level = message->config.opts.log_level;
#if CONFIG_LIBC_NEWLIB
if (!message->config.opts.constrained_env) {
flockfile(stdout);
}
#endif
if (level > 0 && level < sizeof(lvl)) {
esp_log_printf(message->config, "\033[0;3%cm[%c][%s:000]: ", color_digit[level], lvl[level],
message->tag ? message->tag : "esp-idf");
}
esp_log_vprintf(message->config, message->format, message->args);
if (level > 0 && level < sizeof(lvl)) {
esp_log_printf(message->config, "\033[0m\n");
} else {
esp_log_printf(message->config, "\n");
}
#if CONFIG_LIBC_NEWLIB
if (!message->config.opts.constrained_env) {
funlockfile(stdout);
}
#endif
}
extern "C" {
// IRAM_ATTR required because ESP-IDF places esp_log_format in IRAM when
// CONFIG_LOG_IN_IRAM is enabled, and it may be called from constrained
// environments (ISR, cache disabled) where flash is inaccessible.
void IRAM_ATTR esp_log_format(esp_log_msg_t *message) {
// Check if ESPHome's vprintf hook is installed by comparing against default.
// Before logger init, esp_log_vprint_func == &vprintf (the default).
extern vprintf_like_t esp_log_vprint_func;
extern int vprintf(const char *, __gnuc_va_list); // NOLINT
if (esp_log_vprint_func == &vprintf || message->config.opts.constrained_env) [[unlikely]] {
// Early boot or constrained env (ISR, cache disabled):
// use ROM functions only — flash may be inaccessible.
esp_log_format_early_(message);
return;
}
// After hook installed, normal environment: skip formatting, forward body only
esp_log_vprintf(message->config, message->format, message->args);
}
} // extern "C"
#endif