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