2026-02-13 12:16:39 +01:00
|
|
|
#include "Logging.h"
|
|
|
|
|
|
2026-03-06 17:46:13 +01:00
|
|
|
#include <string>
|
|
|
|
|
|
|
|
|
|
#define MAX_ENTRY_LEN 256
|
|
|
|
|
#define MAX_LOG_LINES 16
|
|
|
|
|
|
|
|
|
|
// Simple ring buffer log, useful for error reporting when we encounter a crash
|
|
|
|
|
RTC_NOINIT_ATTR char logMessages[MAX_LOG_LINES][MAX_ENTRY_LEN];
|
|
|
|
|
RTC_NOINIT_ATTR size_t logHead = 0;
|
2026-03-09 21:53:38 +01:00
|
|
|
// Magic word written alongside logHead to detect uninitialized RTC memory.
|
|
|
|
|
// RTC_NOINIT_ATTR is not zeroed on cold boot, so logHead may appear in-range
|
|
|
|
|
// (0..MAX_LOG_LINES-1) by chance even though logMessages is garbage. The magic
|
|
|
|
|
// value is only set by clearLastLogs(), so its absence means the buffer was
|
|
|
|
|
// never properly initialized.
|
|
|
|
|
RTC_NOINIT_ATTR uint32_t rtcLogMagic;
|
|
|
|
|
static constexpr uint32_t LOG_RTC_MAGIC = 0xDEADBEEF;
|
2026-03-06 17:46:13 +01:00
|
|
|
|
|
|
|
|
void addToLogRingBuffer(const char* message) {
|
2026-03-09 21:53:38 +01:00
|
|
|
// Add the message to the ring buffer, overwriting old messages if necessary.
|
|
|
|
|
// If the magic is wrong or logHead is out of range (RTC_NOINIT_ATTR garbage
|
|
|
|
|
// on cold boot), clear the entire buffer so subsequent reads are safe.
|
|
|
|
|
if (rtcLogMagic != LOG_RTC_MAGIC || logHead >= MAX_LOG_LINES) {
|
|
|
|
|
memset(logMessages, 0, sizeof(logMessages));
|
|
|
|
|
logHead = 0;
|
|
|
|
|
rtcLogMagic = LOG_RTC_MAGIC;
|
|
|
|
|
}
|
2026-03-06 17:46:13 +01:00
|
|
|
strncpy(logMessages[logHead], message, MAX_ENTRY_LEN - 1);
|
|
|
|
|
logMessages[logHead][MAX_ENTRY_LEN - 1] = '\0';
|
|
|
|
|
logHead = (logHead + 1) % MAX_LOG_LINES;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-13 12:16:39 +01:00
|
|
|
// Since logging can take a large amount of flash, we want to make the format string as short as possible.
|
|
|
|
|
// This logPrintf prepend the timestamp, level and origin to the user-provided message, so that the user only needs to
|
|
|
|
|
// provide the format string for the message itself.
|
|
|
|
|
void logPrintf(const char* level, const char* origin, const char* format, ...) {
|
|
|
|
|
va_list args;
|
|
|
|
|
va_start(args, format);
|
2026-03-06 17:46:13 +01:00
|
|
|
char buf[MAX_ENTRY_LEN];
|
2026-02-13 12:16:39 +01:00
|
|
|
char* c = buf;
|
2026-04-08 05:25:08 +02:00
|
|
|
// add timestamp, level and origin
|
2026-02-13 12:16:39 +01:00
|
|
|
{
|
|
|
|
|
unsigned long ms = millis();
|
2026-04-08 05:25:08 +02:00
|
|
|
int len = snprintf(c, sizeof(buf), "[%lu] [%s] [%s] ", ms, level, origin);
|
2026-04-20 14:12:40 +08:00
|
|
|
// error while writing => return
|
2026-02-13 12:16:39 +01:00
|
|
|
if (len < 0) {
|
2026-04-08 05:25:08 +02:00
|
|
|
va_end(args);
|
|
|
|
|
return;
|
2026-02-13 12:16:39 +01:00
|
|
|
}
|
2026-04-08 05:25:08 +02:00
|
|
|
// clamp c to be in buffer range
|
|
|
|
|
c += std::min(len, MAX_ENTRY_LEN);
|
2026-02-13 12:16:39 +01:00
|
|
|
}
|
|
|
|
|
// add the user message
|
2026-04-08 05:25:08 +02:00
|
|
|
{
|
|
|
|
|
int len = vsnprintf(c, sizeof(buf) - (c - buf), format, args);
|
|
|
|
|
if (len < 0) {
|
|
|
|
|
va_end(args);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-13 12:16:39 +01:00
|
|
|
va_end(args);
|
2026-03-06 17:46:13 +01:00
|
|
|
if (logSerial) {
|
|
|
|
|
logSerial.print(buf);
|
|
|
|
|
}
|
|
|
|
|
addToLogRingBuffer(buf);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string getLastLogs() {
|
2026-03-09 21:53:38 +01:00
|
|
|
if (rtcLogMagic != LOG_RTC_MAGIC) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
2026-03-06 17:46:13 +01:00
|
|
|
std::string output;
|
|
|
|
|
for (size_t i = 0; i < MAX_LOG_LINES; i++) {
|
|
|
|
|
size_t idx = (logHead + i) % MAX_LOG_LINES;
|
|
|
|
|
if (logMessages[idx][0] != '\0') {
|
2026-03-09 21:53:38 +01:00
|
|
|
const size_t len = strnlen(logMessages[idx], MAX_ENTRY_LEN);
|
|
|
|
|
output.append(logMessages[idx], len);
|
2026-03-06 17:46:13 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 21:53:38 +01:00
|
|
|
// Checks whether the RTC log state is consistent: rtcLogMagic must equal
|
|
|
|
|
// LOG_RTC_MAGIC and logHead must be in 0..MAX_LOG_LINES-1. Returns true if
|
|
|
|
|
// corruption is detected, in which case rtcLogMagic is still invalid and
|
|
|
|
|
// logMessages may contain garbage. Callers (e.g. HalSystem::begin on the
|
|
|
|
|
// panic-reboot path) must call clearLastLogs() after a true result to fully
|
|
|
|
|
// reinitialize the ring buffer and stamp the magic before getLastLogs() is used.
|
|
|
|
|
bool sanitizeLogHead() {
|
|
|
|
|
if (rtcLogMagic != LOG_RTC_MAGIC || logHead >= MAX_LOG_LINES) {
|
|
|
|
|
logHead = 0;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 17:46:13 +01:00
|
|
|
void clearLastLogs() {
|
|
|
|
|
for (size_t i = 0; i < MAX_LOG_LINES; i++) {
|
|
|
|
|
logMessages[i][0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
logHead = 0;
|
2026-03-09 21:53:38 +01:00
|
|
|
rtcLogMagic = LOG_RTC_MAGIC;
|
2026-02-13 12:16:39 +01:00
|
|
|
}
|