Files
libucore/src/logging.c
T
Nils O. Selåsdal 0e2d4f07c3 Adhere to coding style in logging module (struct uc_log_destination->
struct UCLogDestination, and similar for other structs)
2014-01-07 23:39:01 +01:00

609 lines
14 KiB
C

#include <pthread.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <syslog.h>
#include <sys/queue.h>
#include "ucore/logging.h"
#include "ucore/utils.h"
//used to realise per destination settings
struct UCLogModule_setting {
int log_level;
};
struct UCLogDestination {
SLIST_ENTRY(UCLogDestination) 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 UCLogModule_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 UCLogArgs {
struct UCLogDestination *dest;
const struct UCLogModule *module;
int log_level;
const char *location;
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 UCLogModules g_modules;
static const struct UCLogModule 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(, UCLogDestination) g_destinations;
static int uc_log_reopen_file(struct UCLogDestination *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",
"UNKNOWN_8",
"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 UCLogDestination *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 UCLogDestination *uc_log_new_stderr(int log_level, int log_location)
{
struct UCLogDestination *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 UCLogDestination *uc_log_new_syslog(const char *ident, int facility, int log_level, int log_location)
{
struct UCLogDestination *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 UCLogDestination *uc_log_new_file(const char *filename, int log_level, int log_location)
{
struct UCLogDestination *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 UCLogDestination *dest)
{
struct UCLogDestination *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 UCLogDestination *dest)
{
struct UCLogDestination *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, UCLogDestination, entry);
}
}
void uc_log_remove_destination(struct UCLogDestination *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 UCLogDestination *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 UCLogDestination *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 UCLogDestination *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 UCLogDestination *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 UCLogDestination *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 UCLogModules *user_modules)
{
g_modules = *user_modules;
}
static inline void uc_vlog_file(const struct UCLogArgs *args, const char *fmt, va_list ap)
{
struct UCLogDestination *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 [%s] : ",
uc_ll_2_str(args->log_level), time_buf,
args->location, 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 UCLogArgs *args, const char *fmt, va_list ap)
{
struct UCLogDestination *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 ", args->location);
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 UCLogDestination *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 UCLogDestination *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 *location, const char *fmt, ...)
{
va_list ap;
struct UCLogDestination *dest;
va_start(ap, fmt);
pthread_mutex_lock(&g_log_lock);
//do logging for each destination
SLIST_FOREACH(dest, &g_destinations, entry) {
const struct UCLogModule *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 UCLogArgs args = {
.dest = dest,
.module = log_module,
.log_level = log_level,
.location = location,
.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);
}