diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 1b41bc3d47..89e8edcf44 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -23,36 +23,48 @@ static const char *const TAG = "logger"; // - Messages are serialized through main loop for proper console output // - Fallback to emergency console logging only if ring buffer is full // - WITHOUT task log buffer: Only emergency console output, no callbacks +// +// Optimized for the common case: 99.9% of logs come from the main thread 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)) return; -#if defined(USE_ESP32) || defined(USE_LIBRETINY) - TaskHandle_t current_task = xTaskGetCurrentTaskHandle(); - bool is_main_task = (current_task == main_task_); -#else // USE_HOST - pthread_t current_thread = pthread_self(); - bool is_main_task = pthread_equal(current_thread, main_thread_); -#endif + const bool is_main_task = this->is_current_main_task_(); - // Check and set recursion guard - uses pthread TLS for per-thread/task state - if (this->check_and_set_task_log_recursion_(is_main_task)) { - return; // Recursion detected - } - - // Main thread/task uses the shared buffer for efficiency - if (is_main_task) { + // 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->reset_task_log_recursion_(is_main_task); return; } + // Main task with recursion - silently drop to prevent infinite loop + if (is_main_task) { + return; + } + + // Non-main thread handling (~0.1% of logs) + this->log_vprintf_non_main_thread_(level, tag, line, format, args); +} + +// Handles non-main thread logging only +// Kept separate from hot path to improve instruction cache performance +void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args) { + // Check if already in recursion for this non-main thread/task + if (this->is_non_main_task_recursive_()) { + return; + } + + // RAII guard - automatically resets on any return path + 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 #if defined(USE_ESP32) || defined(USE_LIBRETINY) - message_sent = - this->log_buffer_->send_message_thread_safe(level, tag, static_cast(line), current_task, format, args); + message_sent = this->log_buffer_->send_message_thread_safe(level, tag, static_cast(line), + xTaskGetCurrentTaskHandle(), format, args); #else // USE_HOST message_sent = this->log_buffer_->send_message_thread_safe(level, tag, static_cast(line), format, args); #endif @@ -85,21 +97,17 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch this->write_msg_(console_buffer, buffer_at); } - // Reset the recursion guard for this thread/task - this->reset_task_log_recursion_(is_main_task); + // RAII guard automatically resets on return } #else -// Implementation for all other platforms +// Implementation for all other platforms (single-task, no threading) 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; - global_recursion_guard_ = true; - + 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); - - global_recursion_guard_ = false; } #endif // USE_ESP32 / USE_HOST / USE_LIBRETINY @@ -130,7 +138,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; - global_recursion_guard_ = true; + RecursionGuard guard(global_recursion_guard_); this->tx_buffer_at_ = 0; // Copy format string from progmem @@ -140,9 +148,8 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++); } - // Buffer full from copying format + // Buffer full from copying format - RAII guard handles cleanup on return if (this->tx_buffer_at_ >= this->tx_buffer_size_) { - global_recursion_guard_ = false; // Make sure to reset the recursion guard before returning return; } @@ -161,8 +168,6 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas // Write to console starting at the msg_start this->write_tx_buffer_to_console_(msg_start, &msg_length); - - global_recursion_guard_ = false; } #endif // USE_STORE_LOG_STR_IN_FLASH diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index c58ca8ddce..1e8ddf25d5 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -229,6 +229,29 @@ class Logger : public Component { #endif protected: + // RAII guard for recursion flags - sets flag on construction, clears on destruction + class RecursionGuard { + public: + explicit RecursionGuard(bool &flag) : flag_(flag) { flag_ = true; } + ~RecursionGuard() { flag_ = false; } + RecursionGuard(const RecursionGuard &) = delete; + RecursionGuard &operator=(const RecursionGuard &) = delete; + + private: + bool &flag_; + }; + +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) + // Handles non-main thread logging only (~0.1% of calls) + void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args); + + // Platform-specific main task/thread check - inlined for fast path performance +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + inline bool is_current_main_task_() const { return xTaskGetCurrentTaskHandle() == this->main_task_; } +#else // USE_HOST + inline bool is_current_main_task_() const { return pthread_equal(pthread_self(), this->main_thread_); } +#endif +#endif void process_messages_(); void write_msg_(const char *msg, size_t len); @@ -348,10 +371,10 @@ class Logger : public Component { const device *uart_dev_{nullptr}; #endif #if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) - void *main_task_ = nullptr; // Only used for thread name identification + void *main_task_{nullptr}; // Main thread/task for fast path comparison #endif #ifdef USE_HOST - pthread_t main_thread_{}; // Main thread for identification + pthread_t main_thread_{}; // Main thread for pthread_equal() comparison #endif #ifdef USE_ESP32 // Task-specific recursion guards: @@ -434,29 +457,26 @@ class Logger : public Component { #endif #if defined(USE_ESP32) || defined(USE_HOST) - inline bool HOT check_and_set_task_log_recursion_(bool is_main_task) { - if (is_main_task) { - const bool was_recursive = main_task_recursion_guard_; - main_task_recursion_guard_ = true; - return was_recursive; + // RAII guard for non-main task recursion using pthread TLS + class NonMainTaskRecursionGuard { + public: + explicit NonMainTaskRecursionGuard(pthread_key_t key) : key_(key) { + pthread_setspecific(key_, reinterpret_cast(1)); } + ~NonMainTaskRecursionGuard() { pthread_setspecific(key_, nullptr); } + NonMainTaskRecursionGuard(const NonMainTaskRecursionGuard &) = delete; + NonMainTaskRecursionGuard &operator=(const NonMainTaskRecursionGuard &) = delete; - intptr_t current = (intptr_t) pthread_getspecific(log_recursion_key_); - if (current != 0) - return true; + private: + pthread_key_t key_; + }; - pthread_setspecific(log_recursion_key_, (void *) 1); - return false; - } + // Check if non-main task is already in recursion (via TLS) + inline bool HOT is_non_main_task_recursive_() const { return pthread_getspecific(log_recursion_key_) != nullptr; } - inline void HOT reset_task_log_recursion_(bool is_main_task) { - if (is_main_task) { - main_task_recursion_guard_ = false; - return; - } + // Create RAII guard for non-main task recursion + inline NonMainTaskRecursionGuard make_non_main_task_guard_() { return NonMainTaskRecursionGuard(log_recursion_key_); } - pthread_setspecific(log_recursion_key_, (void *) 0); - } #elif defined(USE_LIBRETINY) // LibreTiny doesn't have FreeRTOS TLS, so use a simple approach: // - Main task uses dedicated boolean (same as ESP32) @@ -466,29 +486,11 @@ class Logger : public Component { // - Cross-task "recursion" is prevented by the buffer mutex anyway // - Missing a recursive call from another task is acceptable (falls back to direct output) - inline bool HOT check_and_set_task_log_recursion_(bool is_main_task) { - if (is_main_task) { - const bool was_recursive = main_task_recursion_guard_; - main_task_recursion_guard_ = true; - return was_recursive; - } + // Check if non-main task is already in recursion + inline bool HOT is_non_main_task_recursive_() const { return non_main_task_recursion_guard_; } - // For non-main tasks, use a simple shared guard - // This may block legitimate concurrent logs from different tasks, - // but that's acceptable - they'll fall back to direct console output - const bool was_recursive = non_main_task_recursion_guard_; - non_main_task_recursion_guard_ = true; - return was_recursive; - } - - inline void HOT reset_task_log_recursion_(bool is_main_task) { - if (is_main_task) { - main_task_recursion_guard_ = false; - return; - } - - non_main_task_recursion_guard_ = false; - } + // Create RAII guard for non-main task recursion (uses shared boolean for all non-main tasks) + inline RecursionGuard make_non_main_task_guard_() { return RecursionGuard(non_main_task_recursion_guard_); } #endif #ifdef USE_HOST