#include #include #include #include #include #include #include #include #include "ucore/logging.h" #include "ucore/utils.h" //used to realise per destination settings struct uc_log_module_setting { int log_level; }; struct uc_log_destination { SLIST_ENTRY(uc_log_destination) entry; enum UC_LOG_DESTINATION dest_type; //log level for this destination int log_level; //Whether to log the source file name and line number int log_location; struct uc_log_module_setting *module_settings; union { //for syslog openlog() struct { char *ident; int facility; } syslog; //for stderr and files struct { FILE* file; char *file_name; //NULL for stderr destinaton } file; } type; }; /* Helper struct for arguments to our main logging function */ struct uc_log_args { struct uc_log_destination *dest; const struct uc_log_module *module; int log_level; const char *file; int line; int raw; }; //Global lock for the logging system, as logging could be performed from any thread. All operations must grab //this lock before accessing any the global data. static pthread_mutex_t g_log_lock = PTHREAD_MUTEX_INITIALIZER; static struct uc_log_modules g_modules; static const struct uc_log_module g_unknown_module = { .id = -1, .short_name = "UNKNOWN", .long_name = "Unknown log module", .log_level = UC_LL_DEBUG, }; //All destinations. The g_log_lock must be held while accessing //this list an anything contained within it. static SLIST_HEAD(, uc_log_destination) g_destinations; static int uc_log_reopen_file(struct uc_log_destination *dest); //Uses enum UC_LOG_LEVEL as index static const char *const g_log_levels[] = { "UNKNOWN_0", "DEBUG", "UNKNOWN_2", "INFO", "UNKNOWN_4", "WARNING", "UNKNOWN_6", "ERROR", "NONE" }; const char *uc_ll_2_str(enum UC_LOG_LEVEL l) { if (l < ARRAY_SIZE(g_log_levels)) { return g_log_levels[l]; } else { return "???"; } } //returns if the string is not a known log level static int uc_str_2_ll(const char *str, size_t len) { size_t i; for (i = 0; i < ARRAY_SIZE(g_log_levels); i++) { if (strncasecmp(str, g_log_levels[i], len) == 0) { return (int)i; } } return -1; } static inline int uc_ll_2_syslog(enum UC_LOG_LEVEL l) { switch(l) { case UC_LL_DEBUG: return LOG_DEBUG; case UC_LL_INFO: return LOG_INFO; case UC_LL_WARNING: return LOG_WARNING; case UC_LL_ERROR: return LOG_ERR; case UC_LL_NONE: return LOG_CRIT; //Fishy, but this should never happen } return LOG_INFO; } //Note, this grabs the global lock static int uc_log_init_settings(struct uc_log_destination *dest) { int i; int rc = -1; pthread_mutex_lock(&g_log_lock); dest->module_settings = calloc(g_modules.cnt, sizeof *dest->module_settings); if (dest->module_settings != NULL) { for (i = 0; i < g_modules.cnt; i++) { dest->module_settings[i].log_level = g_modules.mods[i].log_level; } rc = 0; } pthread_mutex_unlock(&g_log_lock); return rc; } struct uc_log_destination *uc_log_new_stderr(int log_level, int log_location) { struct uc_log_destination *dest = calloc(1, sizeof *dest); if(dest == NULL) return NULL; if (uc_log_init_settings(dest) != 0) { free(dest); return NULL; } dest->dest_type = UC_LDEST_STDERR; dest->type.file.file = stderr; dest->log_level = log_level;; dest->log_location = log_location; return dest; } struct uc_log_destination *uc_log_new_syslog(const char *ident, int facility, int log_level, int log_location) { struct uc_log_destination *dest = calloc(1, sizeof *dest); char *id; if (dest == NULL) return NULL; if (uc_log_init_settings(dest) != 0) { free(dest); return NULL; } //copy the ident - though it's unused inside the struct for now id = strdup(ident); if (id == NULL) { free(dest->module_settings); free(dest); return NULL; } openlog(ident, LOG_NDELAY | LOG_PID, facility); dest->dest_type = UC_LDEST_SYSLOG; dest->type.syslog.ident = id; dest->type.syslog.facility = facility; dest->log_level = log_level;; dest->log_location = log_location; return dest; } struct uc_log_destination *uc_log_new_file(const char *filename, int log_level, int log_location) { struct uc_log_destination *dest = calloc(1, sizeof *dest); FILE *f; char *name; if (dest == NULL) return NULL; if (uc_log_init_settings(dest) != 0) { free(dest); return NULL; } name = strdup(filename); if (name == NULL) { free(dest->module_settings); free(dest); return NULL; } f = fopen(filename, "a"); if (f == NULL) { int saved_errno = errno; free(dest->module_settings); free(name); free(dest); errno = saved_errno; return NULL; } dest->dest_type = UC_LDEST_FILE; dest->type.file.file = f; dest->type.file.file_name = name; dest->log_level = log_level;; dest->log_location = log_location; return dest; } void uc_log_add_destination(struct uc_log_destination *dest) { struct uc_log_destination *it; if (dest == NULL) return; pthread_mutex_lock(&g_log_lock); //make sure the destination isn't added twice. SLIST_FOREACH(it, &g_destinations, entry) { if (dest == it) break; } //add if it isn't already in the list if (it == NULL) { SLIST_INSERT_HEAD(&g_destinations, dest, entry); } pthread_mutex_unlock(&g_log_lock); } //hold g_log_lock while calling this. static inline void uc_log_remove_dest_unlocked(struct uc_log_destination *dest) { struct uc_log_destination *it; //check that it's not already removed SLIST_FOREACH(it, &g_destinations, entry) { if (it == dest) break; } if (it != NULL) { SLIST_REMOVE(&g_destinations, dest, uc_log_destination, entry); } } void uc_log_remove_destination(struct uc_log_destination *dest) { if (dest == NULL) return; pthread_mutex_lock(&g_log_lock); uc_log_remove_dest_unlocked(dest); pthread_mutex_unlock(&g_log_lock); } void uc_log_delete_destination(struct uc_log_destination *dest) { if (dest == NULL) return; pthread_mutex_lock(&g_log_lock); //remove it, it's ok if the destination is already removed uc_log_remove_dest_unlocked(dest); //clean up, according to the type switch (dest->dest_type) { case UC_LDEST_SYSLOG: free(dest->type.syslog.ident); closelog(); break; case UC_LDEST_STDERR: //nothing to do break; case UC_LDEST_FILE: if(dest->type.file.file != NULL) { fclose(dest->type.file.file); dest->type.file.file = NULL; } free(dest->type.file.file_name); break; } free(dest->module_settings); free(dest); pthread_mutex_unlock(&g_log_lock); } void uc_log_destination_set_loglevel(struct uc_log_destination *dest, enum UC_LOG_LEVEL level) { if (dest == NULL) return; pthread_mutex_lock(&g_log_lock); dest->log_level = level; pthread_mutex_unlock(&g_log_lock); } void uc_log_destination_set_log_location(struct uc_log_destination *dest, int log_location) { if (dest == NULL) return; pthread_mutex_lock(&g_log_lock); dest->log_location = log_location; pthread_mutex_unlock(&g_log_lock); } static int uc_log_reopen_file(struct uc_log_destination *dest) { int rc = 0; if (dest == NULL) return -1; if (dest->type.file.file != NULL) { fclose(dest->type.file.file); dest->type.file.file = NULL; } if (dest->type.file.file_name != NULL) { dest->type.file.file = fopen(dest->type.file.file_name, "a"); if (dest->type.file.file == NULL) rc = errno; } return rc; } int uc_log_reopen_files(void) { struct uc_log_destination *dest; int rc = 0; pthread_mutex_lock(&g_log_lock); SLIST_FOREACH(dest, &g_destinations, entry) { if (dest->dest_type == UC_LDEST_FILE) { rc = uc_log_reopen_file(dest); } } pthread_mutex_unlock(&g_log_lock); return rc; } void uc_log_init(struct uc_log_modules *user_modules) { g_modules = *user_modules; } static inline void uc_vlog_file(const struct uc_log_args *args, const char *fmt, va_list ap) { struct uc_log_destination *dest = args->dest; char time_buf[48]; time_t now; struct tm t; if (dest->type.file.file == NULL) return; now = time(NULL); localtime_r(&now, &t); if (!args->raw) { snprintf(time_buf, sizeof time_buf, "%04d-%02d-%02d %02d:%02d:%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); time_buf[sizeof time_buf -1] = 0; if (dest->log_location) { fprintf(dest->type.file.file, "[%-7s] %s %s:%d [%s] : ", uc_ll_2_str(args->log_level), time_buf, args->file, args->line, args->module->short_name); } else { fprintf(dest->type.file.file, "[%-7s] %s [%s]: ", uc_ll_2_str(args->log_level), time_buf, args->module->short_name); } } vfprintf(dest->type.file.file, fmt, ap); fflush(dest->type.file.file); //strictly not needed for stderr // though stderr could be redirected to a file } static inline void uc_vlog_syslog(const struct uc_log_args *args, const char *fmt, va_list ap) { struct uc_log_destination *dest = args->dest; int pri = uc_ll_2_syslog(args->log_level); if (!args->raw) { //go via a buffer as we must do just 1 syslog call char buf[4096]; int offset = 0; int rc; buf[0] = 0; if (dest->log_location) { rc = snprintf(buf, sizeof buf, "%s:%d ", args->file, args->line); if (rc < 0) goto log; offset += rc; } rc = snprintf(&buf[offset], sizeof buf - (size_t)offset, "[%s] ", args->module->short_name); if (rc < 0) goto log; offset +=rc; log: vsnprintf(&buf[offset], sizeof buf - (size_t)offset, fmt, ap); syslog(pri, "%s", buf); } else { //raw logging vsyslog(pri, fmt, ap); } } void uc_log_destination_set_module_loglevel( struct uc_log_destination *dest, int module, enum UC_LOG_LEVEL log_level ) { if (dest == NULL) return; pthread_mutex_lock(&g_log_lock); if (module >= 0 && module < g_modules.cnt) dest->module_settings[module].log_level = log_level; pthread_mutex_unlock(&g_log_lock); } void uc_log_destination_set_mask( struct uc_log_destination *dest, const char *log_mask ) { size_t module_len; if (dest == NULL || log_mask == NULL) return; pthread_mutex_lock(&g_log_lock); while ((module_len = strcspn(log_mask, "=")) != 0) { int i; int level_len; const char *level_str; int log_level; const char *module_str = log_mask; log_mask += module_len; if (log_mask[0] == '=') log_mask++; level_len = strcspn(log_mask, ":"); if (level_len == 0) continue; level_str = log_mask; log_mask += level_len; if (log_mask[0] == ':') log_mask++; log_level = uc_str_2_ll(level_str, level_len); if (log_level == -1) continue; for (i = 0; i < g_modules.cnt; i++) { size_t short_name_len;; if (g_modules.mods[i].short_name == NULL) continue; short_name_len = strlen(g_modules.mods[i].short_name); module_len = UC_MAX(module_len, short_name_len); if (strncasecmp(g_modules.mods[i].short_name, module_str, module_len) == 0) { dest->module_settings[i].log_level = log_level; } } } pthread_mutex_unlock(&g_log_lock); } void uc_logf(int log_level, int module, int raw, const char *file, int line, const char *fmt, ...) { va_list ap; struct uc_log_destination *dest; va_start(ap, fmt); pthread_mutex_lock(&g_log_lock); //do logging for each destination SLIST_FOREACH(dest, &g_destinations, entry) { const struct uc_log_module *log_module; va_list apc; int module_log_level; //destnation level if (log_level < dest->log_level) continue; //Find the module doing the logging, or use the unknown module - //the latter would indicate a bug somewhere in the application as it should //always specify a known module if (module >= 0 && module < g_modules.cnt) { module_log_level = dest->module_settings[module].log_level; log_module = &g_modules.mods[module]; } else { log_module = &g_unknown_module; module_log_level = log_module->log_level; } if (log_level < module_log_level) continue; const struct uc_log_args args = { .dest = dest, .module = log_module, .log_level = log_level, .file = file, .line = line, .raw = raw }; //log func will mess with the va_list, //so make a copy va_copy(apc, ap); switch (dest->dest_type) { case UC_LDEST_SYSLOG: uc_vlog_syslog(&args, fmt, apc); break; case UC_LDEST_STDERR: case UC_LDEST_FILE: uc_vlog_file(&args, fmt, apc); break; } va_end(apc); } pthread_mutex_unlock(&g_log_lock); va_end(ap); }