#include #include #include #include #include #include #include #include #include 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; 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 log_lock = PTHREAD_MUTEX_INITIALIZER; static struct uc_log_modules modules; static const struct uc_log_module unknown_module = { .id = -1, .short_name = "UNKNOWN", .long_name = "Unknown log module", .log_level = UC_LL_DEBUG, }; //All destinations. The log_lock must be held while accessing //this list an anything contained within it. static SLIST_HEAD(, uc_log_destination) destinations; inline const char *uc_ll_2_str(enum UC_LOG_LEVEL l) { switch(l) { case UC_LL_NONE: return "NONE"; case UC_LL_DEBUG: return "DEBUG"; case UC_LL_INFO: return "INFO"; case UC_LL_WARNING: return "WARNING"; case UC_LL_ERROR: return "ERROR"; } return ""; } static inline int uc_ll_2_syslog(enum UC_LOG_LEVEL l) { switch(l) { case UC_LL_NONE: 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; } return LOG_INFO; } 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; 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; //copy the ident - though it's unused inside the struct for now id = strdup(ident); if(id == NULL) { 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; name = strdup(filename); if(name == NULL) { free(dest); return NULL; } f = fopen(filename, "a"); if(f == NULL) { free(name); free(dest); 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(&log_lock); //make sure the destination isn't added twice. SLIST_FOREACH(it, &destinations, entry) { if(dest == it) break; } //add if it isn't already in the list if(it == NULL) { SLIST_INSERT_HEAD(&destinations, dest, entry); } pthread_mutex_unlock(&log_lock); } //hold 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, &destinations, entry) { if(it == dest) break; } if(it != NULL) { SLIST_REMOVE(&destinations, dest, uc_log_destination, entry); } } void uc_log_remove_destination(struct uc_log_destination *dest) { if(dest == NULL) return; pthread_mutex_lock(&log_lock); uc_log_remove_dest_unlocked(dest); pthread_mutex_unlock(&log_lock); } void uc_log_delete_destination(struct uc_log_destination *dest) { if(dest == NULL) return; pthread_mutex_lock(&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); pthread_mutex_unlock(&log_lock); } int uc_log_reopen_files(void) { struct uc_log_destination *dest; int rc = 0; pthread_mutex_lock(&log_lock); SLIST_FOREACH(dest, &destinations, entry) { if(dest->dest_type == UC_LDEST_FILE) { 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; } } } pthread_mutex_unlock(&log_lock); return rc; } void uc_log_init(struct uc_log_modules *user_modules) { modules = *user_modules; } int uc_log_module_set_loglevel(int module, enum UC_LOG_LEVEL level) { int rc = -1; int i; pthread_mutex_lock(&log_lock); for(i = 0; i < modules.cnt; i++) { if(modules.mods[i].id == module) { modules.mods[i].log_level = level; rc = 0; break; } } pthread_mutex_unlock(&log_lock); return rc; } 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, "[%s] %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, "[%s] %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_logf(int log_level, int module, int raw, const char *file, int line, const char *fmt, ...) { va_list ap; struct uc_log_destination *dest; const struct uc_log_module *log_module; va_start(ap, fmt); pthread_mutex_lock(&log_lock); //Find the module doign 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 < modules.cnt) log_module = &modules.mods[module]; else log_module = &unknown_module; //Check if the module should not be logged if(log_level < log_module->log_level) goto out; //do logging for each destination SLIST_FOREACH(dest, &destinations, entry) { va_list apc; if(log_level >= dest->log_level) { 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); } } out: pthread_mutex_unlock(&log_lock); va_end(ap); }