ESPHome 2026.5.0-dev
Loading...
Searching...
No Matches
logger.h
Go to the documentation of this file.
1#pragma once
2
3#include <cstdarg>
4#include <map>
5#include <span>
6#include <type_traits>
7#if defined(USE_ESP32) || defined(USE_HOST)
8#include <pthread.h>
9#endif
14#include "esphome/core/log.h"
15
16#include "log_buffer.h"
21
22#ifdef USE_ARDUINO
23#if defined(USE_ESP8266)
24#include <HardwareSerial.h>
25#endif // USE_ESP8266
26#ifdef USE_RP2040
27#include <HardwareSerial.h>
28#include <SerialUSB.h>
29#endif // USE_RP2040
30#endif // USE_ARDUINO
31
32#ifdef USE_ESP32
33#include <driver/uart.h>
34#endif // USE_ESP32
35
36#ifdef USE_ZEPHYR
37#include <zephyr/kernel.h>
38struct device;
39#endif
40
41namespace esphome::logger {
42
57 void *instance;
58 void (*fn)(void *, uint8_t, const char *, const char *, size_t);
59 void invoke(uint8_t level, const char *tag, const char *message, size_t message_len) const {
60 this->fn(this->instance, level, tag, message, message_len);
61 }
62};
63
64#ifdef USE_LOGGER_LEVEL_LISTENERS
83 public:
84 virtual void on_log_level_change(uint8_t level) = 0;
85};
86#endif
87
88#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
89// Comparison function for const char* keys in log_levels_ map
91 bool operator()(const char *a, const char *b) const { return strcmp(a, b) < 0; }
92};
93#endif
94
95// Stack buffer size for retrieving thread/task names from the OS
96// macOS allows up to 64 bytes, Linux up to 16
97static constexpr size_t THREAD_NAME_BUF_SIZE = 64;
98
99#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
104enum UARTSelection : uint8_t {
105#ifdef USE_LIBRETINY
108#else
110#endif
112#if defined(USE_LIBRETINY) || defined(USE_ESP32_VARIANT_ESP32)
114#endif
115#ifdef USE_LOGGER_USB_CDC
117#endif
118#ifdef USE_LOGGER_USB_SERIAL_JTAG
120#endif
121#ifdef USE_ESP8266
123#endif // USE_ESP8266
124};
125#endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY || USE_ZEPHYR
126
144class Logger final : public Component {
145 public:
146 explicit Logger(uint32_t baud_rate);
147#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
148 void loop() override;
149#endif
151 void set_baud_rate(uint32_t baud_rate);
153#if defined(USE_ARDUINO) && !defined(USE_ESP32)
154 Stream *get_hw_serial() const { return hw_serial_; }
155#endif
156#ifdef USE_ESP32
157 uart_port_t get_uart_num() const { return uart_num_; }
158 void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); }
159#endif
160#ifdef USE_HOST
161 void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); }
162#endif
163#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
164 void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
166 UARTSelection get_uart() const;
167#endif
168
170 void set_log_level(uint8_t level);
171#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
173 void set_log_level(const char *tag, uint8_t log_level);
174#endif
175 uint8_t get_log_level() { return this->current_level_; }
176
177 // ========== INTERNAL METHODS ==========
178 // (In most use cases you won't need these)
180 void pre_setup();
181 void dump_config() override;
182
183 inline uint8_t level_for(const char *tag);
184
185#ifdef USE_LOG_LISTENERS
187 void add_log_callback(void *instance, void (*fn)(void *, uint8_t, const char *, const char *, size_t)) {
188 this->log_callbacks_.push_back(LogCallback{instance, fn});
189 }
190#else
192 void add_log_callback(void *instance, void (*fn)(void *, uint8_t, const char *, const char *, size_t)) {}
193#endif
194
195#ifdef USE_LOGGER_LEVEL_LISTENERS
197 void add_level_listener(LoggerLevelListener *listener) { this->level_listeners_.push_back(listener); }
198#endif
199
200 float get_setup_priority() const override;
201
202 void log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args); // NOLINT
203#ifdef USE_STORE_LOG_STR_IN_FLASH
204 void log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
205 va_list args); // NOLINT
206#endif
207
208 protected:
209 // RAII guard for recursion flags - sets flag on construction, clears on destruction
211 public:
212 explicit RecursionGuard(bool &flag) : flag_(flag) { flag_ = true; }
213 ~RecursionGuard() { flag_ = false; }
218
219 private:
220 bool &flag_;
221 };
222
223#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
224 // Handles non-main thread logging only (~0.1% of calls)
225 // thread_name is resolved by the caller from the task handle, avoiding redundant lookups
226 void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args,
227 const char *thread_name);
228#endif
229#if defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC)
230 void cdc_loop_();
231#endif
232 void process_messages_();
233#if defined(USE_HOST) || defined(USE_ZEPHYR)
234 void write_msg_(const char *msg, uint16_t len);
235#else
236 inline void write_msg_(const char *msg, uint16_t len); // Defined in platform-specific logger_*.h
237#endif
238
239 // Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
240 // thread_name: name of the calling thread/task, or nullptr for main task (callers already know which task they're on)
241 inline void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format,
242 va_list args, LogBuffer &buf, const char *thread_name) {
243 buf.write_header(level, tag, line, thread_name);
244 buf.format_body(format, args);
245 }
246
247#ifdef USE_STORE_LOG_STR_IN_FLASH
248 // Format a log message with flash string format and write it to a buffer with header, footer, and null terminator
249 // ESP8266-only (single-task), thread_name is always nullptr
250 inline void HOT format_log_to_buffer_with_terminator_P_(uint8_t level, const char *tag, int line,
251 const __FlashStringHelper *format, va_list args,
252 LogBuffer &buf) {
253 buf.write_header(level, tag, line, nullptr);
254 buf.format_body_P(reinterpret_cast<PGM_P>(format), args);
255 }
256#endif
257
258 // Helper to notify log callbacks
259 inline void HOT notify_listeners_(uint8_t level, const char *tag, const LogBuffer &buf) {
260#ifdef USE_LOG_LISTENERS
261 for (auto &cb : this->log_callbacks_)
262 cb.invoke(level, tag, buf.data, buf.pos);
263#endif
264 }
265
266 // Helper to write log buffer to console (replaces null terminator with newline and writes)
267 inline void HOT write_to_console_(LogBuffer &buf) {
269 this->write_msg_(buf.data, buf.pos);
270 }
271
272 // Helper to write log buffer to console if logging is enabled
274 if (this->baud_rate_ > 0)
275 this->write_to_console_(buf);
276 }
277
278 // Helper to format and send a log message to both console and listeners
279 // Template handles both const char* (RAM) and __FlashStringHelper* (flash) format strings
280 // thread_name: name of the calling thread/task, or nullptr for main task
281 template<typename FormatType>
282 inline void HOT log_message_to_buffer_and_send_(bool &recursion_guard, uint8_t level, const char *tag, int line,
283 FormatType format, va_list args, const char *thread_name) {
284 RecursionGuard guard(recursion_guard);
285 LogBuffer buf{this->tx_buffer_, ESPHOME_LOGGER_TX_BUFFER_SIZE};
286#ifdef USE_STORE_LOG_STR_IN_FLASH
287 if constexpr (std::is_same_v<FormatType, const __FlashStringHelper *>) {
289 } else
290#endif
291 {
292 this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf, thread_name);
293 }
294 this->notify_listeners_(level, tag, buf);
296 }
297
298#ifdef USE_ESPHOME_TASK_LOG_BUFFER
299 // Helper to format a pre-formatted message from the task log buffer and notify listeners
300 // Used by process_messages_ to avoid code duplication between ESP32 and host platforms
301 inline void HOT format_buffered_message_and_notify_(uint8_t level, const char *tag, uint16_t line,
302 const char *thread_name, const char *text, uint16_t text_length,
303 LogBuffer &buf) {
304 buf.write_header(level, tag, line, thread_name);
305 buf.write_body(text, text_length);
306 this->notify_listeners_(level, tag, buf);
307 }
308#endif
309
310#ifndef USE_HOST
311 const LogString *get_uart_selection_();
312#endif
313
314 // Group 4-byte aligned members first
316#if defined(USE_ARDUINO) && !defined(USE_ESP32)
317 Stream *hw_serial_{nullptr};
318#endif
319#if defined(USE_ZEPHYR)
320 void dump_crash_();
321 const device *uart_dev_{nullptr};
322#endif
323#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
324 void *main_task_{nullptr}; // Main thread/task for fast path comparison
325#endif
326#ifdef USE_HOST
327 pthread_t main_thread_{}; // Main thread for pthread_equal() comparison
328#endif
329#ifdef USE_ESP32
330 // Task-specific recursion guards:
331 // - Main task uses a dedicated member variable for efficiency
332 // - Other tasks use pthread TLS with a dynamically created key via pthread_key_create
333 pthread_key_t log_recursion_key_; // 4 bytes
334 uart_port_t uart_num_; // 4 bytes (enum defaults to int size)
335#endif
336#ifdef USE_HOST
337 // Thread-specific recursion guards using pthread TLS
338 pthread_key_t log_recursion_key_;
339#endif
340
341 // Large objects (internally aligned)
342#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
343 std::map<const char *, uint8_t, CStrCompare> log_levels_{};
344#endif
345#ifdef USE_LOG_LISTENERS
347 log_callbacks_; // Log message callbacks (API, MQTT, syslog, etc.)
348#endif
349#ifdef USE_LOGGER_LEVEL_LISTENERS
350 std::vector<LoggerLevelListener *> level_listeners_; // Log level change listeners
351#endif
352 // Group smaller types together at the end
353 uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
354#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_ZEPHYR)
356#endif
357#ifdef USE_LIBRETINY
359#endif
360#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
362#ifdef USE_LIBRETINY
363 bool non_main_task_recursion_guard_{false}; // Shared guard for all non-main tasks on LibreTiny
364#endif
365#else
366 bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
367#endif
368
369 // Large buffers placed last to keep frequently-accessed member offsets small
370 char tx_buffer_[ESPHOME_LOGGER_TX_BUFFER_SIZE + 1]; // +1 for null terminator
371#ifdef USE_ESPHOME_TASK_LOG_BUFFER
372 logger::TaskLogBuffer log_buffer_; // Embedded in Logger (no separate heap allocation)
373#endif
374
375 // --- get_thread_name_ overloads (per-platform) ---
376
377#if defined(USE_ESP32) || defined(USE_LIBRETINY)
378 // Primary overload - takes a task handle directly to avoid redundant xTaskGetCurrentTaskHandle() calls
379 // when the caller already has the handle (e.g. from the main task check in log_vprintf_)
380 const char *get_thread_name_(TaskHandle_t task) {
381 if (task == this->main_task_) {
382 return nullptr; // Main task
383 }
384#if defined(USE_ESP32)
385 return pcTaskGetName(task);
386#elif defined(USE_LIBRETINY)
387 return pcTaskGetTaskName(task);
388#endif
389 }
390
391 // Convenience overload - gets the current task handle and delegates
392 const char *HOT get_thread_name_() { return this->get_thread_name_(xTaskGetCurrentTaskHandle()); }
393
394#elif defined(USE_HOST)
395 // Takes a caller-provided buffer for the thread name (stack-allocated for thread safety)
396 const char *HOT get_thread_name_(std::span<char> buff) {
397 pthread_t current_thread = pthread_self();
398 if (pthread_equal(current_thread, main_thread_)) {
399 return nullptr; // Main thread
400 }
401 // For non-main threads, get the thread name into the caller-provided buffer
402 if (pthread_getname_np(current_thread, buff.data(), buff.size()) == 0) {
403 return buff.data();
404 }
405 return nullptr;
406 }
407
408#elif defined(USE_ZEPHYR)
409 const char *HOT get_thread_name_(std::span<char> buff, k_tid_t current_task = nullptr) {
410 if (current_task == nullptr) {
411 current_task = k_current_get();
412 }
413 if (current_task == main_task_) {
414 return nullptr; // Main task
415 }
416 const char *name = k_thread_name_get(current_task);
417 if (name) {
418 // zephyr print task names only if debug component is present
419 return name;
420 }
421 std::snprintf(buff.data(), buff.size(), "%p", current_task);
422 return buff.data();
423 }
424#endif
425
426 // --- Non-main task recursion guards (per-platform) ---
427
428#if defined(USE_ESP32) || defined(USE_HOST)
429 // RAII guard for non-main task recursion using pthread TLS
431 public:
432 explicit NonMainTaskRecursionGuard(pthread_key_t key) : key_(key) {
433 pthread_setspecific(key_, reinterpret_cast<void *>(1));
434 }
435 ~NonMainTaskRecursionGuard() { pthread_setspecific(key_, nullptr); }
440
441 private:
442 pthread_key_t key_;
443 };
444
445 // Check if non-main task is already in recursion (via TLS)
446 inline bool HOT is_non_main_task_recursive_() const { return pthread_getspecific(log_recursion_key_) != nullptr; }
447
448 // Create RAII guard for non-main task recursion
450
451#elif defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
452 // LibreTiny doesn't have FreeRTOS TLS, so use a simple approach:
453 // - Main task uses dedicated boolean (same as ESP32)
454 // - Non-main tasks share a single recursion guard
455 // This is safe because:
456 // - Recursion from logging within logging is the main concern
457 // - Cross-task "recursion" is prevented by the buffer mutex anyway
458 // - Missing a recursive call from another task is acceptable (falls back to direct output)
459 //
460 // Zephyr use __thread as TLS
461
462 // Check if non-main task is already in recursion
464
465 // Create RAII guard for non-main task recursion (uses shared boolean for all non-main tasks)
467#endif
468
469#if defined(USE_ESPHOME_TASK_LOG_BUFFER) && !(defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
470 // Disable loop when task buffer is empty
471 // Zephyr with USB CDC needs loop active to poll port readiness via cdc_loop_()
473 // Thread safety note: This is safe even if another task calls enable_loop_soon_any_context()
474 // concurrently. If that happens between our check and disable_loop(), the enable request
475 // will be processed on the next main loop iteration since:
476 // - disable_loop() takes effect immediately
477 // - enable_loop_soon_any_context() sets a pending flag that's checked at loop start
478 this->disable_loop();
479 }
480#endif
481};
482extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
483
484class LoggerMessageTrigger final : public Trigger<uint8_t, const char *, const char *> {
485 public:
486 explicit LoggerMessageTrigger(Logger *parent, uint8_t level) : level_(level) {
487 parent->add_log_callback(this,
488 [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) {
489 auto *trigger = static_cast<LoggerMessageTrigger *>(self);
490 if (level <= trigger->level_) {
491 trigger->trigger(level, tag, message);
492 }
493 });
494 }
495
496 protected:
497 uint8_t level_;
498};
499
500} // namespace esphome::logger
501
502// Platform-specific inline implementations of write_msg_()
503// Must be included after the Logger class definition is complete
504#if defined(USE_ESP32)
505#include "logger_esp32.h"
506#elif defined(USE_ESP8266)
507#include "logger_esp8266.h"
508#elif defined(USE_RP2040)
509#include "logger_rp2040.h"
510#elif defined(USE_LIBRETINY)
511#include "logger_libretiny.h"
512#endif
void disable_loop()
Disable this component's loop.
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:210
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Definition automation.h:482
NonMainTaskRecursionGuard & operator=(const NonMainTaskRecursionGuard &)=delete
NonMainTaskRecursionGuard & operator=(NonMainTaskRecursionGuard &&)=delete
NonMainTaskRecursionGuard(const NonMainTaskRecursionGuard &)=delete
NonMainTaskRecursionGuard(NonMainTaskRecursionGuard &&)=delete
RecursionGuard & operator=(const RecursionGuard &)=delete
RecursionGuard(const RecursionGuard &)=delete
RecursionGuard & operator=(RecursionGuard &&)=delete
RecursionGuard(RecursionGuard &&)=delete
Logger component for all ESPHome logging.
Definition logger.h:144
void add_level_listener(LoggerLevelListener *listener)
Register a listener for log level changes.
Definition logger.h:197
void HOT notify_listeners_(uint8_t level, const char *tag, const LogBuffer &buf)
Definition logger.h:259
UARTSelection uart_
Definition logger.h:355
void write_msg_(const char *msg, uint16_t len)
Definition logger_esp32.h:9
const char *HOT get_thread_name_()
Definition logger.h:392
void dump_config() override
Definition logger.cpp:218
const char * get_thread_name_(TaskHandle_t task)
Definition logger.h:380
bool HOT is_non_main_task_recursive_() const
Definition logger.h:446
uint32_t get_baud_rate() const
Definition logger.h:152
const char *HOT get_thread_name_(std::span< char > buff, k_tid_t current_task=nullptr)
Definition logger.h:409
NonMainTaskRecursionGuard make_non_main_task_guard_()
Definition logger.h:449
const LogString * get_uart_selection_()
uint8_t level_for(const char *tag)
Definition logger.cpp:146
StaticVector< LogCallback, ESPHOME_LOG_MAX_LISTENERS > log_callbacks_
Definition logger.h:347
void HOT format_log_to_buffer_with_terminator_P_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format, va_list args, LogBuffer &buf)
Definition logger.h:250
void loop() override
Definition logger.cpp:166
uint8_t get_log_level()
Definition logger.h:175
Stream * get_hw_serial() const
Definition logger.h:154
void log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args)
Definition logger.cpp:26
void HOT log_message_to_buffer_and_send_(bool &recursion_guard, uint8_t level, const char *tag, int line, FormatType format, va_list args, const char *thread_name)
Definition logger.h:282
void pre_setup()
Set up this component.
char tx_buffer_[ESPHOME_LOGGER_TX_BUFFER_SIZE+1]
Definition logger.h:370
void HOT format_buffered_message_and_notify_(uint8_t level, const char *tag, uint16_t line, const char *thread_name, const char *text, uint16_t text_length, LogBuffer &buf)
Definition logger.h:301
void write_msg_(const char *msg, uint16_t len)
float get_setup_priority() const override
Definition logger.cpp:209
std::map< const char *, uint8_t, CStrCompare > log_levels_
Definition logger.h:343
const char *HOT get_thread_name_(std::span< char > buff)
Definition logger.h:396
UARTSelection get_uart() const
Get the UART used by the logger.
Definition logger.cpp:206
pthread_t main_thread_
Definition logger.h:327
std::vector< LoggerLevelListener * > level_listeners_
Definition logger.h:350
bool non_main_task_recursion_guard_
Definition logger.h:363
void disable_loop_when_buffer_empty_()
Definition logger.h:472
uart_port_t uart_num_
Definition logger.h:334
void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format, va_list args, LogBuffer &buf, const char *thread_name)
Definition logger.h:241
pthread_key_t log_recursion_key_
Definition logger.h:333
RecursionGuard make_non_main_task_guard_()
Definition logger.h:466
void HOT write_log_buffer_to_console_(LogBuffer &buf)
Definition logger.h:273
uart_port_t get_uart_num() const
Definition logger.h:157
void set_log_level(uint8_t level)
Set the default log level for this logger.
Definition logger.cpp:259
void set_baud_rate(uint32_t baud_rate)
Manually set the baud rate for serial, set to 0 to disable.
Definition logger.cpp:200
void set_uart_selection(UARTSelection uart_selection)
Definition logger.h:164
void add_log_callback(void *instance, void(*fn)(void *, uint8_t, const char *, const char *, size_t))
Register a log callback to receive log messages.
Definition logger.h:187
void HOT write_to_console_(LogBuffer &buf)
Definition logger.h:267
void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args, const char *thread_name)
Definition logger.cpp:72
logger::TaskLogBuffer log_buffer_
Definition logger.h:372
bool main_task_recursion_guard_
Definition logger.h:361
const device * uart_dev_
Definition logger.h:321
Logger(uint32_t baud_rate)
Definition logger.cpp:155
Interface for receiving log level changes without std::function overhead.
Definition logger.h:82
virtual void on_log_level_change(uint8_t level)=0
LoggerMessageTrigger(Logger *parent, uint8_t level)
Definition logger.h:486
Task log buffer for ESP32 platform using FreeRTOS ring buffer.
const char * message
Definition component.cpp:35
UARTSelection
Enum for logging UART selection.
Definition logger.h:104
@ UART_SELECTION_UART0_SWAP
Definition logger.h:122
@ UART_SELECTION_UART2
Definition logger.h:113
@ UART_SELECTION_USB_SERIAL_JTAG
Definition logger.h:119
@ UART_SELECTION_DEFAULT
Definition logger.h:106
@ UART_SELECTION_USB_CDC
Definition logger.h:116
@ UART_SELECTION_UART0
Definition logger.h:107
@ UART_SELECTION_UART1
Definition logger.h:111
Logger * global_logger
Definition logger.cpp:272
const char int line
Definition log.h:74
const char * tag
Definition log.h:74
const char int const __FlashStringHelper * format
Definition log.h:74
const char int const __FlashStringHelper va_list args
Definition log.h:74
std::string size_t len
Definition helpers.h:1045
static void uint32_t
bool operator()(const char *a, const char *b) const
Definition logger.h:91
void HOT format_body_P(PGM_P format, va_list args)
Definition log_buffer.h:109
void write_body(const char *text, uint16_t text_length)
Definition log_buffer.h:114
void HOT write_header(uint8_t level, const char *tag, int line, const char *thread_name)
Definition log_buffer.h:51
void HOT format_body(const char *format, va_list args)
Definition log_buffer.h:104
Lightweight callback for receiving log messages without virtual dispatch overhead.
Definition logger.h:56
void invoke(uint8_t level, const char *tag, const char *message, size_t message_len) const
Definition logger.h:59
void(* fn)(void *, uint8_t, const char *, const char *, size_t)
Definition logger.h:58