ESPHome 2026.3.3
Loading...
Searching...
No Matches
log_buffer.h
Go to the documentation of this file.
1#pragma once
2
4#include "esphome/core/log.h"
5
6namespace esphome::logger {
7
8// Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin)
9static constexpr uint16_t MAX_HEADER_SIZE = 128;
10
11// ANSI color code last digit (30-38 range, store only last digit to save RAM)
12static constexpr char LOG_LEVEL_COLOR_DIGIT[] = {
13 '\0', // NONE
14 '1', // ERROR (31 = red)
15 '3', // WARNING (33 = yellow)
16 '2', // INFO (32 = green)
17 '5', // CONFIG (35 = magenta)
18 '6', // DEBUG (36 = cyan)
19 '7', // VERBOSE (37 = gray)
20 '8', // VERY_VERBOSE (38 = white)
21};
22
23static constexpr char LOG_LEVEL_LETTER_CHARS[] = {
24 '\0', // NONE
25 'E', // ERROR
26 'W', // WARNING
27 'I', // INFO
28 'C', // CONFIG
29 'D', // DEBUG
30 'V', // VERBOSE (VERY_VERBOSE uses two 'V's)
31};
32
33// Buffer wrapper for log formatting functions
34struct LogBuffer {
35 char *data;
36 uint16_t size;
37 uint16_t pos{0};
38 // Replaces the null terminator with a newline for console output.
39 // Must be called after notify_listeners_() since listeners need null-terminated strings.
40 // Console output uses length-based writes (buf.pos), so null terminator is not needed.
42 if (this->pos < this->size) {
43 this->data[this->pos++] = '\n';
44 } else if (this->size > 0) {
45 // Buffer was full - replace last char with newline to ensure it's visible
46 this->data[this->size - 1] = '\n';
47 this->pos = this->size;
48 }
49 }
50 void HOT write_header(uint8_t level, const char *tag, int line, const char *thread_name) {
51 // Early return if insufficient space - intentionally don't update pos to prevent partial writes
52 if (this->pos + MAX_HEADER_SIZE > this->size)
53 return;
54
55 char *p = this->current_();
56
57 // Write ANSI color
58 this->write_ansi_color_(p, level);
59
60 // Construct: [LEVEL][tag:line]
61 *p++ = '[';
62 if (level != 0) {
63 if (level >= 7) {
64 *p++ = 'V'; // VERY_VERBOSE = "VV"
65 *p++ = 'V';
66 } else {
67 *p++ = LOG_LEVEL_LETTER_CHARS[level];
68 }
69 }
70 *p++ = ']';
71 *p++ = '[';
72
73 // Copy tag
74 this->copy_string_(p, tag);
75
76 *p++ = ':';
77
78 // Format line number using subtraction loops (no division - important for ESP8266 which lacks hardware divider)
79 if (line > 999) [[unlikely]] {
80 write_digit(p, line, 1000);
81 }
82 write_digit(p, line, 100);
83 write_digit(p, line, 10);
84 *p++ = '0' + line;
85 *p++ = ']';
86
87#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) || defined(USE_HOST)
88 // Write thread name with bold red color
89 if (thread_name != nullptr) {
90 this->write_ansi_color_(p, 1); // Bold red for thread name
91 *p++ = '[';
92 this->copy_string_(p, thread_name);
93 *p++ = ']';
94 this->write_ansi_color_(p, level); // Restore original color
95 }
96#endif
97
98 *p++ = ':';
99 *p++ = ' ';
100
101 this->pos = p - this->data;
102 }
103 void HOT format_body(const char *format, va_list args) {
104 this->format_vsnprintf_(format, args);
105 this->finalize_();
106 }
107#ifdef USE_STORE_LOG_STR_IN_FLASH
108 void HOT format_body_P(PGM_P format, va_list args) {
109 this->format_vsnprintf_P_(format, args);
110 this->finalize_();
111 }
112#endif
113 void write_body(const char *text, uint16_t text_length) {
114 this->write_(text, text_length);
115 this->finalize_();
116 }
117
118 private:
119 bool full_() const { return this->pos >= this->size; }
120 uint16_t remaining_() const { return this->size - this->pos; }
121 char *current_() { return this->data + this->pos; }
122 void write_(const char *value, uint16_t length) {
123 const uint16_t available = this->remaining_();
124 const uint16_t copy_len = (length < available) ? length : available;
125 if (copy_len > 0) {
126 memcpy(this->current_(), value, copy_len);
127 this->pos += copy_len;
128 }
129 }
130 void finalize_() {
131 // Write color reset sequence
132 static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1;
133 this->write_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN);
134 // Null terminate
135 this->data[this->full_() ? this->size - 1 : this->pos] = '\0';
136 }
137 void strip_trailing_newlines_() {
138 while (this->pos > 0 && this->data[this->pos - 1] == '\n')
139 this->pos--;
140 }
141 void process_vsnprintf_result_(int ret) {
142 if (ret < 0)
143 return;
144 const uint16_t rem = this->remaining_();
145 this->pos += (ret >= rem) ? (rem - 1) : static_cast<uint16_t>(ret);
146 this->strip_trailing_newlines_();
147 }
148 void format_vsnprintf_(const char *format, va_list args) {
149 if (this->full_())
150 return;
151 this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args));
152 }
153#ifdef USE_STORE_LOG_STR_IN_FLASH
154 void format_vsnprintf_P_(PGM_P format, va_list args) {
155 if (this->full_())
156 return;
157 this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args));
158 }
159#endif
160 // Extract one decimal digit via subtraction (no division - important for ESP8266)
161 static inline void ESPHOME_ALWAYS_INLINE write_digit(char *&p, int &value, int divisor) {
162 char d = '0';
163 while (value >= divisor) {
164 d++;
165 value -= divisor;
166 }
167 *p++ = d;
168 }
169 // Write ANSI color escape sequence to buffer, updates pointer in place
170 // Caller is responsible for ensuring buffer has sufficient space
171 void write_ansi_color_(char *&p, uint8_t level) {
172 if (level == 0)
173 return;
174 // Direct buffer fill: "\033[{bold};3{color}m" (7 bytes)
175 *p++ = '\033';
176 *p++ = '[';
177 *p++ = (level == 1) ? '1' : '0'; // Only ERROR is bold
178 *p++ = ';';
179 *p++ = '3';
180 *p++ = LOG_LEVEL_COLOR_DIGIT[level];
181 *p++ = 'm';
182 }
183 // Copy string without null terminator, updates pointer in place
184 // Caller is responsible for ensuring buffer has sufficient space
185 void copy_string_(char *&p, const char *str) {
186 const size_t len = strlen(str);
187 // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - intentionally no null terminator, building string piece by
188 // piece
189 memcpy(p, str, len);
190 p += len;
191 }
192};
193
194} // namespace esphome::logger
const char int line
Definition log.h:74
const char * tag
Definition log.h:74
const char int const __FlashStringHelper * format
Definition log.h:74
const char int const __FlashStringHelper va_list args
Definition log.h:74
std::string size_t len
Definition helpers.h:892
void HOT format_body_P(PGM_P format, va_list args)
Definition log_buffer.h:108
void write_body(const char *text, uint16_t text_length)
Definition log_buffer.h:113
void HOT write_header(uint8_t level, const char *tag, int line, const char *thread_name)
Definition log_buffer.h:50
void HOT format_body(const char *format, va_list args)
Definition log_buffer.h:103
uint16_t length
Definition tt21100.cpp:0