From 068fc3476b4c9d93ff6d1a8545a811a3f18865f9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 3 Feb 2026 17:08:25 +0100 Subject: [PATCH 01/28] [logger] Extract shared helpers to reduce code duplication --- esphome/components/logger/logger.cpp | 22 +----------- esphome/components/logger/logger.h | 54 +++++++++++++++++++--------- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 25243ff3f6..ac37a7bbbf 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -136,27 +136,7 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas return; RecursionGuard guard(global_recursion_guard_); - this->tx_buffer_at_ = 0; - - // Write header, format body directly from flash, and write footer - this->write_header_to_buffer_(level, tag, line, nullptr, this->tx_buffer_, &this->tx_buffer_at_, - this->tx_buffer_size_); - this->format_body_to_buffer_P_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_, - reinterpret_cast(format), args); - this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); - - // Ensure null termination - uint16_t null_pos = this->tx_buffer_at_ >= this->tx_buffer_size_ ? this->tx_buffer_size_ - 1 : this->tx_buffer_at_; - this->tx_buffer_[null_pos] = '\0'; - - // Listeners get message first (before console write) -#ifdef USE_LOG_LISTENERS - for (auto *listener : this->log_listeners_) - listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_); -#endif - - // Write to console - this->write_tx_buffer_to_console_(); + this->log_message_to_buffer_and_send_P_(level, tag, line, reinterpret_cast(format), args); } #endif // USE_STORE_LOG_STR_IN_FLASH diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 40ac9a38aa..288de2eb81 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -277,14 +277,7 @@ class Logger : public Component { #endif this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, args); this->write_footer_to_buffer_(buffer, buffer_at, buffer_size); - - // Always ensure the buffer has a null terminator, even if we need to - // overwrite the last character of the actual content - if (*buffer_at >= buffer_size) { - buffer[buffer_size - 1] = '\0'; // Truncate and ensure null termination - } else { - buffer[*buffer_at] = '\0'; // Normal case, append null terminator - } + ensure_null_terminated_(buffer, *buffer_at, buffer_size); } // Helper to add newline to buffer before writing to console @@ -314,14 +307,13 @@ class Logger : public Component { } } - // Helper to format and send a log message to both console and listeners - inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format, - va_list args) { - // Format to tx_buffer and prepare for output - this->tx_buffer_at_ = 0; // Initialize buffer position - this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, this->tx_buffer_, &this->tx_buffer_at_, - this->tx_buffer_size_); + // Ensure tx_buffer_ has null termination (truncate if buffer was full) + inline void HOT ensure_tx_buffer_null_terminated_() { + ensure_null_terminated_(this->tx_buffer_, this->tx_buffer_at_, this->tx_buffer_size_); + } + // Helper to notify listeners and write to console - shared by all log_vprintf_ variants + inline void HOT notify_listeners_and_send_(uint8_t level, const char *tag) { // Listeners get message WITHOUT newline (for API/MQTT/syslog) #ifdef USE_LOG_LISTENERS for (auto *listener : this->log_listeners_) @@ -332,6 +324,30 @@ class Logger : public Component { this->write_tx_buffer_to_console_(); } + // Helper to format and send a log message to both console and listeners + inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format, + va_list args) { + // Format to tx_buffer and prepare for output + this->tx_buffer_at_ = 0; // Initialize buffer position + this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, this->tx_buffer_, &this->tx_buffer_at_, + this->tx_buffer_size_); + this->notify_listeners_and_send_(level, tag); + } + +#ifdef USE_STORE_LOG_STR_IN_FLASH + // ESP8266 variant: format flash string and send to both console and listeners + inline void HOT log_message_to_buffer_and_send_P_(uint8_t level, const char *tag, int line, PGM_P format, + va_list args) { + this->tx_buffer_at_ = 0; + this->write_header_to_buffer_(level, tag, line, nullptr, this->tx_buffer_, &this->tx_buffer_at_, + this->tx_buffer_size_); + this->format_body_to_buffer_P_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_, format, args); + this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); + this->ensure_tx_buffer_null_terminated_(); + this->notify_listeners_and_send_(level, tag); + } +#endif + #ifdef USE_ESPHOME_TASK_LOG_BUFFER // Helper to format a pre-formatted message from the task log buffer and notify listeners // Used by process_messages_ to avoid code duplication between ESP32 and host platforms @@ -342,7 +358,7 @@ class Logger : public Component { this->tx_buffer_size_); this->write_body_to_buffer_(text, text_length, this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); - this->tx_buffer_[this->tx_buffer_at_] = '\0'; + this->ensure_tx_buffer_null_terminated_(); #ifdef USE_LOG_LISTENERS for (auto *listener : this->log_listeners_) listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_); @@ -525,6 +541,12 @@ class Logger : public Component { } #endif + // Ensure buffer has null termination (truncate if buffer was full) + static inline void ensure_null_terminated_(char *buffer, uint16_t buffer_at, uint16_t buffer_size) { + uint16_t null_pos = buffer_at >= buffer_size ? buffer_size - 1 : buffer_at; + buffer[null_pos] = '\0'; + } + static inline void copy_string(char *buffer, uint16_t &pos, const char *str) { const size_t len = strlen(str); // Intentionally no null terminator, building larger string From a5869c2a9caa7fac7bc8a04628f1ba81db04eb8a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 05:50:41 +0100 Subject: [PATCH 02/28] wip --- esphome/components/logger/logger.cpp | 36 +- esphome/components/logger/logger.h | 388 ++++++++---------- esphome/components/logger/logger_esp32.cpp | 8 +- esphome/components/logger/logger_esp8266.cpp | 4 +- esphome/components/logger/logger_host.cpp | 6 +- .../components/logger/logger_libretiny.cpp | 2 +- esphome/components/logger/logger_rp2040.cpp | 4 +- esphome/components/logger/logger_zephyr.cpp | 8 +- 8 files changed, 210 insertions(+), 246 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index ac37a7bbbf..f79508b0cc 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -101,12 +101,12 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144; #endif char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety - uint16_t buffer_at = 0; // Initialize buffer position - this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at, - MAX_CONSOLE_LOG_MSG_SIZE); + uint16_t console_pos; + LogBuffer buf(console_buffer, console_pos, MAX_CONSOLE_LOG_MSG_SIZE); + this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf); // Add newline before writing to console - this->add_newline_to_buffer_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE); - this->write_msg_(console_buffer, buffer_at); + buf.add_newline(); + this->write_msg_(buf); } // RAII guard automatically resets on return @@ -118,8 +118,10 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch return; RecursionGuard guard(global_recursion_guard_); - // Format and send to both console and callbacks - this->log_message_to_buffer_and_send_(level, tag, line, format, args); + LogBuffer buf(this->tx_buffer_, this->tx_buffer_at_, this->tx_buffer_size_); + this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf); + this->notify_listeners_(level, tag); + this->write_log_buffer_to_console_(buf); } #endif // USE_ESP32 / USE_HOST / USE_LIBRETINY @@ -136,7 +138,10 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas return; RecursionGuard guard(global_recursion_guard_); - this->log_message_to_buffer_and_send_P_(level, tag, line, reinterpret_cast(format), args); + LogBuffer buf(this->tx_buffer_, this->tx_buffer_at_, this->tx_buffer_size_); + this->format_log_to_buffer_with_terminator_P_(level, tag, line, format, args, buf); + this->notify_listeners_(level, tag); + this->write_log_buffer_to_console_(buf); } #endif // USE_STORE_LOG_STR_IN_FLASH @@ -195,10 +200,11 @@ void Logger::process_messages_() { logger::TaskLogBufferHost::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_at_, this->tx_buffer_size_); this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, message->text, - message->text_length); + message->text_length, buf); this->log_buffer_->release_message_main_loop(); - this->write_tx_buffer_to_console_(); + this->write_log_buffer_to_console_(buf); } #elif defined(USE_ESP32) logger::TaskLogBuffer::LogMessage *message; @@ -206,22 +212,24 @@ void Logger::process_messages_() { 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_at_, this->tx_buffer_size_); this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, text, - message->text_length); + 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_tx_buffer_to_console_(); + this->write_log_buffer_to_console_(buf); } #elif defined(USE_LIBRETINY) logger::TaskLogBufferLibreTiny::LogMessage *message; 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; + LogBuffer buf(this->tx_buffer_, this->tx_buffer_at_, this->tx_buffer_size_); this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, text, - message->text_length); + 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(); - this->write_tx_buffer_to_console_(); + this->write_log_buffer_to_console_(buf); } #endif } diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 288de2eb81..5c59f476b1 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -40,6 +40,11 @@ struct device; #endif +// Platforms that support thread names in log output +#ifdef USE_LOGGER_THREAD_NAME +#define USE_LOGGER_THREAD_NAME +#endif + namespace esphome::logger { /** Interface for receiving log messages without std::function overhead. @@ -123,6 +128,140 @@ 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; + uint16_t &pos; + uint16_t size; + + LogBuffer(char *buf, uint16_t &buf_pos, uint16_t buf_size) : data(buf), pos(buf_pos), size(buf_size) { + this->pos = 0; + } + uint16_t remaining() const { return this->size - this->pos; } + char *current() { return this->data + this->pos; } + void null_terminate() { this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; } + void write(const char *value, size_t length) { + if (this->full_()) + return; + const uint16_t available = this->remaining(); + const size_t copy_len = (length < static_cast(available)) ? length : available; + if (copy_len > 0) { + memcpy(this->current(), value, copy_len); + this->pos += copy_len; + } + } + void add_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 write_color_reset() { + static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1; + this->write(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN); + } + 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; + + // Construct: [LEVEL][tag:line]: + this->write_ansi_color_(level); + this->put_char_('['); + if (level != 0) { + if (level >= 7) { + this->put_char_('V'); // VERY_VERBOSE = "VV" + this->put_char_('V'); + } else { + this->put_char_(LOG_LEVEL_LETTER_CHARS[level]); + } + } + this->put_char_(']'); + this->put_char_('['); + this->copy_string_(tag); + this->put_char_(':'); + // Format line number without modulo operations (passed by value, safe to mutate) + if (line > 999) [[unlikely]] { + int thousands = line / 1000; + this->put_char_('0' + thousands); + line -= thousands * 1000; + } + int hundreds = line / 100; + int remainder = line - hundreds * 100; + int tens = remainder / 10; + this->put_char_('0' + hundreds); + this->put_char_('0' + tens); + this->put_char_('0' + (remainder - tens * 10)); + this->put_char_(']'); + +#ifdef USE_LOGGER_THREAD_NAME + this->write_thread_name_(thread_name, level); +#endif + + this->put_char_(':'); + this->put_char_(' '); + } + void HOT format_body(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 HOT format_body_P(PGM_P format, va_list args) { + if (this->full_()) + return; + this->process_vsnprintf_result_(vsnprintf_P(this->current(), this->remaining(), format, args)); + } +#endif + + private: + bool full_() const { return this->pos >= this->size; } + void put_char_(char c) { this->data[this->pos++] = c; } + void strip_trailing_newlines_() { + while (this->pos > 0 && this->data[this->pos - 1] == '\n') + this->pos--; + } + __attribute__((always_inline)) 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 copy_string_(const char *str) { + const size_t len = strlen(str); + memcpy(this->current(), str, len); // NOLINT(bugprone-not-null-terminated-result) + this->pos += len; + } + void write_ansi_color_(uint8_t level) { + if (level == 0) + return; + // Construct ANSI escape sequence: "\033[{bold};3{color}m" + // Example: "\033[1;31m" for ERROR (bold red) + this->put_char_('\033'); + this->put_char_('['); + this->put_char_((level == 1) ? '1' : '0'); // Only ERROR is bold + this->put_char_(';'); + this->put_char_('3'); + this->put_char_(LOG_LEVEL_COLOR_DIGIT[level]); + this->put_char_('m'); + } +#ifdef USE_LOGGER_THREAD_NAME + void write_thread_name_(const char *thread_name, uint8_t level) { + if (thread_name == nullptr) + return; + this->write_ansi_color_(1); // Always use bold red for thread name + this->put_char_('['); + this->copy_string_(thread_name); + this->put_char_(']'); + this->write_ansi_color_(level); // Restore original color + } +#endif +}; + #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) /** Enum for logging UART selection * @@ -260,130 +399,66 @@ class Logger : public Component { #endif #endif void process_messages_(); - void write_msg_(const char *msg, size_t len); + void write_msg_(const LogBuffer &buf); // Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator - // It's the caller's responsibility to initialize buffer_at (typically to 0) inline void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format, - va_list args, char *buffer, uint16_t *buffer_at, - uint16_t buffer_size) { + va_list args, LogBuffer &buf) { #if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_HOST) - this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(), buffer, buffer_at, buffer_size); + buf.write_header(level, tag, line, this->get_thread_name_()); #elif defined(USE_ZEPHYR) - char buff[MAX_POINTER_REPRESENTATION]; - this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(buff), buffer, buffer_at, buffer_size); + char tmp[MAX_POINTER_REPRESENTATION]; + buf.write_header(level, tag, line, this->get_thread_name_(tmp)); #else - this->write_header_to_buffer_(level, tag, line, nullptr, buffer, buffer_at, buffer_size); + buf.write_header(level, tag, line, nullptr); #endif - this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, args); - this->write_footer_to_buffer_(buffer, buffer_at, buffer_size); - ensure_null_terminated_(buffer, *buffer_at, buffer_size); + buf.format_body(format, args); + buf.write_color_reset(); + buf.null_terminate(); } - // Helper to add newline to buffer before writing to console - // Modifies buffer_at to include the newline - inline void HOT add_newline_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { - // Add newline - don't need to maintain null termination - // write_msg_ receives explicit length, so we can safely overwrite the null terminator - // This is safe because: - // 1. Callbacks already received the message (before we add newline) - // 2. write_msg_ receives the length explicitly (doesn't need null terminator) - if (*buffer_at < buffer_size) { - buffer[(*buffer_at)++] = '\n'; - } else if (buffer_size > 0) { - // Buffer was full - replace last char with newline to ensure it's visible - buffer[buffer_size - 1] = '\n'; - *buffer_at = buffer_size; - } +#ifdef USE_STORE_LOG_STR_IN_FLASH + // Format a log message with flash string format and write it to a buffer with header, footer, and null terminator + inline void HOT format_log_to_buffer_with_terminator_P_(uint8_t level, const char *tag, int line, + const __FlashStringHelper *format, va_list args, + LogBuffer &buf) { + buf.write_header(level, tag, line, nullptr); + buf.format_body_P(reinterpret_cast(format), args); + buf.write_color_reset(); + buf.null_terminate(); } +#endif - // Helper to write tx_buffer_ to console if logging is enabled - // INTERNAL USE ONLY - offset > 0 requires length parameter to be non-null - inline void HOT write_tx_buffer_to_console_(uint16_t offset = 0, uint16_t *length = nullptr) { - if (this->baud_rate_ > 0) { - uint16_t *len_ptr = length ? length : &this->tx_buffer_at_; - this->add_newline_to_buffer_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset); - this->write_msg_(this->tx_buffer_ + offset, *len_ptr); - } - } - - // Ensure tx_buffer_ has null termination (truncate if buffer was full) - inline void HOT ensure_tx_buffer_null_terminated_() { - ensure_null_terminated_(this->tx_buffer_, this->tx_buffer_at_, this->tx_buffer_size_); - } - - // Helper to notify listeners and write to console - shared by all log_vprintf_ variants - inline void HOT notify_listeners_and_send_(uint8_t level, const char *tag) { - // Listeners get message WITHOUT newline (for API/MQTT/syslog) + // Helper to notify log listeners + inline void HOT notify_listeners_(uint8_t level, const char *tag) { #ifdef USE_LOG_LISTENERS for (auto *listener : this->log_listeners_) listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_); #endif - - // Console gets message WITH newline (if platform needs it) - this->write_tx_buffer_to_console_(); } - // Helper to format and send a log message to both console and listeners - inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format, - va_list args) { - // Format to tx_buffer and prepare for output - this->tx_buffer_at_ = 0; // Initialize buffer position - this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, this->tx_buffer_, &this->tx_buffer_at_, - this->tx_buffer_size_); - this->notify_listeners_and_send_(level, tag); + // Helper to write log buffer to console if logging is enabled + inline void HOT write_log_buffer_to_console_(LogBuffer &buf) { + if (this->baud_rate_ > 0) { + buf.add_newline(); + this->write_msg_(buf); + } } -#ifdef USE_STORE_LOG_STR_IN_FLASH - // ESP8266 variant: format flash string and send to both console and listeners - inline void HOT log_message_to_buffer_and_send_P_(uint8_t level, const char *tag, int line, PGM_P format, - va_list args) { - this->tx_buffer_at_ = 0; - this->write_header_to_buffer_(level, tag, line, nullptr, this->tx_buffer_, &this->tx_buffer_at_, - this->tx_buffer_size_); - this->format_body_to_buffer_P_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_, format, args); - this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); - this->ensure_tx_buffer_null_terminated_(); - this->notify_listeners_and_send_(level, tag); - } -#endif - #ifdef USE_ESPHOME_TASK_LOG_BUFFER // Helper to format a pre-formatted message from the task log buffer and notify listeners // Used by process_messages_ to avoid code duplication between ESP32 and host platforms inline void HOT format_buffered_message_and_notify_(uint8_t level, const char *tag, uint16_t line, - const char *thread_name, const char *text, size_t text_length) { - this->tx_buffer_at_ = 0; - this->write_header_to_buffer_(level, tag, line, thread_name, this->tx_buffer_, &this->tx_buffer_at_, - this->tx_buffer_size_); - this->write_body_to_buffer_(text, text_length, this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); - this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); - this->ensure_tx_buffer_null_terminated_(); -#ifdef USE_LOG_LISTENERS - for (auto *listener : this->log_listeners_) - listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_); -#endif + const char *thread_name, const char *text, size_t text_length, + LogBuffer &buf) { + buf.write_header(level, tag, line, thread_name); + buf.write(text, text_length); + buf.write_color_reset(); + buf.null_terminate(); + this->notify_listeners_(level, tag); } #endif - // Write the body of the log message to the buffer - inline void write_body_to_buffer_(const char *value, size_t length, char *buffer, uint16_t *buffer_at, - uint16_t buffer_size) { - // Calculate available space - if (*buffer_at >= buffer_size) - return; - const uint16_t available = buffer_size - *buffer_at; - - // Determine copy length (minimum of remaining capacity and string length) - const size_t copy_len = (length < static_cast(available)) ? length : available; - - // Copy the data - if (copy_len > 0) { - memcpy(buffer + *buffer_at, value, copy_len); - *buffer_at += copy_len; - } - } - #ifndef USE_HOST const LogString *get_uart_selection_(); #endif @@ -541,123 +616,6 @@ class Logger : public Component { } #endif - // Ensure buffer has null termination (truncate if buffer was full) - static inline void ensure_null_terminated_(char *buffer, uint16_t buffer_at, uint16_t buffer_size) { - uint16_t null_pos = buffer_at >= buffer_size ? buffer_size - 1 : buffer_at; - buffer[null_pos] = '\0'; - } - - static inline void copy_string(char *buffer, uint16_t &pos, const char *str) { - const size_t len = strlen(str); - // Intentionally no null terminator, building larger string - memcpy(buffer + pos, str, len); // NOLINT(bugprone-not-null-terminated-result) - pos += len; - } - - static inline void write_ansi_color_for_level(char *buffer, uint16_t &pos, uint8_t level) { - if (level == 0) - return; - // Construct ANSI escape sequence: "\033[{bold};3{color}m" - // Example: "\033[1;31m" for ERROR (bold red) - buffer[pos++] = '\033'; - buffer[pos++] = '['; - buffer[pos++] = (level == 1) ? '1' : '0'; // Only ERROR is bold - buffer[pos++] = ';'; - buffer[pos++] = '3'; - buffer[pos++] = LOG_LEVEL_COLOR_DIGIT[level]; - buffer[pos++] = 'm'; - } - - inline void HOT write_header_to_buffer_(uint8_t level, const char *tag, int line, const char *thread_name, - char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { - uint16_t pos = *buffer_at; - // Early return if insufficient space - intentionally don't update buffer_at to prevent partial writes - if (pos + MAX_HEADER_SIZE > buffer_size) - return; - - // Construct: [LEVEL][tag:line]: - write_ansi_color_for_level(buffer, pos, level); - buffer[pos++] = '['; - if (level != 0) { - if (level >= 7) { - buffer[pos++] = 'V'; // VERY_VERBOSE = "VV" - buffer[pos++] = 'V'; - } else { - buffer[pos++] = LOG_LEVEL_LETTER_CHARS[level]; - } - } - buffer[pos++] = ']'; - buffer[pos++] = '['; - copy_string(buffer, pos, tag); - buffer[pos++] = ':'; - // Format line number without modulo operations (passed by value, safe to mutate) - if (line > 999) [[unlikely]] { - int thousands = line / 1000; - buffer[pos++] = '0' + thousands; - line -= thousands * 1000; - } - int hundreds = line / 100; - int remainder = line - hundreds * 100; - int tens = remainder / 10; - buffer[pos++] = '0' + hundreds; - buffer[pos++] = '0' + tens; - buffer[pos++] = '0' + (remainder - tens * 10); - buffer[pos++] = ']'; - -#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) || defined(USE_HOST) - if (thread_name != nullptr) { - write_ansi_color_for_level(buffer, pos, 1); // Always use bold red for thread name - buffer[pos++] = '['; - copy_string(buffer, pos, thread_name); - buffer[pos++] = ']'; - write_ansi_color_for_level(buffer, pos, level); // Restore original color - } -#endif - - buffer[pos++] = ':'; - buffer[pos++] = ' '; - *buffer_at = pos; - } - - // Helper to process vsnprintf return value and strip trailing newlines. - // Updates buffer_at with the formatted length, handling truncation: - // - When vsnprintf truncates (ret >= remaining), it writes (remaining - 1) chars + null terminator - // - When it doesn't truncate (ret < remaining), it writes ret chars + null terminator - __attribute__((always_inline)) static inline void process_vsnprintf_result(const char *buffer, uint16_t *buffer_at, - uint16_t remaining, int ret) { - if (ret < 0) - return; // Encoding error, do not increment buffer_at - *buffer_at += (ret >= remaining) ? (remaining - 1) : static_cast(ret); - // Remove all trailing newlines right after formatting - while (*buffer_at > 0 && buffer[*buffer_at - 1] == '\n') - (*buffer_at)--; - } - - inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, - va_list args) { - // Check remaining capacity in the buffer - if (*buffer_at >= buffer_size) - return; - const uint16_t remaining = buffer_size - *buffer_at; - process_vsnprintf_result(buffer, buffer_at, remaining, vsnprintf(buffer + *buffer_at, remaining, format, args)); - } - -#ifdef USE_STORE_LOG_STR_IN_FLASH - // ESP8266 variant that reads format string directly from flash using vsnprintf_P - inline void HOT format_body_to_buffer_P_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, PGM_P format, - va_list args) { - if (*buffer_at >= buffer_size) - return; - const uint16_t remaining = buffer_size - *buffer_at; - process_vsnprintf_result(buffer, buffer_at, remaining, vsnprintf_P(buffer + *buffer_at, remaining, format, args)); - } -#endif - - inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { - static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1; - this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size); - } - #if defined(USE_ESP32) || defined(USE_LIBRETINY) // Disable loop when task buffer is empty (with USB CDC check on ESP32) inline void disable_loop_when_buffer_empty_() { diff --git a/esphome/components/logger/logger_esp32.cpp b/esphome/components/logger/logger_esp32.cpp index 9defb6c166..4d186d950e 100644 --- a/esphome/components/logger/logger_esp32.cpp +++ b/esphome/components/logger/logger_esp32.cpp @@ -118,9 +118,7 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg, size_t len) { - // Length is now always passed explicitly - no strlen() fallback needed - +void HOT Logger::write_msg_(const LogBuffer &buf) { #if defined(USE_LOGGER_UART_SELECTION_USB_CDC) || defined(USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG) // USB CDC/JTAG - single write including newline (already in buffer) // Use fwrite to stdout which goes through VFS to USB console @@ -130,10 +128,10 @@ void HOT Logger::write_msg_(const char *msg, size_t len) { // This is compile-time selection, not runtime detection - if USB is configured, it's always used. // There is no fallback to regular UART if "USB isn't connected" - that's the user's responsibility // to configure correctly for their hardware. This approach eliminates runtime overhead. - fwrite(msg, 1, len, stdout); + fwrite(buf.data, 1, buf.pos, stdout); #else // Regular UART - single write including newline (already in buffer) - uart_write_bytes(this->uart_num_, msg, len); + uart_write_bytes(this->uart_num_, buf.data, buf.pos); #endif } diff --git a/esphome/components/logger/logger_esp8266.cpp b/esphome/components/logger/logger_esp8266.cpp index 6cee1baca5..84474dd9df 100644 --- a/esphome/components/logger/logger_esp8266.cpp +++ b/esphome/components/logger/logger_esp8266.cpp @@ -28,9 +28,9 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg, size_t len) { +void HOT Logger::write_msg_(const LogBuffer &buf) { // Single write with newline already in buffer (added by caller) - this->hw_serial_->write(msg, len); + this->hw_serial_->write(buf.data, buf.pos); } const LogString *Logger::get_uart_selection_() { diff --git a/esphome/components/logger/logger_host.cpp b/esphome/components/logger/logger_host.cpp index 874cdabd22..612b47b9a9 100644 --- a/esphome/components/logger/logger_host.cpp +++ b/esphome/components/logger/logger_host.cpp @@ -3,7 +3,7 @@ namespace esphome::logger { -void HOT Logger::write_msg_(const char *msg, size_t len) { +void HOT Logger::write_msg_(const LogBuffer &buf) { static constexpr size_t TIMESTAMP_LEN = 10; // "[HH:MM:SS]" // tx_buffer_size_ defaults to 512, so 768 covers default + headroom char buffer[TIMESTAMP_LEN + 768]; @@ -15,8 +15,8 @@ void HOT Logger::write_msg_(const char *msg, size_t len) { size_t pos = strftime(buffer, TIMESTAMP_LEN + 1, "[%H:%M:%S]", &timeinfo); // Copy message (with newline already included by caller) - size_t copy_len = std::min(len, sizeof(buffer) - pos); - memcpy(buffer + pos, msg, copy_len); + size_t copy_len = std::min(static_cast(buf.pos), sizeof(buffer) - pos); + memcpy(buffer + pos, buf.data, copy_len); pos += copy_len; // Single write for everything diff --git a/esphome/components/logger/logger_libretiny.cpp b/esphome/components/logger/logger_libretiny.cpp index cdf55e710c..8635407845 100644 --- a/esphome/components/logger/logger_libretiny.cpp +++ b/esphome/components/logger/logger_libretiny.cpp @@ -49,7 +49,7 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg, size_t len) { this->hw_serial_->write(msg, len); } +void HOT Logger::write_msg_(const LogBuffer &buf) { this->hw_serial_->write(buf.data, buf.pos); } const LogString *Logger::get_uart_selection_() { switch (this->uart_) { diff --git a/esphome/components/logger/logger_rp2040.cpp b/esphome/components/logger/logger_rp2040.cpp index be8252f56a..fc210f0bdd 100644 --- a/esphome/components/logger/logger_rp2040.cpp +++ b/esphome/components/logger/logger_rp2040.cpp @@ -27,9 +27,9 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg, size_t len) { +void HOT Logger::write_msg_(const LogBuffer &buf) { // Single write with newline already in buffer (added by caller) - this->hw_serial_->write(msg, len); + this->hw_serial_->write(buf.data, buf.pos); } const LogString *Logger::get_uart_selection_() { diff --git a/esphome/components/logger/logger_zephyr.cpp b/esphome/components/logger/logger_zephyr.cpp index 41f53beec0..9676c44485 100644 --- a/esphome/components/logger/logger_zephyr.cpp +++ b/esphome/components/logger/logger_zephyr.cpp @@ -63,18 +63,18 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg, size_t len) { +void HOT Logger::write_msg_(const LogBuffer &buf) { // Single write with newline already in buffer (added by caller) #ifdef CONFIG_PRINTK // Requires the debug component and an active SWD connection. // It is used for pyocd rtt -t nrf52840 - k_str_out(const_cast(msg), len); + k_str_out(const_cast(buf.data), buf.pos); #endif if (this->uart_dev_ == nullptr) { return; } - for (size_t i = 0; i < len; ++i) { - uart_poll_out(this->uart_dev_, msg[i]); + for (uint16_t i = 0; i < buf.pos; ++i) { + uart_poll_out(this->uart_dev_, buf.data[i]); } } From cf26ca60430cc27bbd3f6d5ba50aae683cceeb19 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 05:56:36 +0100 Subject: [PATCH 03/28] wip --- esphome/components/logger/logger.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 5c59f476b1..3df210c915 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -137,16 +137,14 @@ struct LogBuffer { LogBuffer(char *buf, uint16_t &buf_pos, uint16_t buf_size) : data(buf), pos(buf_pos), size(buf_size) { this->pos = 0; } - uint16_t remaining() const { return this->size - this->pos; } - char *current() { return this->data + this->pos; } void null_terminate() { this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; } void write(const char *value, size_t length) { if (this->full_()) return; - const uint16_t available = this->remaining(); + const uint16_t available = this->remaining_(); const size_t copy_len = (length < static_cast(available)) ? length : available; if (copy_len > 0) { - memcpy(this->current(), value, copy_len); + memcpy(this->current_(), value, copy_len); this->pos += copy_len; } } @@ -207,18 +205,20 @@ struct LogBuffer { void HOT format_body(const char *format, va_list args) { if (this->full_()) return; - this->process_vsnprintf_result_(vsnprintf(this->current(), this->remaining(), format, args)); + this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args)); } #ifdef USE_STORE_LOG_STR_IN_FLASH void HOT format_body_P(PGM_P format, va_list args) { if (this->full_()) return; - this->process_vsnprintf_result_(vsnprintf_P(this->current(), this->remaining(), format, args)); + this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args)); } #endif 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 put_char_(char c) { this->data[this->pos++] = c; } void strip_trailing_newlines_() { while (this->pos > 0 && this->data[this->pos - 1] == '\n') @@ -227,13 +227,13 @@ struct LogBuffer { __attribute__((always_inline)) void process_vsnprintf_result_(int ret) { if (ret < 0) return; - const uint16_t rem = this->remaining(); + const uint16_t rem = this->remaining_(); this->pos += (ret >= rem) ? (rem - 1) : static_cast(ret); this->strip_trailing_newlines_(); } void copy_string_(const char *str) { const size_t len = strlen(str); - memcpy(this->current(), str, len); // NOLINT(bugprone-not-null-terminated-result) + memcpy(this->current_(), str, len); // NOLINT(bugprone-not-null-terminated-result) this->pos += len; } void write_ansi_color_(uint8_t level) { From f0199d5de9ff371b2f1903d3e616dac3f1d4701e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 05:57:38 +0100 Subject: [PATCH 04/28] wip --- esphome/components/logger/logger.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 3df210c915..623e8ed1b6 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -203,15 +203,13 @@ struct LogBuffer { this->put_char_(' '); } void HOT format_body(const char *format, va_list args) { - if (this->full_()) - return; - this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args)); + if (!this->full_()) + this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args)); } #ifdef USE_STORE_LOG_STR_IN_FLASH void HOT format_body_P(PGM_P format, va_list args) { - if (this->full_()) - return; - this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args)); + if (!this->full_()) + this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args)); } #endif From 70debb1c98fb5a8ff2cbf868229314fde6d250f2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 06:04:27 +0100 Subject: [PATCH 05/28] wip --- esphome/components/logger/logger.cpp | 10 ++-------- esphome/components/logger/logger.h | 10 ++++++++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index f79508b0cc..2e86c00b7e 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -36,9 +36,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch // Fast path: main thread, no recursion (99.9% of all logs) if (is_main_task && !this->main_task_recursion_guard_) [[likely]] { - RecursionGuard guard(this->main_task_recursion_guard_); - // Format and send to both console and callbacks - this->log_message_to_buffer_and_send_(level, tag, line, format, args); + this->log_message_to_buffer_and_send_(this->main_task_recursion_guard_, level, tag, line, format, args); return; } @@ -117,11 +115,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch if (level > this->level_for(tag) || global_recursion_guard_) return; - RecursionGuard guard(global_recursion_guard_); - LogBuffer buf(this->tx_buffer_, this->tx_buffer_at_, this->tx_buffer_size_); - this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf); - this->notify_listeners_(level, tag); - this->write_log_buffer_to_console_(buf); + this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args); } #endif // USE_ESP32 / USE_HOST / USE_LIBRETINY diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 623e8ed1b6..28bfa2e8df 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -443,6 +443,16 @@ class Logger : public Component { } } + // Helper to format and send a log message to both console and listeners + inline void HOT log_message_to_buffer_and_send_(bool &recursion_guard, uint8_t level, const char *tag, int line, + const char *format, va_list args) { + RecursionGuard guard(recursion_guard); + LogBuffer buf(this->tx_buffer_, this->tx_buffer_at_, this->tx_buffer_size_); + this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf); + this->notify_listeners_(level, tag); + this->write_log_buffer_to_console_(buf); + } + #ifdef USE_ESPHOME_TASK_LOG_BUFFER // Helper to format a pre-formatted message from the task log buffer and notify listeners // Used by process_messages_ to avoid code duplication between ESP32 and host platforms From 54c62428ae039ae067a497c5b43e6ec129e87c49 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 06:14:04 +0100 Subject: [PATCH 06/28] tweak --- esphome/components/logger/logger.h | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 28bfa2e8df..407ad7c2c4 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -137,7 +137,6 @@ struct LogBuffer { LogBuffer(char *buf, uint16_t &buf_pos, uint16_t buf_size) : data(buf), pos(buf_pos), size(buf_size) { this->pos = 0; } - void null_terminate() { this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; } void write(const char *value, size_t length) { if (this->full_()) return; @@ -157,10 +156,6 @@ struct LogBuffer { this->pos = this->size; } } - void write_color_reset() { - static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1; - this->write(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN); - } 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) @@ -205,11 +200,20 @@ struct LogBuffer { void HOT format_body(const char *format, va_list args) { if (!this->full_()) this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args)); + this->write_color_reset_(); + this->null_terminate_(); + } + void write_body(const char *text, size_t text_length) { + this->write(text, text_length); + this->write_color_reset_(); + this->null_terminate_(); } #ifdef USE_STORE_LOG_STR_IN_FLASH void HOT format_body_P(PGM_P format, va_list args) { if (!this->full_()) this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args)); + this->write_color_reset_(); + this->null_terminate_(); } #endif @@ -218,6 +222,11 @@ struct LogBuffer { uint16_t remaining_() const { return this->size - this->pos; } char *current_() { return this->data + this->pos; } void put_char_(char c) { this->data[this->pos++] = c; } + void null_terminate_() { this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; } + void write_color_reset_() { + static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1; + this->write(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN); + } void strip_trailing_newlines_() { while (this->pos > 0 && this->data[this->pos - 1] == '\n') this->pos--; @@ -411,8 +420,6 @@ class Logger : public Component { buf.write_header(level, tag, line, nullptr); #endif buf.format_body(format, args); - buf.write_color_reset(); - buf.null_terminate(); } #ifdef USE_STORE_LOG_STR_IN_FLASH @@ -422,8 +429,6 @@ class Logger : public Component { LogBuffer &buf) { buf.write_header(level, tag, line, nullptr); buf.format_body_P(reinterpret_cast(format), args); - buf.write_color_reset(); - buf.null_terminate(); } #endif @@ -460,9 +465,7 @@ class Logger : public Component { const char *thread_name, const char *text, size_t text_length, LogBuffer &buf) { buf.write_header(level, tag, line, thread_name); - buf.write(text, text_length); - buf.write_color_reset(); - buf.null_terminate(); + buf.write_body(text, text_length); this->notify_listeners_(level, tag); } #endif From 159e2bed69a9f32815128780f9ae6c20b6233875 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 06:15:10 +0100 Subject: [PATCH 07/28] tweak --- esphome/components/logger/logger.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 407ad7c2c4..2f43e5625a 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -137,16 +137,6 @@ struct LogBuffer { LogBuffer(char *buf, uint16_t &buf_pos, uint16_t buf_size) : data(buf), pos(buf_pos), size(buf_size) { this->pos = 0; } - void write(const char *value, size_t length) { - if (this->full_()) - return; - const uint16_t available = this->remaining_(); - const size_t copy_len = (length < static_cast(available)) ? length : available; - if (copy_len > 0) { - memcpy(this->current_(), value, copy_len); - this->pos += copy_len; - } - } void add_newline() { if (this->pos < this->size) { this->data[this->pos++] = '\n'; @@ -204,7 +194,7 @@ struct LogBuffer { this->null_terminate_(); } void write_body(const char *text, size_t text_length) { - this->write(text, text_length); + this->write_(text, text_length); this->write_color_reset_(); this->null_terminate_(); } @@ -223,9 +213,19 @@ struct LogBuffer { char *current_() { return this->data + this->pos; } void put_char_(char c) { this->data[this->pos++] = c; } void null_terminate_() { this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; } + void write_(const char *value, size_t length) { + if (this->full_()) + return; + const uint16_t available = this->remaining_(); + const size_t copy_len = (length < static_cast(available)) ? length : available; + if (copy_len > 0) { + memcpy(this->current_(), value, copy_len); + this->pos += copy_len; + } + } void write_color_reset_() { static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1; - this->write(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN); + this->write_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN); } void strip_trailing_newlines_() { while (this->pos > 0 && this->data[this->pos - 1] == '\n') From 4cc2c39a19ad335af32080047644ad51b97fa28c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 06:15:22 +0100 Subject: [PATCH 08/28] tweak --- esphome/components/logger/logger.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 2f43e5625a..83f45b3c15 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -193,11 +193,6 @@ struct LogBuffer { this->write_color_reset_(); this->null_terminate_(); } - void write_body(const char *text, size_t text_length) { - this->write_(text, text_length); - this->write_color_reset_(); - this->null_terminate_(); - } #ifdef USE_STORE_LOG_STR_IN_FLASH void HOT format_body_P(PGM_P format, va_list args) { if (!this->full_()) @@ -206,6 +201,11 @@ struct LogBuffer { this->null_terminate_(); } #endif + void write_body(const char *text, size_t text_length) { + this->write_(text, text_length); + this->write_color_reset_(); + this->null_terminate_(); + } private: bool full_() const { return this->pos >= this->size; } From f27c80cbe40162d48961cadda5bdf9ec242eff4a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 06:20:39 +0100 Subject: [PATCH 09/28] tweak --- esphome/components/logger/logger.h | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 83f45b3c15..15e7217565 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -188,15 +188,13 @@ struct LogBuffer { this->put_char_(' '); } void HOT format_body(const char *format, va_list args) { - if (!this->full_()) - this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args)); + this->format_vsnprintf_(format, args); this->write_color_reset_(); this->null_terminate_(); } #ifdef USE_STORE_LOG_STR_IN_FLASH void HOT format_body_P(PGM_P format, va_list args) { - if (!this->full_()) - this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args)); + this->format_vsnprintf_P_(format, args); this->write_color_reset_(); this->null_terminate_(); } @@ -231,13 +229,25 @@ struct LogBuffer { while (this->pos > 0 && this->data[this->pos - 1] == '\n') this->pos--; } - __attribute__((always_inline)) void process_vsnprintf_result_(int ret) { + 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 void copy_string_(const char *str) { const size_t len = strlen(str); memcpy(this->current_(), str, len); // NOLINT(bugprone-not-null-terminated-result) From b2cc98e083e854ce4b3a9be45a0a29f160d3d08a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 06:23:05 +0100 Subject: [PATCH 10/28] tweak --- esphome/components/logger/logger.cpp | 4 +--- esphome/components/logger/logger.h | 12 ++++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 2e86c00b7e..e8df5fbb16 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -102,9 +102,7 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li uint16_t console_pos; LogBuffer buf(console_buffer, console_pos, MAX_CONSOLE_LOG_MSG_SIZE); this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf); - // Add newline before writing to console - buf.add_newline(); - this->write_msg_(buf); + this->write_to_console_(buf); } // RAII guard automatically resets on return diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 15e7217565..f3c26395e8 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -450,12 +450,16 @@ class Logger : public Component { #endif } + // Helper to write log buffer to console (adds newline and writes) + inline void HOT write_to_console_(LogBuffer &buf) { + buf.add_newline(); + this->write_msg_(buf); + } + // Helper to write log buffer to console if logging is enabled inline void HOT write_log_buffer_to_console_(LogBuffer &buf) { - if (this->baud_rate_ > 0) { - buf.add_newline(); - this->write_msg_(buf); - } + if (this->baud_rate_ > 0) + this->write_to_console_(buf); } // Helper to format and send a log message to both console and listeners From 9758b15508e37ef8d00c1caa13e1e6bed164706a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 06:27:16 +0100 Subject: [PATCH 11/28] tweak --- esphome/components/logger/logger.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index f3c26395e8..3912d769a0 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -137,7 +137,10 @@ struct LogBuffer { LogBuffer(char *buf, uint16_t &buf_pos, uint16_t buf_size) : data(buf), pos(buf_pos), size(buf_size) { this->pos = 0; } - void add_newline() { + // 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) { @@ -450,9 +453,9 @@ class Logger : public Component { #endif } - // Helper to write log buffer to console (adds newline and writes) + // Helper to write log buffer to console (replaces null terminator with newline and writes) inline void HOT write_to_console_(LogBuffer &buf) { - buf.add_newline(); + buf.terminate_with_newline(); this->write_msg_(buf); } From d2e9e8ebd6f4eaadb474c18639e3622770294221 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 06:37:07 +0100 Subject: [PATCH 12/28] tweak --- esphome/components/logger/logger.cpp | 6 +----- esphome/components/logger/logger.h | 12 ++++++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index e8df5fbb16..51a29a63e5 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -129,11 +129,7 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas if (level > this->level_for(tag) || global_recursion_guard_) return; - RecursionGuard guard(global_recursion_guard_); - LogBuffer buf(this->tx_buffer_, this->tx_buffer_at_, this->tx_buffer_size_); - this->format_log_to_buffer_with_terminator_P_(level, tag, line, format, args, buf); - this->notify_listeners_(level, tag); - this->write_log_buffer_to_console_(buf); + this->log_message_to_buffer_and_send_P_(global_recursion_guard_, level, tag, line, format, args); } #endif // USE_STORE_LOG_STR_IN_FLASH diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 3912d769a0..87eaa957d1 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -475,6 +475,18 @@ class Logger : public Component { this->write_log_buffer_to_console_(buf); } +#ifdef USE_STORE_LOG_STR_IN_FLASH + // Helper to format and send a log message with flash format string (ESP8266) + inline void HOT log_message_to_buffer_and_send_P_(bool &recursion_guard, uint8_t level, const char *tag, int line, + const __FlashStringHelper *format, va_list args) { + RecursionGuard guard(recursion_guard); + LogBuffer buf(this->tx_buffer_, this->tx_buffer_at_, this->tx_buffer_size_); + this->format_log_to_buffer_with_terminator_P_(level, tag, line, format, args, buf); + this->notify_listeners_(level, tag); + this->write_log_buffer_to_console_(buf); + } +#endif + #ifdef USE_ESPHOME_TASK_LOG_BUFFER // Helper to format a pre-formatted message from the task log buffer and notify listeners // Used by process_messages_ to avoid code duplication between ESP32 and host platforms From 7f17b90fb9a1aa4460caf2c3b8c16971f751ea63 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 06:38:33 +0100 Subject: [PATCH 13/28] tweak --- esphome/components/logger/logger.cpp | 2 +- esphome/components/logger/logger.h | 23 +++++++++-------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 51a29a63e5..0f879b6ca1 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -129,7 +129,7 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas if (level > this->level_for(tag) || global_recursion_guard_) return; - this->log_message_to_buffer_and_send_P_(global_recursion_guard_, level, tag, line, format, args); + this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args); } #endif // USE_STORE_LOG_STR_IN_FLASH diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 87eaa957d1..c0a807c991 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -2,6 +2,7 @@ #include #include +#include #if defined(USE_ESP32) || defined(USE_HOST) #include #endif @@ -466,27 +467,21 @@ class Logger : public Component { } // Helper to format and send a log message to both console and listeners + // Template handles both const char* (RAM) and __FlashStringHelper* (flash) format strings + template inline void HOT log_message_to_buffer_and_send_(bool &recursion_guard, uint8_t level, const char *tag, int line, - const char *format, va_list args) { + FormatType format, va_list args) { RecursionGuard guard(recursion_guard); LogBuffer buf(this->tx_buffer_, this->tx_buffer_at_, this->tx_buffer_size_); - this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf); + if constexpr (std::is_same_v) { + this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf); + } else { + this->format_log_to_buffer_with_terminator_P_(level, tag, line, format, args, buf); + } this->notify_listeners_(level, tag); this->write_log_buffer_to_console_(buf); } -#ifdef USE_STORE_LOG_STR_IN_FLASH - // Helper to format and send a log message with flash format string (ESP8266) - inline void HOT log_message_to_buffer_and_send_P_(bool &recursion_guard, uint8_t level, const char *tag, int line, - const __FlashStringHelper *format, va_list args) { - RecursionGuard guard(recursion_guard); - LogBuffer buf(this->tx_buffer_, this->tx_buffer_at_, this->tx_buffer_size_); - this->format_log_to_buffer_with_terminator_P_(level, tag, line, format, args, buf); - this->notify_listeners_(level, tag); - this->write_log_buffer_to_console_(buf); - } -#endif - #ifdef USE_ESPHOME_TASK_LOG_BUFFER // Helper to format a pre-formatted message from the task log buffer and notify listeners // Used by process_messages_ to avoid code duplication between ESP32 and host platforms From d9261ae66dafc1cbdc5eaaf3b7aeabe1b453c15b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 06:40:09 +0100 Subject: [PATCH 14/28] tweak --- esphome/components/logger/logger.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index c0a807c991..984b626733 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -473,10 +473,13 @@ class Logger : public Component { FormatType format, va_list args) { RecursionGuard guard(recursion_guard); LogBuffer buf(this->tx_buffer_, this->tx_buffer_at_, this->tx_buffer_size_); - if constexpr (std::is_same_v) { - this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf); - } else { +#ifdef USE_STORE_LOG_STR_IN_FLASH + if constexpr (std::is_same_v) { this->format_log_to_buffer_with_terminator_P_(level, tag, line, format, args, buf); + } else +#endif + { + this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf); } this->notify_listeners_(level, tag); this->write_log_buffer_to_console_(buf); From b39b2fbe234ee8a9e51fb9a5320c48b8a6059716 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 06:55:06 +0100 Subject: [PATCH 15/28] fix --- esphome/components/logger/logger.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 984b626733..aa5e75cbd3 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -42,7 +42,7 @@ struct device; #endif // Platforms that support thread names in log output -#ifdef USE_LOGGER_THREAD_NAME +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) || defined(USE_HOST) #define USE_LOGGER_THREAD_NAME #endif From c4994d4f628af989b60f6700cdc8b4ae1be82340 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 07:04:09 +0100 Subject: [PATCH 16/28] tweak --- esphome/components/logger/logger.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index aa5e75cbd3..38e28f7624 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -213,8 +213,8 @@ struct LogBuffer { 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 put_char_(char c) { this->data[this->pos++] = c; } - void null_terminate_() { this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; } + inline void put_char_(char c) { this->data[this->pos++] = c; } + inline void null_terminate_() { this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; } void write_(const char *value, size_t length) { if (this->full_()) return; From 935d496c70f51fbf579976a4d441e4b2b2b14521 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 07:10:28 +0100 Subject: [PATCH 17/28] tweak --- esphome/components/logger/logger.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 38e28f7624..46ddf22719 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -193,20 +193,17 @@ struct LogBuffer { } void HOT format_body(const char *format, va_list args) { this->format_vsnprintf_(format, args); - this->write_color_reset_(); - this->null_terminate_(); + 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->write_color_reset_(); - this->null_terminate_(); + this->finalize_(); } #endif void write_body(const char *text, size_t text_length) { this->write_(text, text_length); - this->write_color_reset_(); - this->null_terminate_(); + this->finalize_(); } private: @@ -215,6 +212,10 @@ struct LogBuffer { char *current_() { return this->data + this->pos; } inline void put_char_(char c) { this->data[this->pos++] = c; } inline void null_terminate_() { this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; } + void finalize_() { + this->write_color_reset_(); + this->null_terminate_(); + } void write_(const char *value, size_t length) { if (this->full_()) return; From 54154e7c68269181bbd0cb5a7bf1f954c98cd256 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 07:18:46 +0100 Subject: [PATCH 18/28] tweak --- esphome/components/logger/logger.h | 99 +++++++++++++++++------------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 46ddf22719..29e8e67c3d 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -155,41 +155,53 @@ struct LogBuffer { if (this->pos + MAX_HEADER_SIZE > this->size) return; - // Construct: [LEVEL][tag:line]: - this->write_ansi_color_(level); - this->put_char_('['); + char *p = this->current_(); + + // Write ANSI color + p = this->write_ansi_color_(p, level); + + // Construct: [LEVEL][tag:line] + *p++ = '['; if (level != 0) { if (level >= 7) { - this->put_char_('V'); // VERY_VERBOSE = "VV" - this->put_char_('V'); + *p++ = 'V'; // VERY_VERBOSE = "VV" + *p++ = 'V'; } else { - this->put_char_(LOG_LEVEL_LETTER_CHARS[level]); + *p++ = LOG_LEVEL_LETTER_CHARS[level]; } } - this->put_char_(']'); - this->put_char_('['); - this->copy_string_(tag); - this->put_char_(':'); - // Format line number without modulo operations (passed by value, safe to mutate) + *p++ = ']'; + *p++ = '['; + + // Copy tag + const size_t tag_len = strlen(tag); + memcpy(p, tag, tag_len); + p += tag_len; + + *p++ = ':'; + + // Format line number without modulo operations if (line > 999) [[unlikely]] { int thousands = line / 1000; - this->put_char_('0' + thousands); + *p++ = '0' + thousands; line -= thousands * 1000; } int hundreds = line / 100; int remainder = line - hundreds * 100; int tens = remainder / 10; - this->put_char_('0' + hundreds); - this->put_char_('0' + tens); - this->put_char_('0' + (remainder - tens * 10)); - this->put_char_(']'); + *p++ = '0' + hundreds; + *p++ = '0' + tens; + *p++ = '0' + (remainder - tens * 10); + *p++ = ']'; #ifdef USE_LOGGER_THREAD_NAME - this->write_thread_name_(thread_name, level); + p = this->write_thread_name_(p, thread_name, level); #endif - this->put_char_(':'); - this->put_char_(' '); + *p++ = ':'; + *p++ = ' '; + + this->pos = p - this->data; } void HOT format_body(const char *format, va_list args) { this->format_vsnprintf_(format, args); @@ -210,8 +222,7 @@ struct LogBuffer { bool full_() const { return this->pos >= this->size; } uint16_t remaining_() const { return this->size - this->pos; } char *current_() { return this->data + this->pos; } - inline void put_char_(char c) { this->data[this->pos++] = c; } - inline void null_terminate_() { this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; } + void null_terminate_() { this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; } void finalize_() { this->write_color_reset_(); this->null_terminate_(); @@ -253,33 +264,33 @@ struct LogBuffer { this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args)); } #endif - void copy_string_(const char *str) { - const size_t len = strlen(str); - memcpy(this->current_(), str, len); // NOLINT(bugprone-not-null-terminated-result) - this->pos += len; - } - void write_ansi_color_(uint8_t level) { + // Write ANSI color escape sequence to buffer, returns new pointer position + char *write_ansi_color_(char *p, uint8_t level) { if (level == 0) - return; - // Construct ANSI escape sequence: "\033[{bold};3{color}m" - // Example: "\033[1;31m" for ERROR (bold red) - this->put_char_('\033'); - this->put_char_('['); - this->put_char_((level == 1) ? '1' : '0'); // Only ERROR is bold - this->put_char_(';'); - this->put_char_('3'); - this->put_char_(LOG_LEVEL_COLOR_DIGIT[level]); - this->put_char_('m'); + return p; + // 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'; + return p; } #ifdef USE_LOGGER_THREAD_NAME - void write_thread_name_(const char *thread_name, uint8_t level) { + // Write thread name with bold red color, returns new pointer position + char *write_thread_name_(char *p, const char *thread_name, uint8_t level) { if (thread_name == nullptr) - return; - this->write_ansi_color_(1); // Always use bold red for thread name - this->put_char_('['); - this->copy_string_(thread_name); - this->put_char_(']'); - this->write_ansi_color_(level); // Restore original color + return p; + p = this->write_ansi_color_(p, 1); // Bold red for thread name + *p++ = '['; + const size_t name_len = strlen(thread_name); + memcpy(p, thread_name, name_len); + p += name_len; + *p++ = ']'; + p = this->write_ansi_color_(p, level); // Restore original color + return p; } #endif }; From efd17f78bf2ddae8e3697310b6cc63785049aef6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 07:21:14 +0100 Subject: [PATCH 19/28] tweak --- esphome/components/logger/logger.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 29e8e67c3d..ed815f1dac 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -158,7 +158,7 @@ struct LogBuffer { char *p = this->current_(); // Write ANSI color - p = this->write_ansi_color_(p, level); + this->write_ansi_color_(p, level); // Construct: [LEVEL][tag:line] *p++ = '['; @@ -195,7 +195,7 @@ struct LogBuffer { *p++ = ']'; #ifdef USE_LOGGER_THREAD_NAME - p = this->write_thread_name_(p, thread_name, level); + this->write_thread_name_(p, thread_name, level); #endif *p++ = ':'; @@ -264,10 +264,11 @@ struct LogBuffer { this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args)); } #endif - // Write ANSI color escape sequence to buffer, returns new pointer position - char *write_ansi_color_(char *p, uint8_t level) { + // 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 p; + return; // Direct buffer fill: "\033[{bold};3{color}m" (7 bytes) *p++ = '\033'; *p++ = '['; @@ -276,21 +277,20 @@ struct LogBuffer { *p++ = '3'; *p++ = LOG_LEVEL_COLOR_DIGIT[level]; *p++ = 'm'; - return p; } #ifdef USE_LOGGER_THREAD_NAME - // Write thread name with bold red color, returns new pointer position - char *write_thread_name_(char *p, const char *thread_name, uint8_t level) { + // Write thread name with bold red color, updates pointer in place + // Caller is responsible for ensuring buffer has sufficient space + void write_thread_name_(char *&p, const char *thread_name, uint8_t level) { if (thread_name == nullptr) - return p; - p = this->write_ansi_color_(p, 1); // Bold red for thread name + return; + this->write_ansi_color_(p, 1); // Bold red for thread name *p++ = '['; const size_t name_len = strlen(thread_name); memcpy(p, thread_name, name_len); p += name_len; *p++ = ']'; - p = this->write_ansi_color_(p, level); // Restore original color - return p; + this->write_ansi_color_(p, level); // Restore original color } #endif }; From 2de3b6aed7045059052e72b6a0798d8cf29f9a0c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 07:24:44 +0100 Subject: [PATCH 20/28] tweak --- esphome/components/logger/logger.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index ed815f1dac..12799c16f3 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -222,11 +222,6 @@ struct LogBuffer { 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 null_terminate_() { this->data[this->full_() ? this->size - 1 : this->pos] = '\0'; } - void finalize_() { - this->write_color_reset_(); - this->null_terminate_(); - } void write_(const char *value, size_t length) { if (this->full_()) return; @@ -237,9 +232,12 @@ struct LogBuffer { this->pos += copy_len; } } - void write_color_reset_() { + 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') From 4bf9cc6546b9cb3ac77eb26d6df43e5c3b81e7fc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 07:26:34 +0100 Subject: [PATCH 21/28] tweak --- esphome/components/logger/logger.h | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 12799c16f3..353c33aef5 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -195,7 +195,16 @@ struct LogBuffer { *p++ = ']'; #ifdef USE_LOGGER_THREAD_NAME - this->write_thread_name_(p, thread_name, level); + // Write thread name with bold red color + if (thread_name != nullptr) { + this->write_ansi_color_(p, 1); // Bold red for thread name + *p++ = '['; + const size_t name_len = strlen(thread_name); + memcpy(p, thread_name, name_len); + p += name_len; + *p++ = ']'; + this->write_ansi_color_(p, level); // Restore original color + } #endif *p++ = ':'; @@ -276,21 +285,6 @@ struct LogBuffer { *p++ = LOG_LEVEL_COLOR_DIGIT[level]; *p++ = 'm'; } -#ifdef USE_LOGGER_THREAD_NAME - // Write thread name with bold red color, updates pointer in place - // Caller is responsible for ensuring buffer has sufficient space - void write_thread_name_(char *&p, const char *thread_name, uint8_t level) { - if (thread_name == nullptr) - return; - this->write_ansi_color_(p, 1); // Bold red for thread name - *p++ = '['; - const size_t name_len = strlen(thread_name); - memcpy(p, thread_name, name_len); - p += name_len; - *p++ = ']'; - this->write_ansi_color_(p, level); // Restore original color - } -#endif }; #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) From eeb24b67e172ef1280fbf50bcd0b2a88ccd2bd9b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 07:29:34 +0100 Subject: [PATCH 22/28] tweak --- esphome/components/logger/logger.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 353c33aef5..0dfe16a35c 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -232,8 +232,6 @@ struct LogBuffer { uint16_t remaining_() const { return this->size - this->pos; } char *current_() { return this->data + this->pos; } void write_(const char *value, size_t length) { - if (this->full_()) - return; const uint16_t available = this->remaining_(); const size_t copy_len = (length < static_cast(available)) ? length : available; if (copy_len > 0) { From 3d43b740fdac414172047a7a40b2b3f3216d5b21 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 07:33:53 +0100 Subject: [PATCH 23/28] tweak --- esphome/components/logger/logger.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 0dfe16a35c..580fd4d33c 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -222,7 +222,7 @@ struct LogBuffer { this->finalize_(); } #endif - void write_body(const char *text, size_t text_length) { + void write_body(const char *text, uint16_t text_length) { this->write_(text, text_length); this->finalize_(); } @@ -231,9 +231,9 @@ struct LogBuffer { 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, size_t length) { + void write_(const char *value, uint16_t length) { const uint16_t available = this->remaining_(); - const size_t copy_len = (length < static_cast(available)) ? length : available; + const uint16_t copy_len = (length < available) ? length : available; if (copy_len > 0) { memcpy(this->current_(), value, copy_len); this->pos += copy_len; @@ -491,7 +491,7 @@ class Logger : public Component { // Helper to format a pre-formatted message from the task log buffer and notify listeners // Used by process_messages_ to avoid code duplication between ESP32 and host platforms inline void HOT format_buffered_message_and_notify_(uint8_t level, const char *tag, uint16_t line, - const char *thread_name, const char *text, size_t text_length, + const char *thread_name, const char *text, uint16_t text_length, LogBuffer &buf) { buf.write_header(level, tag, line, thread_name); buf.write_body(text, text_length); From 4d9b7c47f892cc195e6169ccd69287af7ee54a88 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 07:42:40 +0100 Subject: [PATCH 24/28] tidy --- esphome/components/logger/logger.h | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 580fd4d33c..2931ad2283 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -174,9 +174,7 @@ struct LogBuffer { *p++ = '['; // Copy tag - const size_t tag_len = strlen(tag); - memcpy(p, tag, tag_len); - p += tag_len; + this->copy_string_(p, tag); *p++ = ':'; @@ -199,9 +197,7 @@ struct LogBuffer { if (thread_name != nullptr) { this->write_ansi_color_(p, 1); // Bold red for thread name *p++ = '['; - const size_t name_len = strlen(thread_name); - memcpy(p, thread_name, name_len); - p += name_len; + this->copy_string_(p, thread_name); *p++ = ']'; this->write_ansi_color_(p, level); // Restore original color } @@ -283,6 +279,15 @@ struct LogBuffer { *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) From 4da67712a9f1cd7c7febe9b312ed922a78d59593 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 07:47:21 +0100 Subject: [PATCH 25/28] tidy --- esphome/components/logger/logger.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 2931ad2283..6103bd2a8e 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -41,11 +41,6 @@ struct device; #endif -// Platforms that support thread names in log output -#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) || defined(USE_HOST) -#define USE_LOGGER_THREAD_NAME -#endif - namespace esphome::logger { /** Interface for receiving log messages without std::function overhead. @@ -192,7 +187,7 @@ struct LogBuffer { *p++ = '0' + (remainder - tens * 10); *p++ = ']'; -#ifdef USE_LOGGER_THREAD_NAME +#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 From a74940f1c0e9ad9267b191886c0f63492770906f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 07:48:51 +0100 Subject: [PATCH 26/28] tidy --- esphome/components/logger/logger.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 6103bd2a8e..62b93ddafa 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -130,9 +130,7 @@ struct LogBuffer { uint16_t &pos; uint16_t size; - LogBuffer(char *buf, uint16_t &buf_pos, uint16_t buf_size) : data(buf), pos(buf_pos), size(buf_size) { - this->pos = 0; - } + LogBuffer(char *buf, uint16_t &buf_pos, uint16_t buf_size) : data(buf), pos(buf_pos = 0), size(buf_size) {} // 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. From c9dfaa36b47874ee0efc03eee699c1641b2ca56f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 08:06:21 +0100 Subject: [PATCH 27/28] simplify design --- esphome/components/logger/logger.cpp | 9 ++++----- esphome/components/logger/logger.h | 15 ++++++--------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 0f879b6ca1..54b5670016 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -99,8 +99,7 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144; #endif char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety - uint16_t console_pos; - LogBuffer buf(console_buffer, console_pos, MAX_CONSOLE_LOG_MSG_SIZE); + LogBuffer buf{console_buffer, MAX_CONSOLE_LOG_MSG_SIZE}; this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf); this->write_to_console_(buf); } @@ -188,7 +187,7 @@ void Logger::process_messages_() { logger::TaskLogBufferHost::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_at_, this->tx_buffer_size_); + 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(); @@ -200,7 +199,7 @@ void Logger::process_messages_() { 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_at_, this->tx_buffer_size_); + 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 @@ -212,7 +211,7 @@ void Logger::process_messages_() { 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; - LogBuffer buf(this->tx_buffer_, this->tx_buffer_at_, this->tx_buffer_size_); + 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 diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 62b93ddafa..a8fa0f34a7 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -127,10 +127,8 @@ static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; // Buffer wrapper for log formatting functions struct LogBuffer { char *data; - uint16_t &pos; uint16_t size; - - LogBuffer(char *buf, uint16_t &buf_pos, uint16_t buf_size) : data(buf), pos(buf_pos = 0), size(buf_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. @@ -447,10 +445,10 @@ class Logger : public Component { #endif // Helper to notify log listeners - inline void HOT notify_listeners_(uint8_t level, const char *tag) { + inline void HOT notify_listeners_(uint8_t level, const char *tag, const LogBuffer &buf) { #ifdef USE_LOG_LISTENERS for (auto *listener : this->log_listeners_) - listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_); + listener->on_log(level, tag, buf.data, buf.pos); #endif } @@ -472,7 +470,7 @@ class Logger : public Component { inline void HOT log_message_to_buffer_and_send_(bool &recursion_guard, uint8_t level, const char *tag, int line, FormatType format, va_list args) { RecursionGuard guard(recursion_guard); - LogBuffer buf(this->tx_buffer_, this->tx_buffer_at_, this->tx_buffer_size_); + LogBuffer buf{this->tx_buffer_, this->tx_buffer_size_}; #ifdef USE_STORE_LOG_STR_IN_FLASH if constexpr (std::is_same_v) { this->format_log_to_buffer_with_terminator_P_(level, tag, line, format, args, buf); @@ -481,7 +479,7 @@ class Logger : public Component { { this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf); } - this->notify_listeners_(level, tag); + this->notify_listeners_(level, tag, buf); this->write_log_buffer_to_console_(buf); } @@ -493,7 +491,7 @@ class Logger : public Component { LogBuffer &buf) { buf.write_header(level, tag, line, thread_name); buf.write_body(text, text_length); - this->notify_listeners_(level, tag); + this->notify_listeners_(level, tag, buf); } #endif @@ -550,7 +548,6 @@ class Logger : public Component { #endif // Group smaller types together at the end - uint16_t tx_buffer_at_{0}; uint16_t tx_buffer_size_{0}; uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE}; #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_ZEPHYR) From 5ef8a90aa0cd88442dd14dbbe47d48bbd5f043a5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 08:47:35 +0100 Subject: [PATCH 28/28] api change not needed now --- esphome/components/logger/logger.h | 4 ++-- esphome/components/logger/logger_esp32.cpp | 6 +++--- esphome/components/logger/logger_esp8266.cpp | 4 ++-- esphome/components/logger/logger_host.cpp | 6 +++--- esphome/components/logger/logger_libretiny.cpp | 2 +- esphome/components/logger/logger_rp2040.cpp | 4 ++-- esphome/components/logger/logger_zephyr.cpp | 8 ++++---- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index a8fa0f34a7..1678fed5f5 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -418,7 +418,7 @@ class Logger : public Component { #endif #endif void process_messages_(); - void write_msg_(const LogBuffer &buf); + void write_msg_(const char *msg, uint16_t len); // Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator inline void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format, @@ -455,7 +455,7 @@ class Logger : public Component { // Helper to write log buffer to console (replaces null terminator with newline and writes) inline void HOT write_to_console_(LogBuffer &buf) { buf.terminate_with_newline(); - this->write_msg_(buf); + this->write_msg_(buf.data, buf.pos); } // Helper to write log buffer to console if logging is enabled diff --git a/esphome/components/logger/logger_esp32.cpp b/esphome/components/logger/logger_esp32.cpp index 4d186d950e..dfa643d5e9 100644 --- a/esphome/components/logger/logger_esp32.cpp +++ b/esphome/components/logger/logger_esp32.cpp @@ -118,7 +118,7 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const LogBuffer &buf) { +void HOT Logger::write_msg_(const char *msg, uint16_t len) { #if defined(USE_LOGGER_UART_SELECTION_USB_CDC) || defined(USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG) // USB CDC/JTAG - single write including newline (already in buffer) // Use fwrite to stdout which goes through VFS to USB console @@ -128,10 +128,10 @@ void HOT Logger::write_msg_(const LogBuffer &buf) { // This is compile-time selection, not runtime detection - if USB is configured, it's always used. // There is no fallback to regular UART if "USB isn't connected" - that's the user's responsibility // to configure correctly for their hardware. This approach eliminates runtime overhead. - fwrite(buf.data, 1, buf.pos, stdout); + fwrite(msg, 1, len, stdout); #else // Regular UART - single write including newline (already in buffer) - uart_write_bytes(this->uart_num_, buf.data, buf.pos); + uart_write_bytes(this->uart_num_, msg, len); #endif } diff --git a/esphome/components/logger/logger_esp8266.cpp b/esphome/components/logger/logger_esp8266.cpp index 84474dd9df..0a3433d132 100644 --- a/esphome/components/logger/logger_esp8266.cpp +++ b/esphome/components/logger/logger_esp8266.cpp @@ -28,9 +28,9 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const LogBuffer &buf) { +void HOT Logger::write_msg_(const char *msg, uint16_t len) { // Single write with newline already in buffer (added by caller) - this->hw_serial_->write(buf.data, buf.pos); + this->hw_serial_->write(msg, len); } const LogString *Logger::get_uart_selection_() { diff --git a/esphome/components/logger/logger_host.cpp b/esphome/components/logger/logger_host.cpp index 612b47b9a9..be12b6df6a 100644 --- a/esphome/components/logger/logger_host.cpp +++ b/esphome/components/logger/logger_host.cpp @@ -3,7 +3,7 @@ namespace esphome::logger { -void HOT Logger::write_msg_(const LogBuffer &buf) { +void HOT Logger::write_msg_(const char *msg, uint16_t len) { static constexpr size_t TIMESTAMP_LEN = 10; // "[HH:MM:SS]" // tx_buffer_size_ defaults to 512, so 768 covers default + headroom char buffer[TIMESTAMP_LEN + 768]; @@ -15,8 +15,8 @@ void HOT Logger::write_msg_(const LogBuffer &buf) { size_t pos = strftime(buffer, TIMESTAMP_LEN + 1, "[%H:%M:%S]", &timeinfo); // Copy message (with newline already included by caller) - size_t copy_len = std::min(static_cast(buf.pos), sizeof(buffer) - pos); - memcpy(buffer + pos, buf.data, copy_len); + size_t copy_len = std::min(static_cast(len), sizeof(buffer) - pos); + memcpy(buffer + pos, msg, copy_len); pos += copy_len; // Single write for everything diff --git a/esphome/components/logger/logger_libretiny.cpp b/esphome/components/logger/logger_libretiny.cpp index 8635407845..aab8a97abf 100644 --- a/esphome/components/logger/logger_libretiny.cpp +++ b/esphome/components/logger/logger_libretiny.cpp @@ -49,7 +49,7 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const LogBuffer &buf) { this->hw_serial_->write(buf.data, buf.pos); } +void HOT Logger::write_msg_(const char *msg, uint16_t len) { this->hw_serial_->write(msg, len); } const LogString *Logger::get_uart_selection_() { switch (this->uart_) { diff --git a/esphome/components/logger/logger_rp2040.cpp b/esphome/components/logger/logger_rp2040.cpp index fc210f0bdd..1f435031f6 100644 --- a/esphome/components/logger/logger_rp2040.cpp +++ b/esphome/components/logger/logger_rp2040.cpp @@ -27,9 +27,9 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const LogBuffer &buf) { +void HOT Logger::write_msg_(const char *msg, uint16_t len) { // Single write with newline already in buffer (added by caller) - this->hw_serial_->write(buf.data, buf.pos); + this->hw_serial_->write(msg, len); } const LogString *Logger::get_uart_selection_() { diff --git a/esphome/components/logger/logger_zephyr.cpp b/esphome/components/logger/logger_zephyr.cpp index 9676c44485..ef1702c5c1 100644 --- a/esphome/components/logger/logger_zephyr.cpp +++ b/esphome/components/logger/logger_zephyr.cpp @@ -63,18 +63,18 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const LogBuffer &buf) { +void HOT Logger::write_msg_(const char *msg, uint16_t len) { // Single write with newline already in buffer (added by caller) #ifdef CONFIG_PRINTK // Requires the debug component and an active SWD connection. // It is used for pyocd rtt -t nrf52840 - k_str_out(const_cast(buf.data), buf.pos); + k_str_out(const_cast(msg), len); #endif if (this->uart_dev_ == nullptr) { return; } - for (uint16_t i = 0; i < buf.pos; ++i) { - uart_poll_out(this->uart_dev_, buf.data[i]); + for (uint16_t i = 0; i < len; ++i) { + uart_poll_out(this->uart_dev_, msg[i]); } }