From 0e35ecbc29a60be435dda22a947d5df8ce8f6f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20O=2E=20Sel=C3=A5sdal?= Date: Wed, 23 Apr 2014 02:04:08 +0200 Subject: [PATCH] Initial attempt of log rotation based on file sizes --- include/ucore/logging.h | 38 ++++++++++++++ src/logging.c | 114 +++++++++++++++++++++++++++++++++++----- test/logging.c | 11 +++- 3 files changed, 149 insertions(+), 14 deletions(-) diff --git a/include/ucore/logging.h b/include/ucore/logging.h index 7dd4f7d..0d7f72a 100644 --- a/include/ucore/logging.h +++ b/include/ucore/logging.h @@ -132,6 +132,33 @@ struct UCLogModules { struct UCLogDestination; +enum UC_LOG_ROTATE_POLICY { + /** Don't rotate. Default setting*/ + UC_LOG_ROTATE_NONE, + /** Create a new log file every 'size' bytes */ + UC_LOG_ROTATE_SIZE +}; + +/** Log rotate settings that can be set on a UC_LDEST_FILE destination + * The default is to not rotate log files. + * The current log file wil always be the specified file name, + * For UC_LOG_ROTATE_SIZE the current file will be renamed when it exceeds + * the specified max_size with a .number suffix (e.g .0 , .1 , .2 and so on), + * up to num_files are kept and a new log file is created. + */ +struct UCLogRotateSettings { + /** Policy */ + enum UC_LOG_ROTATE_POLICY policy; + /** For UC_LOG_ROTATE_SIZE policy */ + struct { + /** Max size of an individual log file */ + size_t max_size; + /** Max number of log files to keep. + * Is forced to be at least 1*/ + unsigned int num_files; + } size_settings; +}; + /* Returns a string representation of the log level. * @@ -204,6 +231,17 @@ void uc_log_destination_set_loglevel(struct UCLogDestination *dest, enum UC_LOG_ * @param log_location 1=log the locaton, 0=don't log the location */ void uc_log_destination_set_log_location(struct UCLogDestination *dest, int log_location); + +/** Sets the log rotation settings. + * Does nothing if the destination is not a UC_LDEST_FILE, or the policy is invalid. + * The default is to not do log rotation + * + * @param dest log destination to change + * @param settings new policy to set + */ +void uc_log_destination_set_rotate_policy(struct UCLogDestination *dest, + const struct UCLogRotateSettings *settings); + /** Remove and free a destination. * This frees the memory allocated to the destination. For * UC_LDEST_FILE the log file is closed. diff --git a/src/logging.c b/src/logging.c index 4e54a96..0712125 100644 --- a/src/logging.c +++ b/src/logging.c @@ -1,7 +1,9 @@ #include +#include #include #include #include +#include #include #include #include @@ -10,6 +12,10 @@ #include "ucore/logging.h" #include "ucore/utils.h" +/** Max suffixes we'll try to apply to a log file when rotating before + * giving up + */ +#define UC_MAX_RENAME_CNT (128) //used to realise per destination settings struct UCLogModule_setting { @@ -33,8 +39,11 @@ struct UCLogDestination { //for stderr and files struct { FILE* file; - char *file_name; //NULL for stderr destinaton + char *file_name; //NULL for stderr destinaton. If rotate policy is in place + //this is the base file name (e.g. witout a date or count) size_t size; //keeps track of no of bytes written + struct UCLogRotateSettings rotate; + unsigned rotate_count; //current num_file } file; } type; }; @@ -239,11 +248,14 @@ struct UCLogDestination *uc_log_new_file(const char *filename, int log_level, in dest->log_level = log_level;; dest->log_location = log_location; + dest->type.file.rotate.policy = UC_LOG_ROTATE_NONE; + dest->type.file.rotate_count = 0; + return dest; } //Check if a destination already exists (hold g_log_lock when calling this) -static int uc_log_dest_exists_unlocked(struct UCLogDestination *dest) +static int uc_log_dest_exists(struct UCLogDestination *dest) { struct UCLogDestination *it; @@ -265,7 +277,7 @@ void uc_log_add_destination(struct UCLogDestination *dest) pthread_mutex_lock(&g_log_lock); - exists = uc_log_dest_exists_unlocked(dest); + exists = uc_log_dest_exists(dest); //add if it isn't already in the list if (!exists) { uc_tailq_insert_head(&g_destinations, &dest->entry); @@ -275,12 +287,12 @@ void uc_log_add_destination(struct UCLogDestination *dest) } //hold g_log_lock while calling this. -static inline void uc_log_remove_dest_unlocked(struct UCLogDestination *dest) +static inline void uc_log_remove_dest(struct UCLogDestination *dest) { int exists; //check that it's not already removed - exists = uc_log_dest_exists_unlocked(dest); + exists = uc_log_dest_exists(dest); if (exists) { uc_tailq_remove(&dest->entry); @@ -294,7 +306,7 @@ void uc_log_remove_destination(struct UCLogDestination *dest) pthread_mutex_lock(&g_log_lock); - uc_log_remove_dest_unlocked(dest); + uc_log_remove_dest(dest); pthread_mutex_unlock(&g_log_lock); } @@ -307,7 +319,7 @@ void uc_log_delete_destination(struct UCLogDestination *dest) pthread_mutex_lock(&g_log_lock); //remove it, it's ok if the destination is already removed - uc_log_remove_dest_unlocked(dest); + uc_log_remove_dest(dest); //clean up, according to the type switch (dest->dest_type) { @@ -357,6 +369,30 @@ void uc_log_destination_set_log_location(struct UCLogDestination *dest, int log_ pthread_mutex_unlock(&g_log_lock); } +void uc_log_destination_set_rotate_policy(struct UCLogDestination *dest, + const struct UCLogRotateSettings *settings) +{ + + if (dest->dest_type != UC_LDEST_FILE) { + return; + } + + if (settings->policy != UC_LOG_ROTATE_NONE && settings->policy != UC_LOG_ROTATE_SIZE) { + return; + } + + pthread_mutex_lock(&g_log_lock); + + dest->type.file.rotate = *settings; + if (dest->type.file.rotate.size_settings.num_files == 0) { + dest->type.file.rotate.size_settings.num_files = 1; + } + + pthread_mutex_unlock(&g_log_lock); + +} + +//hold g_log_lock while calling this static int uc_log_reopen_file(struct UCLogDestination *dest) { int rc = 0; @@ -404,20 +440,18 @@ void uc_log_init(struct UCLogModules *user_modules) g_modules = *user_modules; } - -static inline void uc_vlog_file(const struct UCLogArgs *args, const char *fmt, va_list ap) +//hold g_log_lock while calling this +static inline void uc_vlog_file(const struct UCLogArgs *args, time_t tstamp, const char *fmt, va_list ap) { struct UCLogDestination *dest = args->dest; char time_buf[48]; - time_t now; struct tm t; int rc; if (dest->type.file.file == NULL) return; - now = time(NULL); - localtime_r(&now, &t); + localtime_r(&tstamp, &t); if (!args->raw) { snprintf(time_buf, sizeof time_buf, "%04d-%02d-%02d %02d:%02d:%02d", t.tm_year + 1900, @@ -451,6 +485,7 @@ static inline void uc_vlog_file(const struct UCLogArgs *args, const char *fmt, v } } +//hold g_log_lock while calling this static inline void uc_vlog_syslog(const struct UCLogArgs *args, const char *fmt, va_list ap) { struct UCLogDestination *dest = args->dest; @@ -562,6 +597,53 @@ void uc_log_destination_set_mask( pthread_mutex_unlock(&g_log_lock); } +//assumes the destination is a UC_LDEST_FILE +//hold g_log_lock while calling this +static int uc_log_file_rotate_size(struct UCLogDestination *dest) +{ + char new_filename[_POSIX_PATH_MAX * 2]; + int rc; + int rotate_count; + + if (dest->type.file.file_name == NULL) { + return 0; + } + + rotate_count = ++dest->type.file.rotate_count; + rotate_count %= dest->type.file.rotate.size_settings.num_files; + + snprintf(new_filename, sizeof new_filename, "%s.%d", + dest->type.file.file_name, + rotate_count); + + rc = rename(dest->type.file.file_name, new_filename); + if (rc != -1) { + dest->type.file.rotate_count = rotate_count; + } + + uc_log_reopen_file(dest); //try to reopen the log file regardless if renaming failed + + return rc; +} + +//assumes the destination is a UC_LDEST_FILE +//hold g_log_lock while calling this +static inline void uc_log_file_rotate_if_neeed(struct UCLogDestination *dest, time_t tstamp) +{ + struct UCLogRotateSettings *rotate = &dest->type.file.rotate; + + switch (rotate->policy) { + case UC_LOG_ROTATE_NONE: + //Nothing to do + break; + case UC_LOG_ROTATE_SIZE: + if (dest->type.file.size >= rotate->size_settings.max_size) { + uc_log_file_rotate_size(dest); + } + break; + } +} + void uc_logf(int log_level, int module, int raw, const char *location, const char *fmt, ...) { @@ -605,6 +687,7 @@ void uc_logf(int log_level, int module, int raw, .raw = raw }; + time_t tstamp; //log func will mess with the va_list, //so make a copy va_copy(apc, ap); @@ -614,8 +697,13 @@ void uc_logf(int log_level, int module, int raw, uc_vlog_syslog(&args, fmt, apc); break; case UC_LDEST_STDERR: + tstamp = time(NULL); + uc_vlog_file(&args, tstamp,fmt, apc); + break; case UC_LDEST_FILE: - uc_vlog_file(&args, fmt, apc); + tstamp = time(NULL); + uc_vlog_file(&args, tstamp, fmt, apc); + uc_log_file_rotate_if_neeed(dest, tstamp); break; } diff --git a/test/logging.c b/test/logging.c index 1e794e9..fe4be1c 100644 --- a/test/logging.c +++ b/test/logging.c @@ -36,14 +36,23 @@ struct UCLogModules modules = { int main(int argc, char *argv[]) { struct UCLogDestination *dest; + struct UCLogRotateSettings settings; + settings.policy = UC_LOG_ROTATE_SIZE; + settings.size_settings.max_size = 300; + settings.size_settings.num_files = 2; + uc_log_init(&modules); - dest = uc_log_new_file("test.log", UC_LL_INFO, 1); + dest = uc_log_new_file("test.log", UC_LL_DEBUG, 1); uc_log_add_destination(dest); + uc_log_destination_set_rotate_policy(dest, &settings); dest = uc_log_new_syslog("log.test", LOG_DAEMON, UC_LL_INFO, 0); uc_log_add_destination(dest); dest = uc_log_new_stderr(UC_LL_INFO, 0); uc_log_add_destination(dest); UC_LOGF( UC_LL_INFO, CC, "Test cc info %d\n", 2); + UC_LOGF( UC_LL_INFO, CC, "Test cc info %d\n", 3); + UC_LOGF( UC_LL_INFO, CC, "Test cc info %d\n", 4); + UC_LOGF( UC_LL_INFO, CC, "Test cc info %d\n", 5); UC_LOGF( UC_LL_WARNING, HO, "Test HO warning%d\n", 2); UC_LOGF( UC_LL_INFO, MAIN, "Test MAIN info \n" ); UC_LOGF( UC_LL_WARNING, MAIN, "Test MAIN warning\n" );