#pragma once #include #include "config.h" // Rolling debug log stored in LittleFS at /debug.log // Format: one JSON line per entry: {"t":,"m":"message"} // Max MAX_LOG_ENTRIES entries; oldest are trimmed on save. #define LOG_FILE "/debug.log" #define MAX_LOG_ENTRIES 250 #define MAX_LOG_MSG 128 class DebugLog { public: void begin() { _entryCount = 0; if (LittleFS.exists(LOG_FILE)) { File f = LittleFS.open(LOG_FILE, "r"); if (f) { while (f.available()) { String line = f.readStringUntil('\n'); line.trim(); if (line.length() > 0) _entryCount++; } f.close(); } } } void log(const char* msg) { // Mirror to serial for live debugging Serial.printf("[LOG] %s\n", msg); if (_entryCount >= MAX_LOG_ENTRIES) { trimLog(); } File f = LittleFS.open(LOG_FILE, "a"); if (!f) return; char escaped[MAX_LOG_MSG * 2]; escapeJson(msg, escaped, sizeof(escaped)); f.printf("{\"t\":%lu,\"m\":\"%s\"}\n", millis(), escaped); f.close(); _entryCount++; } void logf(const char* fmt, ...) { char buf[MAX_LOG_MSG]; va_list args; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); log(buf); } // Send the entire log as a JSON array over serial (handles "get_log" cmd). void sendOverSerial() { Serial.print("{\"rsp\":\"log\",\"entries\":["); Serial.flush(); if (LittleFS.exists(LOG_FILE)) { File f = LittleFS.open(LOG_FILE, "r"); if (f) { bool first = true; while (f.available()) { String line = f.readStringUntil('\n'); line.trim(); if (line.length() == 0) continue; if (!first) Serial.print(","); Serial.print(line); first = false; Serial.flush(); } f.close(); } } Serial.println("]}"); Serial.flush(); } void clear() { LittleFS.remove(LOG_FILE); _entryCount = 0; } private: int _entryCount = 0; // Drop the oldest half of the log when MAX_LOG_ENTRIES is hit. void trimLog() { File f = LittleFS.open(LOG_FILE, "r"); if (!f) return; String lines[MAX_LOG_ENTRIES]; int count = 0; while (f.available() && count < MAX_LOG_ENTRIES) { String line = f.readStringUntil('\n'); line.trim(); if (line.length() > 0) { lines[count++] = line; } } f.close(); int keepFrom = count / 2; File out = LittleFS.open(LOG_FILE, "w"); if (!out) return; for (int i = keepFrom; i < count; i++) { out.println(lines[i]); } out.close(); _entryCount = count - keepFrom; } void escapeJson(const char* input, char* output, size_t maxLen) { size_t o = 0; for (const char* p = input; *p && o < maxLen - 7; p++) { unsigned char c = (unsigned char)*p; if (c == '"' || c == '\\') { output[o++] = '\\'; output[o++] = c; } else if (c == '\n') { output[o++] = '\\'; output[o++] = 'n'; } else if (c == '\r') { output[o++] = '\\'; output[o++] = 'r'; } else if (c == '\t') { output[o++] = '\\'; output[o++] = 't'; } else if (c < 0x20) { o += snprintf(output + o, maxLen - o, "\\u%04x", c); } else { output[o++] = c; } } output[o] = '\0'; } };