mirror of
https://github.com/esphome/esphome.git
synced 2026-02-05 10:20:11 +08:00
[logger] Use vsnprintf_P directly for ESP8266 flash format strings (#13716)
This commit is contained in:
@@ -128,22 +128,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
||||
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
|
||||
//
|
||||
// This function handles format strings stored in flash memory (PROGMEM) to save RAM.
|
||||
// The buffer is used in a special way to avoid allocating extra memory:
|
||||
//
|
||||
// Memory layout during execution:
|
||||
// Step 1: Copy format string from flash to buffer
|
||||
// tx_buffer_: [format_string][null][.....................]
|
||||
// tx_buffer_at_: ------------------^
|
||||
// msg_start: saved here -----------^
|
||||
//
|
||||
// Step 2: format_log_to_buffer_with_terminator_ reads format string from beginning
|
||||
// and writes formatted output starting at msg_start position
|
||||
// tx_buffer_: [format_string][null][formatted_message][null]
|
||||
// tx_buffer_at_: -------------------------------------^
|
||||
//
|
||||
// Step 3: Output the formatted message (starting at msg_start)
|
||||
// write_msg_ and callbacks receive: this->tx_buffer_ + msg_start
|
||||
// which points to: [formatted_message][null]
|
||||
// Uses vsnprintf_P to read the format string directly from flash without copying to RAM.
|
||||
//
|
||||
void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
|
||||
va_list args) { // NOLINT
|
||||
@@ -153,35 +138,25 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
||||
RecursionGuard guard(global_recursion_guard_);
|
||||
this->tx_buffer_at_ = 0;
|
||||
|
||||
// Copy format string from progmem
|
||||
auto *format_pgm_p = reinterpret_cast<const uint8_t *>(format);
|
||||
char ch = '.';
|
||||
while (this->tx_buffer_at_ < this->tx_buffer_size_ && ch != '\0') {
|
||||
this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++);
|
||||
}
|
||||
// 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<PGM_P>(format), args);
|
||||
this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
|
||||
|
||||
// Buffer full from copying format - RAII guard handles cleanup on return
|
||||
if (this->tx_buffer_at_ >= this->tx_buffer_size_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the offset before calling format_log_to_buffer_with_terminator_
|
||||
// since it will increment tx_buffer_at_ to the end of the formatted string
|
||||
uint16_t msg_start = this->tx_buffer_at_;
|
||||
this->format_log_to_buffer_with_terminator_(level, tag, line, this->tx_buffer_, args, this->tx_buffer_,
|
||||
&this->tx_buffer_at_, this->tx_buffer_size_);
|
||||
|
||||
uint16_t msg_length =
|
||||
this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
|
||||
// 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_ + msg_start, msg_length);
|
||||
listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_);
|
||||
#endif
|
||||
|
||||
// Write to console starting at the msg_start
|
||||
this->write_tx_buffer_to_console_(msg_start, &msg_length);
|
||||
// Write to console
|
||||
this->write_tx_buffer_to_console_();
|
||||
}
|
||||
#endif // USE_STORE_LOG_STR_IN_FLASH
|
||||
|
||||
|
||||
@@ -597,31 +597,40 @@ class Logger : public Component {
|
||||
*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<uint16_t>(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) {
|
||||
// Get remaining capacity in the buffer
|
||||
// Check remaining capacity in the buffer
|
||||
if (*buffer_at >= buffer_size)
|
||||
return;
|
||||
const uint16_t remaining = buffer_size - *buffer_at;
|
||||
|
||||
const int ret = vsnprintf(buffer + *buffer_at, remaining, format, args);
|
||||
|
||||
if (ret < 0) {
|
||||
return; // Encoding error, do not increment buffer_at
|
||||
}
|
||||
|
||||
// Update buffer_at with the formatted length (handle 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
|
||||
uint16_t formatted_len = (ret >= remaining) ? (remaining - 1) : ret;
|
||||
*buffer_at += formatted_len;
|
||||
|
||||
// Remove all trailing newlines right after formatting
|
||||
while (*buffer_at > 0 && buffer[*buffer_at - 1] == '\n') {
|
||||
(*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);
|
||||
|
||||
Reference in New Issue
Block a user