[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.
This commit is contained in:
J. Nick Koston
2026-02-18 22:42:59 -06:00
parent 565443b710
commit e9fee86c9d
11 changed files with 65 additions and 68 deletions

View File

@@ -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<APIServer *>(self)->on_log(level, tag, message, message_len);
});
}
#endif

View File

@@ -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<camera::CameraImage> &image) override;

View File

@@ -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<BLENUS *>(self)->on_log(level, tag, message, message_len);
});
}
#endif
}

View File

@@ -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:

View File

@@ -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<MyComponent *>(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<const char *, uint8_t, CStrCompare> log_levels_{};
#endif
#ifdef USE_LOG_LISTENERS
StaticVector<LogListener *, ESPHOME_LOG_MAX_LISTENERS>
log_listeners_; // Log message listeners (API, MQTT, syslog, etc.)
StaticVector<LogCallback, ESPHOME_LOG_MAX_LISTENERS>
log_callbacks_; // Log message callbacks (API, MQTT, syslog, etc.)
#endif
#ifdef USE_LOGGER_LEVEL_LISTENERS
std::vector<LoggerLevelListener *> 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<uint8_t, const char *, const char *>, public LogListener {
class LoggerMessageTrigger : public Trigger<uint8_t, const char *, const char *> {
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<LoggerMessageTrigger *>(self);
if (level <= trigger->level_) {
trigger->trigger(level, tag, message);
}
});
}
protected:

View File

@@ -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<MQTTClientComponent *>(self)->on_log(level, tag, message, message_len);
});
}
#endif

View File

@@ -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);

View File

@@ -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<Syslog *>(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);

View File

@@ -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<udp::UDPComponent>, public logger::LogListener {
class Syslog : public Component, public Parented<udp::UDPComponent> {
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; }

View File

@@ -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<WebServer *>(self)->on_log(level, tag, message, message_len);
});
}
#endif

View File

@@ -186,14 +186,7 @@ class DeferredUpdateEventSourceList : public std::list<DeferredUpdateEventSource
* under the '/light/...', '/sensor/...', ... URLs. A full documentation for this API
* can be found under https://esphome.io/web-api/.
*/
class WebServer : public Controller,
public Component,
public AsyncWebHandler
#ifdef USE_LOGGER
,
public logger::LogListener
#endif
{
class WebServer : public Controller, public Component, public AsyncWebHandler {
#if !defined(USE_ESP32) && defined(USE_ARDUINO)
friend class DeferredUpdateEventSourceList;
#endif
@@ -254,7 +247,7 @@ class WebServer : public Controller,
void dump_config() 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
/// MQTT setup priority.