ESPHome 2025.8.0b1
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 this->write_msg_(console_buffer);
69 }
70
71 // Reset the recursion guard for this task
72 this->reset_task_log_recursion_(is_main_task);
73}
74#else
75// Implementation for all other platforms
76void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
77 if (level > this->level_for(tag) || global_recursion_guard_)
78 return;
79
81
82 // Format and send to both console and callbacks
83 this->log_message_to_buffer_and_send_(level, tag, line, format, args);
84
86}
87#endif // !USE_ESP32
88
89#ifdef USE_STORE_LOG_STR_IN_FLASH
90// Implementation for ESP8266 with flash string support.
91// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
92//
93// This function handles format strings stored in flash memory (PROGMEM) to save RAM.
94// The buffer is used in a special way to avoid allocating extra memory:
95//
96// Memory layout during execution:
97// Step 1: Copy format string from flash to buffer
98// tx_buffer_: [format_string][null][.....................]
99// tx_buffer_at_: ------------------^
100// msg_start: saved here -----------^
101//
102// Step 2: format_log_to_buffer_with_terminator_ reads format string from beginning
103// and writes formatted output starting at msg_start position
104// tx_buffer_: [format_string][null][formatted_message][null]
105// tx_buffer_at_: -------------------------------------^
106//
107// Step 3: Output the formatted message (starting at msg_start)
108// write_msg_ and callbacks receive: this->tx_buffer_ + msg_start
109// which points to: [formatted_message][null]
110//
111void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
112 va_list args) { // NOLINT
113 if (level > this->level_for(tag) || global_recursion_guard_)
114 return;
115
117 this->tx_buffer_at_ = 0;
118
119 // Copy format string from progmem
120 auto *format_pgm_p = reinterpret_cast<const uint8_t *>(format);
121 char ch = '.';
122 while (this->tx_buffer_at_ < this->tx_buffer_size_ && ch != '\0') {
123 this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++);
124 }
125
126 // Buffer full from copying format
127 if (this->tx_buffer_at_ >= this->tx_buffer_size_) {
128 global_recursion_guard_ = false; // Make sure to reset the recursion guard before returning
129 return;
130 }
131
132 // Save the offset before calling format_log_to_buffer_with_terminator_
133 // since it will increment tx_buffer_at_ to the end of the formatted string
134 uint32_t msg_start = this->tx_buffer_at_;
135 this->format_log_to_buffer_with_terminator_(level, tag, line, this->tx_buffer_, args, this->tx_buffer_,
136 &this->tx_buffer_at_, this->tx_buffer_size_);
137
138 // Write to console and send callback starting at the msg_start
139 if (this->baud_rate_ > 0) {
140 this->write_msg_(this->tx_buffer_ + msg_start);
141 }
142 size_t msg_length =
143 this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
144 this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start, msg_length);
145
147}
148#endif // USE_STORE_LOG_STR_IN_FLASH
149
150inline uint8_t Logger::level_for(const char *tag) {
151 auto it = this->log_levels_.find(tag);
152 if (it != this->log_levels_.end())
153 return it->second;
154 return this->current_level_;
155}
156
157Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) {
158 // add 1 to buffer size for null terminator
159 this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
160#if defined(USE_ESP32) || defined(USE_LIBRETINY)
161 this->main_task_ = xTaskGetCurrentTaskHandle();
162#elif defined(USE_ZEPHYR)
163 this->main_task_ = k_current_get();
164#endif
165}
166#ifdef USE_ESPHOME_TASK_LOG_BUFFER
167void Logger::init_log_buffer(size_t total_buffer_size) {
168 this->log_buffer_ = esphome::make_unique<logger::TaskLogBuffer>(total_buffer_size);
169
170 // Start with loop disabled when using task buffer (unless using USB CDC)
171 // The loop will be enabled automatically when messages arrive
173}
174#endif
175
176#ifndef USE_ZEPHYR
177#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32)
179#if defined(USE_LOGGER_USB_CDC) && defined(USE_ARDUINO)
180 if (this->uart_ == UART_SELECTION_USB_CDC) {
181 static bool opened = false;
182 if (opened == Serial) {
183 return;
184 }
185 if (false == opened) {
187 }
188 opened = !opened;
189 }
190#endif
191 this->process_messages_();
192}
193#endif
194#endif
195
197#ifdef USE_ESPHOME_TASK_LOG_BUFFER
198 // Process any buffered messages when available
199 if (this->log_buffer_->has_messages()) {
201 const char *text;
202 void *received_token;
203
204 // Process messages from the buffer
205 while (this->log_buffer_->borrow_message_main_loop(&message, &text, &received_token)) {
206 this->tx_buffer_at_ = 0;
207 // Use the thread name that was stored when the message was created
208 // This avoids potential crashes if the task no longer exists
209 const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr;
210 this->write_header_to_buffer_(message->level, message->tag, message->line, thread_name, this->tx_buffer_,
211 &this->tx_buffer_at_, this->tx_buffer_size_);
212 this->write_body_to_buffer_(text, message->text_length, this->tx_buffer_, &this->tx_buffer_at_,
213 this->tx_buffer_size_);
215 this->tx_buffer_[this->tx_buffer_at_] = '\0';
216 size_t msg_len = this->tx_buffer_at_; // We already know the length from tx_buffer_at_
217 this->log_callback_.call(message->level, message->tag, this->tx_buffer_, msg_len);
218 // At this point all the data we need from message has been transferred to the tx_buffer
219 // so we can release the message to allow other tasks to use it as soon as possible.
220 this->log_buffer_->release_message_main_loop(received_token);
221
222 // Write to console from the main loop to prevent corruption from concurrent writes
223 // This ensures all log messages appear on the console in a clean, serialized manner
224 // Note: Messages may appear slightly out of order due to async processing, but
225 // this is preferred over corrupted/interleaved console output
226 if (this->baud_rate_ > 0) {
227 this->write_msg_(this->tx_buffer_);
228 }
229 }
230 } else {
231 // No messages to process, disable loop if appropriate
232 // This reduces overhead when there's no async logging activity
234 }
235#endif
236}
237
238void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
239void Logger::set_log_level(const std::string &tag, uint8_t log_level) { this->log_levels_[tag] = log_level; }
240
241#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
242UARTSelection Logger::get_uart() const { return this->uart_; }
243#endif
244
245void Logger::add_on_log_callback(std::function<void(uint8_t, const char *, const char *, size_t)> &&callback) {
246 this->log_callback_.add(std::move(callback));
247}
248float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
249static const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
250
252 ESP_LOGCONFIG(TAG,
253 "Logger:\n"
254 " Max Level: %s\n"
255 " Initial Level: %s",
256 LOG_LEVELS[ESPHOME_LOG_LEVEL], LOG_LEVELS[this->current_level_]);
257#ifndef USE_HOST
258 ESP_LOGCONFIG(TAG,
259 " Log Baud Rate: %" PRIu32 "\n"
260 " Hardware UART: %s",
262#endif
263#ifdef USE_ESPHOME_TASK_LOG_BUFFER
264 if (this->log_buffer_) {
265 ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u", this->log_buffer_->size());
266 }
267#endif
268
269 for (auto &it : this->log_levels_) {
270 ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.first.c_str(), LOG_LEVELS[it.second]);
271 }
272}
273
274void Logger::set_log_level(uint8_t level) {
275 if (level > ESPHOME_LOG_LEVEL) {
276 level = ESPHOME_LOG_LEVEL;
277 ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]);
278 }
279 this->current_level_ = level;
280 this->level_callback_.call(level);
281}
282
283Logger *global_logger = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
284
285} // 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:107
UARTSelection uart_
Definition logger.h:267
CallbackManager< void(uint8_t, const char *, const char *, size_t)> log_callback_
Definition logger.h:256
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:168
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:189
void dump_config() override
Definition logger.cpp:251
const char * 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:300
void loop() override
Definition logger.cpp:178
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:257
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:245
void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size)
Definition logger.h:370
float get_setup_priority() const override
Definition logger.cpp:248
UARTSelection get_uart() const
Get the UART used by the logger.
Definition logger.cpp:242
std::map< std::string, uint8_t > log_levels_
Definition logger.h:255
void disable_loop_when_buffer_empty_()
Definition logger.h:377
std::unique_ptr< logger::TaskLogBuffer > log_buffer_
Definition logger.h:259
void init_log_buffer(size_t total_buffer_size)
Definition logger.cpp:167
void set_log_level(uint8_t level)
Set the default log level for this logger.
Definition logger.cpp:274
void set_baud_rate(uint32_t baud_rate)
Manually set the baud rate for serial, set to 0 to disable.
Definition logger.cpp:238
Logger(uint32_t baud_rate, size_t tx_buffer_size)
Definition logger.cpp:157
void HOT reset_task_log_recursion_(bool is_main_task)
Definition logger.h:315
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:203
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:325
uint16_t tx_buffer_size_
Definition logger.h:264
UARTSelection
Enum for logging UART selection.
Definition logger.h:67
@ UART_SELECTION_USB_CDC
Definition logger.h:79
Logger * global_logger
Definition logger.cpp:283
const float BUS
For communication buses like i2c/spi.
Definition component.cpp:47
Application App
Global storage of Application pointer - only one Application can exist.
uint8_t progmem_read_byte(const uint8_t *addr)
Definition core.cpp:58