diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index b5fb15d347..48ad70f95d 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -37,7 +37,7 @@ struct device; namespace esphome::logger { // Color and letter constants for log levels -static const char *const LOG_LEVEL_COLORS[] = { +static constexpr const char *const LOG_LEVEL_COLORS[] = { "", // NONE ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), // ERROR ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_YELLOW), // WARNING @@ -48,16 +48,21 @@ static const char *const LOG_LEVEL_COLORS[] = { ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_WHITE), // VERY_VERBOSE }; -static const char *const LOG_LEVEL_LETTERS[] = { - "", // NONE - "E", // ERROR - "W", // WARNING - "I", // INFO - "C", // CONFIG - "D", // DEBUG - "V", // VERBOSE - "VV", // VERY_VERBOSE -}; +// Single character log level letters (E, W, I, C, D, V) +static constexpr char LOG_LEVEL_LETTER_CHARS[] = {'\0', 'E', 'W', 'I', 'C', 'D', 'V'}; + +// ANSI color codes are always 7 characters ("\033[0;32m") +static constexpr uint8_t ANSI_COLOR_LEN = 7; + +// Maximum header size (conservative estimate) +static constexpr uint16_t MAX_HEADER_SIZE = 128; + +// Compile-time string length calculation +static constexpr size_t constexpr_strlen(const char *str) { return *str ? 1 + constexpr_strlen(str + 1) : 0; } + +// Compile-time validation of log level string lengths +static_assert(constexpr_strlen(LOG_LEVEL_COLORS[0]) == 0, "Level 0 color must be empty"); +static_assert(constexpr_strlen(LOG_LEVEL_COLORS[1]) == 7, "Color codes must be 7 chars"); #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) /** Enum for logging UART selection @@ -215,14 +220,6 @@ class Logger : public Component { } } - // Format string to explicit buffer with varargs - inline void printf_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, ...) { - va_list arg; - va_start(arg, format); - this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, arg); - va_end(arg); - } - #ifndef USE_HOST const LogString *get_uart_selection_(); #endif @@ -318,26 +315,59 @@ class Logger : public Component { } #endif + // Helper: copy fixed-length data to buffer and advance position + static inline void copy_and_advance(char *buffer, uint16_t &pos, const char *data, uint8_t len) { + memcpy(buffer + pos, data, len); + pos += len; + } + + // Helper: copy string to buffer and advance position (calculates length with strlen) + static inline void copy_string(char *buffer, uint16_t &pos, const char *str) { + copy_and_advance(buffer, pos, str, strlen(str)); + } + 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) { - // Format header - // uint8_t level is already bounded 0-255, just ensure it's <= 7 - if (level > 7) - level = 7; + uint16_t pos = *buffer_at; + if (pos + MAX_HEADER_SIZE > buffer_size) + return; - const char *color = esphome::logger::LOG_LEVEL_COLORS[level]; - const char *letter = esphome::logger::LOG_LEVEL_LETTERS[level]; + const char *color = LOG_LEVEL_COLORS[level]; + const uint8_t color_len = (level == 0) ? 0 : ANSI_COLOR_LEN; + + // Construct: [LEVEL][tag:line]: + copy_and_advance(buffer, pos, color, color_len); + 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++] = ':'; + buffer[pos++] = '0' + (line / 100) % 10; + buffer[pos++] = '0' + (line / 10) % 10; + buffer[pos++] = '0' + line % 10; + buffer[pos++] = ']'; #if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) if (thread_name != nullptr) { - // Non-main task with thread name - this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]%s[%s]%s: ", color, letter, tag, line, - ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), thread_name, color); - return; + copy_and_advance(buffer, pos, LOG_LEVEL_COLORS[1], ANSI_COLOR_LEN); // Bold red (error color) + buffer[pos++] = '['; + copy_string(buffer, pos, thread_name); + buffer[pos++] = ']'; + copy_and_advance(buffer, pos, color, color_len); } #endif - // Main task or non ESP32/LibreTiny platform - this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]: ", color, letter, tag, line); + + buffer[pos++] = ':'; + buffer[pos++] = ' '; + *buffer_at = pos; } inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format,