From 417f8ee12d47f397dda1fa2827deba5c804d84e3 Mon Sep 17 00:00:00 2001 From: chuckwolber Date: Fri, 7 Aug 2020 19:41:57 -0700 Subject: [PATCH] Rolling Appender Added a rolling appender function for basic log file management. Most applications that are concerned with this behavior should consider sending messages to syslog and use traditional logrotate mechanisms to manage log files. However, there is a niche of applications that could benefit from this feature in a compact logging library. --- README.md | 14 ++++++++++ src/log.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++----- src/log.h | 26 +++++++++++++++++-- 3 files changed, 107 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a8756e9..2c508e0 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,20 @@ function is passed a `log_Event` structure containing the `line` number, `filename`, `fmt` string, `va` printf va\_list, `level` and the given `udata`. +#### log_add_rolling_appender(rolling_appender ra, int level) +One or more rolling log appenders. This is functionally equivalent to +`log_add_fp()` from a logging standpoint. The function also handles log _file_ +management based on the values provided by the `rolling_appender` struct +argument. + +After a log entry pushes the log size over the `ra.max_log_size` value, your +active log is automatically rolled. + +Rolled logs appear in the same directory as the active log, and retain the same +name as the active log, except a numeric value from `1` to `ra.max_logs` +is appended to the end of the file name. + + #### log_set_lock(log_LockFn fn, void *udata) If the log will be written to from multiple threads a lock function can be set. The function is passed the boolean `true` if the lock should be acquired or diff --git a/src/log.c b/src/log.c index 1a7626e..97c9cca 100644 --- a/src/log.c +++ b/src/log.c @@ -28,6 +28,7 @@ typedef struct { log_LogFn fn; void *udata; int level; + rolling_appender ra; } Callback; static struct { @@ -50,6 +51,13 @@ static const char *level_colors[] = { #endif +int get_next_available_callback() { + for (int i = 0; i < MAX_CALLBACKS; i++) + if (!L.callbacks[i].fn) + return i; + return -1; +} + static void stdout_callback(log_Event *ev) { char buf[16]; buf[strftime(buf, sizeof(buf), "%H:%M:%S", ev->time)] = '\0'; @@ -81,6 +89,49 @@ static void file_callback(log_Event *ev) { } +static void rolling_appender_callback(log_Event *ev) { + char* msg = (char*)calloc(strlen(ev->ra.file_name)+1024, sizeof(char)); + + if (ev->udata == NULL) + ev->udata = fopen(ev->ra.file_name, "a"); + if (ev->udata == NULL) { + sprintf(msg, "Unable to open log file: %s", ev->ra.file_name); + perror(msg); + free(msg); + return; + } + + file_callback(ev); + + struct stat buf; + if (stat(ev->ra.file_name, &buf) < 0) { + sprintf(msg, "Unable to stat log file: %s", ev->ra.file_name); + perror(msg); + free(msg); + return; + } + + free(msg); + + if (buf.st_size >= ev->ra.max_log_size) { + char* old = (char*)calloc(strlen(ev->ra.file_name)+10, sizeof(char)); + char* new = (char*)calloc(strlen(ev->ra.file_name)+10, sizeof(char)); + fclose(ev->udata); + ev->udata = NULL; + for (unsigned int i = ev->ra.max_logs-1; i >= 1; i--) { + sprintf(old, "%s.%u", ev->ra.file_name, i); + sprintf(new, "%s.%u", ev->ra.file_name, i+1); + rename(old, new); + } + sprintf(new, "%s.1", ev->ra.file_name); + rename(ev->ra.file_name, new); + + free(old); + free(new); + } +} + + static void lock(void) { if (L.lock) { L.lock(true, L.udata); } } @@ -113,13 +164,22 @@ void log_set_quiet(bool enable) { int log_add_callback(log_LogFn fn, void *udata, int level) { - for (int i = 0; i < MAX_CALLBACKS; i++) { - if (!L.callbacks[i].fn) { - L.callbacks[i] = (Callback) { fn, udata, level }; - return 0; - } - } - return -1; + int nac = get_next_available_callback(); + if (nac < 0) + return nac; + + L.callbacks[nac] = (Callback) { fn, udata, level, {0} }; + return 0; +} + + +int log_add_rolling_appender(rolling_appender ra, int level) { + int nac = get_next_available_callback(); + if (nac < 0) + return nac; + + L.callbacks[nac] = (Callback) { rolling_appender_callback, NULL, level, ra }; + return 0; } @@ -159,7 +219,9 @@ void log_log(int level, const char *file, int line, const char *fmt, ...) { if (level >= cb->level) { init_event(&ev, cb->udata); va_start(ev.ap, fmt); + ev.ra = cb->ra; cb->fn(&ev); + cb->udata = ev.udata; va_end(ev.ap); } } diff --git a/src/log.h b/src/log.h index b1fae24..53f28f6 100644 --- a/src/log.h +++ b/src/log.h @@ -8,12 +8,28 @@ #ifndef LOG_H #define LOG_H -#include +#ifdef __cplusplus +extern "C" +{ +#endif + #include +#include #include +#include +#include +#include +#include #include +#include -#define LOG_VERSION "0.1.0" +#define LOG_VERSION "0.2.0" + +typedef struct { + char* file_name; + off_t max_log_size; + unsigned int max_logs; +} rolling_appender; typedef struct { va_list ap; @@ -23,6 +39,7 @@ typedef struct { void *udata; int line; int level; + rolling_appender ra; } log_Event; typedef void (*log_LogFn)(log_Event *ev); @@ -42,8 +59,13 @@ void log_set_lock(log_LockFn fn, void *udata); void log_set_level(int level); void log_set_quiet(bool enable); int log_add_callback(log_LogFn fn, void *udata, int level); +int log_add_rolling_appender(rolling_appender ra, int level); int log_add_fp(FILE *fp, int level); void log_log(int level, const char *file, int line, const char *fmt, ...); +#ifdef __cplusplus +} +#endif + #endif