ESPHome 2026.1.5
Loading...
Searching...
No Matches
logger.cpp
Go to the documentation of this file.
1#include "logger.h"
2#include <cinttypes>
3#ifdef USE_ESPHOME_TASK_LOG_BUFFER
4#include <memory> // For unique_ptr
5#endif
6
8#include "esphome/core/hal.h"
9#include "esphome/core/log.h"
10
11namespace esphome::logger {
12
13static const char *const TAG = "logger";
14
15#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY)
16// Implementation for multi-threaded platforms (ESP32 with FreeRTOS, Host with pthreads, LibreTiny with FreeRTOS)
17// Main thread/task always uses direct buffer access for console output and callbacks
18//
19// For non-main threads/tasks:
20// - WITH task log buffer: Prefer sending to ring buffer for async processing
21// - Avoids allocating stack memory for console output in normal operation
22// - Prevents console corruption from concurrent writes by multiple threads
23// - Messages are serialized through main loop for proper console output
24// - Fallback to emergency console logging only if ring buffer is full
25// - WITHOUT task log buffer: Only emergency console output, no callbacks
26//
27// Optimized for the common case: 99.9% of logs come from the main thread
28void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
29 if (level > this->level_for(tag))
30 return;
31
32#if defined(USE_ESP32) || defined(USE_LIBRETINY)
33 // Get task handle once - used for both main task check and passing to non-main thread handler
34 TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
35 const bool is_main_task = (current_task == this->main_task_);
36#else // USE_HOST
37 const bool is_main_task = pthread_equal(pthread_self(), this->main_thread_);
38#endif
39
40 // Fast path: main thread, no recursion (99.9% of all logs)
41 if (is_main_task && !this->main_task_recursion_guard_) [[likely]] {
42 RecursionGuard guard(this->main_task_recursion_guard_);
43 // Format and send to both console and callbacks
44 this->log_message_to_buffer_and_send_(level, tag, line, format, args);
45 return;
46 }
47
48 // Main task with recursion - silently drop to prevent infinite loop
49 if (is_main_task) {
50 return;
51 }
52
53 // Non-main thread handling (~0.1% of logs)
54#if defined(USE_ESP32) || defined(USE_LIBRETINY)
55 this->log_vprintf_non_main_thread_(level, tag, line, format, args, current_task);
56#else // USE_HOST
57 this->log_vprintf_non_main_thread_(level, tag, line, format, args);
58#endif
59}
60
61// Handles non-main thread logging only
62// Kept separate from hot path to improve instruction cache performance
63#if defined(USE_ESP32) || defined(USE_LIBRETINY)
64void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args,
65 TaskHandle_t current_task) {
66#else // USE_HOST
67void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args) {
68#endif
69 // Check if already in recursion for this non-main thread/task
70 if (this->is_non_main_task_recursive_()) {
71 return;
72 }
73
74 // RAII guard - automatically resets on any return path
75 auto guard = this->make_non_main_task_guard_();
76
77 bool message_sent = false;
78#ifdef USE_ESPHOME_TASK_LOG_BUFFER
79 // For non-main threads/tasks, queue the message for callbacks
80#if defined(USE_ESP32) || defined(USE_LIBRETINY)
81 message_sent =
82 this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), current_task, format, args);
83#else // USE_HOST
84 message_sent = this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), format, args);
85#endif
86 if (message_sent) {
87 // Enable logger loop to process the buffered message
88 // This is safe to call from any context including ISRs
90 }
91#endif // USE_ESPHOME_TASK_LOG_BUFFER
92
93 // Emergency console logging for non-main threads when ring buffer is full or disabled
94 // This is a fallback mechanism to ensure critical log messages are visible
95 // Note: This may cause interleaved/corrupted console output if multiple threads
96 // log simultaneously, but it's better than losing important messages entirely
97#ifdef USE_HOST
98 if (!message_sent) {
99 // Host always has console output - no baud_rate check needed
100 static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 512;
101#else
102 if (!message_sent && this->baud_rate_ > 0) { // If logging is enabled, write to console
103 // Maximum size for console log messages (includes null terminator)
104 static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144;
105#endif
106 char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety
107 uint16_t buffer_at = 0; // Initialize buffer position
108 this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at,
109 MAX_CONSOLE_LOG_MSG_SIZE);
110 // Add newline before writing to console
111 this->add_newline_to_buffer_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE);
112 this->write_msg_(console_buffer, buffer_at);
113 }
114
115 // RAII guard automatically resets on return
116}
117#else
118// Implementation for all other platforms (single-task, no threading)
119void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
120 if (level > this->level_for(tag) || global_recursion_guard_)
121 return;
122
123 RecursionGuard guard(global_recursion_guard_);
124 // Format and send to both console and callbacks
125 this->log_message_to_buffer_and_send_(level, tag, line, format, args);
126}
127#endif // USE_ESP32 / USE_HOST / USE_LIBRETINY
128
129#ifdef USE_STORE_LOG_STR_IN_FLASH
130// Implementation for ESP8266 with flash string support.
131// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
132//
133// This function handles format strings stored in flash memory (PROGMEM) to save RAM.
134// The buffer is used in a special way to avoid allocating extra memory:
135//
136// Memory layout during execution:
137// Step 1: Copy format string from flash to buffer
138// tx_buffer_: [format_string][null][.....................]
139// tx_buffer_at_: ------------------^
140// msg_start: saved here -----------^
141//
142// Step 2: format_log_to_buffer_with_terminator_ reads format string from beginning
143// and writes formatted output starting at msg_start position
144// tx_buffer_: [format_string][null][formatted_message][null]
145// tx_buffer_at_: -------------------------------------^
146//
147// Step 3: Output the formatted message (starting at msg_start)
148// write_msg_ and callbacks receive: this->tx_buffer_ + msg_start
149// which points to: [formatted_message][null]
150//
151void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
152 va_list args) { // NOLINT
153 if (level > this->level_for(tag) || global_recursion_guard_)
154 return;
155
156 RecursionGuard guard(global_recursion_guard_);
157 this->tx_buffer_at_ = 0;
158
159 // Copy format string from progmem
160 auto *format_pgm_p = reinterpret_cast<const uint8_t *>(format);
161 char ch = '.';
162 while (this->tx_buffer_at_ < this->tx_buffer_size_ && ch != '\0') {
163 this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++);
164 }
165
166 // Buffer full from copying format - RAII guard handles cleanup on return
167 if (this->tx_buffer_at_ >= this->tx_buffer_size_) {
168 return;
169 }
170
171 // Save the offset before calling format_log_to_buffer_with_terminator_
172 // since it will increment tx_buffer_at_ to the end of the formatted string
173 uint16_t msg_start = this->tx_buffer_at_;
174 this->format_log_to_buffer_with_terminator_(level, tag, line, this->tx_buffer_, args, this->tx_buffer_,
175 &this->tx_buffer_at_, this->tx_buffer_size_);
176
177 uint16_t msg_length =
178 this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
179
180 // Listeners get message first (before console write)
181#ifdef USE_LOG_LISTENERS
182 for (auto *listener : this->log_listeners_)
183 listener->on_log(level, tag, this->tx_buffer_ + msg_start, msg_length);
184#endif
185
186 // Write to console starting at the msg_start
187 this->write_tx_buffer_to_console_(msg_start, &msg_length);
188}
189#endif // USE_STORE_LOG_STR_IN_FLASH
190
191inline uint8_t Logger::level_for(const char *tag) {
192#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
193 auto it = this->log_levels_.find(tag);
194 if (it != this->log_levels_.end())
195 return it->second;
196#endif
197 return this->current_level_;
198}
199
200Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) {
201 // add 1 to buffer size for null terminator
202 this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
203#if defined(USE_ESP32) || defined(USE_LIBRETINY)
204 this->main_task_ = xTaskGetCurrentTaskHandle();
205#elif defined(USE_ZEPHYR)
206 this->main_task_ = k_current_get();
207#elif defined(USE_HOST)
208 this->main_thread_ = pthread_self();
209#endif
210}
211#ifdef USE_ESPHOME_TASK_LOG_BUFFER
212void Logger::init_log_buffer(size_t total_buffer_size) {
213#ifdef USE_HOST
214 // Host uses slot count instead of byte size
215 this->log_buffer_ = esphome::make_unique<logger::TaskLogBufferHost>(total_buffer_size);
216#elif defined(USE_ESP32)
217 this->log_buffer_ = esphome::make_unique<logger::TaskLogBuffer>(total_buffer_size);
218#elif defined(USE_LIBRETINY)
219 this->log_buffer_ = esphome::make_unique<logger::TaskLogBufferLibreTiny>(total_buffer_size);
220#endif
221
222#if defined(USE_ESP32) || defined(USE_LIBRETINY)
223 // Start with loop disabled when using task buffer (unless using USB CDC on ESP32)
224 // The loop will be enabled automatically when messages arrive
225 this->disable_loop_when_buffer_empty_();
226#endif
227}
228#endif
229
230#ifdef USE_ESPHOME_TASK_LOG_BUFFER
231void Logger::loop() { this->process_messages_(); }
232#endif
233
234void Logger::process_messages_() {
235#ifdef USE_ESPHOME_TASK_LOG_BUFFER
236 // Process any buffered messages when available
237 if (this->log_buffer_->has_messages()) {
238#ifdef USE_HOST
239 logger::TaskLogBufferHost::LogMessage *message;
240 while (this->log_buffer_->get_message_main_loop(&message)) {
241 const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr;
242 this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, message->text,
243 message->text_length);
244 this->log_buffer_->release_message_main_loop();
245 this->write_tx_buffer_to_console_();
246 }
247#elif defined(USE_ESP32)
248 logger::TaskLogBuffer::LogMessage *message;
249 const char *text;
250 void *received_token;
251 while (this->log_buffer_->borrow_message_main_loop(&message, &text, &received_token)) {
252 const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr;
253 this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, text,
254 message->text_length);
255 // Release the message to allow other tasks to use it as soon as possible
256 this->log_buffer_->release_message_main_loop(received_token);
257 this->write_tx_buffer_to_console_();
258 }
259#elif defined(USE_LIBRETINY)
260 logger::TaskLogBufferLibreTiny::LogMessage *message;
261 const char *text;
262 while (this->log_buffer_->borrow_message_main_loop(&message, &text)) {
263 const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr;
264 this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, text,
265 message->text_length);
266 // Release the message to allow other tasks to use it as soon as possible
267 this->log_buffer_->release_message_main_loop();
268 this->write_tx_buffer_to_console_();
269 }
270#endif
271 }
272#if defined(USE_ESP32) || defined(USE_LIBRETINY)
273 else {
274 // No messages to process, disable loop if appropriate
275 // This reduces overhead when there's no async logging activity
276 this->disable_loop_when_buffer_empty_();
277 }
278#endif
279#endif // USE_ESPHOME_TASK_LOG_BUFFER
280}
281
282void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
283#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
284void Logger::set_log_level(const char *tag, uint8_t log_level) { this->log_levels_[tag] = log_level; }
285#endif
286
287#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
288UARTSelection Logger::get_uart() const { return this->uart_; }
289#endif
290
291float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
292
293#ifdef USE_STORE_LOG_STR_IN_FLASH
294// ESP8266: PSTR() cannot be used in array initializers, so we need to declare
295// each string separately as a global constant first
296static const char LOG_LEVEL_NONE[] PROGMEM = "NONE";
297static const char LOG_LEVEL_ERROR[] PROGMEM = "ERROR";
298static const char LOG_LEVEL_WARN[] PROGMEM = "WARN";
299static const char LOG_LEVEL_INFO[] PROGMEM = "INFO";
300static const char LOG_LEVEL_CONFIG[] PROGMEM = "CONFIG";
301static const char LOG_LEVEL_DEBUG[] PROGMEM = "DEBUG";
302static const char LOG_LEVEL_VERBOSE[] PROGMEM = "VERBOSE";
303static const char LOG_LEVEL_VERY_VERBOSE[] PROGMEM = "VERY_VERBOSE";
304
305static const LogString *const LOG_LEVELS[] = {
306 reinterpret_cast<const LogString *>(LOG_LEVEL_NONE), reinterpret_cast<const LogString *>(LOG_LEVEL_ERROR),
307 reinterpret_cast<const LogString *>(LOG_LEVEL_WARN), reinterpret_cast<const LogString *>(LOG_LEVEL_INFO),
308 reinterpret_cast<const LogString *>(LOG_LEVEL_CONFIG), reinterpret_cast<const LogString *>(LOG_LEVEL_DEBUG),
309 reinterpret_cast<const LogString *>(LOG_LEVEL_VERBOSE), reinterpret_cast<const LogString *>(LOG_LEVEL_VERY_VERBOSE),
310};
311#else
312static const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
313#endif
314
315void Logger::dump_config() {
316 ESP_LOGCONFIG(TAG,
317 "Logger:\n"
318 " Max Level: %s\n"
319 " Initial Level: %s",
320 LOG_STR_ARG(LOG_LEVELS[ESPHOME_LOG_LEVEL]), LOG_STR_ARG(LOG_LEVELS[this->current_level_]));
321#ifndef USE_HOST
322 ESP_LOGCONFIG(TAG,
323 " Log Baud Rate: %" PRIu32 "\n"
324 " Hardware UART: %s",
325 this->baud_rate_, LOG_STR_ARG(get_uart_selection_()));
326#endif
327#ifdef USE_ESPHOME_TASK_LOG_BUFFER
328 if (this->log_buffer_) {
329#ifdef USE_HOST
330 ESP_LOGCONFIG(TAG, " Task Log Buffer Slots: %u", static_cast<unsigned int>(this->log_buffer_->size()));
331#else
332 ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u bytes", static_cast<unsigned int>(this->log_buffer_->size()));
333#endif
334 }
335#endif
336
337#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
338 for (auto &it : this->log_levels_) {
339 ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.first, LOG_STR_ARG(LOG_LEVELS[it.second]));
340 }
341#endif
342}
343
344void Logger::set_log_level(uint8_t level) {
345 if (level > ESPHOME_LOG_LEVEL) {
346 level = ESPHOME_LOG_LEVEL;
347 ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_STR_ARG(LOG_LEVELS[ESPHOME_LOG_LEVEL]));
348 }
349 this->current_level_ = level;
350#ifdef USE_LOGGER_LEVEL_LISTENERS
351 for (auto *listener : this->level_listeners_)
352 listener->on_log_level_change(level);
353#endif
354}
355
356Logger *global_logger = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
357
358} // namespace esphome::logger
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args)
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:267
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:318
bool HOT is_non_main_task_recursive_() const
Definition logger.h:491
NonMainTaskRecursionGuard make_non_main_task_guard_()
Definition logger.h:494
uint8_t level_for(const char *tag)
StaticVector< LogListener *, ESPHOME_LOG_MAX_LISTENERS > log_listeners_
Definition logger.h:408
void HOT add_newline_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size)
Definition logger.h:292
std::map< const char *, uint8_t, CStrCompare > log_levels_
Definition logger.h:404
pthread_t main_thread_
Definition logger.h:388
void write_msg_(const char *msg, size_t len)
void HOT write_tx_buffer_to_console_(uint16_t offset=0, uint16_t *length=nullptr)
Definition logger.h:309
void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args, TaskHandle_t current_task)
Logger(uint32_t baud_rate, size_t tx_buffer_size)
std::unique_ptr< logger::TaskLogBufferHost > log_buffer_
Definition logger.h:415
bool main_task_recursion_guard_
Definition logger.h:434
uint16_t tx_buffer_size_
Definition logger.h:425
const char * message
Definition component.cpp:38
UARTSelection
Enum for logging UART selection.
Definition logger.h:131
Logger * global_logger
const char *const TAG
Definition spi.cpp:7
uint8_t progmem_read_byte(const uint8_t *addr)
Definition core.cpp:49
const uint8_t ESPHOME_WEBSERVER_INDEX_HTML[] PROGMEM
Definition web_server.h:27