[core][logger] Keep ESP8266 log strings in flash and deprecate the RAM option (#19577)

This commit is contained in:
J. Nick Koston
2026-09-24 13:11:50 +01:00
committed by GitHub
parent cd5ff787f4
commit 64127632f5
14 changed files with 87 additions and 41 deletions
+17 -4
View File
@@ -1,3 +1,4 @@
import logging
import re
from typing import Any
@@ -63,6 +64,8 @@ from esphome.core import CORE, ID, CoroPriority, Lambda, coroutine_with_priority
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@esphome/core"]
logger_ns = cg.esphome_ns.namespace("logger")
LOG_LEVELS = {
@@ -105,6 +108,7 @@ DEFAULT = "DEFAULT"
CONF_INITIAL_LEVEL = "initial_level"
CONF_LOGGER_ID = "logger_id"
CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH = "esp8266_store_log_strings_in_flash"
CONF_RUNTIME_TAG_LEVELS = "runtime_tag_levels"
CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size"
CONF_WAIT_FOR_CDC = "wait_for_cdc"
@@ -219,6 +223,18 @@ def validate_initial_no_higher_than_global(config: ConfigType) -> ConfigType:
return config
def warn_ram_log_strings(config: ConfigType) -> ConfigType:
# Remove before 2027.4.0
if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH) is False:
_LOGGER.warning(
"'%s: false' is ignored and will be rejected in 2027.4.0. Log format strings "
"always stay in flash now; copying them into RAM gave no speed gain and the "
"lost RAM caused crashes. Remove the option",
CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH,
)
return config
def validate_wait_for_cdc(config: ConfigType) -> ConfigType:
if config.get(CONF_WAIT_FOR_CDC) and config.get(CONF_HARDWARE_UART) != USB_CDC:
raise cv.Invalid("wait_for_cdc requires hardware_uart: USB_CDC")
@@ -232,7 +248,6 @@ LoggerMessageTrigger = logger_ns.class_(
)
CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH = "esp8266_store_log_strings_in_flash"
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@@ -332,6 +347,7 @@ CONFIG_SCHEMA = cv.All(
validate_local_no_higher_than_global,
validate_initial_no_higher_than_global,
validate_wait_for_cdc,
warn_ram_log_strings,
)
@@ -450,9 +466,6 @@ async def _late_logger_init(config: ConfigType) -> None:
cg.add_build_flag("-DCORE_DEBUG_LEVEL=5")
if CORE.is_esp32 and is_at_least_very_verbose:
cg.add_build_flag("-DENABLE_I2C_DEBUG_BUFFER")
if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH):
cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH")
if CORE.is_esp32:
if config[CONF_HARDWARE_UART] == USB_CDC:
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
+5 -5
View File
@@ -105,9 +105,9 @@ struct LogBuffer {
this->format_vsnprintf_(format, args);
this->finalize_();
}
#ifdef USE_STORE_LOG_STR_IN_FLASH
void HOT format_body_P(PGM_P format, va_list args) {
this->format_vsnprintf_P_(format, args);
#ifdef USE_ESP8266
void HOT format_body_p(PGM_P format, va_list args) {
this->format_vsnprintf_p_(format, args);
this->finalize_();
}
#endif
@@ -158,8 +158,8 @@ struct LogBuffer {
return;
this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args));
}
#ifdef USE_STORE_LOG_STR_IN_FLASH
void format_vsnprintf_P_(PGM_P format, va_list args) {
#ifdef USE_ESP8266
void format_vsnprintf_p_(PGM_P format, va_list args) {
if (this->full_())
return;
this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args));
+3 -4
View File
@@ -127,9 +127,8 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
}
#endif // USE_ESP32 || USE_HOST || USE_LIBRETINY || USE_ZEPHYR
#ifdef USE_STORE_LOG_STR_IN_FLASH
// Implementation for ESP8266 with flash string support.
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
#ifdef USE_ESP8266
// ESP8266 keeps log format strings in flash.
//
// This function handles format strings stored in flash memory (PROGMEM) to save RAM.
// Uses vsnprintf_P to read the format string directly from flash without copying to RAM.
@@ -141,7 +140,7 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, nullptr);
}
#endif // USE_STORE_LOG_STR_IN_FLASH
#endif // USE_ESP8266
inline uint8_t Logger::level_for(const char *tag) {
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
+8 -8
View File
@@ -200,9 +200,9 @@ class Logger final : public Component {
float get_setup_priority() const override { return setup_priority::BUS + 500.0f; }
void log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args); // NOLINT
#ifdef USE_STORE_LOG_STR_IN_FLASH
void log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
va_list args); // NOLINT
#ifdef USE_ESP8266
// NOLINTNEXTLINE(readability-identifier-naming)
void log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format, va_list args);
#endif
protected:
@@ -244,14 +244,14 @@ class Logger final : public Component {
buf.format_body(format, args);
}
#ifdef USE_STORE_LOG_STR_IN_FLASH
#ifdef USE_ESP8266
// Format a log message with flash string format and write it to a buffer with header, footer, and null terminator
// ESP8266-only (single-task), thread_name is always nullptr
inline void HOT format_log_to_buffer_with_terminator_P_(uint8_t level, const char *tag, int line,
inline void HOT format_log_to_buffer_with_terminator_p_(uint8_t level, const char *tag, int line,
const __FlashStringHelper *format, va_list args,
LogBuffer &buf) {
buf.write_header(level, tag, line, nullptr);
buf.format_body_P(reinterpret_cast<PGM_P>(format), args);
buf.format_body_p(reinterpret_cast<PGM_P>(format), args);
}
#endif
@@ -283,9 +283,9 @@ class Logger final : public Component {
FormatType format, va_list args, const char *thread_name) {
RecursionGuard guard(recursion_guard);
LogBuffer buf{this->tx_buffer_, ESPHOME_LOGGER_TX_BUFFER_SIZE};
#ifdef USE_STORE_LOG_STR_IN_FLASH
#ifdef USE_ESP8266
if constexpr (std::is_same_v<FormatType, const __FlashStringHelper *>) {
this->format_log_to_buffer_with_terminator_P_(level, tag, line, format, args, buf);
this->format_log_to_buffer_with_terminator_p_(level, tag, line, format, args, buf);
} else
#endif
{
+1 -1
View File
@@ -50,7 +50,7 @@ bool MQTTLockComponent::send_initial_state() { return this->publish_state(); }
bool MQTTLockComponent::publish_state() {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
#ifdef USE_STORE_LOG_STR_IN_FLASH
#ifdef USE_ESP8266
char buf[LOCK_STATE_STR_SIZE];
strncpy_P(buf, (PGM_P) lock_state_to_string(this->lock_->state), sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
+1 -1
View File
@@ -59,7 +59,7 @@ void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon
root[MQTT_FORCE_UPDATE] = true;
if (this->sensor_->get_state_class() != STATE_CLASS_NONE) {
#ifdef USE_STORE_LOG_STR_IN_FLASH
#ifdef USE_ESP8266
root[MQTT_STATE_CLASS] = (const __FlashStringHelper *) state_class_to_string(this->sensor_->get_state_class());
#else
root[MQTT_STATE_CLASS] = LOG_STR_ARG(state_class_to_string(this->sensor_->get_state_class()));
@@ -903,7 +903,7 @@ void PrometheusHandler::valve_row_(AsyncResponseStream *stream, valve::Valve *ob
stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str());
stream->print(ESPHOME_F("\",operation=\""));
#ifdef USE_STORE_LOG_STR_IN_FLASH
#ifdef USE_ESP8266
stream->print((const __FlashStringHelper *) valve::valve_operation_to_str(obj->current_operation));
#else
stream->print((const char *) valve::valve_operation_to_str(obj->current_operation));
@@ -947,7 +947,11 @@ void PrometheusHandler::climate_setting_row_(AsyncResponseStream *stream, climat
stream->print(ESPHOME_F("\",category=\""));
stream->print(setting.c_str());
stream->print(ESPHOME_F("\",setting_value=\""));
#ifdef USE_ESP8266
stream->print((const __FlashStringHelper *) setting_value);
#else
stream->print(LOG_STR_ARG(setting_value));
#endif
stream->print(ESPHOME_F("\"} "));
stream->print(ESPHOME_F("1.0"));
stream->print(ESPHOME_F("\n"));
+1 -1
View File
@@ -5,7 +5,7 @@ namespace esphome::script {
static const char *const TAG = "script";
#ifdef USE_STORE_LOG_STR_IN_FLASH
#ifdef USE_ESP8266
void ScriptLogger::esp_log_(int level, int line, const __FlashStringHelper *format, const char *param) {
esp_log_printf_(level, TAG, line, format, param);
}
+1 -1
View File
@@ -13,7 +13,7 @@ namespace esphome::script {
class ScriptLogger {
protected:
#ifdef USE_STORE_LOG_STR_IN_FLASH
#ifdef USE_ESP8266
void esp_logw_(int line, const __FlashStringHelper *format, const char *param) {
esp_log_(ESPHOME_LOG_LEVEL_WARN, line, format, param);
}
+1 -1
View File
@@ -36,7 +36,7 @@ void HOT esp_log_printf_(int level, const char *tag, int line, const char *forma
#endif
}
#ifdef USE_STORE_LOG_STR_IN_FLASH
#ifdef USE_ESP8266
void HOT esp_log_printf_(int level, const char *tag, int line, const __FlashStringHelper *format, ...) {
#ifdef USE_LOGGER
ESPHOME_DEBUG_ASSERT(logger::global_logger != nullptr);
+6 -5
View File
@@ -16,7 +16,7 @@
#include <cinttypes>
#include <string>
#ifdef USE_STORE_LOG_STR_IN_FLASH
#ifdef USE_ESP8266
#include "WString.h"
#endif
@@ -63,7 +63,8 @@ namespace esphome {
void esp_log_printf_(int level, const char *tag, int line, const char *format, ...) // NOLINT
__attribute__((format(printf, 4, 5)));
#ifdef USE_STORE_LOG_STR_IN_FLASH
#ifdef USE_ESP8266
// NOLINTNEXTLINE(readability-identifier-naming)
void esp_log_printf_(int level, const char *tag, int line, const __FlashStringHelper *format, ...);
#endif
void esp_log_vprintf_(int level, const char *tag, int line, const char *format, va_list args); // NOLINT
@@ -71,7 +72,7 @@ void esp_log_vprintf_(int level, const char *tag, int line, const char *format,
int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
#endif
#ifdef USE_STORE_LOG_STR_IN_FLASH
#ifdef USE_ESP8266
#define ESPHOME_LOG_FORMAT(format) F(format)
#else
#define ESPHOME_LOG_FORMAT(format) format
@@ -172,7 +173,7 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
// Helper class that identifies strings that may be stored in flash storage (similar to Arduino's __FlashStringHelper)
struct LogString;
#ifdef USE_STORE_LOG_STR_IN_FLASH
#ifdef USE_ESP8266
#include <pgmspace.h>
@@ -181,7 +182,7 @@ struct LogString;
#define LOG_STR(s) (reinterpret_cast<const LogString *>(PSTR(s)))
#define LOG_STR_LITERAL(s) LOG_STR_ARG(LOG_STR(s))
#else // !USE_STORE_LOG_STR_IN_FLASH
#else // !USE_ESP8266
#define LOG_STR(s) (reinterpret_cast<const LogString *>(s))
#define LOG_STR_ARG(s) (reinterpret_cast<const char *>(s))
+2 -9
View File
@@ -27,17 +27,10 @@
#define ESPHOME_strlen_P strlen_P
// Type for pointers to PROGMEM strings (for use with ESPHOME_F return values)
using ProgmemStr = const __FlashStringHelper *;
// Storage class for PROGMEM_STRING_TABLE data. Mirrors the logger's choice of
// LOG_STR_ARG: when LOG_STR_ARG treats the LogString as PROGMEM (PGM_P), the
// table data must actually be in flash; when LOG_STR_ARG treats it as a plain
// const char* (assumes RAM), the table data must live in RAM or non-logger
// consumers (ArduinoJson, Print, MQTT publish) crash on unaligned flash reads.
#ifdef USE_STORE_LOG_STR_IN_FLASH
// Storage class for PROGMEM_STRING_TABLE data; LOG_STR_ARG treats a LogString as PGM_P
// on ESP8266, so the table data must be in flash to match.
#define ESPHOME_PROGMEM_STRING_TABLE_STORAGE PROGMEM
#else
#define ESPHOME_PROGMEM_STRING_TABLE_STORAGE
#endif
#else
#define ESPHOME_F(string_literal) (string_literal)
#define ESPHOME_PGM_P const char *
#define ESPHOME_PSTR(s) (s)
@@ -1,7 +1,13 @@
"""Tests for the logger component."""
from collections.abc import Callable
from pathlib import Path
import re
import pytest
from esphome.core import CORE
def test_logger_pre_setup_before_other_components(generate_main):
"""Logger::pre_setup() must be called before any other component is created.
@@ -84,3 +90,24 @@ def test_libretiny_uart0_is_emitted(generate_main):
)
assert "set_uart_selection(logger::UART_SELECTION_UART0);" in main_cpp
def test_ram_log_strings_is_deprecated(
generate_main: Callable[[str | Path], str],
caplog: pytest.LogCaptureFixture,
) -> None:
"""`false` is accepted but ignored: no RAM build flag, and a warning with the removal version."""
generate_main("tests/component_tests/logger/test_logger_ram_log_strings.yaml")
assert not any("STORE_LOG_STR" in flag for flag in CORE.build_flags)
assert "esp8266_store_log_strings_in_flash: false' is ignored" in caplog.text
assert "2027.4.0" in caplog.text
def test_flash_log_strings_default_does_not_warn(
generate_main: Callable[[str | Path], str],
caplog: pytest.LogCaptureFixture,
) -> None:
generate_main("tests/component_tests/logger/test_logger.yaml")
assert "esp8266_store_log_strings_in_flash" not in caplog.text
@@ -0,0 +1,9 @@
---
esphome:
name: test
esp8266:
board: d1_mini_lite
logger:
esp8266_store_log_strings_in_flash: false