1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-08 00:31:58 +00:00

[logger] Use StaticVector for log listeners with compile-time sizing (#13196)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
J. Nick Koston
2026-01-13 15:43:17 -10:00
committed by GitHub
parent 5dfdd05122
commit 2793e33baf
9 changed files with 55 additions and 4 deletions

View File

@@ -4,6 +4,7 @@ import logging
from esphome import automation
from esphome.automation import Condition
import esphome.codegen as cg
from esphome.components.logger import request_log_listener
from esphome.config_helpers import get_logger_level
import esphome.config_validation as cv
from esphome.const import (
@@ -326,6 +327,9 @@ async def to_code(config: ConfigType) -> None:
# Track controller registration for StaticVector sizing
CORE.register_controller()
# Request a log listener slot for API log streaming
request_log_listener()
cg.add(var.set_port(config[CONF_PORT]))
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))

View File

@@ -1,4 +1,5 @@
import esphome.codegen as cg
from esphome.components.logger import request_log_listener
from esphome.components.zephyr import zephyr_add_prj_conf
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_LOGS, CONF_TYPE
@@ -25,5 +26,8 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
zephyr_add_prj_conf("BT_NUS", True)
cg.add(var.set_expose_log(config[CONF_TYPE] == CONF_LOGS))
expose_log = config[CONF_TYPE] == CONF_LOGS
cg.add(var.set_expose_log(expose_log))
if expose_log:
request_log_listener() # Request a log listener slot for BLE NUS log streaming
await cg.register_component(var, config)

View File

@@ -421,6 +421,7 @@ async def to_code(config):
await cg.register_component(log, config)
for conf in config.get(CONF_ON_MESSAGE, []):
request_log_listener() # Each on_message trigger needs a listener slot
trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID], log, LOG_LEVEL_SEVERITY.index(conf[CONF_LEVEL])
)
@@ -546,6 +547,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
# Keys for CORE.data storage
DOMAIN = "logger"
KEY_LEVEL_LISTENERS = "level_listeners"
KEY_LOG_LISTENERS = "log_listeners"
def request_logger_level_listeners() -> None:
@@ -558,8 +560,26 @@ def request_logger_level_listeners() -> None:
CORE.data.setdefault(DOMAIN, {})[KEY_LEVEL_LISTENERS] = True
def request_log_listener() -> None:
"""Request a log listener slot.
Components that need to receive log messages should call this function
during their code generation. This increments the listener count used
to size the StaticVector.
"""
data = CORE.data.setdefault(DOMAIN, {})
data[KEY_LOG_LISTENERS] = data.get(KEY_LOG_LISTENERS, 0) + 1
@coroutine_with_priority(CoroPriority.FINAL)
async def final_step():
"""Final code generation step to configure optional logger features."""
if CORE.data.get(DOMAIN, {}).get(KEY_LEVEL_LISTENERS, False):
domain_data = CORE.data.get(DOMAIN, {})
if domain_data.get(KEY_LEVEL_LISTENERS, False):
cg.add_define("USE_LOGGER_LEVEL_LISTENERS")
# Only generate log listener code if any component needs it
log_listener_count = domain_data.get(KEY_LOG_LISTENERS, 0)
if log_listener_count > 0:
cg.add_define("USE_LOG_LISTENERS")
cg.add_define("ESPHOME_LOG_MAX_LISTENERS", log_listener_count)

View File

@@ -178,8 +178,10 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
// Listeners get message first (before console write)
#ifdef USE_LOG_LISTENERS
for (auto *listener : this->log_listeners_)
listener->on_log(level, tag, this->tx_buffer_ + msg_start, msg_length);
#endif
// Write to console starting at the msg_start
this->write_tx_buffer_to_console_(msg_start, &msg_length);

View File

@@ -212,8 +212,13 @@ class Logger : public Component {
inline uint8_t level_for(const char *tag);
#ifdef USE_LOG_LISTENERS
/// Register a log listener to receive log messages
void add_log_listener(LogListener *listener) { this->log_listeners_.push_back(listener); }
#else
/// No-op when log listeners are disabled
void add_log_listener(LogListener *listener) {}
#endif
#ifdef USE_LOGGER_LEVEL_LISTENERS
/// Register a listener for log level changes
@@ -318,8 +323,10 @@ class Logger : public Component {
this->tx_buffer_size_);
// Listeners get message WITHOUT newline (for API/MQTT/syslog)
#ifdef USE_LOG_LISTENERS
for (auto *listener : this->log_listeners_)
listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_);
#endif
// Console gets message WITH newline (if platform needs it)
this->write_tx_buffer_to_console_();
@@ -336,8 +343,10 @@ class Logger : public Component {
this->write_body_to_buffer_(text, text_length, this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
this->tx_buffer_[this->tx_buffer_at_] = '\0';
#ifdef USE_LOG_LISTENERS
for (auto *listener : this->log_listeners_)
listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_);
#endif
}
#endif
@@ -394,7 +403,10 @@ class Logger : public Component {
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
std::map<const char *, uint8_t, CStrCompare> log_levels_{};
#endif
std::vector<LogListener *> log_listeners_; // Log message listeners (API, MQTT, syslog, etc.)
#ifdef USE_LOG_LISTENERS
StaticVector<LogListener *, ESPHOME_LOG_MAX_LISTENERS>
log_listeners_; // Log message listeners (API, MQTT, syslog, etc.)
#endif
#ifdef USE_LOGGER_LEVEL_LISTENERS
std::vector<LoggerLevelListener *> level_listeners_; // Log level change listeners
#endif

View File

@@ -350,6 +350,7 @@ def exp_mqtt_message(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
# Add required libraries for ESP8266 and LibreTiny
if CORE.is_esp8266 or CORE.is_libretiny:
# https://github.com/heman/async-mqtt-client/blob/master/library.json
@@ -432,6 +433,8 @@ async def to_code(config):
cg.add(var.disable_log_message())
else:
cg.add(var.set_log_message_template(exp_mqtt_message(log_topic)))
# Request a log listener slot only when log topic is enabled
logger.request_log_listener()
if CONF_LEVEL in log_topic:
cg.add(var.set_log_level(logger.LOG_LEVELS[log_topic[CONF_LEVEL]]))

View File

@@ -1,6 +1,6 @@
import esphome.codegen as cg
from esphome.components import udp
from esphome.components.logger import LOG_LEVELS, is_log_level
from esphome.components.logger import LOG_LEVELS, is_log_level, request_log_listener
from esphome.components.time import RealTimeClock
from esphome.components.udp import CONF_UDP_ID
import esphome.config_validation as cv
@@ -36,6 +36,7 @@ async def to_code(config):
level = LOG_LEVELS[config[CONF_LEVEL]]
var = cg.new_Pvariable(config[CONF_ID], level, time)
await cg.register_component(var, config)
request_log_listener() # Request a log listener slot for syslog
await cg.register_parented(var, parent)
cg.add(var.set_strip(config[CONF_STRIP]))
cg.add(var.set_facility(config[CONF_FACILITY]))

View File

@@ -4,6 +4,7 @@ import gzip
import esphome.codegen as cg
from esphome.components import web_server_base
from esphome.components.logger import request_log_listener
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
import esphome.config_validation as cv
from esphome.const import (
@@ -313,6 +314,8 @@ async def to_code(config):
if config.get(CONF_OTA) is False:
cg.add_define("USE_WEBSERVER_OTA_DISABLED")
cg.add(var.set_expose_log(config[CONF_LOG]))
if config[CONF_LOG]:
request_log_listener() # Request a log listener slot for web server log streaming
if config[CONF_ENABLE_PRIVATE_NETWORK_ACCESS]:
cg.add_define("USE_WEBSERVER_PRIVATE_NETWORK_ACCESS")
if CONF_AUTH in config:

View File

@@ -20,6 +20,8 @@
// logger
#define ESPHOME_LOG_LEVEL ESPHOME_LOG_LEVEL_VERY_VERBOSE
#define USE_LOG_LISTENERS
#define ESPHOME_LOG_MAX_LISTENERS 8
// Feature flags
#define USE_ALARM_CONTROL_PANEL