3#include <esp_heap_caps.h>
11#ifndef USE_ESP_IDF_LOG
13 "USE_ESP_IDF_LOG is not defined: Arduino-ESP32 reroutes ESP_LOGx to " \
14 "log_printf, which bypasses esp_log_set_vprintf. The web log viewer will " \
15 "capture nothing. Build with -D USE_ESP_IDF_LOG."
20LogBuffer* LogBuffer::instance_ =
nullptr;
23 size_t max_line_length,
size_t min_headroom_bytes)
24 : max_lines_(max_lines),
25 max_age_ms_(max_age_ms),
26 max_line_length_(max_line_length),
27 min_headroom_bytes_(min_headroom_bytes),
29 mutex_ = xSemaphoreCreateMutexStatic(&mutex_buffer_);
37 if (previous_vprintf_ !=
nullptr) {
45 session_id_ = esp_random() ^
static_cast<uint32_t
>(esp_timer_get_time());
54 if (capture_queue_ !=
nullptr &&
55 xTaskCreate(&LogBuffer::capture_task,
"LogCapture",
58 vQueueDelete(capture_queue_);
59 capture_queue_ =
nullptr;
63 previous_vprintf_ = esp_log_set_vprintf(&LogBuffer::vprintf_trampoline);
66void LogBuffer::enqueue_capture(
LogQueueItem& item,
int n) {
71 item.
length = (
static_cast<size_t>(n) <
sizeof(item.
text))
72 ?
static_cast<uint16_t
>(n)
73 :
static_cast<uint16_t
>(
sizeof(item.
text) - 1);
77 xQueueSend(capture_queue_, &item, 0);
80void LogBuffer::capture_task(
void* arg) {
84 if (xQueueReceive(
self->capture_queue_, &item, portMAX_DELAY) == pdTRUE) {
87 self->push_line(item.text, item.length, item.timestamp_ms);
92int LogBuffer::vprintf_trampoline(
const char* format, va_list args) {
95#if !defined(ESP_LOG_VERSION) || ESP_LOG_VERSION == 1
105 if (inst ==
nullptr || inst->capture_queue_ ==
nullptr ||
106 xPortInIsrContext()) {
107 if (inst !=
nullptr && inst->previous_vprintf_ !=
nullptr) {
108 return inst->previous_vprintf_(format, args);
115 va_copy(args_copy, args);
116 int n = vsnprintf(item.text,
sizeof(item.text), format, args);
123 if (
static_cast<size_t>(n) >=
sizeof(item.text)) {
130 if (inst->previous_vprintf_ !=
nullptr) {
131 ret = inst->previous_vprintf_(format, args_copy);
138 fwrite(item.text, 1,
static_cast<size_t>(n), stdout);
144 inst->enqueue_capture(item, n);
151 if (inst !=
nullptr && inst->previous_vprintf_ !=
nullptr) {
154 ret = inst->previous_vprintf_(format, copy);
157 if (inst !=
nullptr && inst->capture_queue_ !=
nullptr &&
158 !xPortInIsrContext()) {
162 int n = vsnprintf(item.text,
sizeof(item.text), format, copy);
164 inst->enqueue_capture(item, n);
177std::string strip_ansi(
const char* text,
size_t length) {
182 if (text[i] ==
'\x1b') {
184 if (i < length && text[i] ==
'[') {
187 unsigned char c =
static_cast<unsigned char>(text[i]);
189 if (c >= 0x40 && c <= 0x7e) {
196 out.push_back(text[i]);
205 while (length > 0 && (text[length - 1] ==
'\n' || text[length - 1] ==
'\r')) {
218 if (heap_caps_get_largest_free_block(MALLOC_CAP_8BIT) < min_headroom_bytes_) {
224 std::string line = strip_ansi(text, length);
228 if (line.size() > max_line_length_) {
229 line.resize(max_line_length_);
232 xSemaphoreTake(mutex_, portMAX_DELAY);
233 prune_locked(now_ms);
234 records_.push_back({next_seq_, now_ms, std::move(line)});
237 while (records_.size() > max_lines_) {
238 records_.pop_front();
240 xSemaphoreGive(mutex_);
243void LogBuffer::prune_locked(uint32_t now_ms) {
244 while (!records_.empty()) {
245 const LogRecord& front = records_.front();
248 records_.pop_front();
259 snapshot.
gap =
false;
261 xSemaphoreTake(mutex_, portMAX_DELAY);
262 prune_locked(now_ms);
263 snapshot.
next = next_seq_;
267 for (
const auto& record : records_) {
268 snapshot.
lines.push_back(record.text);
271 if (records_.empty()) {
274 snapshot.
gap = since < next_seq_;
275 }
else if (records_.front().seq > since) {
279 for (
const auto& record : records_) {
280 if (record.seq >= since) {
281 snapshot.
lines.push_back(record.text);
285 xSemaphoreGive(mutex_);
295 xSemaphoreTake(mutex_, portMAX_DELAY);
296 size_t count = records_.size();
297 xSemaphoreGive(mutex_);
void push_line(const char *text, size_t length, uint32_t now_ms)
Append a pre-formatted log line to the buffer.
LogBuffer(size_t max_lines=100, uint32_t max_age_ms=2000, size_t max_line_length=kLogCaptureLineMax, size_t min_headroom_bytes=kLogCaptureMinHeadroomBytes)
LogSnapshot snapshot_since(uint32_t since, bool has_since, uint32_t now_ms)
Return retained lines newer than since.
void install()
Install the chained vprintf hook. Call once, early in startup.
size_t size()
Test/inspection helper: current number of retained records.
constexpr size_t kLogCaptureQueueDepth
constexpr UBaseType_t kLogCaptureTaskPriority
constexpr uint32_t kLogCaptureTaskStackSize
char text[kLogCaptureLineMax]
A single captured log line.
Result of a snapshot_since() query, ready to serialize for the web UI.
std::vector< std::string > lines