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