summaryrefslogtreecommitdiff
path: root/src/shl_log.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/shl_log.c')
-rw-r--r--src/shl_log.c509
1 files changed, 509 insertions, 0 deletions
diff --git a/src/shl_log.c b/src/shl_log.c
new file mode 100644
index 0000000..d35a31f
--- /dev/null
+++ b/src/shl_log.c
@@ -0,0 +1,509 @@
+/*
+ * Log/Debug Interface
+ * Copyright (c) 2011-2012 David Herrmann <dh.herrmann@googlemail.com>
+ * Dedicated to the Public Domain
+ */
+
+/*
+ * Log/Debug API Implementation
+ * We provide thread-safety so we need a global lock. Function which
+ * are prefixed with log__* need the lock to be held. All other functions must
+ * be called without the lock held.
+ */
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include "githead.h"
+#include "shl_log.h"
+#include "shl_misc.h"
+
+/*
+ * Locking
+ * We need a global locking mechanism. Use pthread here.
+ */
+
+static pthread_mutex_t log__mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static inline void log_lock()
+{
+ pthread_mutex_lock(&log__mutex);
+}
+
+static inline void log_unlock()
+{
+ pthread_mutex_unlock(&log__mutex);
+}
+
+/*
+ * Time Management
+ * We print seconds and microseconds since application start for each
+ * log-message.
+ */
+
+static struct timeval log__ftime;
+
+static void log__time(long long *sec, long long *usec)
+{
+ struct timeval t;
+
+ if (log__ftime.tv_sec == 0 && log__ftime.tv_usec == 0) {
+ gettimeofday(&log__ftime, NULL);
+ *sec = 0;
+ *usec = 0;
+ } else {
+ gettimeofday(&t, NULL);
+ *sec = t.tv_sec - log__ftime.tv_sec;
+ *usec = (long long)t.tv_usec - (long long)log__ftime.tv_usec;
+ if (*usec < 0) {
+ *sec -= 1;
+ *usec = 1000000 + *usec;
+ }
+ }
+}
+
+/*
+ * Default Values
+ * Several logging-parameters may be omitted by applications. To provide sane
+ * default values we provide constants here.
+ *
+ * LOG_SUBSYSTEM: By default no subsystem is specified
+ */
+
+SHL_EXPORT
+const struct log_config LOG_CONFIG = {
+ .sev = {
+ [LOG_DEBUG] = 2,
+ [LOG_INFO] = 2,
+ [LOG_NOTICE] = 2,
+ [LOG_WARNING] = 2,
+ [LOG_ERROR] = 2,
+ [LOG_CRITICAL] = 2,
+ [LOG_ALERT] = 2,
+ [LOG_FATAL] = 2,
+ }
+};
+
+const char *LOG_SUBSYSTEM = NULL;
+
+/*
+ * Filters
+ * By default DEBUG and INFO messages are disabled. If LOG_ENABLE_DEBUG is not
+ * defined, then all log_debug() statements compile to zero-code and they cannot
+ * be enabled on runtime.
+ * To enable DEBUG or INFO messages at runtime, you can either specify that they
+ * should be enabled globally, per file or specify a custom filter. Other
+ * messages than DEBUG and INFO cannot be configured. However, additional
+ * configuration options may be added later.
+ *
+ * Use log_set_config() to enable debug/info messages globally. If you
+ * enable a global message type, then all other filters are skipped. If you
+ * disable a global message type then fine-grained filters can take effect.
+ *
+ * To enable DEBUG/INFO messages for a specific source-file, you can add
+ * this line to the top of the source file:
+ * #define LOG_CONFIG LOG_STATIC_CONFIG(true, true)
+ * So info and debug messages are enabled for this file on compile-time. First
+ * parameter of LOG_STATIC_CONFIG is for debug, second one for info.
+ *
+ * Or you can add new configurations on runtime. Runtime configurations take a
+ * filter parameter and a config parameter. The filter specifies what messages
+ * are affected and the config parameter specifies what action is performed.
+ */
+
+static struct log_config log__gconfig = {
+ .sev = {
+ [LOG_DEBUG] = 0,
+ [LOG_INFO] = 0,
+ [LOG_NOTICE] = 1,
+ [LOG_WARNING] = 1,
+ [LOG_ERROR] = 1,
+ [LOG_CRITICAL] = 1,
+ [LOG_ALERT] = 1,
+ [LOG_FATAL] = 1,
+ }
+};
+
+struct log_dynconf {
+ struct log_dynconf *next;
+ int handle;
+ struct log_filter filter;
+ struct log_config config;
+};
+
+static struct log_dynconf *log__dconfig = NULL;
+
+void log_set_config(const struct log_config *config)
+{
+ if (!config)
+ return;
+
+ log_lock();
+ log__gconfig = *config;
+ log_unlock();
+}
+
+int log_add_filter(const struct log_filter *filter,
+ const struct log_config *config)
+{
+ struct log_dynconf *dconf;
+ int ret;
+
+ if (!filter || !config)
+ return -EINVAL;
+
+ dconf = malloc(sizeof(*dconf));
+ if (!dconf)
+ return -ENOMEM;
+
+ memset(dconf, 0, sizeof(*dconf));
+ memcpy(&dconf->filter, filter, sizeof(*filter));
+ memcpy(&dconf->config, config, sizeof(*config));
+
+ log_lock();
+ if (log__dconfig)
+ dconf->handle = log__dconfig->handle + 1;
+ dconf->next = log__dconfig;
+ log__dconfig = dconf;
+ ret = dconf->handle;
+ log_unlock();
+
+ return ret;
+}
+
+void log_rm_filter(int handle)
+{
+ struct log_dynconf *dconf, *i;
+
+ dconf = NULL;
+
+ log_lock();
+ if (log__dconfig) {
+ if (log__dconfig->handle == handle) {
+ dconf = log__dconfig;
+ log__dconfig = dconf->next;
+ } else for (i = log__dconfig; i->next; i = i->next) {
+ dconf = i->next;
+ if (dconf->handle == handle) {
+ i->next = dconf->next;
+ break;
+ }
+ }
+ }
+ log_unlock();
+
+ free(dconf);
+}
+
+void log_clean_filters()
+{
+ struct log_dynconf *dconf;
+
+ log_lock();
+ while ((dconf = log__dconfig)) {
+ log__dconfig = dconf->next;
+ free(dconf);
+ }
+ log_unlock();
+}
+
+static bool log__matches(const struct log_filter *filter,
+ const char *file,
+ int line,
+ const char *func,
+ const char *subs)
+{
+ if (*filter->file) {
+ if (!file || strncmp(filter->file, file, LOG_STRMAX))
+ return false;
+ }
+ if (filter->line >= 0 && filter->line != line)
+ return false;
+ if (*filter->func) {
+ if (!func || strncmp(filter->func, func, LOG_STRMAX))
+ return false;
+ }
+ if (*filter->subs) {
+ if (!subs || strncmp(filter->subs, subs, LOG_STRMAX))
+ return false;
+ }
+ return true;
+}
+
+static bool log__omit(const char *file,
+ int line,
+ const char *func,
+ const struct log_config *config,
+ const char *subs,
+ enum log_severity sev)
+{
+ int val;
+ struct log_dynconf *dconf;
+
+ if (sev >= LOG_SEV_NUM)
+ return false;
+
+ if (config) {
+ val = config->sev[sev];
+ if (val == 0)
+ return true;
+ if (val == 1)
+ return false;
+ }
+
+ for (dconf = log__dconfig; dconf; dconf = dconf->next) {
+ if (log__matches(&dconf->filter, file, line, func, subs)) {
+ val = dconf->config.sev[sev];
+ if (val == 0)
+ return true;
+ if (val == 1)
+ return false;
+ }
+ }
+
+ val = log__gconfig.sev[sev];
+ if (val == 0)
+ return true;
+ if (val == 1)
+ return false;
+
+ return false;
+}
+
+/*
+ * Forward declaration so we can use the locked-versions in other functions
+ * here. Be careful to avoid deadlocks, though.
+ * Also set default log-subsystem to "log" for all logging inside this API.
+ */
+
+static void log__submit(const char *file,
+ int line,
+ const char *func,
+ const struct log_config *config,
+ const char *subs,
+ unsigned int sev,
+ const char *format,
+ va_list args);
+
+static void log__format(const char *file,
+ int line,
+ const char *func,
+ const struct log_config *config,
+ const char *subs,
+ unsigned int sev,
+ const char *format,
+ ...);
+
+#define LOG_SUBSYSTEM "log"
+
+/*
+ * Log-File
+ * By default logging is done to stderr. However, you can set a file which is
+ * used instead of stderr for logging. We do not provide complex log-rotation or
+ * management functions, you can add them yourself or use a proper init-system
+ * like systemd which does this for you.
+ * We cannot set this to "stderr" as stderr might not be a compile-time
+ * constant. Therefore, NULL means stderr.
+ */
+
+static FILE *log__file = NULL;
+
+int log_set_file(const char *file)
+{
+ FILE *f, *old;
+
+ if (file) {
+ f = fopen(file, "a");
+ if (!f) {
+ log_err("cannot change log-file to %s (%d): %s",
+ file, errno, strerror(errno));
+ return -EFAULT;
+ }
+ } else {
+ f = NULL;
+ file = "<default>";
+ }
+
+ old = NULL;
+
+ log_lock();
+ if (log__file != f) {
+ log__format(LOG_DEFAULT, LOG_NOTICE,
+ "set log-file to %s", file);
+ old = log__file;
+ log__file = f;
+ f = NULL;
+ }
+ log_unlock();
+
+ if (f)
+ fclose(f);
+ if (old)
+ fclose(old);
+
+ return 0;
+}
+
+/*
+ * Basic logger
+ * The log__submit function writes the message into the current log-target. It
+ * must be called with log__mutex locked.
+ * log__format does the same but first converts the argument list into a
+ * va_list.
+ * By default the current time elapsed since the first message was logged is
+ * prepended to the message. file, line and func information are appended to the
+ * message if sev == LOG_DEBUG.
+ * The subsystem, if not NULL, is prepended as "SUBS: " to the message and a
+ * newline is always appended by default. Multiline-messages are not allowed and
+ * do not make sense here.
+ */
+
+static const char *log__sev2str[] = {
+ [LOG_DEBUG] = "DEBUG",
+ [LOG_INFO] = "INFO",
+ [LOG_NOTICE] = "NOTICE",
+ [LOG_WARNING] = "WARNING",
+ [LOG_ERROR] = "ERROR",
+ [LOG_CRITICAL] = "CRITICAL",
+ [LOG_ALERT] = "ALERT",
+ [LOG_FATAL] = "FATAL",
+};
+
+static void log__submit(const char *file,
+ int line,
+ const char *func,
+ const struct log_config *config,
+ const char *subs,
+ unsigned int sev,
+ const char *format,
+ va_list args)
+{
+ const char *prefix = NULL;
+ FILE *out;
+ long long sec, usec;
+
+ if (log__omit(file, line, func, config, subs, sev))
+ return;
+
+ if (log__file)
+ out = log__file;
+ else
+ out = stderr;
+
+ log__time(&sec, &usec);
+
+ if (sev < LOG_SEV_NUM)
+ prefix = log__sev2str[sev];
+
+ if (prefix) {
+ if (subs)
+ fprintf(out, "[%.4lld.%.6lld] %s: %s: ",
+ sec, usec, prefix, subs);
+ else
+ fprintf(out, "[%.4lld.%.6lld] %s: ",
+ sec, usec, prefix);
+ } else {
+ if (subs)
+ fprintf(out, "[%.4lld.%.6lld] %s: ", sec, usec, subs);
+ else
+ fprintf(out, "[%.4lld.%.6lld] ", sec, usec);
+ }
+
+ vfprintf(out, format, args);
+
+ if (sev == LOG_DEBUG) {
+ if (!func)
+ func = "<unknown>";
+ if (!file)
+ file = "<unknown>";
+ if (line < 0)
+ line = 0;
+ fprintf(out, " (%s() in %s:%d)\n", func, file, line);
+ } else {
+ fprintf(out, "\n");
+ }
+}
+
+static void log__format(const char *file,
+ int line,
+ const char *func,
+ const struct log_config *config,
+ const char *subs,
+ unsigned int sev,
+ const char *format,
+ ...)
+{
+ va_list list;
+
+ va_start(list, format);
+ log__submit(file, line, func, config, subs, sev, format, list);
+ va_end(list);
+}
+
+SHL_EXPORT
+void log_submit(const char *file,
+ int line,
+ const char *func,
+ const struct log_config *config,
+ const char *subs,
+ unsigned int sev,
+ const char *format,
+ va_list args)
+{
+ int saved_errno = errno;
+
+ log_lock();
+ log__submit(file, line, func, config, subs, sev, format, args);
+ log_unlock();
+
+ errno = saved_errno;
+}
+
+SHL_EXPORT
+void log_format(const char *file,
+ int line,
+ const char *func,
+ const struct log_config *config,
+ const char *subs,
+ unsigned int sev,
+ const char *format,
+ ...)
+{
+ va_list list;
+ int saved_errno = errno;
+
+ va_start(list, format);
+ log_lock();
+ log__submit(file, line, func, config, subs, sev, format, list);
+ log_unlock();
+ va_end(list);
+
+ errno = saved_errno;
+}
+
+SHL_EXPORT
+void log_llog(void *data,
+ const char *file,
+ int line,
+ const char *func,
+ const char *subs,
+ unsigned int sev,
+ const char *format,
+ va_list args)
+{
+ log_submit(file, line, func, NULL, subs, sev, format, args);
+}
+
+void log_print_init(const char *appname)
+{
+ if (!appname)
+ appname = "<unknown>";
+ log_format(LOG_DEFAULT_CONF, NULL, LOG_NOTICE,
+ "%s Revision %s %s %s", appname,
+ BUILD_GIT_HEAD, __DATE__, __TIME__);
+}