ESPHome 2025.12.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#ifdef USE_ESP32
6#include <pthread.h>
7#endif
12#include "esphome/core/log.h"
13
14#ifdef USE_ESPHOME_TASK_LOG_BUFFER
15#include "task_log_buffer.h"
16#endif
17
18#ifdef USE_ARDUINO
19#if defined(USE_ESP8266)
20#include <HardwareSerial.h>
21#endif // USE_ESP8266
22#ifdef USE_RP2040
23#include <HardwareSerial.h>
24#include <SerialUSB.h>
25#endif // USE_RP2040
26#endif // USE_ARDUINO
27
28#ifdef USE_ESP32
29#include <driver/uart.h>
30#endif // USE_ESP32
31
32#ifdef USE_ZEPHYR
33#include <zephyr/kernel.h>
34struct device;
35#endif
36
37namespace esphome::logger {
38
39#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
40// Comparison function for const char* keys in log_levels_ map
42 bool operator()(const char *a, const char *b) const { return strcmp(a, b) < 0; }
43};
44#endif
45
46// ANSI color code last digit (30-38 range, store only last digit to save RAM)
47static constexpr char LOG_LEVEL_COLOR_DIGIT[] = {
48 '\0', // NONE
49 '1', // ERROR (31 = red)
50 '3', // WARNING (33 = yellow)
51 '2', // INFO (32 = green)
52 '5', // CONFIG (35 = magenta)
53 '6', // DEBUG (36 = cyan)
54 '7', // VERBOSE (37 = gray)
55 '8', // VERY_VERBOSE (38 = white)
56};
57
58static constexpr char LOG_LEVEL_LETTER_CHARS[] = {
59 '\0', // NONE
60 'E', // ERROR
61 'W', // WARNING
62 'I', // INFO
63 'C', // CONFIG
64 'D', // DEBUG
65 'V', // VERBOSE (VERY_VERBOSE uses two 'V's)
66};
67
68// Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin)
69static constexpr uint16_t MAX_HEADER_SIZE = 128;
70
71// "0x" + 2 hex digits per byte + '\0'
72static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1;
73
74#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
79enum UARTSelection : uint8_t {
80#ifdef USE_LIBRETINY
83#else
85#endif
87#if defined(USE_LIBRETINY) || defined(USE_ESP32_VARIANT_ESP32)
89#endif
90#ifdef USE_LOGGER_USB_CDC
92#endif
93#ifdef USE_LOGGER_USB_SERIAL_JTAG
95#endif
96#ifdef USE_ESP8266
98#endif // USE_ESP8266
99};
100#endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY || USE_ZEPHYR
101
119class Logger : public Component {
120 public:
121 explicit Logger(uint32_t baud_rate, size_t tx_buffer_size);
122#ifdef USE_ESPHOME_TASK_LOG_BUFFER
123 void init_log_buffer(size_t total_buffer_size);
124#endif
125#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_USB_CDC))
126 void loop() override;
127#endif
129 void set_baud_rate(uint32_t baud_rate);
130 uint32_t get_baud_rate() const { return baud_rate_; }
131#if defined(USE_ARDUINO) && !defined(USE_ESP32)
132 Stream *get_hw_serial() const { return hw_serial_; }
133#endif
134#ifdef USE_ESP32
135 uart_port_t get_uart_num() const { return uart_num_; }
136 void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); }
137#endif
138#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
139 void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
141 UARTSelection get_uart() const;
142#endif
143
145 void set_log_level(uint8_t level);
146#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
148 void set_log_level(const char *tag, uint8_t log_level);
149#endif
150 uint8_t get_log_level() { return this->current_level_; }
151
152 // ========== INTERNAL METHODS ==========
153 // (In most use cases you won't need these)
155 void pre_setup();
156 void dump_config() override;
157
158 inline uint8_t level_for(const char *tag);
159
161 void add_on_log_callback(std::function<void(uint8_t, const char *, const char *, size_t)> &&callback);
162
163 // add a listener for log level changes
164 void add_listener(std::function<void(uint8_t)> &&callback) { this->level_callback_.add(std::move(callback)); }
165
166 float get_setup_priority() const override;
167
168 void log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args); // NOLINT
169#ifdef USE_STORE_LOG_STR_IN_FLASH
170 void log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
171 va_list args); // NOLINT
172#endif
173
174 protected:
175 void process_messages_();
176 void write_msg_(const char *msg);
177
178 // Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
179 // It's the caller's responsibility to initialize buffer_at (typically to 0)
180 inline void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format,
181 va_list args, char *buffer, uint16_t *buffer_at,
182 uint16_t buffer_size) {
183#if defined(USE_ESP32) || defined(USE_LIBRETINY)
184 this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(), buffer, buffer_at, buffer_size);
185#elif defined(USE_ZEPHYR)
186 char buff[MAX_POINTER_REPRESENTATION];
187 this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(buff), buffer, buffer_at, buffer_size);
188#else
189 this->write_header_to_buffer_(level, tag, line, nullptr, buffer, buffer_at, buffer_size);
190#endif
191 this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, args);
192 this->write_footer_to_buffer_(buffer, buffer_at, buffer_size);
193
194 // Always ensure the buffer has a null terminator, even if we need to
195 // overwrite the last character of the actual content
196 if (*buffer_at >= buffer_size) {
197 buffer[buffer_size - 1] = '\0'; // Truncate and ensure null termination
198 } else {
199 buffer[*buffer_at] = '\0'; // Normal case, append null terminator
200 }
201 }
202
203 // Helper to format and send a log message to both console and callbacks
204 inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format,
205 va_list args) {
206 // Format to tx_buffer and prepare for output
207 this->tx_buffer_at_ = 0; // Initialize buffer position
208 this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, this->tx_buffer_, &this->tx_buffer_at_,
209 this->tx_buffer_size_);
210
211 if (this->baud_rate_ > 0) {
212 this->write_msg_(this->tx_buffer_); // If logging is enabled, write to console
213 }
214 this->log_callback_.call(level, tag, this->tx_buffer_, this->tx_buffer_at_);
215 }
216
217 // Write the body of the log message to the buffer
218 inline void write_body_to_buffer_(const char *value, size_t length, char *buffer, uint16_t *buffer_at,
219 uint16_t buffer_size) {
220 // Calculate available space
221 if (*buffer_at >= buffer_size)
222 return;
223 const uint16_t available = buffer_size - *buffer_at;
224
225 // Determine copy length (minimum of remaining capacity and string length)
226 const size_t copy_len = (length < static_cast<size_t>(available)) ? length : available;
227
228 // Copy the data
229 if (copy_len > 0) {
230 memcpy(buffer + *buffer_at, value, copy_len);
231 *buffer_at += copy_len;
232 }
233 }
234
235#ifndef USE_HOST
236 const LogString *get_uart_selection_();
237#endif
238
239 // Group 4-byte aligned members first
240 uint32_t baud_rate_;
241 char *tx_buffer_{nullptr};
242#if defined(USE_ARDUINO) && !defined(USE_ESP32)
243 Stream *hw_serial_{nullptr};
244#endif
245#if defined(USE_ZEPHYR)
246 const device *uart_dev_{nullptr};
247#endif
248#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
249 void *main_task_ = nullptr; // Only used for thread name identification
250#endif
251#ifdef USE_ESP32
252 // Task-specific recursion guards:
253 // - Main task uses a dedicated member variable for efficiency
254 // - Other tasks use pthread TLS with a dynamically created key via pthread_key_create
255 pthread_key_t log_recursion_key_; // 4 bytes
256 uart_port_t uart_num_; // 4 bytes (enum defaults to int size)
257#endif
258
259 // Large objects (internally aligned)
260#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
261 std::map<const char *, uint8_t, CStrCompare> log_levels_{};
262#endif
263 CallbackManager<void(uint8_t, const char *, const char *, size_t)> log_callback_{};
265#ifdef USE_ESPHOME_TASK_LOG_BUFFER
266 std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
267#endif
268
269 // Group smaller types together at the end
270 uint16_t tx_buffer_at_{0};
271 uint16_t tx_buffer_size_{0};
272 uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
273#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_ZEPHYR)
275#endif
276#ifdef USE_LIBRETINY
278#endif
279#ifdef USE_ESP32
281#else
282 bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
283#endif
284
285#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
286 const char *HOT get_thread_name_(
287#ifdef USE_ZEPHYR
288 char *buff
289#endif
290 ) {
291#ifdef USE_ZEPHYR
292 k_tid_t current_task = k_current_get();
293#else
294 TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
295#endif
296 if (current_task == main_task_) {
297 return nullptr; // Main task
298 } else {
299#if defined(USE_ESP32)
300 return pcTaskGetName(current_task);
301#elif defined(USE_LIBRETINY)
302 return pcTaskGetTaskName(current_task);
303#elif defined(USE_ZEPHYR)
304 const char *name = k_thread_name_get(current_task);
305 if (name) {
306 // zephyr print task names only if debug component is present
307 return name;
308 }
309 std::snprintf(buff, MAX_POINTER_REPRESENTATION, "%p", current_task);
310 return buff;
311#endif
312 }
313 }
314#endif
315
316#ifdef USE_ESP32
317 inline bool HOT check_and_set_task_log_recursion_(bool is_main_task) {
318 if (is_main_task) {
319 const bool was_recursive = main_task_recursion_guard_;
321 return was_recursive;
322 }
323
324 intptr_t current = (intptr_t) pthread_getspecific(log_recursion_key_);
325 if (current != 0)
326 return true;
327
328 pthread_setspecific(log_recursion_key_, (void *) 1);
329 return false;
330 }
331
332 inline void HOT reset_task_log_recursion_(bool is_main_task) {
333 if (is_main_task) {
335 return;
336 }
337
338 pthread_setspecific(log_recursion_key_, (void *) 0);
339 }
340#endif
341
342 static inline void copy_string(char *buffer, uint16_t &pos, const char *str) {
343 const size_t len = strlen(str);
344 // Intentionally no null terminator, building larger string
345 memcpy(buffer + pos, str, len); // NOLINT(bugprone-not-null-terminated-result)
346 pos += len;
347 }
348
349 static inline void write_ansi_color_for_level(char *buffer, uint16_t &pos, uint8_t level) {
350 if (level == 0)
351 return;
352 // Construct ANSI escape sequence: "\033[{bold};3{color}m"
353 // Example: "\033[1;31m" for ERROR (bold red)
354 buffer[pos++] = '\033';
355 buffer[pos++] = '[';
356 buffer[pos++] = (level == 1) ? '1' : '0'; // Only ERROR is bold
357 buffer[pos++] = ';';
358 buffer[pos++] = '3';
359 buffer[pos++] = LOG_LEVEL_COLOR_DIGIT[level];
360 buffer[pos++] = 'm';
361 }
362
363 inline void HOT write_header_to_buffer_(uint8_t level, const char *tag, int line, const char *thread_name,
364 char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
365 uint16_t pos = *buffer_at;
366 // Early return if insufficient space - intentionally don't update buffer_at to prevent partial writes
367 if (pos + MAX_HEADER_SIZE > buffer_size)
368 return;
369
370 // Construct: <color>[LEVEL][tag:line]:
371 write_ansi_color_for_level(buffer, pos, level);
372 buffer[pos++] = '[';
373 if (level != 0) {
374 if (level >= 7) {
375 buffer[pos++] = 'V'; // VERY_VERBOSE = "VV"
376 buffer[pos++] = 'V';
377 } else {
378 buffer[pos++] = LOG_LEVEL_LETTER_CHARS[level];
379 }
380 }
381 buffer[pos++] = ']';
382 buffer[pos++] = '[';
383 copy_string(buffer, pos, tag);
384 buffer[pos++] = ':';
385 // Format line number without modulo operations (passed by value, safe to mutate)
386 if (line > 999) [[unlikely]] {
387 int thousands = line / 1000;
388 buffer[pos++] = '0' + thousands;
389 line -= thousands * 1000;
390 }
391 int hundreds = line / 100;
392 int remainder = line - hundreds * 100;
393 int tens = remainder / 10;
394 buffer[pos++] = '0' + hundreds;
395 buffer[pos++] = '0' + tens;
396 buffer[pos++] = '0' + (remainder - tens * 10);
397 buffer[pos++] = ']';
398
399#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
400 if (thread_name != nullptr) {
401 write_ansi_color_for_level(buffer, pos, 1); // Always use bold red for thread name
402 buffer[pos++] = '[';
403 copy_string(buffer, pos, thread_name);
404 buffer[pos++] = ']';
405 write_ansi_color_for_level(buffer, pos, level); // Restore original color
406 }
407#endif
408
409 buffer[pos++] = ':';
410 buffer[pos++] = ' ';
411 *buffer_at = pos;
412 }
413
414 inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format,
415 va_list args) {
416 // Get remaining capacity in the buffer
417 if (*buffer_at >= buffer_size)
418 return;
419 const uint16_t remaining = buffer_size - *buffer_at;
420
421 const int ret = vsnprintf(buffer + *buffer_at, remaining, format, args);
422
423 if (ret < 0) {
424 return; // Encoding error, do not increment buffer_at
425 }
426
427 // Update buffer_at with the formatted length (handle truncation)
428 uint16_t formatted_len = (ret >= remaining) ? remaining : ret;
429 *buffer_at += formatted_len;
430
431 // Remove all trailing newlines right after formatting
432 while (*buffer_at > 0 && buffer[*buffer_at - 1] == '\n') {
433 (*buffer_at)--;
434 }
435 }
436
437 inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
438 static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1;
439 this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size);
440 }
441
442#ifdef USE_ESP32
443 // Disable loop when task buffer is empty (with USB CDC check)
445 // Thread safety note: This is safe even if another task calls enable_loop_soon_any_context()
446 // concurrently. If that happens between our check and disable_loop(), the enable request
447 // will be processed on the next main loop iteration since:
448 // - disable_loop() takes effect immediately
449 // - enable_loop_soon_any_context() sets a pending flag that's checked at loop start
450 this->disable_loop();
451 }
452#endif
453};
454extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
455
456class LoggerMessageTrigger : public Trigger<uint8_t, const char *, const char *> {
457 public:
458 explicit LoggerMessageTrigger(Logger *parent, uint8_t level) {
459 this->level_ = level;
460 parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message, size_t message_len) {
461 if (level <= this->level_) {
462 this->trigger(level, tag, message);
463 }
464 });
465 }
466
467 protected:
468 uint8_t level_;
469};
470
471} // namespace esphome::logger
void disable_loop()
Disable this component's loop.
Logger component for all ESPHome logging.
Definition logger.h:119
UARTSelection uart_
Definition logger.h:274
CallbackManager< void(uint8_t, const char *, const char *, size_t)> log_callback_
Definition logger.h:263
void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format, va_list args, char *buffer, uint16_t *buffer_at, uint16_t buffer_size)
Definition logger.h:180
void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format, va_list args)
Definition logger.h:204
void dump_config() override
Definition logger.cpp:260
void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, va_list args)
Definition logger.h:414
uint32_t get_baud_rate() const
Definition logger.h:130
const LogString * get_uart_selection_()
uint8_t level_for(const char *tag)
Definition logger.cpp:150
bool HOT check_and_set_task_log_recursion_(bool is_main_task)
Definition logger.h:317
void loop() override
Definition logger.cpp:179
uint8_t get_log_level()
Definition logger.h:150
Stream * get_hw_serial() const
Definition logger.h:132
void log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args)
Definition logger.cpp:26
CallbackManager< void(uint8_t)> level_callback_
Definition logger.h:264
void pre_setup()
Set up this component.
const char *HOT get_thread_name_(#ifdef USE_ZEPHYR char *buff #endif)
Definition logger.h:286
void add_on_log_callback(std::function< void(uint8_t, const char *, const char *, size_t)> &&callback)
Register a callback that will be called for every log message sent.
Definition logger.cpp:233
void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size)
Definition logger.h:437
float get_setup_priority() const override
Definition logger.cpp:236
std::map< const char *, uint8_t, CStrCompare > log_levels_
Definition logger.h:261
UARTSelection get_uart() const
Get the UART used by the logger.
Definition logger.cpp:230
void disable_loop_when_buffer_empty_()
Definition logger.h:444
uart_port_t uart_num_
Definition logger.h:256
std::unique_ptr< logger::TaskLogBuffer > log_buffer_
Definition logger.h:266
static void copy_string(char *buffer, uint16_t &pos, const char *str)
Definition logger.h:342
pthread_key_t log_recursion_key_
Definition logger.h:255
uart_port_t get_uart_num() const
Definition logger.h:135
void init_log_buffer(size_t total_buffer_size)
Definition logger.cpp:169
void set_log_level(uint8_t level)
Set the default log level for this logger.
Definition logger.cpp:285
void set_baud_rate(uint32_t baud_rate)
Manually set the baud rate for serial, set to 0 to disable.
Definition logger.cpp:224
void set_uart_selection(UARTSelection uart_selection)
Definition logger.h:139
static void write_ansi_color_for_level(char *buffer, uint16_t &pos, uint8_t level)
Definition logger.h:349
Logger(uint32_t baud_rate, size_t tx_buffer_size)
Definition logger.cpp:159
void add_listener(std::function< void(uint8_t)> &&callback)
Definition logger.h:164
void HOT reset_task_log_recursion_(bool is_main_task)
Definition logger.h:332
void write_msg_(const char *msg)
void write_body_to_buffer_(const char *value, size_t length, char *buffer, uint16_t *buffer_at, uint16_t buffer_size)
Definition logger.h:218
void HOT write_header_to_buffer_(uint8_t level, const char *tag, int line, const char *thread_name, char *buffer, uint16_t *buffer_at, uint16_t buffer_size)
Definition logger.h:363
bool main_task_recursion_guard_
Definition logger.h:280
const device * uart_dev_
Definition logger.h:246
uint16_t tx_buffer_size_
Definition logger.h:271
LoggerMessageTrigger(Logger *parent, uint8_t level)
Definition logger.h:458
const char * message
Definition component.cpp:38
UARTSelection
Enum for logging UART selection.
Definition logger.h:79
@ UART_SELECTION_UART0_SWAP
Definition logger.h:97
@ UART_SELECTION_UART2
Definition logger.h:88
@ UART_SELECTION_USB_SERIAL_JTAG
Definition logger.h:94
@ UART_SELECTION_DEFAULT
Definition logger.h:81
@ UART_SELECTION_USB_CDC
Definition logger.h:91
@ UART_SELECTION_UART0
Definition logger.h:82
@ UART_SELECTION_UART1
Definition logger.h:86
Logger * global_logger
Definition logger.cpp:294
std::string size_t len
Definition helpers.h:500
bool operator()(const char *a, const char *b) const
Definition logger.h:42
uint16_t length
Definition tt21100.cpp:0