232 lines
6.7 KiB
C++
232 lines
6.7 KiB
C++
#include <cstdarg>
|
|
#include <cstdbool>
|
|
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
|
|
#include <Arduino.h>
|
|
#include "BadgeLog.h"
|
|
#ifdef SAMD21
|
|
#include "SerialBridge.h"
|
|
#endif
|
|
|
|
/**
|
|
* Internal definitions
|
|
*/
|
|
#ifndef LOG_RINGBUF_SZ
|
|
#define LOG_RINGBUF_SZ (4096U)
|
|
#endif//LOG_RINGBUF_SZ
|
|
|
|
/* Log entries are truncated to a maximum of `LOG_MAX_SZ - 1` characters */
|
|
#ifndef LOG_MAX_SZ
|
|
#define LOG_MAX_SZ (256U)
|
|
#endif//LOG_MAX_SZ$
|
|
|
|
#ifndef DEFAULT_BADGEOS_LOG_LEVEL
|
|
#define DEFAULT_BADGEOS_LOG_LEVEL (LOG_INFO)
|
|
#endif//DEFAULT_BADGEOS_LOG_LEVEL
|
|
|
|
#ifndef LOG_LEVEL
|
|
# ifdef CONFIG_BADGEOS_LOG_LEVEL
|
|
# define LOG_LEVEL (CONFIG_BADGEOS_LOG_LEVEL)
|
|
# else
|
|
# define LOG_LEVEL (DEFAULT_BADGEOS_LOG_LEVEL)
|
|
# endif
|
|
#endif//LOG_LEVEL
|
|
|
|
#ifndef LOG_DECORATE
|
|
#define LOG_DECORATE (LOG_DECORATED)
|
|
#endif//LOG_DECORATE
|
|
|
|
/**
|
|
* Logging ringbuffer
|
|
*/
|
|
static char ringbuf[LOG_RINGBUF_SZ];
|
|
static char *cur_r = ringbuf, *cur_w = ringbuf;
|
|
#define _RINGBUF_END (&ringbuf[sizeof(ringbuf)])
|
|
|
|
/* Cursor management */
|
|
static inline size_t _cur_w_sz(bool contig) {
|
|
char *stop;
|
|
ptrdiff_t sz;
|
|
|
|
if (contig) {
|
|
stop = cur_r > cur_w ? cur_r : _RINGBUF_END;
|
|
} else {
|
|
stop = cur_r + sizeof(ringbuf);
|
|
}
|
|
sz = stop - cur_w;
|
|
return (sz >= 0) ? (size_t) sz : 0;
|
|
}
|
|
static inline void _cur_adv(char **cur_p, const size_t sz) {
|
|
*cur_p += sz;
|
|
while (*cur_p >= _RINGBUF_END) *cur_p -= LOG_RINGBUF_SZ;
|
|
}
|
|
/* R/W */
|
|
static inline size_t _ring_rd(char *buf, size_t buf_sz) {
|
|
size_t i;
|
|
for (i = 0; buf_sz-- >= 0; ++i, ++buf) {
|
|
/* Immediately bail if we're about to copy from the write cursor */
|
|
if (cur_r == cur_w) break;
|
|
*buf = *cur_r;
|
|
/* Check if we hit a NUL before advancing `cur_r` */
|
|
if (*(cur_r++) == '\0') break;
|
|
if (cur_r >= _RINGBUF_END) cur_r -= LOG_RINGBUF_SZ;
|
|
}
|
|
/* Ensure we always NUL terminate */
|
|
*buf = '\0';
|
|
return i;
|
|
}
|
|
static inline void _ring_wr(const char * buf, size_t buf_sz) {
|
|
while (buf_sz-- >= 0 && *buf != '\0') {
|
|
*(cur_w++) = *(buf++);
|
|
if (cur_w >= _RINGBUF_END) cur_w -= LOG_RINGBUF_SZ;
|
|
}
|
|
*(cur_w++) = '\0';
|
|
}
|
|
|
|
/* Entry truncation logic */
|
|
static const char _trunc_s[] = "[trunc]";
|
|
static inline void _mark_trunc(char * const buf, const size_t buf_sz) {
|
|
/* If we don't have room for the marker, do nothing */
|
|
if (sizeof(_trunc_s) < buf_sz) {
|
|
strncpy(buf + buf_sz - sizeof(_trunc_s), _trunc_s, sizeof(_trunc_s));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Actual logging
|
|
*/
|
|
static log_level_e _g_level = LOG_LEVEL;
|
|
void set_log_level(const log_level_e level) {
|
|
_g_level = level;
|
|
}
|
|
|
|
static log_decoration_e _g_decorate = LOG_DECORATE;
|
|
void set_log_decoration(const log_decoration_e decorate) {
|
|
_g_decorate = decorate;
|
|
}
|
|
|
|
static const char *_level_as[] = {
|
|
[LOG_NOTSET] = "NOT SET",
|
|
[LOG_TRACE] = "TRACE",
|
|
[LOG_DEBUG] = "DEBUG",
|
|
[LOG_INFO] = "INFO",
|
|
[LOG_WARN] = "WARN",
|
|
[LOG_ERROR] = "ERROR",
|
|
[LOG_CRITICAL] = "CRITICAL",
|
|
[LOG_PANIC] = "PANIC",
|
|
[_LOG_MAX_LV] = "UNKNOWN",
|
|
};
|
|
|
|
void _do_log(
|
|
const log_level_e level, const log_decoration_e decoration,
|
|
const char * const fmt, va_list ap
|
|
) {
|
|
bool direct;
|
|
char *ent, *p;
|
|
int sprint_sz, ent_sz = 0;
|
|
|
|
/* Bail early if this is below the current level */
|
|
if (level < _g_level) return;
|
|
|
|
/* Fastest path is to format directly into the ring buffer */
|
|
if (_cur_w_sz(true) >= LOG_MAX_SZ) {
|
|
direct = true;
|
|
ent = cur_w;
|
|
} else {
|
|
direct = false;
|
|
ent = (char *) alloca(LOG_MAX_SZ);
|
|
}
|
|
/* We have a pointer to our current cursor in the entry being formatted */
|
|
p = ent;
|
|
|
|
/* If we need to decorate the log entry, attempt to do so but never consume
|
|
* more than a quarter of the buffer size (arbitrarily selected).
|
|
*/
|
|
if (decoration == LOG_DECORATED) {
|
|
const size_t dec_max = LOG_MAX_SZ >> 2;
|
|
/* Find the appropriate log level string for decoration and fall back
|
|
* to the maximum value for "UNKNOWN" if we are out of bounds or NULL.
|
|
*/
|
|
const char *lv_s = _level_as[level < _LOG_MAX_LV ? level : _LOG_MAX_LV];
|
|
if (lv_s == NULL) lv_s = _level_as[_LOG_MAX_LV];
|
|
/* If we fail here, we can't really recover. Bail immediately without
|
|
* advancing the cursor (for direct writes) so that the next entry will
|
|
* overwrite the decoration we just wrote, or allow it to fall out of
|
|
* the stack once we return (for indirect).
|
|
*/
|
|
sprint_sz = snprintf(p, dec_max, "[%s:%lu] ", lv_s, millis());
|
|
if (sprint_sz < 0) return;
|
|
/* Ensure that if the decoration was truncated we have a close brace */
|
|
if (sprint_sz >= dec_max) {
|
|
ent[dec_max - 1] = ']';
|
|
sprint_sz = dec_max; /* Advance to the trailing NUL */
|
|
}
|
|
/* Advance the entry cursor and size */
|
|
p += sprint_sz;
|
|
ent_sz += sprint_sz;
|
|
}
|
|
|
|
/* Perform the log string formatting */
|
|
sprint_sz = vsnprintf(p, LOG_MAX_SZ - ent_sz, fmt, ap);
|
|
|
|
/* If we didn't error out, we need to either advance the cursor for direct
|
|
* writes or use the ringbuf helper to do a potentialy segmented write.
|
|
* Otherwise we'll fall through without advancing the cursor/writing to the
|
|
* ring buffer.
|
|
*/
|
|
if (sprint_sz >= 0) {
|
|
ent_sz += sprint_sz;
|
|
/* If the sum of the prior `ent_sz` and the size returned by
|
|
* `vsnprintf()` is equal to or greater than the maximum buffer size,
|
|
* that indicates the formatted string was truncated and we filled the
|
|
* space in the buffer we allowed ourselves to use (including a NUL).
|
|
*/
|
|
if (ent_sz >= LOG_MAX_SZ) {
|
|
_mark_trunc(ent, LOG_MAX_SZ);
|
|
/* We only wrote this much, not including the trailing NUL which will
|
|
* be accounted for below.
|
|
*/
|
|
ent_sz = LOG_MAX_SZ - 1;
|
|
}
|
|
ent_sz += 1; /* Account for the trailing NUL first */
|
|
if (direct) _cur_adv(&cur_w, (size_t) ent_sz);
|
|
else _ring_wr(ent, ent_sz);
|
|
}
|
|
}
|
|
|
|
void log(const log_level_e level,
|
|
const char * const fmt, ...
|
|
) {
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
_do_log(level, _g_decorate, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void log(const log_level_e level, const log_decoration_e decoration,
|
|
const char * const fmt, ...
|
|
) {
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
_do_log(level, decoration, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
/* Dump all pending log entries manually */
|
|
void log_dump(void) {
|
|
char buf[LOG_MAX_SZ];
|
|
while (_ring_rd(buf, sizeof(buf)) > 0) {
|
|
#ifndef __unix__
|
|
Serial.println(buf);
|
|
#ifdef SAMD21
|
|
DebugSerial.println(buf);
|
|
#endif
|
|
#else
|
|
printf("%s\n", buf);
|
|
fflush(NULL);
|
|
#endif
|
|
}
|
|
}
|