From 82eb4538afc2d659838f27c8e526f996157d405d Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Sun, 8 Feb 2026 13:40:39 +0100 Subject: [PATCH 01/22] [nrf52,logger] add support for task_log_buffer_size --- esphome/components/logger/__init__.py | 14 ++- esphome/components/logger/logger.cpp | 57 +++++---- esphome/components/logger/logger.h | 21 ++-- esphome/components/logger/logger_zephyr.cpp | 2 +- .../logger/task_log_buffer_esp32.cpp | 13 ++- .../components/logger/task_log_buffer_esp32.h | 5 +- .../logger/task_log_buffer_zephyr.cpp | 108 ++++++++++++++++++ .../logger/task_log_buffer_zephyr.h | 64 +++++++++++ 8 files changed, 240 insertions(+), 44 deletions(-) create mode 100644 esphome/components/logger/task_log_buffer_zephyr.cpp create mode 100644 esphome/components/logger/task_log_buffer_zephyr.h diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 40ceaec7dc6..8de52db0cb9 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -231,9 +231,16 @@ CONFIG_SCHEMA = cv.All( bk72xx=768, ln882x=768, rtl87xx=768, + nrf52=768, ): cv.All( cv.only_on( - [PLATFORM_ESP32, PLATFORM_BK72XX, PLATFORM_LN882X, PLATFORM_RTL87XX] + [ + PLATFORM_ESP32, + PLATFORM_BK72XX, + PLATFORM_LN882X, + PLATFORM_RTL87XX, + PLATFORM_NRF52, + ] ), cv.validate_bytes, cv.Any( @@ -313,11 +320,14 @@ async def to_code(config): ) if CORE.is_esp32: cg.add(log.create_pthread_key()) - if CORE.is_esp32 or CORE.is_libretiny: + if CORE.is_esp32 or CORE.is_libretiny or CORE.is_nrf52: task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE] if task_log_buffer_size > 0: cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER") cg.add(log.init_log_buffer(task_log_buffer_size)) + if CORE.using_zephyr: + zephyr_add_prj_conf("THREAD_LOCAL_STORAGE", True) + zephyr_add_prj_conf("MPSC_PBUF", True) elif CORE.is_host: cg.add(log.create_pthread_key()) cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER") diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 4cbd4f1bf1b..63f04ff2c85 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -10,7 +10,7 @@ namespace esphome::logger { static const char *const TAG = "logger"; -#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) // Implementation for multi-threaded platforms (ESP32 with FreeRTOS, Host with pthreads, LibreTiny with FreeRTOS) // Main thread/task always uses direct buffer access for console output and callbacks // @@ -31,6 +31,9 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch // Get task handle once - used for both main task check and passing to non-main thread handler TaskHandle_t current_task = xTaskGetCurrentTaskHandle(); const bool is_main_task = (current_task == this->main_task_); +#elif (USE_ZEPHYR) + k_tid_t current_task = k_current_get(); + const bool is_main_task = (current_task == this->main_task_); #else // USE_HOST const bool is_main_task = pthread_equal(pthread_self(), this->main_thread_); #endif @@ -47,7 +50,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch } // Non-main thread handling (~0.1% of logs) -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) this->log_vprintf_non_main_thread_(level, tag, line, format, args, current_task); #else // USE_HOST this->log_vprintf_non_main_thread_(level, tag, line, format, args); @@ -56,12 +59,13 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch // Handles non-main thread logging only // Kept separate from hot path to improve instruction cache performance -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args, - TaskHandle_t current_task) { + void *current_task) #else // USE_HOST -void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args) { +void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args) #endif +{ // Check if already in recursion for this non-main thread/task if (this->is_non_main_task_recursive_()) { return; @@ -73,7 +77,7 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li bool message_sent = false; #ifdef USE_ESPHOME_TASK_LOG_BUFFER // For non-main threads/tasks, queue the message for callbacks -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) message_sent = this->log_buffer_->send_message_thread_safe(level, tag, static_cast(line), current_task, format, args); #else // USE_HOST @@ -91,11 +95,15 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li // Note: This may cause interleaved/corrupted console output if multiple threads // log simultaneously, but it's better than losing important messages entirely #ifdef USE_HOST - if (!message_sent) { + if (!message_sent) +#else + if (!message_sent && this->baud_rate_ > 0) // If logging is enabled, write to console +#endif + { +#ifdef USE_HOST // Host always has console output - no baud_rate check needed static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 512; #else - if (!message_sent && this->baud_rate_ > 0) { // If logging is enabled, write to console // Maximum size for console log messages (includes null terminator) static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144; #endif @@ -166,9 +174,12 @@ void Logger::init_log_buffer(size_t total_buffer_size) { #elif defined(USE_LIBRETINY) // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed this->log_buffer_ = new logger::TaskLogBufferLibreTiny(total_buffer_size); +#elif defined(USE_ZEPHYR) + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed + this->log_buffer_ = new logger::TaskLogBufferZephyr(total_buffer_size); #endif -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || (defined(USE_ZEPHYR) && !defined(USE_LOGGER_USB_CDC)) // Start with loop disabled when using task buffer (unless using USB CDC on ESP32) // The loop will be enabled automatically when messages arrive this->disable_loop_when_buffer_empty_(); @@ -176,8 +187,13 @@ void Logger::init_log_buffer(size_t total_buffer_size) { } #endif -#ifdef USE_ESPHOME_TASK_LOG_BUFFER -void Logger::loop() { this->process_messages_(); } +#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_USB_CDC)) +void Logger::loop() { + this->process_messages_(); +#if defined(USE_ZEPHYR) && defined(USE_LOGGER_USB_CDC) + this->cdc_loop_(); +#endif +} #endif void Logger::process_messages_() { @@ -194,21 +210,14 @@ void Logger::process_messages_() { this->log_buffer_->release_message_main_loop(); this->write_log_buffer_to_console_(buf); } -#elif defined(USE_ESP32) +#elif defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) +#ifdef USE_ESP32 logger::TaskLogBuffer::LogMessage *message; - const char *text; - void *received_token; - while (this->log_buffer_->borrow_message_main_loop(&message, &text, &received_token)) { - const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; - LogBuffer buf{this->tx_buffer_, this->tx_buffer_size_}; - this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, text, - message->text_length, buf); - // Release the message to allow other tasks to use it as soon as possible - this->log_buffer_->release_message_main_loop(received_token); - this->write_log_buffer_to_console_(buf); - } #elif defined(USE_LIBRETINY) logger::TaskLogBufferLibreTiny::LogMessage *message; +#else + logger::TaskLogBufferZephyr::LogMessage *message; +#endif const char *text; while (this->log_buffer_->borrow_message_main_loop(&message, &text)) { const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; @@ -221,7 +230,7 @@ void Logger::process_messages_() { } #endif } -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || (defined(USE_ZEPHYR) && !defined(USE_LOGGER_USB_CDC)) else { // No messages to process, disable loop if appropriate // This reduces overhead when there's no async logging activity diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 1678fed5f5e..88a16583d2c 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -19,6 +19,8 @@ #include "task_log_buffer_esp32.h" #elif defined(USE_LIBRETINY) #include "task_log_buffer_libretiny.h" +#elif defined(USE_ZEPHYR) +#include "task_log_buffer_zephyr.h" #endif #endif @@ -121,9 +123,6 @@ static constexpr char LOG_LEVEL_LETTER_CHARS[] = { // Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin) static constexpr uint16_t MAX_HEADER_SIZE = 128; -// "0x" + 2 hex digits per byte + '\0' -static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; - // Buffer wrapper for log formatting functions struct LogBuffer { char *data; @@ -406,17 +405,18 @@ class Logger : public Component { bool &flag_; }; -#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) // Handles non-main thread logging only (~0.1% of calls) -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) // ESP32/LibreTiny: Pass task handle to avoid calling xTaskGetCurrentTaskHandle() twice void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args, - TaskHandle_t current_task); + void *current_task); #else // USE_HOST // Host: No task handle parameter needed (not used in send_message_thread_safe) void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args); #endif #endif + void cdc_loop_(); void process_messages_(); void write_msg_(const char *msg, uint16_t len); @@ -544,6 +544,8 @@ class Logger : public Component { logger::TaskLogBuffer *log_buffer_{nullptr}; // Allocated once, never freed #elif defined(USE_LIBRETINY) logger::TaskLogBufferLibreTiny *log_buffer_{nullptr}; // Allocated once, never freed +#elif defined(USE_ZEPHYR) + logger::TaskLogBufferZephyr *log_buffer_{nullptr}; // Allocated once, never freed #endif #endif @@ -556,7 +558,7 @@ class Logger : public Component { #ifdef USE_LIBRETINY UARTSelection uart_{UART_SELECTION_DEFAULT}; #endif -#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || USE_ZEPHYR bool main_task_recursion_guard_{false}; #ifdef USE_LIBRETINY bool non_main_task_recursion_guard_{false}; // Shared guard for all non-main tasks on LibreTiny @@ -619,7 +621,7 @@ class Logger : public Component { // Create RAII guard for non-main task recursion inline NonMainTaskRecursionGuard make_non_main_task_guard_() { return NonMainTaskRecursionGuard(log_recursion_key_); } -#elif defined(USE_LIBRETINY) +#elif defined(USE_LIBRETINY) || defined(USE_ZEPHYR) // LibreTiny doesn't have FreeRTOS TLS, so use a simple approach: // - Main task uses dedicated boolean (same as ESP32) // - Non-main tasks share a single recursion guard @@ -627,6 +629,7 @@ class Logger : public Component { // - Recursion from logging within logging is the main concern // - Cross-task "recursion" is prevented by the buffer mutex anyway // - Missing a recursive call from another task is acceptable (falls back to direct output) + // Zephyr use __thread as TLS // Check if non-main task is already in recursion inline bool HOT is_non_main_task_recursive_() const { return non_main_task_recursion_guard_; } @@ -651,7 +654,7 @@ class Logger : public Component { } #endif -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || (defined(USE_ZEPHYR) && !defined(USE_LOGGER_USB_CDC)) // Disable loop when task buffer is empty (with USB CDC check on ESP32) inline void disable_loop_when_buffer_empty_() { // Thread safety note: This is safe even if another task calls enable_loop_soon_any_context() diff --git a/esphome/components/logger/logger_zephyr.cpp b/esphome/components/logger/logger_zephyr.cpp index ef1702c5c1c..ae649249b25 100644 --- a/esphome/components/logger/logger_zephyr.cpp +++ b/esphome/components/logger/logger_zephyr.cpp @@ -14,7 +14,7 @@ namespace esphome::logger { static const char *const TAG = "logger"; #ifdef USE_LOGGER_USB_CDC -void Logger::loop() { +void Logger::cdc_loop_() { if (this->uart_ != UART_SELECTION_USB_CDC || this->uart_dev_ == nullptr) { return; } diff --git a/esphome/components/logger/task_log_buffer_esp32.cpp b/esphome/components/logger/task_log_buffer_esp32.cpp index b9dfe45b7fa..90c0161ac8a 100644 --- a/esphome/components/logger/task_log_buffer_esp32.cpp +++ b/esphome/components/logger/task_log_buffer_esp32.cpp @@ -31,8 +31,8 @@ TaskLogBuffer::~TaskLogBuffer() { } } -bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char **text, void **received_token) { - if (message == nullptr || text == nullptr || received_token == nullptr) { +bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char **text) { + if (message == nullptr || text == nullptr || this->current_token_) { return false; } @@ -45,16 +45,17 @@ bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char ** LogMessage *msg = static_cast(received_item); *message = msg; *text = msg->text_data(); - *received_token = received_item; + this->current_token_ = received_item; return true; } -void TaskLogBuffer::release_message_main_loop(void *token) { - if (token == nullptr) { +void TaskLogBuffer::release_message_main_loop() { + if (this->current_token_ == nullptr) { return; } - vRingbufferReturnItem(ring_buffer_, token); + vRingbufferReturnItem(ring_buffer_, this->current_token_); + this->current_token_ = nullptr; // Update counter to mark all messages as processed last_processed_counter_ = message_counter_.load(std::memory_order_relaxed); } diff --git a/esphome/components/logger/task_log_buffer_esp32.h b/esphome/components/logger/task_log_buffer_esp32.h index fde9bd60d5e..e3103a8d345 100644 --- a/esphome/components/logger/task_log_buffer_esp32.h +++ b/esphome/components/logger/task_log_buffer_esp32.h @@ -52,10 +52,10 @@ class TaskLogBuffer { ~TaskLogBuffer(); // NOT thread-safe - borrow a message from the ring buffer, only call from main loop - bool borrow_message_main_loop(LogMessage **message, const char **text, void **received_token); + bool borrow_message_main_loop(LogMessage **message, const char **text); // NOT thread-safe - release a message buffer and update the counter, only call from main loop - void release_message_main_loop(void *token); + void release_message_main_loop(); // Thread-safe - send a message to the ring buffer from any thread bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, TaskHandle_t task_handle, @@ -78,6 +78,7 @@ class TaskLogBuffer { // Atomic counter for message tracking (only differences matter) std::atomic message_counter_{0}; // Incremented when messages are committed mutable uint16_t last_processed_counter_{0}; // Tracks last processed message + void *current_token_{nullptr}; }; } // namespace esphome::logger diff --git a/esphome/components/logger/task_log_buffer_zephyr.cpp b/esphome/components/logger/task_log_buffer_zephyr.cpp new file mode 100644 index 00000000000..b788f777d83 --- /dev/null +++ b/esphome/components/logger/task_log_buffer_zephyr.cpp @@ -0,0 +1,108 @@ +#ifdef USE_ZEPHYR + +#include "task_log_buffer_zephyr.h" + +namespace esphome::logger { + +__thread bool non_main_task_recursion_guard_; + +TaskLogBufferZephyr::TaskLogBufferZephyr(size_t total_buffer_size) { + // alignment to 4 bytes + total_buffer_size = (total_buffer_size + 3) / sizeof(uint32_t); + this->mpsc_config_.buf = new uint32_t[total_buffer_size]; + this->mpsc_config_.size = total_buffer_size; + this->mpsc_config_.flags = MPSC_PBUF_MODE_OVERWRITE; + // .get_wlen = log_msg_generic_get_wlen, + + mpsc_pbuf_init(&this->log_buffer_, &this->mpsc_config_); +} + +TaskLogBufferZephyr::~TaskLogBufferZephyr() { delete[] this->mpsc_config_.buf; } + +bool TaskLogBufferZephyr::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, void *task_handle, + const char *format, va_list args) { + // First, calculate the exact length needed using a null buffer (no actual writing) + va_list args_copy; + va_copy(args_copy, args); + int ret = vsnprintf(nullptr, 0, format, args_copy); + va_end(args_copy); + + if (ret <= 0) { + return false; // Formatting error or empty message + } + + // Calculate actual text length (capped to maximum size) + static constexpr size_t MAX_TEXT_SIZE = 255; + size_t text_length = (static_cast(ret) > MAX_TEXT_SIZE) ? MAX_TEXT_SIZE : ret; + // Calculate total size in 32-bit words needed (header + text length + null terminator + 3(4 bytes alignment) + size_t total_size = (sizeof(LogMessage) + text_length + 1 + 3) / sizeof(uint32_t); + auto msg = reinterpret_cast(mpsc_pbuf_alloc(&this->log_buffer_, total_size, K_NO_WAIT)); + if (nullptr == msg) { + return false; + } + msg->level = level; + msg->tag = tag; + msg->line = line; + const char *thread_name = k_thread_name_get(static_cast(task_handle)); + if (thread_name) { + strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1); + } else { + std::snprintf(msg->thread_name, MAX_POINTER_REPRESENTATION, "%p", task_handle); + } + + // Format the message text directly into the acquired memory + // We add 1 to text_length to ensure space for null terminator during formatting + char *text_area = msg->text_data(); + ret = vsnprintf(text_area, text_length + 1, format, args); + + // Handle unexpected formatting error + if (ret <= 0) { + // this shall not happened vsnprintf was called already once + // fill with '\n' to not call mpsc_pbuf_free from producer + // it will be trimmed anyway + for (uint16_t i = 0; i < text_length; ++i) { + text_area[i] = '\n'; + } + text_area[text_length] = 0; + } + + msg->text_length = text_length; + + mpsc_pbuf_commit(&this->log_buffer_, reinterpret_cast(msg)); + return true; +} + +bool TaskLogBufferZephyr::borrow_message_main_loop(LogMessage **message, const char **text) { + if (this->current_token_) { + return false; + } + + this->current_token_ = mpsc_pbuf_claim(&this->log_buffer_); + + if (nullptr == this->current_token_) { + return false; + } + + // we claimed buffer alraedy const_cast is safe here + *message = const_cast(reinterpret_cast(this->current_token_)); + + *text = (*message)->text_data(); + + // Remove trailing newlines + while ((*message)->text_length > 0 && *text[(*message)->text_length - 1] == '\n') { + (*message)->text_length--; + } + + return true; +} + +void TaskLogBufferZephyr::release_message_main_loop() { + if (this->current_token_ == nullptr) { + return; + } + mpsc_pbuf_free(&this->log_buffer_, this->current_token_); + this->current_token_ = nullptr; +} +} // namespace esphome::logger + +#endif diff --git a/esphome/components/logger/task_log_buffer_zephyr.h b/esphome/components/logger/task_log_buffer_zephyr.h new file mode 100644 index 00000000000..12e5f4ca34d --- /dev/null +++ b/esphome/components/logger/task_log_buffer_zephyr.h @@ -0,0 +1,64 @@ +#pragma once + +#ifdef USE_ZEPHYR + +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include + +namespace esphome::logger { + +// "0x" + 2 hex digits per byte + '\0' +static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; + +extern __thread bool non_main_task_recursion_guard_; + +class TaskLogBufferZephyr { + public: + // Structure for a log message header (text data follows immediately after) + struct LogMessage { + MPSC_PBUF_HDR; // this is only 2 bits put not more that 30 bits directly after + uint16_t line; // Source code line number + uint8_t level; // Log level (0-7) +#if defined(CONFIG_THREAD_NAME) + char thread_name[CONFIG_THREAD_MAX_NAME_LEN]; // Store thread name directly (only used for non-main threads) +#else + char thread_name[MAX_POINTER_REPRESENTATION]; // Store thread name directly (only used for non-main threads) +#endif + const char *tag; // We store the pointer, assuming tags are static + uint16_t text_length; // Length of the message text (up to ~64KB) + + // Methods for accessing message contents + inline char *text_data() { return reinterpret_cast(this) + sizeof(LogMessage); } + + inline const char *text_data() const { return reinterpret_cast(this) + sizeof(LogMessage); } + }; + // Constructor that takes a total buffer size + explicit TaskLogBufferZephyr(size_t total_buffer_size); + ~TaskLogBufferZephyr(); + + // Check if there are messages ready to be processed using an atomic counter for performance + inline bool HOT has_messages() { return mpsc_pbuf_is_pending(&this->log_buffer_); } + + // Get the total buffer size in bytes + inline size_t size() const { return this->mpsc_config_.size * sizeof(uint32_t); } + + // NOT thread-safe - borrow a message from the ring buffer, only call from main loop + bool borrow_message_main_loop(LogMessage **message, const char **text); + + // NOT thread-safe - release a message buffer and update the counter, only call from main loop + void release_message_main_loop(); + + // Thread-safe - send a message to the ring buffer from any thread + bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, void *task_handle, const char *format, + va_list args); + + protected: + mpsc_pbuf_buffer_config mpsc_config_{}; + mpsc_pbuf_buffer log_buffer_{}; + const mpsc_pbuf_generic *current_token_{}; +}; + +} // namespace esphome::logger + +#endif From 1e7eda1a67cc70ada4e510c7dd15f325f641fe95 Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Sun, 8 Feb 2026 14:21:33 +0100 Subject: [PATCH 02/22] fix --- esphome/components/logger/task_log_buffer_esp32.cpp | 4 ++-- esphome/components/logger/task_log_buffer_esp32.h | 4 ++-- .../components/logger/task_log_buffer_zephyr.cpp | 13 ++++++++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/esphome/components/logger/task_log_buffer_esp32.cpp b/esphome/components/logger/task_log_buffer_esp32.cpp index 90c0161ac8a..a024e6bafbf 100644 --- a/esphome/components/logger/task_log_buffer_esp32.cpp +++ b/esphome/components/logger/task_log_buffer_esp32.cpp @@ -60,7 +60,7 @@ void TaskLogBuffer::release_message_main_loop() { last_processed_counter_ = message_counter_.load(std::memory_order_relaxed); } -bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, TaskHandle_t task_handle, +bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, void *task_handle, const char *format, va_list args) { // First, calculate the exact length needed using a null buffer (no actual writing) va_list args_copy; @@ -96,7 +96,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin // Store the thread name now instead of waiting until main loop processing // This avoids crashes if the task completes or is deleted between when this message // is enqueued and when it's processed by the main loop - const char *thread_name = pcTaskGetName(task_handle); + const char *thread_name = pcTaskGetName(static_cast(task_handle)); if (thread_name != nullptr) { strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1); msg->thread_name[sizeof(msg->thread_name) - 1] = '\0'; // Ensure null termination diff --git a/esphome/components/logger/task_log_buffer_esp32.h b/esphome/components/logger/task_log_buffer_esp32.h index e3103a8d345..73b5478e556 100644 --- a/esphome/components/logger/task_log_buffer_esp32.h +++ b/esphome/components/logger/task_log_buffer_esp32.h @@ -58,8 +58,8 @@ class TaskLogBuffer { void release_message_main_loop(); // Thread-safe - send a message to the ring buffer from any thread - bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, TaskHandle_t task_handle, - const char *format, va_list args); + bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, void *task_handle, const char *format, + va_list args); // Check if there are messages ready to be processed using an atomic counter for performance inline bool HOT has_messages() const { diff --git a/esphome/components/logger/task_log_buffer_zephyr.cpp b/esphome/components/logger/task_log_buffer_zephyr.cpp index b788f777d83..5c9bb496421 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.cpp +++ b/esphome/components/logger/task_log_buffer_zephyr.cpp @@ -6,13 +6,19 @@ namespace esphome::logger { __thread bool non_main_task_recursion_guard_; +static inline uint32_t get_wlen(const mpsc_pbuf_generic *item) { + auto *msg = reinterpret_cast(item); + // Calculate total size in 32-bit words needed (header + text length + null terminator + 3(4 bytes alignment) + return (sizeof(TaskLogBufferZephyr::LogMessage) + msg->text_length + 1 + 3) / sizeof(uint32_t); +} + TaskLogBufferZephyr::TaskLogBufferZephyr(size_t total_buffer_size) { // alignment to 4 bytes total_buffer_size = (total_buffer_size + 3) / sizeof(uint32_t); this->mpsc_config_.buf = new uint32_t[total_buffer_size]; this->mpsc_config_.size = total_buffer_size; this->mpsc_config_.flags = MPSC_PBUF_MODE_OVERWRITE; - // .get_wlen = log_msg_generic_get_wlen, + this->mpsc_config_.get_wlen = get_wlen, mpsc_pbuf_init(&this->log_buffer_, &this->mpsc_config_); } @@ -36,7 +42,7 @@ bool TaskLogBufferZephyr::send_message_thread_safe(uint8_t level, const char *ta size_t text_length = (static_cast(ret) > MAX_TEXT_SIZE) ? MAX_TEXT_SIZE : ret; // Calculate total size in 32-bit words needed (header + text length + null terminator + 3(4 bytes alignment) size_t total_size = (sizeof(LogMessage) + text_length + 1 + 3) / sizeof(uint32_t); - auto msg = reinterpret_cast(mpsc_pbuf_alloc(&this->log_buffer_, total_size, K_NO_WAIT)); + auto *msg = reinterpret_cast(mpsc_pbuf_alloc(&this->log_buffer_, total_size, K_NO_WAIT)); if (nullptr == msg) { return false; } @@ -47,7 +53,7 @@ bool TaskLogBufferZephyr::send_message_thread_safe(uint8_t level, const char *ta if (thread_name) { strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1); } else { - std::snprintf(msg->thread_name, MAX_POINTER_REPRESENTATION, "%p", task_handle); + std::snprintf(msg->thread_name, sizeof(msg->thread_name), "%p", task_handle); } // Format the message text directly into the acquired memory @@ -64,6 +70,7 @@ bool TaskLogBufferZephyr::send_message_thread_safe(uint8_t level, const char *ta text_area[i] = '\n'; } text_area[text_length] = 0; + // do not return false to free the buffer from main thread } msg->text_length = text_length; From 124e5d989816ab894387417e547ad4868e64c9e9 Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Sun, 8 Feb 2026 14:33:19 +0100 Subject: [PATCH 03/22] fix --- esphome/components/logger/logger.h | 8 +------- esphome/components/logger/task_log_buffer_zephyr.cpp | 3 +++ esphome/components/logger/task_log_buffer_zephyr.h | 4 ++++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 88a16583d2c..6e04438521e 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -12,17 +12,11 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#ifdef USE_ESPHOME_TASK_LOG_BUFFER -#ifdef USE_HOST +// all those headers are ifdef inside #include "task_log_buffer_host.h" -#elif defined(USE_ESP32) #include "task_log_buffer_esp32.h" -#elif defined(USE_LIBRETINY) #include "task_log_buffer_libretiny.h" -#elif defined(USE_ZEPHYR) #include "task_log_buffer_zephyr.h" -#endif -#endif #ifdef USE_ARDUINO #if defined(USE_ESP8266) diff --git a/esphome/components/logger/task_log_buffer_zephyr.cpp b/esphome/components/logger/task_log_buffer_zephyr.cpp index 5c9bb496421..77d5511f58a 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.cpp +++ b/esphome/components/logger/task_log_buffer_zephyr.cpp @@ -2,6 +2,8 @@ #include "task_log_buffer_zephyr.h" +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + namespace esphome::logger { __thread bool non_main_task_recursion_guard_; @@ -113,3 +115,4 @@ void TaskLogBufferZephyr::release_message_main_loop() { } // namespace esphome::logger #endif +#endif diff --git a/esphome/components/logger/task_log_buffer_zephyr.h b/esphome/components/logger/task_log_buffer_zephyr.h index 12e5f4ca34d..9d0993f2a0a 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.h +++ b/esphome/components/logger/task_log_buffer_zephyr.h @@ -11,6 +11,8 @@ namespace esphome::logger { // "0x" + 2 hex digits per byte + '\0' static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + extern __thread bool non_main_task_recursion_guard_; class TaskLogBufferZephyr { @@ -59,6 +61,8 @@ class TaskLogBufferZephyr { const mpsc_pbuf_generic *current_token_{}; }; +#endif + } // namespace esphome::logger #endif From 4bcb544bebb670a732b59f49bdbb8ed82e52620e Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Sun, 8 Feb 2026 14:41:16 +0100 Subject: [PATCH 04/22] fix --- esphome/components/logger/logger.h | 1 + esphome/components/logger/task_log_buffer_zephyr.cpp | 4 ++-- esphome/components/logger/task_log_buffer_zephyr.h | 2 +- esphome/core/defines.h | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 6e04438521e..b20f1c23b4e 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -623,6 +623,7 @@ class Logger : public Component { // - Recursion from logging within logging is the main concern // - Cross-task "recursion" is prevented by the buffer mutex anyway // - Missing a recursive call from another task is acceptable (falls back to direct output) + // // Zephyr use __thread as TLS // Check if non-main task is already in recursion diff --git a/esphome/components/logger/task_log_buffer_zephyr.cpp b/esphome/components/logger/task_log_buffer_zephyr.cpp index 77d5511f58a..8c9c5a20b09 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.cpp +++ b/esphome/components/logger/task_log_buffer_zephyr.cpp @@ -6,7 +6,7 @@ namespace esphome::logger { -__thread bool non_main_task_recursion_guard_; +__thread bool non_main_task_recursion_guard_; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static inline uint32_t get_wlen(const mpsc_pbuf_generic *item) { auto *msg = reinterpret_cast(item); @@ -68,7 +68,7 @@ bool TaskLogBufferZephyr::send_message_thread_safe(uint8_t level, const char *ta // this shall not happened vsnprintf was called already once // fill with '\n' to not call mpsc_pbuf_free from producer // it will be trimmed anyway - for (uint16_t i = 0; i < text_length; ++i) { + for (size_t i = 0; i < text_length; ++i) { text_area[i] = '\n'; } text_area[text_length] = 0; diff --git a/esphome/components/logger/task_log_buffer_zephyr.h b/esphome/components/logger/task_log_buffer_zephyr.h index 9d0993f2a0a..4a7d6ff781d 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.h +++ b/esphome/components/logger/task_log_buffer_zephyr.h @@ -13,7 +13,7 @@ static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; #ifdef USE_ESPHOME_TASK_LOG_BUFFER -extern __thread bool non_main_task_recursion_guard_; +extern __thread bool non_main_task_recursion_guard_; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) class TaskLogBufferZephyr { public: diff --git a/esphome/core/defines.h b/esphome/core/defines.h index ee865a7e65a..0c888933bf0 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -320,6 +320,7 @@ #endif #ifdef USE_NRF52 +#define USE_ESPHOME_TASK_LOG_BUFFER #define USE_NRF52_DFU #define USE_NRF52_REG0_VOUT 5 #define USE_NRF52_UICR_ERASE From d42b6ff948a169faf11c0763ac636c5a79f52af0 Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Sun, 8 Feb 2026 16:19:25 +0100 Subject: [PATCH 05/22] fix --- esphome/components/logger/task_log_buffer_zephyr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/logger/task_log_buffer_zephyr.cpp b/esphome/components/logger/task_log_buffer_zephyr.cpp index 8c9c5a20b09..87ecd9cddd8 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.cpp +++ b/esphome/components/logger/task_log_buffer_zephyr.cpp @@ -98,7 +98,7 @@ bool TaskLogBufferZephyr::borrow_message_main_loop(LogMessage **message, const c *text = (*message)->text_data(); // Remove trailing newlines - while ((*message)->text_length > 0 && *text[(*message)->text_length - 1] == '\n') { + while ((*message)->text_length > 0 && (*text)[(*message)->text_length - 1] == '\n') { (*message)->text_length--; } From 26ed6aa3147407076a3ecf3c61389ce29b332162 Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Tue, 10 Feb 2026 23:34:40 +0100 Subject: [PATCH 06/22] fix --- esphome/components/logger/logger.cpp | 5 ++++- esphome/components/logger/logger.h | 6 ++++-- esphome/components/logger/task_log_buffer_zephyr.cpp | 11 +++-------- esphome/components/logger/task_log_buffer_zephyr.h | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 0251e57ffd1..9fc33145c66 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -55,8 +55,11 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch // ESP32/LibreTiny: use TaskHandle_t overload to avoid redundant xTaskGetCurrentTaskHandle() // (we already have the handle from the main task check above). // Host: pass a stack buffer for pthread_getname_np to write into. -#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) const char *thread_name = get_thread_name_(current_task); +#elif defined(USE_ZEPHYR) + char thread_name_buf[MAX_POINTER_REPRESENTATION]; + const char *thread_name = get_thread_name_(thread_name_buf, current_task); #else // USE_HOST char thread_name_buf[THREAD_NAME_BUF_SIZE]; const char *thread_name = this->get_thread_name_(thread_name_buf); diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 3d15b9d3d50..956c94d2df3 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -591,8 +591,10 @@ class Logger : public Component { } #elif defined(USE_ZEPHYR) - const char *HOT get_thread_name_(std::span buff) { - k_tid_t current_task = k_current_get(); + const char *HOT get_thread_name_(std::span buff, k_tid_t current_task = nullptr) { + if (current_task == nullptr) { + current_task = k_current_get(); + } if (current_task == main_task_) { return nullptr; // Main task } diff --git a/esphome/components/logger/task_log_buffer_zephyr.cpp b/esphome/components/logger/task_log_buffer_zephyr.cpp index 87ecd9cddd8..1c0865b0c88 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.cpp +++ b/esphome/components/logger/task_log_buffer_zephyr.cpp @@ -27,8 +27,8 @@ TaskLogBufferZephyr::TaskLogBufferZephyr(size_t total_buffer_size) { TaskLogBufferZephyr::~TaskLogBufferZephyr() { delete[] this->mpsc_config_.buf; } -bool TaskLogBufferZephyr::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, void *task_handle, - const char *format, va_list args) { +bool TaskLogBufferZephyr::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, + const char *thread_name, const char *format, va_list args) { // First, calculate the exact length needed using a null buffer (no actual writing) va_list args_copy; va_copy(args_copy, args); @@ -51,12 +51,7 @@ bool TaskLogBufferZephyr::send_message_thread_safe(uint8_t level, const char *ta msg->level = level; msg->tag = tag; msg->line = line; - const char *thread_name = k_thread_name_get(static_cast(task_handle)); - if (thread_name) { - strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1); - } else { - std::snprintf(msg->thread_name, sizeof(msg->thread_name), "%p", task_handle); - } + strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1); // Format the message text directly into the acquired memory // We add 1 to text_length to ensure space for null terminator during formatting diff --git a/esphome/components/logger/task_log_buffer_zephyr.h b/esphome/components/logger/task_log_buffer_zephyr.h index 4a7d6ff781d..2f85f8b6526 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.h +++ b/esphome/components/logger/task_log_buffer_zephyr.h @@ -52,8 +52,8 @@ class TaskLogBufferZephyr { void release_message_main_loop(); // Thread-safe - send a message to the ring buffer from any thread - bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, void *task_handle, const char *format, - va_list args); + bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name, + const char *format, va_list args); protected: mpsc_pbuf_buffer_config mpsc_config_{}; From d15c466f7ee8496aedf11b20933d70c17eba28b4 Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 11 Feb 2026 09:52:32 +0100 Subject: [PATCH 07/22] fix --- esphome/components/logger/log_buffer.h | 187 +++++++++++++++++++++++++ esphome/components/logger/logger.cpp | 6 +- esphome/components/logger/logger.h | 186 +----------------------- 3 files changed, 192 insertions(+), 187 deletions(-) create mode 100644 esphome/components/logger/log_buffer.h diff --git a/esphome/components/logger/log_buffer.h b/esphome/components/logger/log_buffer.h new file mode 100644 index 00000000000..819b289bcac --- /dev/null +++ b/esphome/components/logger/log_buffer.h @@ -0,0 +1,187 @@ +#pragma once + +namespace esphome::logger { + +// Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin) +static constexpr uint16_t MAX_HEADER_SIZE = 128; + +// ANSI color code last digit (30-38 range, store only last digit to save RAM) +static constexpr char LOG_LEVEL_COLOR_DIGIT[] = { + '\0', // NONE + '1', // ERROR (31 = red) + '3', // WARNING (33 = yellow) + '2', // INFO (32 = green) + '5', // CONFIG (35 = magenta) + '6', // DEBUG (36 = cyan) + '7', // VERBOSE (37 = gray) + '8', // VERY_VERBOSE (38 = white) +}; + +static constexpr char LOG_LEVEL_LETTER_CHARS[] = { + '\0', // NONE + 'E', // ERROR + 'W', // WARNING + 'I', // INFO + 'C', // CONFIG + 'D', // DEBUG + 'V', // VERBOSE (VERY_VERBOSE uses two 'V's) +}; + +// Buffer wrapper for log formatting functions +struct LogBuffer { + char *data; + uint16_t size; + uint16_t pos{0}; + // Replaces the null terminator with a newline for console output. + // Must be called after notify_listeners_() since listeners need null-terminated strings. + // Console output uses length-based writes (buf.pos), so null terminator is not needed. + void terminate_with_newline() { + if (this->pos < this->size) { + this->data[this->pos++] = '\n'; + } else if (this->size > 0) { + // Buffer was full - replace last char with newline to ensure it's visible + this->data[this->size - 1] = '\n'; + this->pos = this->size; + } + } + void HOT write_header(uint8_t level, const char *tag, int line, const char *thread_name) { + // Early return if insufficient space - intentionally don't update pos to prevent partial writes + if (this->pos + MAX_HEADER_SIZE > this->size) + return; + + char *p = this->current_(); + + // Write ANSI color + this->write_ansi_color_(p, level); + + // Construct: [LEVEL][tag:line] + *p++ = '['; + if (level != 0) { + if (level >= 7) { + *p++ = 'V'; // VERY_VERBOSE = "VV" + *p++ = 'V'; + } else { + *p++ = LOG_LEVEL_LETTER_CHARS[level]; + } + } + *p++ = ']'; + *p++ = '['; + + // Copy tag + this->copy_string_(p, tag); + + *p++ = ':'; + + // Format line number without modulo operations + if (line > 999) [[unlikely]] { + int thousands = line / 1000; + *p++ = '0' + thousands; + line -= thousands * 1000; + } + int hundreds = line / 100; + int remainder = line - hundreds * 100; + int tens = remainder / 10; + *p++ = '0' + hundreds; + *p++ = '0' + tens; + *p++ = '0' + (remainder - tens * 10); + *p++ = ']'; + +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) || defined(USE_HOST) + // Write thread name with bold red color + if (thread_name != nullptr) { + this->write_ansi_color_(p, 1); // Bold red for thread name + *p++ = '['; + this->copy_string_(p, thread_name); + *p++ = ']'; + this->write_ansi_color_(p, level); // Restore original color + } +#endif + + *p++ = ':'; + *p++ = ' '; + + this->pos = p - this->data; + } + void HOT format_body(const char *format, va_list args) { + this->format_vsnprintf_(format, args); + this->finalize_(); + } +#ifdef USE_STORE_LOG_STR_IN_FLASH + void HOT format_body_P(PGM_P format, va_list args) { + this->format_vsnprintf_P_(format, args); + this->finalize_(); + } +#endif + void write_body(const char *text, uint16_t text_length) { + this->write_(text, text_length); + this->finalize_(); + } + + private: + bool full_() const { return this->pos >= this->size; } + uint16_t remaining_() const { return this->size - this->pos; } + char *current_() { return this->data + this->pos; } + void write_(const char *value, uint16_t length) { + const uint16_t available = this->remaining_(); + const uint16_t copy_len = (length < available) ? length : available; + if (copy_len > 0) { + memcpy(this->current_(), value, copy_len); + this->pos += copy_len; + } + } + void finalize_() { + // Write color reset sequence + static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1; + this->write_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN); + // Null terminate + this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; + } + void strip_trailing_newlines_() { + while (this->pos > 0 && this->data[this->pos - 1] == '\n') + this->pos--; + } + void process_vsnprintf_result_(int ret) { + if (ret < 0) + return; + const uint16_t rem = this->remaining_(); + this->pos += (ret >= rem) ? (rem - 1) : static_cast(ret); + this->strip_trailing_newlines_(); + } + void format_vsnprintf_(const char *format, va_list args) { + if (this->full_()) + return; + this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args)); + } +#ifdef USE_STORE_LOG_STR_IN_FLASH + void format_vsnprintf_P_(PGM_P format, va_list args) { + if (this->full_()) + return; + this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args)); + } +#endif + // Write ANSI color escape sequence to buffer, updates pointer in place + // Caller is responsible for ensuring buffer has sufficient space + void write_ansi_color_(char *&p, uint8_t level) { + if (level == 0) + return; + // Direct buffer fill: "\033[{bold};3{color}m" (7 bytes) + *p++ = '\033'; + *p++ = '['; + *p++ = (level == 1) ? '1' : '0'; // Only ERROR is bold + *p++ = ';'; + *p++ = '3'; + *p++ = LOG_LEVEL_COLOR_DIGIT[level]; + *p++ = 'm'; + } + // Copy string without null terminator, updates pointer in place + // Caller is responsible for ensuring buffer has sufficient space + void copy_string_(char *&p, const char *str) { + const size_t len = strlen(str); + // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - intentionally no null terminator, building string piece by + // piece + memcpy(p, str, len); + p += len; + } +}; + +} // namespace esphome::logger diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 9fc33145c66..d48f4415715 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -10,9 +10,9 @@ namespace esphome::logger { static const char *const TAG = "logger"; -#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) -// Implementation for multi-threaded platforms (ESP32 with FreeRTOS, Host with pthreads, LibreTiny with FreeRTOS) -// Main thread/task always uses direct buffer access for console output and callbacks +#ifdef USE_ESPHOME_TASK_LOG_BUFFER +// Implementation for multi-threaded platforms (ESP32 with FreeRTOS, Host with pthreads, LibreTiny with FreeRTOS, +// Zephyr) Main thread/task always uses direct buffer access for console output and callbacks // // For non-main threads/tasks: // - WITH task log buffer: Prefer sending to ring buffer for async processing diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 956c94d2df3..ed6c66433cf 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -13,7 +13,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -// all those headers are ifdef inside +#include "log_buffer.h" #include "task_log_buffer_host.h" #include "task_log_buffer_esp32.h" #include "task_log_buffer_libretiny.h" @@ -93,192 +93,10 @@ struct CStrCompare { }; #endif -// ANSI color code last digit (30-38 range, store only last digit to save RAM) -static constexpr char LOG_LEVEL_COLOR_DIGIT[] = { - '\0', // NONE - '1', // ERROR (31 = red) - '3', // WARNING (33 = yellow) - '2', // INFO (32 = green) - '5', // CONFIG (35 = magenta) - '6', // DEBUG (36 = cyan) - '7', // VERBOSE (37 = gray) - '8', // VERY_VERBOSE (38 = white) -}; - -static constexpr char LOG_LEVEL_LETTER_CHARS[] = { - '\0', // NONE - 'E', // ERROR - 'W', // WARNING - 'I', // INFO - 'C', // CONFIG - 'D', // DEBUG - 'V', // VERBOSE (VERY_VERBOSE uses two 'V's) -}; - -// Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin) -static constexpr uint16_t MAX_HEADER_SIZE = 128; - // Stack buffer size for retrieving thread/task names from the OS // macOS allows up to 64 bytes, Linux up to 16 static constexpr size_t THREAD_NAME_BUF_SIZE = 64; -// Buffer wrapper for log formatting functions -struct LogBuffer { - char *data; - uint16_t size; - uint16_t pos{0}; - // Replaces the null terminator with a newline for console output. - // Must be called after notify_listeners_() since listeners need null-terminated strings. - // Console output uses length-based writes (buf.pos), so null terminator is not needed. - void terminate_with_newline() { - if (this->pos < this->size) { - this->data[this->pos++] = '\n'; - } else if (this->size > 0) { - // Buffer was full - replace last char with newline to ensure it's visible - this->data[this->size - 1] = '\n'; - this->pos = this->size; - } - } - void HOT write_header(uint8_t level, const char *tag, int line, const char *thread_name) { - // Early return if insufficient space - intentionally don't update pos to prevent partial writes - if (this->pos + MAX_HEADER_SIZE > this->size) - return; - - char *p = this->current_(); - - // Write ANSI color - this->write_ansi_color_(p, level); - - // Construct: [LEVEL][tag:line] - *p++ = '['; - if (level != 0) { - if (level >= 7) { - *p++ = 'V'; // VERY_VERBOSE = "VV" - *p++ = 'V'; - } else { - *p++ = LOG_LEVEL_LETTER_CHARS[level]; - } - } - *p++ = ']'; - *p++ = '['; - - // Copy tag - this->copy_string_(p, tag); - - *p++ = ':'; - - // Format line number without modulo operations - if (line > 999) [[unlikely]] { - int thousands = line / 1000; - *p++ = '0' + thousands; - line -= thousands * 1000; - } - int hundreds = line / 100; - int remainder = line - hundreds * 100; - int tens = remainder / 10; - *p++ = '0' + hundreds; - *p++ = '0' + tens; - *p++ = '0' + (remainder - tens * 10); - *p++ = ']'; - -#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) || defined(USE_HOST) - // Write thread name with bold red color - if (thread_name != nullptr) { - this->write_ansi_color_(p, 1); // Bold red for thread name - *p++ = '['; - this->copy_string_(p, thread_name); - *p++ = ']'; - this->write_ansi_color_(p, level); // Restore original color - } -#endif - - *p++ = ':'; - *p++ = ' '; - - this->pos = p - this->data; - } - void HOT format_body(const char *format, va_list args) { - this->format_vsnprintf_(format, args); - this->finalize_(); - } -#ifdef USE_STORE_LOG_STR_IN_FLASH - void HOT format_body_P(PGM_P format, va_list args) { - this->format_vsnprintf_P_(format, args); - this->finalize_(); - } -#endif - void write_body(const char *text, uint16_t text_length) { - this->write_(text, text_length); - this->finalize_(); - } - - private: - bool full_() const { return this->pos >= this->size; } - uint16_t remaining_() const { return this->size - this->pos; } - char *current_() { return this->data + this->pos; } - void write_(const char *value, uint16_t length) { - const uint16_t available = this->remaining_(); - const uint16_t copy_len = (length < available) ? length : available; - if (copy_len > 0) { - memcpy(this->current_(), value, copy_len); - this->pos += copy_len; - } - } - void finalize_() { - // Write color reset sequence - static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1; - this->write_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN); - // Null terminate - this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; - } - void strip_trailing_newlines_() { - while (this->pos > 0 && this->data[this->pos - 1] == '\n') - this->pos--; - } - void process_vsnprintf_result_(int ret) { - if (ret < 0) - return; - const uint16_t rem = this->remaining_(); - this->pos += (ret >= rem) ? (rem - 1) : static_cast(ret); - this->strip_trailing_newlines_(); - } - void format_vsnprintf_(const char *format, va_list args) { - if (this->full_()) - return; - this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args)); - } -#ifdef USE_STORE_LOG_STR_IN_FLASH - void format_vsnprintf_P_(PGM_P format, va_list args) { - if (this->full_()) - return; - this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args)); - } -#endif - // Write ANSI color escape sequence to buffer, updates pointer in place - // Caller is responsible for ensuring buffer has sufficient space - void write_ansi_color_(char *&p, uint8_t level) { - if (level == 0) - return; - // Direct buffer fill: "\033[{bold};3{color}m" (7 bytes) - *p++ = '\033'; - *p++ = '['; - *p++ = (level == 1) ? '1' : '0'; // Only ERROR is bold - *p++ = ';'; - *p++ = '3'; - *p++ = LOG_LEVEL_COLOR_DIGIT[level]; - *p++ = 'm'; - } - // Copy string without null terminator, updates pointer in place - // Caller is responsible for ensuring buffer has sufficient space - void copy_string_(char *&p, const char *str) { - const size_t len = strlen(str); - // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - intentionally no null terminator, building string piece by - // piece - memcpy(p, str, len); - p += len; - } -}; - #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) /** Enum for logging UART selection * @@ -404,7 +222,7 @@ class Logger : public Component { bool &flag_; }; -#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) +#ifdef USE_ESPHOME_TASK_LOG_BUFFER // Handles non-main thread logging only (~0.1% of calls) // thread_name is resolved by the caller from the task handle, avoiding redundant lookups void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args, From 9f46bed68699763ed777a0967263690cfbffed82 Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 11 Feb 2026 10:10:47 +0100 Subject: [PATCH 08/22] fix --- esphome/components/logger/logger.cpp | 19 +------------------ esphome/components/logger/logger.h | 8 -------- .../logger/task_log_buffer_host.cpp | 16 ++++++++-------- .../components/logger/task_log_buffer_host.h | 6 +++--- .../logger/task_log_buffer_libretiny.cpp | 14 +++++++------- .../logger/task_log_buffer_libretiny.h | 6 +++--- .../logger/task_log_buffer_zephyr.cpp | 16 ++++++++-------- .../logger/task_log_buffer_zephyr.h | 6 +++--- 8 files changed, 33 insertions(+), 58 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index d48f4415715..afa1f1957b8 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -173,20 +173,9 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate } #ifdef USE_ESPHOME_TASK_LOG_BUFFER void Logger::init_log_buffer(size_t total_buffer_size) { -#ifdef USE_HOST // Host uses slot count instead of byte size - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed - this->log_buffer_ = new logger::TaskLogBufferHost(total_buffer_size); -#elif defined(USE_ESP32) // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed this->log_buffer_ = new logger::TaskLogBuffer(total_buffer_size); -#elif defined(USE_LIBRETINY) - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed - this->log_buffer_ = new logger::TaskLogBufferLibreTiny(total_buffer_size); -#elif defined(USE_ZEPHYR) - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed - this->log_buffer_ = new logger::TaskLogBufferZephyr(total_buffer_size); -#endif #if defined(USE_ESP32) || defined(USE_LIBRETINY) || (defined(USE_ZEPHYR) && !defined(USE_LOGGER_USB_CDC)) // Start with loop disabled when using task buffer (unless using USB CDC on ESP32) @@ -210,7 +199,7 @@ void Logger::process_messages_() { // Process any buffered messages when available if (this->log_buffer_->has_messages()) { #ifdef USE_HOST - logger::TaskLogBufferHost::LogMessage *message; + logger::TaskLogBuffer::LogMessage *message; while (this->log_buffer_->get_message_main_loop(&message)) { const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; LogBuffer buf{this->tx_buffer_, this->tx_buffer_size_}; @@ -220,13 +209,7 @@ void Logger::process_messages_() { this->write_log_buffer_to_console_(buf); } #elif defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) -#ifdef USE_ESP32 logger::TaskLogBuffer::LogMessage *message; -#elif defined(USE_LIBRETINY) - logger::TaskLogBufferLibreTiny::LogMessage *message; -#else - logger::TaskLogBufferZephyr::LogMessage *message; -#endif const char *text; while (this->log_buffer_->borrow_message_main_loop(&message, &text)) { const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index ed6c66433cf..977019adb5f 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -346,15 +346,7 @@ class Logger : public Component { std::vector level_listeners_; // Log level change listeners #endif #ifdef USE_ESPHOME_TASK_LOG_BUFFER -#ifdef USE_HOST - logger::TaskLogBufferHost *log_buffer_{nullptr}; // Allocated once, never freed -#elif defined(USE_ESP32) logger::TaskLogBuffer *log_buffer_{nullptr}; // Allocated once, never freed -#elif defined(USE_LIBRETINY) - logger::TaskLogBufferLibreTiny *log_buffer_{nullptr}; // Allocated once, never freed -#elif defined(USE_ZEPHYR) - logger::TaskLogBufferZephyr *log_buffer_{nullptr}; // Allocated once, never freed -#endif #endif // Group smaller types together at the end diff --git a/esphome/components/logger/task_log_buffer_host.cpp b/esphome/components/logger/task_log_buffer_host.cpp index 676686304a3..c98c062b754 100644 --- a/esphome/components/logger/task_log_buffer_host.cpp +++ b/esphome/components/logger/task_log_buffer_host.cpp @@ -10,16 +10,16 @@ namespace esphome::logger { -TaskLogBufferHost::TaskLogBufferHost(size_t slot_count) : slot_count_(slot_count) { +TaskLogBuffer::TaskLogBuffer(size_t slot_count) : slot_count_(slot_count) { // Allocate message slots this->slots_ = std::make_unique(slot_count); } -TaskLogBufferHost::~TaskLogBufferHost() { +TaskLogBuffer::~TaskLogBuffer() { // unique_ptr handles cleanup automatically } -int TaskLogBufferHost::acquire_write_slot_() { +int TaskLogBuffer::acquire_write_slot_() { // Try to reserve a slot using compare-and-swap size_t current_reserve = this->reserve_index_.load(std::memory_order_relaxed); @@ -43,7 +43,7 @@ int TaskLogBufferHost::acquire_write_slot_() { } } -void TaskLogBufferHost::commit_write_slot_(int slot_index) { +void TaskLogBuffer::commit_write_slot_(int slot_index) { // Mark the slot as ready for reading this->slots_[slot_index].ready.store(true, std::memory_order_release); @@ -70,8 +70,8 @@ void TaskLogBufferHost::commit_write_slot_(int slot_index) { } } -bool TaskLogBufferHost::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name, - const char *format, va_list args) { +bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name, + const char *format, va_list args) { // Acquire a slot int slot_index = this->acquire_write_slot_(); if (slot_index < 0) { @@ -115,7 +115,7 @@ bool TaskLogBufferHost::send_message_thread_safe(uint8_t level, const char *tag, return true; } -bool TaskLogBufferHost::get_message_main_loop(LogMessage **message) { +bool TaskLogBuffer::get_message_main_loop(LogMessage **message) { if (message == nullptr) { return false; } @@ -138,7 +138,7 @@ bool TaskLogBufferHost::get_message_main_loop(LogMessage **message) { return true; } -void TaskLogBufferHost::release_message_main_loop() { +void TaskLogBuffer::release_message_main_loop() { size_t current_read = this->read_index_.load(std::memory_order_relaxed); // Clear the ready flag diff --git a/esphome/components/logger/task_log_buffer_host.h b/esphome/components/logger/task_log_buffer_host.h index f8e4ee7bee5..c9de381cd88 100644 --- a/esphome/components/logger/task_log_buffer_host.h +++ b/esphome/components/logger/task_log_buffer_host.h @@ -48,7 +48,7 @@ namespace esphome::logger { * - Atomic CAS for slot reservation allows multiple producers without locks * - Single consumer (main loop) processes messages in order */ -class TaskLogBufferHost { +class TaskLogBuffer { public: // Default number of message slots - host has plenty of memory static constexpr size_t DEFAULT_SLOT_COUNT = 64; @@ -74,8 +74,8 @@ class TaskLogBufferHost { }; /// Constructor that takes the number of message slots - explicit TaskLogBufferHost(size_t slot_count); - ~TaskLogBufferHost(); + explicit TaskLogBuffer(size_t slot_count); + ~TaskLogBuffer(); // NOT thread-safe - get next message from buffer, only call from main loop // Returns true if a message was retrieved, false if buffer is empty diff --git a/esphome/components/logger/task_log_buffer_libretiny.cpp b/esphome/components/logger/task_log_buffer_libretiny.cpp index 5a22857dcb1..d4662bd9077 100644 --- a/esphome/components/logger/task_log_buffer_libretiny.cpp +++ b/esphome/components/logger/task_log_buffer_libretiny.cpp @@ -8,7 +8,7 @@ namespace esphome::logger { -TaskLogBufferLibreTiny::TaskLogBufferLibreTiny(size_t total_buffer_size) { +TaskLogBuffer::TaskLogBuffer(size_t total_buffer_size) { this->size_ = total_buffer_size; // Allocate memory for the circular buffer using ESPHome's RAM allocator RAMAllocator allocator; @@ -17,7 +17,7 @@ TaskLogBufferLibreTiny::TaskLogBufferLibreTiny(size_t total_buffer_size) { this->mutex_ = xSemaphoreCreateMutex(); } -TaskLogBufferLibreTiny::~TaskLogBufferLibreTiny() { +TaskLogBuffer::~TaskLogBuffer() { if (this->mutex_ != nullptr) { vSemaphoreDelete(this->mutex_); this->mutex_ = nullptr; @@ -29,7 +29,7 @@ TaskLogBufferLibreTiny::~TaskLogBufferLibreTiny() { } } -size_t TaskLogBufferLibreTiny::available_contiguous_space() const { +size_t TaskLogBuffer::available_contiguous_space() const { if (this->head_ >= this->tail_) { // head is ahead of or equal to tail // Available space is from head to end, plus from start to tail @@ -47,7 +47,7 @@ size_t TaskLogBufferLibreTiny::available_contiguous_space() const { } } -bool TaskLogBufferLibreTiny::borrow_message_main_loop(LogMessage **message, const char **text) { +bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char **text) { if (message == nullptr || text == nullptr) { return false; } @@ -85,7 +85,7 @@ bool TaskLogBufferLibreTiny::borrow_message_main_loop(LogMessage **message, cons return true; } -void TaskLogBufferLibreTiny::release_message_main_loop() { +void TaskLogBuffer::release_message_main_loop() { // Advance tail past the current message this->tail_ += this->current_message_size_; @@ -100,8 +100,8 @@ void TaskLogBufferLibreTiny::release_message_main_loop() { xSemaphoreGive(this->mutex_); } -bool TaskLogBufferLibreTiny::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, - const char *thread_name, const char *format, va_list args) { +bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name, + const char *format, va_list args) { // First, calculate the exact length needed using a null buffer (no actual writing) va_list args_copy; va_copy(args_copy, args); diff --git a/esphome/components/logger/task_log_buffer_libretiny.h b/esphome/components/logger/task_log_buffer_libretiny.h index bf315e828a5..8a5cc87bb35 100644 --- a/esphome/components/logger/task_log_buffer_libretiny.h +++ b/esphome/components/logger/task_log_buffer_libretiny.h @@ -40,7 +40,7 @@ namespace esphome::logger { * - Volatile counter enables fast has_messages() without lock overhead * - If message doesn't fit at end, padding is added and message wraps to start */ -class TaskLogBufferLibreTiny { +class TaskLogBuffer { public: // Structure for a log message header (text data follows immediately after) struct LogMessage { @@ -60,8 +60,8 @@ class TaskLogBufferLibreTiny { static constexpr uint8_t PADDING_MARKER_LEVEL = 0xFF; // Constructor that takes a total buffer size - explicit TaskLogBufferLibreTiny(size_t total_buffer_size); - ~TaskLogBufferLibreTiny(); + explicit TaskLogBuffer(size_t total_buffer_size); + ~TaskLogBuffer(); // NOT thread-safe - borrow a message from the buffer, only call from main loop bool borrow_message_main_loop(LogMessage **message, const char **text); diff --git a/esphome/components/logger/task_log_buffer_zephyr.cpp b/esphome/components/logger/task_log_buffer_zephyr.cpp index 1c0865b0c88..174f98d92c5 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.cpp +++ b/esphome/components/logger/task_log_buffer_zephyr.cpp @@ -9,12 +9,12 @@ namespace esphome::logger { __thread bool non_main_task_recursion_guard_; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static inline uint32_t get_wlen(const mpsc_pbuf_generic *item) { - auto *msg = reinterpret_cast(item); + auto *msg = reinterpret_cast(item); // Calculate total size in 32-bit words needed (header + text length + null terminator + 3(4 bytes alignment) - return (sizeof(TaskLogBufferZephyr::LogMessage) + msg->text_length + 1 + 3) / sizeof(uint32_t); + return (sizeof(TaskLogBuffer::LogMessage) + msg->text_length + 1 + 3) / sizeof(uint32_t); } -TaskLogBufferZephyr::TaskLogBufferZephyr(size_t total_buffer_size) { +TaskLogBuffer::TaskLogBuffer(size_t total_buffer_size) { // alignment to 4 bytes total_buffer_size = (total_buffer_size + 3) / sizeof(uint32_t); this->mpsc_config_.buf = new uint32_t[total_buffer_size]; @@ -25,10 +25,10 @@ TaskLogBufferZephyr::TaskLogBufferZephyr(size_t total_buffer_size) { mpsc_pbuf_init(&this->log_buffer_, &this->mpsc_config_); } -TaskLogBufferZephyr::~TaskLogBufferZephyr() { delete[] this->mpsc_config_.buf; } +TaskLogBuffer::~TaskLogBuffer() { delete[] this->mpsc_config_.buf; } -bool TaskLogBufferZephyr::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, - const char *thread_name, const char *format, va_list args) { +bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name, + const char *format, va_list args) { // First, calculate the exact length needed using a null buffer (no actual writing) va_list args_copy; va_copy(args_copy, args); @@ -76,7 +76,7 @@ bool TaskLogBufferZephyr::send_message_thread_safe(uint8_t level, const char *ta return true; } -bool TaskLogBufferZephyr::borrow_message_main_loop(LogMessage **message, const char **text) { +bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char **text) { if (this->current_token_) { return false; } @@ -100,7 +100,7 @@ bool TaskLogBufferZephyr::borrow_message_main_loop(LogMessage **message, const c return true; } -void TaskLogBufferZephyr::release_message_main_loop() { +void TaskLogBuffer::release_message_main_loop() { if (this->current_token_ == nullptr) { return; } diff --git a/esphome/components/logger/task_log_buffer_zephyr.h b/esphome/components/logger/task_log_buffer_zephyr.h index 2f85f8b6526..c7e3ae55999 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.h +++ b/esphome/components/logger/task_log_buffer_zephyr.h @@ -15,7 +15,7 @@ static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; extern __thread bool non_main_task_recursion_guard_; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -class TaskLogBufferZephyr { +class TaskLogBuffer { public: // Structure for a log message header (text data follows immediately after) struct LogMessage { @@ -36,8 +36,8 @@ class TaskLogBufferZephyr { inline const char *text_data() const { return reinterpret_cast(this) + sizeof(LogMessage); } }; // Constructor that takes a total buffer size - explicit TaskLogBufferZephyr(size_t total_buffer_size); - ~TaskLogBufferZephyr(); + explicit TaskLogBuffer(size_t total_buffer_size); + ~TaskLogBuffer(); // Check if there are messages ready to be processed using an atomic counter for performance inline bool HOT has_messages() { return mpsc_pbuf_is_pending(&this->log_buffer_); } From ccb1d7e5bcec01433410deeeede4bd262e92ebab Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 11 Feb 2026 10:25:16 +0100 Subject: [PATCH 09/22] fix --- esphome/components/logger/logger.h | 63 +----------------- esphome/components/logger/logger_listeners.h | 68 ++++++++++++++++++++ 2 files changed, 70 insertions(+), 61 deletions(-) create mode 100644 esphome/components/logger/logger_listeners.h diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 977019adb5f..5ce6a5cd80c 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -40,52 +40,6 @@ struct device; namespace esphome::logger { -/** Interface for receiving log messages without std::function overhead. - * - * Components can implement this interface instead of using lambdas with std::function - * to reduce flash usage from std::function type erasure machinery. - * - * Usage: - * class MyComponent : public Component, public LogListener { - * public: - * void setup() override { - * if (logger::global_logger != nullptr) - * logger::global_logger->add_log_listener(this); - * } - * void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { - * // Handle log message - * } - * }; - */ -class LogListener { - public: - virtual void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) = 0; -}; - -#ifdef USE_LOGGER_LEVEL_LISTENERS -/** Interface for receiving log level changes without std::function overhead. - * - * Components can implement this interface instead of using lambdas with std::function - * to reduce flash usage from std::function type erasure machinery. - * - * Usage: - * class MyComponent : public Component, public LoggerLevelListener { - * public: - * void setup() override { - * if (logger::global_logger != nullptr) - * logger::global_logger->add_logger_level_listener(this); - * } - * void on_log_level_change(uint8_t level) override { - * // Handle log level change - * } - * }; - */ -class LoggerLevelListener { - public: - virtual void on_log_level_change(uint8_t level) = 0; -}; -#endif - #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS // Comparison function for const char* keys in log_levels_ map struct CStrCompare { @@ -93,6 +47,8 @@ struct CStrCompare { }; #endif +class LogListener; + // Stack buffer size for retrieving thread/task names from the OS // macOS allows up to 64 bytes, Linux up to 16 static constexpr size_t THREAD_NAME_BUF_SIZE = 64; @@ -475,19 +431,4 @@ class Logger : public Component { }; extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -class LoggerMessageTrigger : public Trigger, public LogListener { - public: - explicit LoggerMessageTrigger(Logger *parent, uint8_t level) : level_(level) { parent->add_log_listener(this); } - - void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { - (void) message_len; - if (level <= this->level_) { - this->trigger(level, tag, message); - } - } - - protected: - uint8_t level_; -}; - } // namespace esphome::logger diff --git a/esphome/components/logger/logger_listeners.h b/esphome/components/logger/logger_listeners.h new file mode 100644 index 00000000000..11bf840b13a --- /dev/null +++ b/esphome/components/logger/logger_listeners.h @@ -0,0 +1,68 @@ +#pragma once + +#include "logger.h" + +namespace esphome::logger { + +/** Interface for receiving log messages without std::function overhead. + * + * Components can implement this interface instead of using lambdas with std::function + * to reduce flash usage from std::function type erasure machinery. + * + * Usage: + * class MyComponent : public Component, public LogListener { + * public: + * void setup() override { + * if (logger::global_logger != nullptr) + * logger::global_logger->add_log_listener(this); + * } + * void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { + * // Handle log message + * } + * }; + */ +class LogListener { + public: + virtual void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) = 0; +}; + +#ifdef USE_LOGGER_LEVEL_LISTENERS +/** Interface for receiving log level changes without std::function overhead. + * + * Components can implement this interface instead of using lambdas with std::function + * to reduce flash usage from std::function type erasure machinery. + * + * Usage: + * class MyComponent : public Component, public LoggerLevelListener { + * public: + * void setup() override { + * if (logger::global_logger != nullptr) + * logger::global_logger->add_logger_level_listener(this); + * } + * void on_log_level_change(uint8_t level) override { + * // Handle log level change + * } + * }; + */ +class LoggerLevelListener { + public: + virtual void on_log_level_change(uint8_t level) = 0; +}; +#endif + +class LoggerMessageTrigger : public Trigger, public LogListener { + public: + explicit LoggerMessageTrigger(Logger *parent, uint8_t level) : level_(level) { parent->add_log_listener(this); } + + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { + (void) message_len; + if (level <= this->level_) { + this->trigger(level, tag, message); + } + } + + protected: + uint8_t level_; +}; + +} // namespace esphome::logger From 66a286052624dfaf4439cfd1190f41b6e2bafcc5 Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 11 Feb 2026 10:36:13 +0100 Subject: [PATCH 10/22] fix --- esphome/components/logger/logger.cpp | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index afa1f1957b8..f10efc00709 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -198,20 +198,17 @@ void Logger::process_messages_() { #ifdef USE_ESPHOME_TASK_LOG_BUFFER // Process any buffered messages when available if (this->log_buffer_->has_messages()) { -#ifdef USE_HOST - logger::TaskLogBuffer::LogMessage *message; - while (this->log_buffer_->get_message_main_loop(&message)) { - const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; - LogBuffer buf{this->tx_buffer_, this->tx_buffer_size_}; - this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, message->text, - message->text_length, buf); - this->log_buffer_->release_message_main_loop(); - this->write_log_buffer_to_console_(buf); - } -#elif defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) logger::TaskLogBuffer::LogMessage *message; const char *text; - while (this->log_buffer_->borrow_message_main_loop(&message, &text)) { +#ifdef USE_HOST + while (this->log_buffer_->get_message_main_loop(&message)) +#elif defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) + while (this->log_buffer_->borrow_message_main_loop(&message, &text)) +#endif + { +#ifdef USE_HOST + text = message->text; +#endif const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; LogBuffer buf{this->tx_buffer_, this->tx_buffer_size_}; this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, text, @@ -220,7 +217,6 @@ void Logger::process_messages_() { this->log_buffer_->release_message_main_loop(); this->write_log_buffer_to_console_(buf); } -#endif } #if defined(USE_ESP32) || defined(USE_LIBRETINY) || (defined(USE_ZEPHYR) && !defined(USE_LOGGER_USB_CDC)) else { From d348a3ded0e4b74995343d061976d2fbde95496b Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 11 Feb 2026 11:24:37 +0100 Subject: [PATCH 11/22] fix --- esphome/components/logger/logger.cpp | 22 +++++++----------- esphome/components/logger/logger.h | 3 ++- .../logger/task_log_buffer_esp32.cpp | 6 ++--- .../components/logger/task_log_buffer_esp32.h | 2 +- .../logger/task_log_buffer_host.cpp | 9 +++----- .../components/logger/task_log_buffer_host.h | 6 ++--- .../logger/task_log_buffer_libretiny.cpp | 10 +++----- .../logger/task_log_buffer_libretiny.h | 2 +- .../logger/task_log_buffer_zephyr.cpp | 23 ++++++++++--------- .../logger/task_log_buffer_zephyr.h | 4 +--- 10 files changed, 37 insertions(+), 50 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index f10efc00709..14862389caf 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -177,7 +177,8 @@ void Logger::init_log_buffer(size_t total_buffer_size) { // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed this->log_buffer_ = new logger::TaskLogBuffer(total_buffer_size); -#if defined(USE_ESP32) || defined(USE_LIBRETINY) || (defined(USE_ZEPHYR) && !defined(USE_LOGGER_USB_CDC)) +// Zephyr needs loop working to check when CDC port is open +#if !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) // Start with loop disabled when using task buffer (unless using USB CDC on ESP32) // The loop will be enabled automatically when messages arrive this->disable_loop_when_buffer_empty_(); @@ -199,26 +200,19 @@ void Logger::process_messages_() { // Process any buffered messages when available if (this->log_buffer_->has_messages()) { logger::TaskLogBuffer::LogMessage *message; - const char *text; -#ifdef USE_HOST - while (this->log_buffer_->get_message_main_loop(&message)) -#elif defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) - while (this->log_buffer_->borrow_message_main_loop(&message, &text)) -#endif - { -#ifdef USE_HOST - text = message->text; -#endif + uint16_t text_length; + while (this->log_buffer_->borrow_message_main_loop(message, text_length)) { const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; LogBuffer buf{this->tx_buffer_, this->tx_buffer_size_}; - this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, text, - message->text_length, buf); + this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, + message->text_data(), text_length, buf); // Release the message to allow other tasks to use it as soon as possible this->log_buffer_->release_message_main_loop(); this->write_log_buffer_to_console_(buf); } } -#if defined(USE_ESP32) || defined(USE_LIBRETINY) || (defined(USE_ZEPHYR) && !defined(USE_LOGGER_USB_CDC)) +// Zephyr needs loop working to check when CDC port is open +#if !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) else { // No messages to process, disable loop if appropriate // This reduces overhead when there's no async logging activity diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 5ce6a5cd80c..fe3b226e3bb 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -417,7 +417,8 @@ class Logger : public Component { inline RecursionGuard make_non_main_task_guard_() { return RecursionGuard(non_main_task_recursion_guard_); } #endif -#if defined(USE_ESP32) || defined(USE_LIBRETINY) || (defined(USE_ZEPHYR) && !defined(USE_LOGGER_USB_CDC)) +// Zephyr needs loop working to check when CDC port is open +#if defined(USE_ESPHOME_TASK_LOG_BUFFER) && !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) // Disable loop when task buffer is empty (with USB CDC check on ESP32) inline void disable_loop_when_buffer_empty_() { // Thread safety note: This is safe even if another task calls enable_loop_soon_any_context() diff --git a/esphome/components/logger/task_log_buffer_esp32.cpp b/esphome/components/logger/task_log_buffer_esp32.cpp index d092d5f0e47..921ef16eeee 100644 --- a/esphome/components/logger/task_log_buffer_esp32.cpp +++ b/esphome/components/logger/task_log_buffer_esp32.cpp @@ -31,8 +31,8 @@ TaskLogBuffer::~TaskLogBuffer() { } } -bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char **text) { - if (message == nullptr || text == nullptr || this->current_token_) { +bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &text_length) { + if (this->current_token_) { return false; } @@ -44,7 +44,7 @@ bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char ** LogMessage *msg = static_cast(received_item); *message = msg; - *text = msg->text_data(); + text_length = msg->text_length; this->current_token_ = received_item; return true; diff --git a/esphome/components/logger/task_log_buffer_esp32.h b/esphome/components/logger/task_log_buffer_esp32.h index f837814d3ca..88d72eacfc4 100644 --- a/esphome/components/logger/task_log_buffer_esp32.h +++ b/esphome/components/logger/task_log_buffer_esp32.h @@ -52,7 +52,7 @@ class TaskLogBuffer { ~TaskLogBuffer(); // NOT thread-safe - borrow a message from the ring buffer, only call from main loop - bool borrow_message_main_loop(LogMessage **message, const char **text); + bool borrow_message_main_loop(LogMessage *&message, uint16_t &text_length); // NOT thread-safe - release a message buffer and update the counter, only call from main loop void release_message_main_loop(); diff --git a/esphome/components/logger/task_log_buffer_host.cpp b/esphome/components/logger/task_log_buffer_host.cpp index c98c062b754..c2ab009db47 100644 --- a/esphome/components/logger/task_log_buffer_host.cpp +++ b/esphome/components/logger/task_log_buffer_host.cpp @@ -115,11 +115,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin return true; } -bool TaskLogBuffer::get_message_main_loop(LogMessage **message) { - if (message == nullptr) { - return false; - } - +bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &text_length) { size_t current_read = this->read_index_.load(std::memory_order_relaxed); size_t current_write = this->write_index_.load(std::memory_order_acquire); @@ -134,7 +130,8 @@ bool TaskLogBuffer::get_message_main_loop(LogMessage **message) { return false; } - *message = &msg; + message = &msg; + text_length = msg.text_length; return true; } diff --git a/esphome/components/logger/task_log_buffer_host.h b/esphome/components/logger/task_log_buffer_host.h index c9de381cd88..31bded9c9f1 100644 --- a/esphome/components/logger/task_log_buffer_host.h +++ b/esphome/components/logger/task_log_buffer_host.h @@ -21,12 +21,12 @@ namespace esphome::logger { * * Threading Model: Multi-Producer Single-Consumer (MPSC) * - Multiple threads can safely call send_message_thread_safe() concurrently - * - Only the main loop thread calls get_message_main_loop() and release_message_main_loop() + * - Only the main loop thread calls borrow_message_main_loop() and release_message_main_loop() * * Producers (multiple threads) Consumer (main loop only) * │ │ * ▼ ▼ - * acquire_write_slot_() get_message_main_loop() + * acquire_write_slot_() bool borrow_message_main_loop() * CAS on reserve_index_ read write_index_ * │ check ready flag * ▼ │ @@ -79,7 +79,7 @@ class TaskLogBuffer { // NOT thread-safe - get next message from buffer, only call from main loop // Returns true if a message was retrieved, false if buffer is empty - bool get_message_main_loop(LogMessage **message); + bool borrow_message_main_loop(LogMessage *&message, uint16_t &text_length); // NOT thread-safe - release the message after processing, only call from main loop void release_message_main_loop(); diff --git a/esphome/components/logger/task_log_buffer_libretiny.cpp b/esphome/components/logger/task_log_buffer_libretiny.cpp index d4662bd9077..5969f6fb401 100644 --- a/esphome/components/logger/task_log_buffer_libretiny.cpp +++ b/esphome/components/logger/task_log_buffer_libretiny.cpp @@ -47,11 +47,7 @@ size_t TaskLogBuffer::available_contiguous_space() const { } } -bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char **text) { - if (message == nullptr || text == nullptr) { - return false; - } - +bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &text_length) { // Check if buffer was initialized successfully if (this->mutex_ == nullptr || this->storage_ == nullptr) { return false; @@ -77,8 +73,8 @@ bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char ** this->tail_ = 0; msg = reinterpret_cast(this->storage_); } - *message = msg; - *text = msg->text_data(); + message = msg; + text_length = msg->text_length; this->current_message_size_ = message_total_size(msg->text_length); // Keep mutex held until release_message_main_loop() diff --git a/esphome/components/logger/task_log_buffer_libretiny.h b/esphome/components/logger/task_log_buffer_libretiny.h index 8a5cc87bb35..c065065fe78 100644 --- a/esphome/components/logger/task_log_buffer_libretiny.h +++ b/esphome/components/logger/task_log_buffer_libretiny.h @@ -64,7 +64,7 @@ class TaskLogBuffer { ~TaskLogBuffer(); // NOT thread-safe - borrow a message from the buffer, only call from main loop - bool borrow_message_main_loop(LogMessage **message, const char **text); + bool borrow_message_main_loop(LogMessage *&message, uint16_t &text_length); // NOT thread-safe - release a message buffer, only call from main loop void release_message_main_loop(); diff --git a/esphome/components/logger/task_log_buffer_zephyr.cpp b/esphome/components/logger/task_log_buffer_zephyr.cpp index 174f98d92c5..7a21af1ef16 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.cpp +++ b/esphome/components/logger/task_log_buffer_zephyr.cpp @@ -8,10 +8,13 @@ namespace esphome::logger { __thread bool non_main_task_recursion_guard_; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static inline uint32_t get_wlen(const mpsc_pbuf_generic *item) { - auto *msg = reinterpret_cast(item); +static inline uint32_t total_size_in_32bit_words(uint16_t text_length) { // Calculate total size in 32-bit words needed (header + text length + null terminator + 3(4 bytes alignment) - return (sizeof(TaskLogBuffer::LogMessage) + msg->text_length + 1 + 3) / sizeof(uint32_t); + return (sizeof(TaskLogBuffer::LogMessage) + text_length + 1 + 3) / sizeof(uint32_t); +} + +static inline uint32_t get_wlen(const mpsc_pbuf_generic *item) { + return total_size_in_32bit_words(reinterpret_cast(item)->text_length); } TaskLogBuffer::TaskLogBuffer(size_t total_buffer_size) { @@ -42,8 +45,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin // Calculate actual text length (capped to maximum size) static constexpr size_t MAX_TEXT_SIZE = 255; size_t text_length = (static_cast(ret) > MAX_TEXT_SIZE) ? MAX_TEXT_SIZE : ret; - // Calculate total size in 32-bit words needed (header + text length + null terminator + 3(4 bytes alignment) - size_t total_size = (sizeof(LogMessage) + text_length + 1 + 3) / sizeof(uint32_t); + size_t total_size = total_size_in_32bit_words(text_length); auto *msg = reinterpret_cast(mpsc_pbuf_alloc(&this->log_buffer_, total_size, K_NO_WAIT)); if (nullptr == msg) { return false; @@ -76,7 +78,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin return true; } -bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char **text) { +bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &text_length) { if (this->current_token_) { return false; } @@ -88,13 +90,12 @@ bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char ** } // we claimed buffer alraedy const_cast is safe here - *message = const_cast(reinterpret_cast(this->current_token_)); - - *text = (*message)->text_data(); + message = const_cast(reinterpret_cast(this->current_token_)); + text_length = message->text_length; // Remove trailing newlines - while ((*message)->text_length > 0 && (*text)[(*message)->text_length - 1] == '\n') { - (*message)->text_length--; + while (text_length > 0 && message->text_data()[text_length - 1] == '\n') { + text_length--; } return true; diff --git a/esphome/components/logger/task_log_buffer_zephyr.h b/esphome/components/logger/task_log_buffer_zephyr.h index c7e3ae55999..f5487a1c970 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.h +++ b/esphome/components/logger/task_log_buffer_zephyr.h @@ -32,8 +32,6 @@ class TaskLogBuffer { // Methods for accessing message contents inline char *text_data() { return reinterpret_cast(this) + sizeof(LogMessage); } - - inline const char *text_data() const { return reinterpret_cast(this) + sizeof(LogMessage); } }; // Constructor that takes a total buffer size explicit TaskLogBuffer(size_t total_buffer_size); @@ -46,7 +44,7 @@ class TaskLogBuffer { inline size_t size() const { return this->mpsc_config_.size * sizeof(uint32_t); } // NOT thread-safe - borrow a message from the ring buffer, only call from main loop - bool borrow_message_main_loop(LogMessage **message, const char **text); + bool borrow_message_main_loop(LogMessage *&message, uint16_t &text_length); // NOT thread-safe - release a message buffer and update the counter, only call from main loop void release_message_main_loop(); From 89cc5e91b6b4e9c7d578b1004d0377f736b412f5 Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 11 Feb 2026 11:36:21 +0100 Subject: [PATCH 12/22] fix --- esphome/components/logger/logger.h | 63 ++++++++++++++++- esphome/components/logger/logger_listeners.h | 68 ------------------- .../components/logger/task_log_buffer_host.h | 1 + 3 files changed, 62 insertions(+), 70 deletions(-) delete mode 100644 esphome/components/logger/logger_listeners.h diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index fe3b226e3bb..aa349a29144 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -40,6 +40,52 @@ struct device; namespace esphome::logger { +/** Interface for receiving log messages without std::function overhead. + * + * Components can implement this interface instead of using lambdas with std::function + * to reduce flash usage from std::function type erasure machinery. + * + * Usage: + * class MyComponent : public Component, public LogListener { + * public: + * void setup() override { + * if (logger::global_logger != nullptr) + * logger::global_logger->add_log_listener(this); + * } + * void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { + * // Handle log message + * } + * }; + */ +class LogListener { + public: + virtual void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) = 0; +}; + +#ifdef USE_LOGGER_LEVEL_LISTENERS +/** Interface for receiving log level changes without std::function overhead. + * + * Components can implement this interface instead of using lambdas with std::function + * to reduce flash usage from std::function type erasure machinery. + * + * Usage: + * class MyComponent : public Component, public LoggerLevelListener { + * public: + * void setup() override { + * if (logger::global_logger != nullptr) + * logger::global_logger->add_logger_level_listener(this); + * } + * void on_log_level_change(uint8_t level) override { + * // Handle log level change + * } + * }; + */ +class LoggerLevelListener { + public: + virtual void on_log_level_change(uint8_t level) = 0; +}; +#endif + #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS // Comparison function for const char* keys in log_levels_ map struct CStrCompare { @@ -47,8 +93,6 @@ struct CStrCompare { }; #endif -class LogListener; - // Stack buffer size for retrieving thread/task names from the OS // macOS allows up to 64 bytes, Linux up to 16 static constexpr size_t THREAD_NAME_BUF_SIZE = 64; @@ -432,4 +476,19 @@ class Logger : public Component { }; extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +class LoggerMessageTrigger : public Trigger, public LogListener { + public: + explicit LoggerMessageTrigger(Logger *parent, uint8_t level) : level_(level) { parent->add_log_listener(this); } + + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { + (void) message_len; + if (level <= this->level_) { + this->trigger(level, tag, message); + } + } + + protected: + uint8_t level_; +}; + } // namespace esphome::logger diff --git a/esphome/components/logger/logger_listeners.h b/esphome/components/logger/logger_listeners.h deleted file mode 100644 index 11bf840b13a..00000000000 --- a/esphome/components/logger/logger_listeners.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include "logger.h" - -namespace esphome::logger { - -/** Interface for receiving log messages without std::function overhead. - * - * Components can implement this interface instead of using lambdas with std::function - * to reduce flash usage from std::function type erasure machinery. - * - * Usage: - * class MyComponent : public Component, public LogListener { - * public: - * void setup() override { - * if (logger::global_logger != nullptr) - * logger::global_logger->add_log_listener(this); - * } - * void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { - * // Handle log message - * } - * }; - */ -class LogListener { - public: - virtual void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) = 0; -}; - -#ifdef USE_LOGGER_LEVEL_LISTENERS -/** Interface for receiving log level changes without std::function overhead. - * - * Components can implement this interface instead of using lambdas with std::function - * to reduce flash usage from std::function type erasure machinery. - * - * Usage: - * class MyComponent : public Component, public LoggerLevelListener { - * public: - * void setup() override { - * if (logger::global_logger != nullptr) - * logger::global_logger->add_logger_level_listener(this); - * } - * void on_log_level_change(uint8_t level) override { - * // Handle log level change - * } - * }; - */ -class LoggerLevelListener { - public: - virtual void on_log_level_change(uint8_t level) = 0; -}; -#endif - -class LoggerMessageTrigger : public Trigger, public LogListener { - public: - explicit LoggerMessageTrigger(Logger *parent, uint8_t level) : level_(level) { parent->add_log_listener(this); } - - void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { - (void) message_len; - if (level <= this->level_) { - this->trigger(level, tag, message); - } - } - - protected: - uint8_t level_; -}; - -} // namespace esphome::logger diff --git a/esphome/components/logger/task_log_buffer_host.h b/esphome/components/logger/task_log_buffer_host.h index 31bded9c9f1..1d4d2b0ec1c 100644 --- a/esphome/components/logger/task_log_buffer_host.h +++ b/esphome/components/logger/task_log_buffer_host.h @@ -71,6 +71,7 @@ class TaskLogBuffer { thread_name[0] = '\0'; text[0] = '\0'; } + inline char *text_data() { return this->text; } }; /// Constructor that takes the number of message slots From 2abf013eb463e70a0ef6bcf4f0906cde6e0cdfcc Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 11 Feb 2026 12:15:54 +0100 Subject: [PATCH 13/22] fix --- esphome/components/logger/log_buffer.h | 3 +++ esphome/components/logger/logger.cpp | 2 -- esphome/components/logger/task_log_buffer_esp32.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/components/logger/log_buffer.h b/esphome/components/logger/log_buffer.h index 819b289bcac..3d872782480 100644 --- a/esphome/components/logger/log_buffer.h +++ b/esphome/components/logger/log_buffer.h @@ -1,5 +1,8 @@ #pragma once +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + namespace esphome::logger { // Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 14862389caf..ab7957b3302 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -80,7 +80,6 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li auto guard = this->make_non_main_task_guard_(); bool message_sent = false; -#ifdef USE_ESPHOME_TASK_LOG_BUFFER // For non-main threads/tasks, queue the message for callbacks message_sent = this->log_buffer_->send_message_thread_safe(level, tag, static_cast(line), thread_name, format, args); @@ -89,7 +88,6 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li // This is safe to call from any context including ISRs this->enable_loop_soon_any_context(); } -#endif // USE_ESPHOME_TASK_LOG_BUFFER // Emergency console logging for non-main threads when ring buffer is full or disabled // This is a fallback mechanism to ensure critical log messages are visible diff --git a/esphome/components/logger/task_log_buffer_esp32.cpp b/esphome/components/logger/task_log_buffer_esp32.cpp index 921ef16eeee..e747ddc4d8b 100644 --- a/esphome/components/logger/task_log_buffer_esp32.cpp +++ b/esphome/components/logger/task_log_buffer_esp32.cpp @@ -43,7 +43,7 @@ bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &tex } LogMessage *msg = static_cast(received_item); - *message = msg; + message = msg; text_length = msg->text_length; this->current_token_ = received_item; From eee83b0edf6dda633451b32d281bb1334d2bd76d Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 11 Feb 2026 12:27:03 +0100 Subject: [PATCH 14/22] fix --- esphome/components/logger/task_log_buffer_zephyr.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/logger/task_log_buffer_zephyr.cpp b/esphome/components/logger/task_log_buffer_zephyr.cpp index 7a21af1ef16..50b9ca984de 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.cpp +++ b/esphome/components/logger/task_log_buffer_zephyr.cpp @@ -54,6 +54,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin msg->tag = tag; msg->line = line; strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1); + msg->thread_name[sizeof(msg->thread_name) - 1] = '\0'; // Ensure null termination // Format the message text directly into the acquired memory // We add 1 to text_length to ensure space for null terminator during formatting From 054716fa5192b6a87048856f3622927c47eab528 Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 11 Feb 2026 12:54:35 +0100 Subject: [PATCH 15/22] fix --- esphome/components/logger/__init__.py | 2 +- esphome/components/logger/logger.cpp | 21 +++++++------------ esphome/components/logger/logger.h | 2 +- .../logger/task_log_buffer_zephyr.cpp | 7 +++---- .../logger/task_log_buffer_zephyr.h | 4 ++-- .../logger/test.nrf52-adafruit.yaml | 1 + 6 files changed, 16 insertions(+), 21 deletions(-) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 8de52db0cb9..b2952d79956 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -326,7 +326,6 @@ async def to_code(config): cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER") cg.add(log.init_log_buffer(task_log_buffer_size)) if CORE.using_zephyr: - zephyr_add_prj_conf("THREAD_LOCAL_STORAGE", True) zephyr_add_prj_conf("MPSC_PBUF", True) elif CORE.is_host: cg.add(log.create_pthread_key()) @@ -427,6 +426,7 @@ async def to_code(config): pass if CORE.is_nrf52: + zephyr_add_prj_conf("THREAD_LOCAL_STORAGE", True) if config[CONF_HARDWARE_UART] == UART0: zephyr_add_overlay("""&uart0 { status = "okay";};""") if config[CONF_HARDWARE_UART] == UART1: diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index ab7957b3302..74d93daed38 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -10,7 +10,7 @@ namespace esphome::logger { static const char *const TAG = "logger"; -#ifdef USE_ESPHOME_TASK_LOG_BUFFER +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) // Implementation for multi-threaded platforms (ESP32 with FreeRTOS, Host with pthreads, LibreTiny with FreeRTOS, // Zephyr) Main thread/task always uses direct buffer access for console output and callbacks // @@ -80,6 +80,7 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li auto guard = this->make_non_main_task_guard_(); bool message_sent = false; +#ifdef USE_ESPHOME_TASK_LOG_BUFFER // For non-main threads/tasks, queue the message for callbacks message_sent = this->log_buffer_->send_message_thread_safe(level, tag, static_cast(line), thread_name, format, args); @@ -88,7 +89,7 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li // This is safe to call from any context including ISRs this->enable_loop_soon_any_context(); } - +#endif // Emergency console logging for non-main threads when ring buffer is full or disabled // This is a fallback mechanism to ensure critical log messages are visible // Note: This may cause interleaved/corrupted console output if multiple threads @@ -115,22 +116,16 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li // RAII guard automatically resets on return } #else -// Implementation for single-task platforms (ESP8266, RP2040, Zephyr) -// TODO: Zephyr may have multiple threads (work queues, etc.) but uses this single-task path. +// Implementation for single-task platforms (ESP8266, RP2040) // Logging calls are NOT thread-safe: global_recursion_guard_ is a plain bool and tx_buffer_ has no locking. // Not a problem in practice yet since Zephyr has no API support (logs are console-only). void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT if (level > this->level_for(tag) || global_recursion_guard_) return; -#ifdef USE_ZEPHYR - char tmp[MAX_POINTER_REPRESENTATION]; - this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, - this->get_thread_name_(tmp)); -#else // Other single-task platforms don't have thread names, so pass nullptr + // Other single-task platforms don't have thread names, so pass nullptr this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, nullptr); -#endif } -#endif // USE_ESP32 / USE_HOST / USE_LIBRETINY +#endif // USE_ESPHOME_TASK_LOG_BUFFER #ifdef USE_STORE_LOG_STR_IN_FLASH // Implementation for ESP8266 with flash string support. @@ -176,7 +171,7 @@ void Logger::init_log_buffer(size_t total_buffer_size) { this->log_buffer_ = new logger::TaskLogBuffer(total_buffer_size); // Zephyr needs loop working to check when CDC port is open -#if !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) +#if defined(USE_ESPHOME_TASK_LOG_BUFFER) && !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) // Start with loop disabled when using task buffer (unless using USB CDC on ESP32) // The loop will be enabled automatically when messages arrive this->disable_loop_when_buffer_empty_(); @@ -210,7 +205,7 @@ void Logger::process_messages_() { } } // Zephyr needs loop working to check when CDC port is open -#if !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) +#if defined(USE_ESPHOME_TASK_LOG_BUFFER) && !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) else { // No messages to process, disable loop if appropriate // This reduces overhead when there's no async logging activity diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index aa349a29144..4c50acf2874 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -222,7 +222,7 @@ class Logger : public Component { bool &flag_; }; -#ifdef USE_ESPHOME_TASK_LOG_BUFFER +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) // Handles non-main thread logging only (~0.1% of calls) // thread_name is resolved by the caller from the task handle, avoiding redundant lookups void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args, diff --git a/esphome/components/logger/task_log_buffer_zephyr.cpp b/esphome/components/logger/task_log_buffer_zephyr.cpp index 50b9ca984de..3fc0eabf813 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.cpp +++ b/esphome/components/logger/task_log_buffer_zephyr.cpp @@ -2,12 +2,12 @@ #include "task_log_buffer_zephyr.h" -#ifdef USE_ESPHOME_TASK_LOG_BUFFER - namespace esphome::logger { __thread bool non_main_task_recursion_guard_; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + static inline uint32_t total_size_in_32bit_words(uint16_t text_length) { // Calculate total size in 32-bit words needed (header + text length + null terminator + 3(4 bytes alignment) return (sizeof(TaskLogBuffer::LogMessage) + text_length + 1 + 3) / sizeof(uint32_t); @@ -109,7 +109,6 @@ void TaskLogBuffer::release_message_main_loop() { mpsc_pbuf_free(&this->log_buffer_, this->current_token_); this->current_token_ = nullptr; } +#endif } // namespace esphome::logger - -#endif #endif diff --git a/esphome/components/logger/task_log_buffer_zephyr.h b/esphome/components/logger/task_log_buffer_zephyr.h index f5487a1c970..0e82aaf5b7a 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.h +++ b/esphome/components/logger/task_log_buffer_zephyr.h @@ -11,10 +11,10 @@ namespace esphome::logger { // "0x" + 2 hex digits per byte + '\0' static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; -#ifdef USE_ESPHOME_TASK_LOG_BUFFER - extern __thread bool non_main_task_recursion_guard_; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + class TaskLogBuffer { public: // Structure for a log message header (text data follows immediately after) diff --git a/tests/components/logger/test.nrf52-adafruit.yaml b/tests/components/logger/test.nrf52-adafruit.yaml index 70b485daac2..821a1362507 100644 --- a/tests/components/logger/test.nrf52-adafruit.yaml +++ b/tests/components/logger/test.nrf52-adafruit.yaml @@ -5,3 +5,4 @@ esphome: logger: level: DEBUG + task_log_buffer_size: 0 From 22cd2e82746e26393fc89972bbed37bc7ee5cec9 Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 11 Feb 2026 13:08:07 +0100 Subject: [PATCH 16/22] fix --- esphome/components/logger/logger.cpp | 2 +- esphome/components/logger/logger.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 74d93daed38..484761a9213 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -171,7 +171,7 @@ void Logger::init_log_buffer(size_t total_buffer_size) { this->log_buffer_ = new logger::TaskLogBuffer(total_buffer_size); // Zephyr needs loop working to check when CDC port is open -#if defined(USE_ESPHOME_TASK_LOG_BUFFER) && !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) && !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) // Start with loop disabled when using task buffer (unless using USB CDC on ESP32) // The loop will be enabled automatically when messages arrive this->disable_loop_when_buffer_empty_(); diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 4c50acf2874..90a0d73aee3 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -462,7 +462,7 @@ class Logger : public Component { #endif // Zephyr needs loop working to check when CDC port is open -#if defined(USE_ESPHOME_TASK_LOG_BUFFER) && !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) && !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) // Disable loop when task buffer is empty (with USB CDC check on ESP32) inline void disable_loop_when_buffer_empty_() { // Thread safety note: This is safe even if another task calls enable_loop_soon_any_context() From d1033e25fef3da1d73f95e070314f415e2e1c7d0 Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 11 Feb 2026 13:09:03 +0100 Subject: [PATCH 17/22] fix --- esphome/components/logger/logger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 484761a9213..eaaf312ba9a 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -205,7 +205,7 @@ void Logger::process_messages_() { } } // Zephyr needs loop working to check when CDC port is open -#if defined(USE_ESPHOME_TASK_LOG_BUFFER) && !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) +#if !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) else { // No messages to process, disable loop if appropriate // This reduces overhead when there's no async logging activity From 94471be18bfca71cfe2771288eef4d0ccf01d36d Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 11 Feb 2026 13:18:39 +0100 Subject: [PATCH 18/22] fix --- esphome/components/logger/logger.cpp | 2 +- esphome/components/logger/logger.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index eaaf312ba9a..5dedf336251 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -171,7 +171,7 @@ void Logger::init_log_buffer(size_t total_buffer_size) { this->log_buffer_ = new logger::TaskLogBuffer(total_buffer_size); // Zephyr needs loop working to check when CDC port is open -#if defined(USE_ESP32) || defined(USE_LIBRETINY) && !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) +#if !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) // Start with loop disabled when using task buffer (unless using USB CDC on ESP32) // The loop will be enabled automatically when messages arrive this->disable_loop_when_buffer_empty_(); diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 90a0d73aee3..018f7655cfd 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -358,7 +358,7 @@ class Logger : public Component { #ifdef USE_LIBRETINY UARTSelection uart_{UART_SELECTION_DEFAULT}; #endif -#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || USE_ZEPHYR +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) bool main_task_recursion_guard_{false}; #ifdef USE_LIBRETINY bool non_main_task_recursion_guard_{false}; // Shared guard for all non-main tasks on LibreTiny @@ -462,7 +462,7 @@ class Logger : public Component { #endif // Zephyr needs loop working to check when CDC port is open -#if defined(USE_ESP32) || defined(USE_LIBRETINY) && !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) +#if defined(USE_ESPHOME_TASK_LOG_BUFFER) && !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC)) // Disable loop when task buffer is empty (with USB CDC check on ESP32) inline void disable_loop_when_buffer_empty_() { // Thread safety note: This is safe even if another task calls enable_loop_soon_any_context() From 1050d083b4fdd1fdabe853e546d47f37f37afc3a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Feb 2026 08:34:17 -0600 Subject: [PATCH 19/22] cleanup ifdefs and typos --- esphome/components/logger/logger.cpp | 2 +- esphome/components/logger/logger.h | 2 ++ .../components/logger/task_log_buffer_zephyr.cpp | 14 ++++++++------ esphome/components/logger/task_log_buffer_zephyr.h | 6 +++--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 5dedf336251..e1b49bcb614 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -125,7 +125,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch // Other single-task platforms don't have thread names, so pass nullptr this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, nullptr); } -#endif // USE_ESPHOME_TASK_LOG_BUFFER +#endif // USE_ESP32 || USE_HOST || USE_LIBRETINY || USE_ZEPHYR #ifdef USE_STORE_LOG_STR_IN_FLASH // Implementation for ESP8266 with flash string support. diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 018f7655cfd..835542dd8f1 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -228,7 +228,9 @@ class Logger : public Component { void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args, const char *thread_name); #endif +#if defined(USE_ZEPHYR) && defined(USE_LOGGER_USB_CDC) void cdc_loop_(); +#endif void process_messages_(); void write_msg_(const char *msg, uint16_t len); diff --git a/esphome/components/logger/task_log_buffer_zephyr.cpp b/esphome/components/logger/task_log_buffer_zephyr.cpp index 3fc0eabf813..71c75231b72 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.cpp +++ b/esphome/components/logger/task_log_buffer_zephyr.cpp @@ -47,7 +47,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin size_t text_length = (static_cast(ret) > MAX_TEXT_SIZE) ? MAX_TEXT_SIZE : ret; size_t total_size = total_size_in_32bit_words(text_length); auto *msg = reinterpret_cast(mpsc_pbuf_alloc(&this->log_buffer_, total_size, K_NO_WAIT)); - if (nullptr == msg) { + if (msg == nullptr) { return false; } msg->level = level; @@ -63,7 +63,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin // Handle unexpected formatting error if (ret <= 0) { - // this shall not happened vsnprintf was called already once + // this should not happen, vsnprintf was called already once // fill with '\n' to not call mpsc_pbuf_free from producer // it will be trimmed anyway for (size_t i = 0; i < text_length; ++i) { @@ -86,11 +86,11 @@ bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &tex this->current_token_ = mpsc_pbuf_claim(&this->log_buffer_); - if (nullptr == this->current_token_) { + if (this->current_token_ == nullptr) { return false; } - // we claimed buffer alraedy const_cast is safe here + // we claimed buffer already, const_cast is safe here message = const_cast(reinterpret_cast(this->current_token_)); text_length = message->text_length; @@ -109,6 +109,8 @@ void TaskLogBuffer::release_message_main_loop() { mpsc_pbuf_free(&this->log_buffer_, this->current_token_); this->current_token_ = nullptr; } -#endif +#endif // USE_ESPHOME_TASK_LOG_BUFFER + } // namespace esphome::logger -#endif + +#endif // USE_ZEPHYR diff --git a/esphome/components/logger/task_log_buffer_zephyr.h b/esphome/components/logger/task_log_buffer_zephyr.h index 0e82aaf5b7a..cc2ed1f6872 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.h +++ b/esphome/components/logger/task_log_buffer_zephyr.h @@ -19,7 +19,7 @@ class TaskLogBuffer { public: // Structure for a log message header (text data follows immediately after) struct LogMessage { - MPSC_PBUF_HDR; // this is only 2 bits put not more that 30 bits directly after + MPSC_PBUF_HDR; // this is only 2 bits but no more than 30 bits directly after uint16_t line; // Source code line number uint8_t level; // Log level (0-7) #if defined(CONFIG_THREAD_NAME) @@ -59,8 +59,8 @@ class TaskLogBuffer { const mpsc_pbuf_generic *current_token_{}; }; -#endif +#endif // USE_ESPHOME_TASK_LOG_BUFFER } // namespace esphome::logger -#endif +#endif // USE_ZEPHYR From 4f7d4e1fd3454939d63715ef4f4e558853a639e3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Feb 2026 08:36:38 -0600 Subject: [PATCH 20/22] make sure its > 0 in test --- tests/components/logger/test.nrf52-adafruit.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/logger/test.nrf52-adafruit.yaml b/tests/components/logger/test.nrf52-adafruit.yaml index 821a1362507..3914fe7db9e 100644 --- a/tests/components/logger/test.nrf52-adafruit.yaml +++ b/tests/components/logger/test.nrf52-adafruit.yaml @@ -5,4 +5,4 @@ esphome: logger: level: DEBUG - task_log_buffer_size: 0 + task_log_buffer_size: 768 From a451491b476def15acfc3a89605b0793ac6d1103 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Feb 2026 08:46:30 -0600 Subject: [PATCH 21/22] tweak semantics to match --- esphome/components/logger/task_log_buffer_zephyr.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/logger/task_log_buffer_zephyr.cpp b/esphome/components/logger/task_log_buffer_zephyr.cpp index 71c75231b72..44d12d08a3b 100644 --- a/esphome/components/logger/task_log_buffer_zephyr.cpp +++ b/esphome/components/logger/task_log_buffer_zephyr.cpp @@ -61,8 +61,8 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin char *text_area = msg->text_data(); ret = vsnprintf(text_area, text_length + 1, format, args); - // Handle unexpected formatting error - if (ret <= 0) { + // Handle unexpected formatting error (ret < 0 is encoding error; ret == 0 is valid empty output) + if (ret < 0) { // this should not happen, vsnprintf was called already once // fill with '\n' to not call mpsc_pbuf_free from producer // it will be trimmed anyway From 8b53f9a649131aa8a2e092787fd5f88cef5c73d6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Feb 2026 08:48:22 -0600 Subject: [PATCH 22/22] revert to 0 disabled to test off path --- tests/components/logger/test.nrf52-adafruit.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/logger/test.nrf52-adafruit.yaml b/tests/components/logger/test.nrf52-adafruit.yaml index 3914fe7db9e..821a1362507 100644 --- a/tests/components/logger/test.nrf52-adafruit.yaml +++ b/tests/components/logger/test.nrf52-adafruit.yaml @@ -5,4 +5,4 @@ esphome: logger: level: DEBUG - task_log_buffer_size: 768 + task_log_buffer_size: 0