Skip to content

LogListener Virtual Interface Replaced with LogCallback

The logger::LogListener abstract class has been removed. Components that receive log messages must now register a callback with logger::global_logger->add_log_callback(instance, callback) instead of inheriting from LogListener and calling add_log_listener().

This is a breaking change for external components in ESPHome 2026.3.0 and later.

PR #14084: Replace LogListener virtual interface with LogCallback struct

The LogListener pattern required every log-receiving class to inherit from LogListener and implement its pure virtual on_log() method. This added a vtable sub-table, thunk code, and constructor overhead to every implementer — all unnecessary since the call pattern is a simple function dispatch.

The replacement uses a LogCallback struct containing a function pointer + instance pointer. Non-capturing lambdas at registration sites decay to plain function pointers at compile time, producing zero closure overhead.

Savings: 112 bytes of flash saved across the built-in implementers (APIServer, WebServer, MQTTClientComponent, Syslog), with net zero heap change.

The LogListener class and add_log_listener() method are removed entirely:

// Removed
class LogListener {
public:
virtual void on_log(uint8_t level, const char *tag,
const char *message, size_t message_len) = 0;
};
// Removed
void Logger::add_log_listener(LogListener *listener);

Replaced by:

// New
struct LogCallback {
void *instance;
void (*callback)(void *instance, uint8_t level, const char *tag,
const char *message, size_t message_len);
};
void Logger::add_log_callback(void *instance,
void (*callback)(void *, uint8_t, const char *, const char *, size_t));

External components that inherit from logger::LogListener to receive log messages. Common use cases include custom log forwarders, remote logging over serial/network, and debug components.

Standard YAML configurations are not affected.

Replace LogListener inheritance with add_log_callback()

Section titled “Replace LogListener inheritance with add_log_callback()”
// Before
#include "esphome/components/logger/logger.h"
class MyLogForwarder : public Component, public logger::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 {
// Forward log message
this->send_log(level, tag, message, message_len);
}
};
// After
#include "esphome/components/logger/logger.h"
class MyLogForwarder : public Component {
public:
void setup() override {
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<MyLogForwarder *>(self)->on_log(
level, tag, message, message_len);
});
}
void on_log(uint8_t level, const char *tag,
const char *message, size_t message_len) {
// Forward log message (no longer 'override')
this->send_log(level, tag, message, message_len);
}
};

Key changes:

  1. Remove , public logger::LogListener from the class declaration
  2. Replace add_log_listener(this) with add_log_callback(this, lambda)
  3. The lambda must be non-capturing (use the void *self parameter instead)
  4. Remove override from on_log() — it’s now a regular method
void setup() override {
if (logger::global_logger != nullptr) {
#if ESPHOME_VERSION_CODE >= VERSION_CODE(2026, 3, 0)
logger::global_logger->add_log_callback(
this,
[](void *self, uint8_t level, const char *tag,
const char *message, size_t message_len) {
static_cast<MyLogForwarder *>(self)->on_log(
level, tag, message, message_len);
});
#else
logger::global_logger->add_log_listener(this);
#endif
}
}

When using the version guard, keep the LogListener inheritance behind the same guard:

class MyLogForwarder : public Component
#if ESPHOME_VERSION_CODE < VERSION_CODE(2026, 3, 0)
, public logger::LogListener
#endif
{
// ...
};
  • ESPHome 2026.3.0 (March 2026): LogListener class and add_log_listener() removed, add_log_callback(instance, callback) available
Terminal window
# Find LogListener inheritance
grep -rn 'LogListener' your_component/
# Find add_log_listener calls
grep -rn 'add_log_listener' your_component/
# Find on_log overrides
grep -rn 'on_log.*override' your_component/

If you have questions about migrating your external component, please ask in:

Comments

Feel free to leave a comment here to discuss this post with others. You can ask questions, share your experience, or suggest improvements. If you have a question about a specific feature or issue, please consider using the ESPHome Discord. Stick to English and follow ESPHome's code of conduct. These comments exist on a discussion on GitHub, so you can also comment there directly if you prefer.