[esp32] Enable ESP-IDF Log V2 on IDF 6.1+

This commit is contained in:
J. Nick Koston
2026-07-01 18:52:48 -05:00
parent 7522780c67
commit d0bb1e5a98
3 changed files with 89 additions and 0 deletions
+12
View File
@@ -2214,6 +2214,18 @@ 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)
# ESP-IDF Log V2 centralizes formatting inside esp_log(), removing the
# per-site macro expansions of V1 (saves ~4KB flash, ~180B RAM). Only enable
# on IDF >= 6.1, where CONFIG_LOG_API_CONSTRAINED_ENV_SAFE=n lets
# ESP_DRAM_LOGx / ESP_EARLY_LOGx expand directly to esp_rom_printf and drops
# the ~1.2KB esp_rom_vprintf that would otherwise sit in IRAM. Below 6.1 the
# option does not exist, so we stay on V1 unchanged.
if idf_version() >= cv.Version(6, 1, 0):
add_idf_sdkconfig_option("CONFIG_LOG_VERSION_1", False)
add_idf_sdkconfig_option("CONFIG_LOG_VERSION_2", True)
add_idf_sdkconfig_option("CONFIG_LOG_API_CONSTRAINED_ENV_SAFE", False)
cg.add_define("USE_ESP32_LOG_V2")
# Reduce PHY TX power in the event of a brownout
add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True)
+1
View File
@@ -246,6 +246,7 @@
#define BLUETOOTH_PROXY_MAX_CONNECTIONS 3
#define BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE 16
#define USE_CAPTIVE_PORTAL
#define USE_ESP32_LOG_V2
#define USE_ESP32_BLE
#define USE_ESP32_BLE_MAX_CONNECTIONS 3
#define USE_ESP32_BLE_CLIENT
+76
View File
@@ -86,3 +86,79 @@ int HOT esp_idf_log_vprintf_(const char *format, va_list args) { // NOLINT
#endif
} // namespace esphome
#if defined(USE_ESP32_LOG_V2) && !defined(BOOTLOADER_BUILD)
// Override esp_log_format to prevent V2's 3-call vprintf fragmentation.
// Without this, Log V2 calls the vprintf hook 3 times per message (header,
// body, newline) which creates 3 separate log entries in ESPHome's logger.
// This strong definition overrides the archive symbol from ESP-IDF's liblog,
// affecting all callers including precompiled blobs (e.g. wifi).
#include <esp_private/log_message.h>
#include <esp_log_write.h>
// Format an ESP-IDF log message directly to the console, bypassing the
// ESPHome logger hook. Used when the hook isn't installed (early boot) or
// can't be used safely (constrained env: PHY init, efuse reads -- fwrite
// locks crash on USB JTAG devices).
// Formats in ESPHome style with ANSI colors into a 512-byte stack buffer,
// then outputs atomically via esp_rom_printf.
// This path is cold on 99.9% of builds -- it only runs during early boot
// and at DEBUG framework log level (default is ERROR).
static void __attribute__((noinline)) esp_log_format_direct_(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)
static const char color_digit[] = {'\0', '1', '3', '2', '6', '7'};
static const char lvl[] = {'\0', 'E', 'W', 'I', 'D', 'V'};
// Format into stack buffer and output atomically via esp_rom_printf.
// Can't use fwrite (locks crash during early boot and PHY init).
char buf[512];
int pos = 0;
uint8_t level = message->config.opts.log_level;
if (level > 0 && level < sizeof(lvl)) {
pos = snprintf(buf, sizeof(buf), "\033[0;3%cm[%c][%s]: ", color_digit[level], lvl[level],
message->tag ? message->tag : "idf");
}
if (pos >= 0 && pos < (int) sizeof(buf) - 2) {
int body = vsnprintf(buf + pos, sizeof(buf) - pos, message->format, message->args);
if (body > 0)
pos += (body < (int) sizeof(buf) - pos) ? body : (int) sizeof(buf) - pos - 1;
}
if (level > 0 && level < sizeof(lvl) && pos < (int) sizeof(buf) - 6) {
pos += snprintf(buf + pos, sizeof(buf) - pos, "\033[0m");
}
if (pos < (int) sizeof(buf) - 1) {
buf[pos++] = '\n';
}
buf[pos] = '\0';
esp_rom_printf("%s", buf);
}
extern "C" {
// Override esp_log_format from liblog.a to prevent V2's 3-call vprintf
// fragmentation. IRAM_ATTR to match esp_log_va's IRAM placement and avoid
// an IRAM->flash cache miss on every ESP-IDF log call. This function is
// tiny (~43 bytes) so the IRAM cost is negligible.
void IRAM_ATTR esp_log_format(esp_log_msg_t *message) {
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 (PHY init, efuse reads, scheduler not
// running). Can't use the ESPHome hook -- fwrite locks crash during PHY
// init on USB JTAG devices and newlib isn't initialized during early boot.
// Format to stack buffer with vsnprintf + esp_rom_printf instead.
//
// Note: if called from an ISR with flash cache disabled, this will crash
// because the format string and tag are in flash. This is the same as V1
// where ESP_EARLY_LOGx from ISR also used flash-resident format strings
// via esp_rom_printf. No ESP-IDF code is known to log from ISR with
// cache disabled.
esp_log_format_direct_(message);
return;
}
// After hook installed, normal environment: skip formatting, forward body only.
// Call esp_log_vprint_func directly to avoid pulling in esp_rom_vprintf
// (1.2KB IRAM) through the esp_log_vprintf inline.
esp_log_vprint_func(message->format, message->args);
}
} // extern "C"
#endif