ESPHome 2026.3.0-dev
Loading...
Searching...
No Matches
logger.cpp
Go to the documentation of this file.
1#include "logger.h"
2#include <cinttypes>
3
5#include "esphome/core/hal.h"
6#include "esphome/core/log.h"
8
9namespace esphome::logger {
10
11static const char *const TAG = "logger";
12
13#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
14// Implementation for multi-threaded platforms (ESP32 with FreeRTOS, Host with pthreads, LibreTiny with FreeRTOS,
15// Zephyr) Main thread/task always uses direct buffer access for console output and callbacks
16//
17// For non-main threads/tasks:
18// - WITH task log buffer: Prefer sending to ring buffer for async processing
19// - Avoids allocating stack memory for console output in normal operation
20// - Prevents console corruption from concurrent writes by multiple threads
21// - Messages are serialized through main loop for proper console output
22// - Fallback to emergency console logging only if ring buffer is full
23// - WITHOUT task log buffer: Only emergency console output, no callbacks
24//
25// Optimized for the common case: 99.9% of logs come from the main thread
26void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
27 if (level > this->level_for(tag))
28 return;
29
30#if defined(USE_ESP32) || defined(USE_LIBRETINY)
31 // Get task handle once - used for both main task check and passing to non-main thread handler
32 TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
33 const bool is_main_task = (current_task == this->main_task_);
34#elif (USE_ZEPHYR)
35 k_tid_t current_task = k_current_get();
36 const bool is_main_task = (current_task == this->main_task_);
37#else // USE_HOST
38 const bool is_main_task = pthread_equal(pthread_self(), this->main_thread_);
39#endif
40
41 // Fast path: main thread, no recursion (99.9% of all logs)
42 // Pass nullptr for thread_name since we already know this is the main task
43 if (is_main_task && !this->main_task_recursion_guard_) [[likely]] {
44 this->log_message_to_buffer_and_send_(this->main_task_recursion_guard_, level, tag, line, format, args, nullptr);
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 // Resolve thread name once and pass it through the logging chain.
55 // ESP32/LibreTiny: use TaskHandle_t overload to avoid redundant xTaskGetCurrentTaskHandle()
56 // (we already have the handle from the main task check above).
57 // Host: pass a stack buffer for pthread_getname_np to write into.
58#if defined(USE_ESP32) || defined(USE_LIBRETINY)
59 const char *thread_name = get_thread_name_(current_task);
60#elif defined(USE_ZEPHYR)
61 char thread_name_buf[MAX_POINTER_REPRESENTATION];
62 const char *thread_name = get_thread_name_(thread_name_buf, current_task);
63#else // USE_HOST
64 char thread_name_buf[THREAD_NAME_BUF_SIZE];
65 const char *thread_name = this->get_thread_name_(thread_name_buf);
66#endif
67 this->log_vprintf_non_main_thread_(level, tag, line, format, args, thread_name);
68}
69
70// Handles non-main thread logging only
71// Kept separate from hot path to improve instruction cache performance
72void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args,
73 const char *thread_name) {
74 // Check if already in recursion for this non-main thread/task
75 if (this->is_non_main_task_recursive_()) {
76 return;
77 }
78
79 // RAII guard - automatically resets on any return path
80 auto guard = this->make_non_main_task_guard_();
81
82 bool message_sent = false;
83#ifdef USE_ESPHOME_TASK_LOG_BUFFER
84 // For non-main threads/tasks, queue the message for callbacks
85 message_sent =
86 this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), thread_name, format, args);
87 if (message_sent) {
88 // Enable logger loop to process the buffered message
89 // This is safe to call from any context including ISRs
91 }
92#endif
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#else
100 if (!message_sent && this->baud_rate_ > 0) // If logging is enabled, write to console
101#endif
102 {
103#ifdef USE_HOST
104 // Host always has console output - no baud_rate check needed
105 static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 512;
106#else
107 // Maximum size for console log messages (includes null terminator)
108 static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144;
109#endif
110 char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety
111 LogBuffer buf{console_buffer, MAX_CONSOLE_LOG_MSG_SIZE};
112 this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf, thread_name);
113 this->write_to_console_(buf);
114 }
115
116 // RAII guard automatically resets on return
117}
118#else
119// Implementation for single-task platforms (ESP8266, RP2040)
120// Logging calls are NOT thread-safe: global_recursion_guard_ is a plain bool and tx_buffer_ has no locking.
121// Not a problem in practice yet since Zephyr has no API support (logs are console-only).
122void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
123 if (level > this->level_for(tag) || global_recursion_guard_)
124 return;
125 // Other single-task platforms don't have thread names, so pass nullptr
126 this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, nullptr);
127}
128#endif // USE_ESP32 || USE_HOST || USE_LIBRETINY || USE_ZEPHYR
129
130#ifdef USE_STORE_LOG_STR_IN_FLASH
131// Implementation for ESP8266 with flash string support.
132// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
133//
134// This function handles format strings stored in flash memory (PROGMEM) to save RAM.
135// Uses vsnprintf_P to read the format string directly from flash without copying to RAM.
136//
137void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
138 va_list args) { // NOLINT
139 if (level > this->level_for(tag) || global_recursion_guard_)
140 return;
141
142 this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, nullptr);
143}
144#endif // USE_STORE_LOG_STR_IN_FLASH
145
146inline uint8_t Logger::level_for(const char *tag) {
147#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
148 auto it = this->log_levels_.find(tag);
149 if (it != this->log_levels_.end())
150 return it->second;
151#endif
152 return this->current_level_;
153}
154
155Logger::Logger(uint32_t baud_rate) : baud_rate_(baud_rate) {
156#if defined(USE_ESP32) || defined(USE_LIBRETINY)
157 this->main_task_ = xTaskGetCurrentTaskHandle();
158#elif defined(USE_ZEPHYR)
159 this->main_task_ = k_current_get();
160#elif defined(USE_HOST)
161 this->main_thread_ = pthread_self();
162#endif
163}
164#ifdef USE_ESPHOME_TASK_LOG_BUFFER
165void Logger::init_log_buffer(size_t total_buffer_size) {
166 // Host uses slot count instead of byte size
167 // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed
168 this->log_buffer_ = new logger::TaskLogBuffer(total_buffer_size);
169
170#if !(defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
171 // Start with loop disabled when using task buffer
172 // The loop will be enabled automatically when messages arrive
173 // Zephyr with USB CDC needs loop active to poll port readiness via cdc_loop_()
175#endif
176}
177#endif
178
179#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
181 this->process_messages_();
182#if defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC)
183 this->cdc_loop_();
184#endif
185}
186#endif
187
189#ifdef USE_ESPHOME_TASK_LOG_BUFFER
190 // Process any buffered messages when available
191 if (this->log_buffer_->has_messages()) {
193 uint16_t text_length;
194 while (this->log_buffer_->borrow_message_main_loop(message, text_length)) {
195 const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr;
196 LogBuffer buf{this->tx_buffer_, ESPHOME_LOGGER_TX_BUFFER_SIZE};
197 this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name,
198 message->text_data(), text_length, buf);
199 // Release the message to allow other tasks to use it as soon as possible
202 }
203 }
204#if !(defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
205 else {
206 // No messages to process, disable loop if appropriate
207 // This reduces overhead when there's no async logging activity
209 }
210#endif
211#endif // USE_ESPHOME_TASK_LOG_BUFFER
212}
213
214void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
215#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
216void Logger::set_log_level(const char *tag, uint8_t log_level) { this->log_levels_[tag] = log_level; }
217#endif
218
219#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
220UARTSelection Logger::get_uart() const { return this->uart_; }
221#endif
222
223float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
224
225// Log level strings - packed into flash on ESP8266, indexed by log level (0-7)
226PROGMEM_STRING_TABLE(LogLevelStrings, "NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE");
227
228static const LogString *get_log_level_str(uint8_t level) {
229 return LogLevelStrings::get_log_str(level, LogLevelStrings::LAST_INDEX);
230}
231
233 ESP_LOGCONFIG(TAG,
234 "Logger:\n"
235 " Max Level: %s\n"
236 " Initial Level: %s",
237 LOG_STR_ARG(get_log_level_str(ESPHOME_LOG_LEVEL)),
238 LOG_STR_ARG(get_log_level_str(this->current_level_)));
239#ifndef USE_HOST
240 ESP_LOGCONFIG(TAG,
241 " Log Baud Rate: %" PRIu32 "\n"
242 " Hardware UART: %s",
243 this->baud_rate_, LOG_STR_ARG(get_uart_selection_()));
244#endif
245#ifdef USE_ESPHOME_TASK_LOG_BUFFER
246 if (this->log_buffer_) {
247#ifdef USE_HOST
248 ESP_LOGCONFIG(TAG, " Task Log Buffer Slots: %u", static_cast<unsigned int>(this->log_buffer_->size()));
249#else
250 ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u bytes", static_cast<unsigned int>(this->log_buffer_->size()));
251#endif
252 }
253#endif
254
255#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
256 for (auto &it : this->log_levels_) {
257 ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.first, LOG_STR_ARG(get_log_level_str(it.second)));
258 }
259#endif
260#ifdef USE_ZEPHYR
261 dump_crash_();
262#endif
263}
264
265void Logger::set_log_level(uint8_t level) {
266 if (level > ESPHOME_LOG_LEVEL) {
267 level = ESPHOME_LOG_LEVEL;
268 ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s",
269 LOG_STR_ARG(get_log_level_str(ESPHOME_LOG_LEVEL)));
270 }
271 this->current_level_ = level;
272#ifdef USE_LOGGER_LEVEL_LISTENERS
273 for (auto *listener : this->level_listeners_)
274 listener->on_log_level_change(level);
275#endif
276}
277
278Logger *global_logger = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
279
280} // namespace esphome::logger
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
Logger component for all ESPHome logging.
Definition logger.h:144
UARTSelection uart_
Definition logger.h:358
const char *HOT get_thread_name_()
Definition logger.h:392
void dump_config() override
Definition logger.cpp:232
bool HOT is_non_main_task_recursive_() const
Definition logger.h:446
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
void loop() override
Definition logger.cpp:180
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:281
char tx_buffer_[ESPHOME_LOGGER_TX_BUFFER_SIZE+1]
Definition logger.h:373
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:300
float get_setup_priority() const override
Definition logger.cpp:223
std::map< const char *, uint8_t, CStrCompare > log_levels_
Definition logger.h:342
UARTSelection get_uart() const
Get the UART used by the logger.
Definition logger.cpp:220
pthread_t main_thread_
Definition logger.h:326
std::vector< LoggerLevelListener * > level_listeners_
Definition logger.h:349
void disable_loop_when_buffer_empty_()
Definition logger.h:472
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:240
void HOT write_log_buffer_to_console_(LogBuffer &buf)
Definition logger.h:272
void init_log_buffer(size_t total_buffer_size)
Definition logger.cpp:165
void set_log_level(uint8_t level)
Set the default log level for this logger.
Definition logger.cpp:265
void set_baud_rate(uint32_t baud_rate)
Manually set the baud rate for serial, set to 0 to disable.
Definition logger.cpp:214
void HOT write_to_console_(LogBuffer &buf)
Definition logger.h:266
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
Logger(uint32_t baud_rate)
Definition logger.cpp:155
logger::TaskLogBuffer * log_buffer_
Definition logger.h:352
Task log buffer for ESP32 platform using FreeRTOS ring buffer.
bool borrow_message_main_loop(LogMessage *&message, uint16_t &text_length)
bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name, const char *format, va_list args)
const char * message
Definition component.cpp:38
UARTSelection
Enum for logging UART selection.
Definition logger.h:104
Logger * global_logger
Definition logger.cpp:278
PROGMEM_STRING_TABLE(LogLevelStrings, "NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE")
constexpr float BUS
For communication buses like i2c/spi.
Definition component.h:25