ESPHome 2026.1.0-dev
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#ifdef USE_ESP32
16// Implementation for ESP32 (multi-task platform with task-specific tracking)
17// Main task always uses direct buffer access for console output and callbacks
18//
19// For non-main 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 tasks
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
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 TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
31 bool is_main_task = (current_task == main_task_);
32
33 // Check and set recursion guard - uses pthread TLS for per-task state
34 if (this->check_and_set_task_log_recursion_(is_main_task)) {
35 return; // Recursion detected
36 }
37
38 // Main task uses the shared buffer for efficiency
39 if (is_main_task) {
40 this->log_message_to_buffer_and_send_(level, tag, line, format, args);
41 this->reset_task_log_recursion_(is_main_task);
42 return;
43 }
44
45 bool message_sent = false;
46#ifdef USE_ESPHOME_TASK_LOG_BUFFER
47 // For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered
48 message_sent =
49 this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), current_task, format, args);
50 if (message_sent) {
51 // Enable logger loop to process the buffered message
52 // This is safe to call from any context including ISRs
54 }
55#endif // USE_ESPHOME_TASK_LOG_BUFFER
56
57 // Emergency console logging for non-main tasks when ring buffer is full or disabled
58 // This is a fallback mechanism to ensure critical log messages are visible
59 // Note: This may cause interleaved/corrupted console output if multiple tasks
60 // log simultaneously, but it's better than losing important messages entirely
61 if (!message_sent && this->baud_rate_ > 0) { // If logging is enabled, write to console
62 // Maximum size for console log messages (includes null terminator)
63 static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144;
64 char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety
65 uint16_t buffer_at = 0; // Initialize buffer position
66 this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at,
67 MAX_CONSOLE_LOG_MSG_SIZE);
68 // Add newline before writing to console
69 this->add_newline_to_buffer_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE);
70 this->write_msg_(console_buffer, buffer_at);
71 }
72
73 // Reset the recursion guard for this task
74 this->reset_task_log_recursion_(is_main_task);
75}
76#else
77// Implementation for all other platforms
78void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
79 if (level > this->level_for(tag) || global_recursion_guard_)
80 return;
81
83
84 // Format and send to both console and callbacks
85 this->log_message_to_buffer_and_send_(level, tag, line, format, args);
86
88}
89#endif // !USE_ESP32
90
91#ifdef USE_STORE_LOG_STR_IN_FLASH
92// Implementation for ESP8266 with flash string support.
93// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
94//
95// This function handles format strings stored in flash memory (PROGMEM) to save RAM.
96// The buffer is used in a special way to avoid allocating extra memory:
97//
98// Memory layout during execution:
99// Step 1: Copy format string from flash to buffer
100// tx_buffer_: [format_string][null][.....................]
101// tx_buffer_at_: ------------------^
102// msg_start: saved here -----------^
103//
104// Step 2: format_log_to_buffer_with_terminator_ reads format string from beginning
105// and writes formatted output starting at msg_start position
106// tx_buffer_: [format_string][null][formatted_message][null]
107// tx_buffer_at_: -------------------------------------^
108//
109// Step 3: Output the formatted message (starting at msg_start)
110// write_msg_ and callbacks receive: this->tx_buffer_ + msg_start
111// which points to: [formatted_message][null]
112//
113void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
114 va_list args) { // NOLINT
115 if (level > this->level_for(tag) || global_recursion_guard_)
116 return;
117
119 this->tx_buffer_at_ = 0;
120
121 // Copy format string from progmem
122 auto *format_pgm_p = reinterpret_cast<const uint8_t *>(format);
123 char ch = '.';
124 while (this->tx_buffer_at_ < this->tx_buffer_size_ && ch != '\0') {
125 this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++);
126 }
127
128 // Buffer full from copying format
129 if (this->tx_buffer_at_ >= this->tx_buffer_size_) {
130 global_recursion_guard_ = false; // Make sure to reset the recursion guard before returning
131 return;
132 }
133
134 // Save the offset before calling format_log_to_buffer_with_terminator_
135 // since it will increment tx_buffer_at_ to the end of the formatted string
136 uint16_t msg_start = this->tx_buffer_at_;
137 this->format_log_to_buffer_with_terminator_(level, tag, line, this->tx_buffer_, args, this->tx_buffer_,
138 &this->tx_buffer_at_, this->tx_buffer_size_);
139
140 uint16_t msg_length =
141 this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
142
143 // Listeners get message first (before console write)
144 for (auto *listener : this->log_listeners_)
145 listener->on_log(level, tag, this->tx_buffer_ + msg_start, msg_length);
146
147 // Write to console starting at the msg_start
148 this->write_tx_buffer_to_console_(msg_start, &msg_length);
149
151}
152#endif // USE_STORE_LOG_STR_IN_FLASH
153
154inline uint8_t Logger::level_for(const char *tag) {
155#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
156 auto it = this->log_levels_.find(tag);
157 if (it != this->log_levels_.end())
158 return it->second;
159#endif
160 return this->current_level_;
161}
162
163Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) {
164 // add 1 to buffer size for null terminator
165 this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
166#if defined(USE_ESP32) || defined(USE_LIBRETINY)
167 this->main_task_ = xTaskGetCurrentTaskHandle();
168#elif defined(USE_ZEPHYR)
169 this->main_task_ = k_current_get();
170#endif
171}
172#ifdef USE_ESPHOME_TASK_LOG_BUFFER
173void Logger::init_log_buffer(size_t total_buffer_size) {
174 this->log_buffer_ = esphome::make_unique<logger::TaskLogBuffer>(total_buffer_size);
175
176 // Start with loop disabled when using task buffer (unless using USB CDC)
177 // The loop will be enabled automatically when messages arrive
179}
180#endif
181
182#ifdef USE_ESPHOME_TASK_LOG_BUFFER
184#endif
185
187#ifdef USE_ESPHOME_TASK_LOG_BUFFER
188 // Process any buffered messages when available
189 if (this->log_buffer_->has_messages()) {
191 const char *text;
192 void *received_token;
193
194 // Process messages from the buffer
195 while (this->log_buffer_->borrow_message_main_loop(&message, &text, &received_token)) {
196 this->tx_buffer_at_ = 0;
197 // Use the thread name that was stored when the message was created
198 // This avoids potential crashes if the task no longer exists
199 const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr;
200 this->write_header_to_buffer_(message->level, message->tag, message->line, thread_name, this->tx_buffer_,
201 &this->tx_buffer_at_, this->tx_buffer_size_);
202 this->write_body_to_buffer_(text, message->text_length, this->tx_buffer_, &this->tx_buffer_at_,
203 this->tx_buffer_size_);
205 this->tx_buffer_[this->tx_buffer_at_] = '\0';
206 size_t msg_len = this->tx_buffer_at_; // We already know the length from tx_buffer_at_
207 for (auto *listener : this->log_listeners_)
208 listener->on_log(message->level, message->tag, this->tx_buffer_, msg_len);
209 // At this point all the data we need from message has been transferred to the tx_buffer
210 // so we can release the message to allow other tasks to use it as soon as possible.
211 this->log_buffer_->release_message_main_loop(received_token);
212
213 // Write to console from the main loop to prevent corruption from concurrent writes
214 // This ensures all log messages appear on the console in a clean, serialized manner
215 // Note: Messages may appear slightly out of order due to async processing, but
216 // this is preferred over corrupted/interleaved console output
218 }
219 } else {
220 // No messages to process, disable loop if appropriate
221 // This reduces overhead when there's no async logging activity
223 }
224#endif
225}
226
227void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
228#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
229void Logger::set_log_level(const char *tag, uint8_t log_level) { this->log_levels_[tag] = log_level; }
230#endif
231
232#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
233UARTSelection Logger::get_uart() const { return this->uart_; }
234#endif
235
236float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
237
238#ifdef USE_STORE_LOG_STR_IN_FLASH
239// ESP8266: PSTR() cannot be used in array initializers, so we need to declare
240// each string separately as a global constant first
241static const char LOG_LEVEL_NONE[] PROGMEM = "NONE";
242static const char LOG_LEVEL_ERROR[] PROGMEM = "ERROR";
243static const char LOG_LEVEL_WARN[] PROGMEM = "WARN";
244static const char LOG_LEVEL_INFO[] PROGMEM = "INFO";
245static const char LOG_LEVEL_CONFIG[] PROGMEM = "CONFIG";
246static const char LOG_LEVEL_DEBUG[] PROGMEM = "DEBUG";
247static const char LOG_LEVEL_VERBOSE[] PROGMEM = "VERBOSE";
248static const char LOG_LEVEL_VERY_VERBOSE[] PROGMEM = "VERY_VERBOSE";
249
250static const LogString *const LOG_LEVELS[] = {
251 reinterpret_cast<const LogString *>(LOG_LEVEL_NONE), reinterpret_cast<const LogString *>(LOG_LEVEL_ERROR),
252 reinterpret_cast<const LogString *>(LOG_LEVEL_WARN), reinterpret_cast<const LogString *>(LOG_LEVEL_INFO),
253 reinterpret_cast<const LogString *>(LOG_LEVEL_CONFIG), reinterpret_cast<const LogString *>(LOG_LEVEL_DEBUG),
254 reinterpret_cast<const LogString *>(LOG_LEVEL_VERBOSE), reinterpret_cast<const LogString *>(LOG_LEVEL_VERY_VERBOSE),
255};
256#else
257static const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
258#endif
259
261 ESP_LOGCONFIG(TAG,
262 "Logger:\n"
263 " Max Level: %s\n"
264 " Initial Level: %s",
265 LOG_STR_ARG(LOG_LEVELS[ESPHOME_LOG_LEVEL]), LOG_STR_ARG(LOG_LEVELS[this->current_level_]));
266#ifndef USE_HOST
267 ESP_LOGCONFIG(TAG,
268 " Log Baud Rate: %" PRIu32 "\n"
269 " Hardware UART: %s",
270 this->baud_rate_, LOG_STR_ARG(get_uart_selection_()));
271#endif
272#ifdef USE_ESPHOME_TASK_LOG_BUFFER
273 if (this->log_buffer_) {
274 ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u", this->log_buffer_->size());
275 }
276#endif
277
278#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
279 for (auto &it : this->log_levels_) {
280 ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.first, LOG_STR_ARG(LOG_LEVELS[it.second]));
281 }
282#endif
283}
284
285void Logger::set_log_level(uint8_t level) {
286 if (level > ESPHOME_LOG_LEVEL) {
287 level = ESPHOME_LOG_LEVEL;
288 ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_STR_ARG(LOG_LEVELS[ESPHOME_LOG_LEVEL]));
289 }
290 this->current_level_ = level;
291#ifdef USE_LOGGER_LEVEL_LISTENERS
292 for (auto *listener : this->level_listeners_)
293 listener->on_log_level_change(level);
294#endif
295}
296
297Logger *global_logger = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
298
299} // 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:165
std::vector< LogListener * > log_listeners_
Definition logger.h:340
UARTSelection uart_
Definition logger.h:353
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:228
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:279
void dump_config() override
Definition logger.cpp:260
const LogString * get_uart_selection_()
uint8_t level_for(const char *tag)
Definition logger.cpp:154
bool HOT check_and_set_task_log_recursion_(bool is_main_task)
Definition logger.h:396
void loop() override
Definition logger.cpp:183
void log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args)
Definition logger.cpp:26
void HOT add_newline_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size)
Definition logger.h:253
void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size)
Definition logger.h:518
float get_setup_priority() const override
Definition logger.cpp:236
std::map< const char *, uint8_t, CStrCompare > log_levels_
Definition logger.h:338
UARTSelection get_uart() const
Get the UART used by the logger.
Definition logger.cpp:233
std::vector< LoggerLevelListener * > level_listeners_
Definition logger.h:342
void write_msg_(const char *msg, size_t len)
void disable_loop_when_buffer_empty_()
Definition logger.h:525
std::unique_ptr< logger::TaskLogBuffer > log_buffer_
Definition logger.h:345
void init_log_buffer(size_t total_buffer_size)
Definition logger.cpp:173
void HOT write_tx_buffer_to_console_(uint16_t offset=0, uint16_t *length=nullptr)
Definition logger.h:270
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:227
Logger(uint32_t baud_rate, size_t tx_buffer_size)
Definition logger.cpp:163
void HOT reset_task_log_recursion_(bool is_main_task)
Definition logger.h:411
void write_body_to_buffer_(const char *value, size_t length, char *buffer, uint16_t *buffer_at, uint16_t buffer_size)
Definition logger.h:295
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:442
uint16_t tx_buffer_size_
Definition logger.h:350
const char * message
Definition component.cpp:38
UARTSelection
Enum for logging UART selection.
Definition logger.h:125
Logger * global_logger
Definition logger.cpp:297
const float BUS
For communication buses like i2c/spi.
Definition component.cpp:78
uint8_t progmem_read_byte(const uint8_t *addr)
Definition core.cpp:49