kmscon/src/log.c
David Herrmann ca446c691f build: provide git-head revision via githead.h
We need the git-revision for module-version checks so provide the
infrastructure now and print it in log_init().

Note that the git-describe string is distributed with the tarballs so
end-users will not have to generate it themself. But when building from
git, the revision will be automatically updated whenever something
changes.

Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
2013-01-02 15:51:54 +01:00

506 lines
11 KiB
C

/*
* 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 <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "githead.h"
#include "log.h"
/*
* Locking
* We need a global locking mechanism. Use pthread here.
*/
#include <pthread.h>
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.
*/
#include <sys/time.h>
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
*/
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);
}
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;
}
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;
}
void log_llog(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__);
}