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.
Background
Section titled “Background”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.
What’s Changing
Section titled “What’s Changing”The LogListener class and add_log_listener() method are removed entirely:
// Removedclass LogListener { public: virtual void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) = 0;};
// Removedvoid Logger::add_log_listener(LogListener *listener);Replaced by:
// Newstruct 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));Who This Affects
Section titled “Who This Affects”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.
Migration Guide
Section titled “Migration Guide”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:
- Remove
, public logger::LogListenerfrom the class declaration - Replace
add_log_listener(this)withadd_log_callback(this, lambda) - The lambda must be non-capturing (use the
void *selfparameter instead) - Remove
overridefromon_log()— it’s now a regular method
Supporting Multiple ESPHome Versions
Section titled “Supporting Multiple ESPHome Versions”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{ // ...};Timeline
Section titled “Timeline”- ESPHome 2026.3.0 (March 2026):
LogListenerclass andadd_log_listener()removed,add_log_callback(instance, callback)available
Finding Code That Needs Updates
Section titled “Finding Code That Needs Updates”# Find LogListener inheritancegrep -rn 'LogListener' your_component/
# Find add_log_listener callsgrep -rn 'add_log_listener' your_component/
# Find on_log overridesgrep -rn 'on_log.*override' your_component/Questions?
Section titled “Questions?”If you have questions about migrating your external component, please ask in:
- ESPHome Discord - #devs channel
- ESPHome GitHub Discussions
Related Documentation
Section titled “Related Documentation”Copyright © 2026 ESPHome - A project from the Open Home Foundation
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.