From e9fee86c9d88f23e51efdb47159414ca2e8a18be Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 18 Feb 2026 22:42:59 -0600 Subject: [PATCH] [logger] Replace LogListener virtual interface with LogCallback struct Replace the LogListener abstract class (single pure virtual method) with a lightweight LogCallback struct containing a function pointer + instance pointer. This eliminates a vtable sub-table and thunk from every class that previously inherited LogListener. Savings per former LogListener implementer: - 12 bytes vtable (sub-table header + thunk slot) - 4 bytes vtable (on_log in primary table) - ~23 bytes thunk code - ~39 bytes total per class Affected classes: APIServer, WebServer, MQTTClientComponent, Syslog, BLENUS, LoggerMessageTrigger (6 classes, ~234 bytes total). The non-capturing lambdas used at registration sites decay to plain function pointers at compile time -- zero closure/std::function overhead. --- esphome/components/api/api_server.cpp | 5 +- esphome/components/api/api_server.h | 6 +- esphome/components/ble_nus/ble_nus.cpp | 5 +- esphome/components/ble_nus/ble_nus.h | 9 +-- esphome/components/logger/logger.h | 66 ++++++++++---------- esphome/components/mqtt/mqtt_client.cpp | 5 +- esphome/components/mqtt/mqtt_client.h | 9 +-- esphome/components/syslog/esphome_syslog.cpp | 7 ++- esphome/components/syslog/esphome_syslog.h | 5 +- esphome/components/web_server/web_server.cpp | 5 +- esphome/components/web_server/web_server.h | 11 +--- 11 files changed, 65 insertions(+), 68 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 67a117e68f..1a1d0b229b 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -92,7 +92,10 @@ void APIServer::setup() { #ifdef USE_LOGGER if (logger::global_logger != nullptr) { - logger::global_logger->add_log_listener(this); + logger::global_logger->add_log_callback( + this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { + static_cast(self)->on_log(level, tag, message, message_len); + }); } #endif diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 323acc2efb..3b9ba0e23b 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -37,10 +37,6 @@ struct SavedNoisePsk { class APIServer : public Component, public Controller -#ifdef USE_LOGGER - , - public logger::LogListener -#endif #ifdef USE_CAMERA , public camera::CameraListener @@ -56,7 +52,7 @@ class APIServer : public Component, void on_shutdown() override; bool teardown() override; #ifdef USE_LOGGER - void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override; + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len); #endif #ifdef USE_CAMERA void on_camera_image(const std::shared_ptr &image) override; diff --git a/esphome/components/ble_nus/ble_nus.cpp b/esphome/components/ble_nus/ble_nus.cpp index 0de65b623f..a10132eb3e 100644 --- a/esphome/components/ble_nus/ble_nus.cpp +++ b/esphome/components/ble_nus/ble_nus.cpp @@ -87,7 +87,10 @@ void BLENUS::setup() { global_ble_nus = this; #ifdef USE_LOGGER if (logger::global_logger != nullptr && this->expose_log_) { - logger::global_logger->add_log_listener(this); + logger::global_logger->add_log_callback( + this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { + static_cast(self)->on_log(level, tag, message, message_len); + }); } #endif } diff --git a/esphome/components/ble_nus/ble_nus.h b/esphome/components/ble_nus/ble_nus.h index ef20fc5e5b..b2b0ee7713 100644 --- a/esphome/components/ble_nus/ble_nus.h +++ b/esphome/components/ble_nus/ble_nus.h @@ -10,12 +10,7 @@ namespace esphome::ble_nus { -class BLENUS : public Component -#ifdef USE_LOGGER - , - public logger::LogListener -#endif -{ +class BLENUS : public Component { enum TxStatus { TX_DISABLED, TX_ENABLED, @@ -29,7 +24,7 @@ class BLENUS : public Component size_t write_array(const uint8_t *data, size_t len); void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; } #ifdef USE_LOGGER - void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override; + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len); #endif protected: diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 835542dd8f..2a7552af92 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -40,26 +40,25 @@ struct device; namespace esphome::logger { -/** Interface for receiving log messages without std::function overhead. +/** Lightweight callback for receiving log messages without virtual dispatch overhead. * - * Components can implement this interface instead of using lambdas with std::function - * to reduce flash usage from std::function type erasure machinery. + * Replaces the former LogListener virtual interface to eliminate per-implementer + * vtable sub-tables and thunk code (~39 bytes saved per class that used LogListener). * * Usage: - * class MyComponent : public Component, public LogListener { - * public: - * void setup() override { - * if (logger::global_logger != nullptr) - * logger::global_logger->add_log_listener(this); - * } - * void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { - * // Handle log message - * } - * }; + * // In your component's setup(): + * if (logger::global_logger != nullptr) + * logger::global_logger->add_log_callback( + * this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { + * static_cast(self)->on_log(level, tag, message, message_len); + * }); */ -class LogListener { - public: - virtual void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) = 0; +struct LogCallback { + void *instance; + void (*fn)(void *, uint8_t, const char *, const char *, size_t); + void invoke(uint8_t level, const char *tag, const char *message, size_t message_len) const { + this->fn(this->instance, level, tag, message, message_len); + } }; #ifdef USE_LOGGER_LEVEL_LISTENERS @@ -187,11 +186,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); } + /// Register a log callback to receive log messages + void add_log_callback(void *instance, void (*fn)(void *, uint8_t, const char *, const char *, size_t)) { + this->log_callbacks_.push_back(LogCallback{instance, fn}); + } #else /// No-op when log listeners are disabled - void add_log_listener(LogListener *listener) {} + void add_log_callback(void *instance, void (*fn)(void *, uint8_t, const char *, const char *, size_t)) {} #endif #ifdef USE_LOGGER_LEVEL_LISTENERS @@ -253,11 +254,11 @@ class Logger : public Component { } #endif - // Helper to notify log listeners + // Helper to notify log callbacks inline void HOT notify_listeners_(uint8_t level, const char *tag, const LogBuffer &buf) { #ifdef USE_LOG_LISTENERS - for (auto *listener : this->log_listeners_) - listener->on_log(level, tag, buf.data, buf.pos); + for (auto &cb : this->log_callbacks_) + cb.invoke(level, tag, buf.data, buf.pos); #endif } @@ -341,8 +342,8 @@ class Logger : public Component { std::map log_levels_{}; #endif #ifdef USE_LOG_LISTENERS - StaticVector - log_listeners_; // Log message listeners (API, MQTT, syslog, etc.) + StaticVector + log_callbacks_; // Log message callbacks (API, MQTT, syslog, etc.) #endif #ifdef USE_LOGGER_LEVEL_LISTENERS std::vector level_listeners_; // Log level change listeners @@ -478,15 +479,16 @@ class Logger : public Component { }; extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -class LoggerMessageTrigger : public Trigger, public LogListener { +class LoggerMessageTrigger : public Trigger { public: - explicit LoggerMessageTrigger(Logger *parent, uint8_t level) : level_(level) { parent->add_log_listener(this); } - - void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { - (void) message_len; - if (level <= this->level_) { - this->trigger(level, tag, message); - } + explicit LoggerMessageTrigger(Logger *parent, uint8_t level) : level_(level) { + parent->add_log_callback(this, + [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { + auto *trigger = static_cast(self); + if (level <= trigger->level_) { + trigger->trigger(level, tag, message); + } + }); } protected: diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 90b423c386..9905b4677e 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -64,7 +64,10 @@ void MQTTClientComponent::setup() { }); #ifdef USE_LOGGER if (this->is_log_message_enabled() && logger::global_logger != nullptr) { - logger::global_logger->add_log_listener(this); + logger::global_logger->add_log_callback( + this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { + static_cast(self)->on_log(level, tag, message, message_len); + }); } #endif diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 38bc0b4da3..21edd53eda 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -99,12 +99,7 @@ enum MQTTClientState { class MQTTComponent; -class MQTTClientComponent : public Component -#ifdef USE_LOGGER - , - public logger::LogListener -#endif -{ +class MQTTClientComponent : public Component { public: MQTTClientComponent(); @@ -252,7 +247,7 @@ class MQTTClientComponent : public Component float get_setup_priority() const override; #ifdef USE_LOGGER - void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override; + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len); #endif void on_message(const std::string &topic, const std::string &payload); diff --git a/esphome/components/syslog/esphome_syslog.cpp b/esphome/components/syslog/esphome_syslog.cpp index 376de54db4..790d08ffa6 100644 --- a/esphome/components/syslog/esphome_syslog.cpp +++ b/esphome/components/syslog/esphome_syslog.cpp @@ -18,7 +18,12 @@ constexpr int LOG_LEVEL_TO_SYSLOG_SEVERITY[] = { 7 // VERY_VERBOSE }; -void Syslog::setup() { logger::global_logger->add_log_listener(this); } +void Syslog::setup() { + logger::global_logger->add_log_callback( + this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { + static_cast(self)->on_log(level, tag, message, message_len); + }); +} void Syslog::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) { this->log_(level, tag, message, message_len); diff --git a/esphome/components/syslog/esphome_syslog.h b/esphome/components/syslog/esphome_syslog.h index bde6ab5ed4..be4fa91436 100644 --- a/esphome/components/syslog/esphome_syslog.h +++ b/esphome/components/syslog/esphome_syslog.h @@ -2,17 +2,16 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/components/logger/logger.h" #include "esphome/components/udp/udp_component.h" #include "esphome/components/time/real_time_clock.h" #ifdef USE_NETWORK namespace esphome::syslog { -class Syslog : public Component, public Parented, public logger::LogListener { +class Syslog : public Component, public Parented { public: Syslog(int level, time::RealTimeClock *time) : log_level_(level), time_(time) {} void setup() override; - void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override; + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len); void set_strip(bool strip) { this->strip_ = strip; } void set_facility(int facility) { this->facility_ = facility; } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 3acd2d2119..c43e5633b8 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -395,7 +395,10 @@ void WebServer::setup() { #ifdef USE_LOGGER if (logger::global_logger != nullptr && this->expose_log_) { - logger::global_logger->add_log_listener(this); + logger::global_logger->add_log_callback( + this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { + static_cast(self)->on_log(level, tag, message, message_len); + }); } #endif diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 026da763ea..6afe618b59 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -186,14 +186,7 @@ class DeferredUpdateEventSourceList : public std::list