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:
@@ -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]))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]]))
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user