diff --git a/ChangeLog b/ChangeLog index 240ddf9c86..304a687d5f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +Mon Dec 22 11:33:07 CET 2008 Daniel Veillard + + * src/logging.h src/logging.c: add the infrastructure and internal + APIs for logging, some of those APIs should be made public later + * src/libvirt_sym.version.in: flag the internal APIs as such + Mon Dec 22 11:31:08 CET 2008 Daniel Veillard * src/xen_internal.h: remove tabs to fix make synatx-check diff --git a/src/libvirt_sym.version.in b/src/libvirt_sym.version.in index 91b08010ec..de919daf4f 100644 --- a/src/libvirt_sym.version.in +++ b/src/libvirt_sym.version.in @@ -462,6 +462,16 @@ LIBVIRT_PRIVATE_@VERSION@ { virRegisterStorageDriver; virRegisterDeviceMonitor; + /* logging.h */ + virLogSetDefaultPriority; + virLogDefineFilter; + virLogDefineOutput; + virLogParseFilters; + virLogParseOutputs; + virLogStartup; + virLogShutdown; + virLogReset; + virLogMessage; # memory.h virAlloc; diff --git a/src/logging.c b/src/logging.c index fd15154a50..27cd236e18 100644 --- a/src/logging.c +++ b/src/logging.c @@ -21,10 +21,747 @@ #include +#include +#include +#include +#include +#include +#include +#include +#include +#if HAVE_SYSLOG_H +#include +#endif + #include "logging.h" +#include "memory.h" +#include "util.h" #ifdef ENABLE_DEBUG int debugFlag = 0; + +/* + * Macro used to format the message as a string in virLogMessage + * and borrowed from libxml2 (also used in virRaiseError) + */ +#define VIR_GET_VAR_STR(msg, str) { \ + int size, prev_size = -1; \ + int chars; \ + char *larger; \ + va_list ap; \ + \ + str = (char *) malloc(150); \ + if (str != NULL) { \ + \ + size = 150; \ + \ + while (1) { \ + va_start(ap, msg); \ + chars = vsnprintf(str, size, msg, ap); \ + va_end(ap); \ + if ((chars > -1) && (chars < size)) { \ + if (prev_size == chars) { \ + break; \ + } else { \ + prev_size = chars; \ + } \ + } \ + if (chars > -1) \ + size += chars + 1; \ + else \ + size += 100; \ + if ((larger = (char *) realloc(str, size)) == NULL) { \ + break; \ + } \ + str = larger; \ + }} \ +} + +/* + * A logging buffer to keep some history over logs + */ +#define LOG_BUFFER_SIZE 64000 + +static char virLogBuffer[LOG_BUFFER_SIZE + 1]; +static int virLogLen = 0; +static int virLogStart = 0; +static int virLogEnd = 0; + +/* + * Filters are used to refine the rules on what to keep or drop + * based on a matching pattern (currently a substring) + */ +struct _virLogFilter { + const char *match; + int priority; +}; +typedef struct _virLogFilter virLogFilter; +typedef virLogFilter *virLogFilterPtr; + +static virLogFilterPtr virLogFilters = NULL; +static int virLogNbFilters = 0; + +/* + * Outputs are used to emit the messages retained + * after filtering, multiple output can be used simultaneously + */ +struct _virLogOutput { + void *data; + virLogOutputFunc f; + virLogCloseFunc c; + int priority; +}; +typedef struct _virLogOutput virLogOutput; +typedef virLogOutput *virLogOutputPtr; + +static virLogOutputPtr virLogOutputs = NULL; +static int virLogNbOutputs = 0; + +/* + * Default priorities + */ +static virLogPriority virLogDefaultPriority = VIR_LOG_WARN; + +static int virLogResetFilters(void); +static int virLogResetOutputs(void); + +/* + * Logs accesses must be serialized though a mutex + */ +PTHREAD_MUTEX_T(virLogMutex); + +static void virLogLock(void) +{ + pthread_mutex_lock(&virLogMutex); +} +static void virLogUnlock(void) +{ + pthread_mutex_unlock(&virLogMutex); +} + + +static const char *virLogPriorityString(virLogPriority lvl) { + switch (lvl) { + case VIR_LOG_DEBUG: + return("debug"); + case VIR_LOG_INFO: + return("info"); + case VIR_LOG_WARN: + return("warning"); + case VIR_LOG_ERROR: + return("error"); + } + return("unknown"); +} + +static int virLogInitialized = 0; + +/** + * virLogStartup: + * + * Initialize the logging module + * + * Returns 0 if successful, and -1 in case or error + */ +int virLogStartup(void) { + if (virLogInitialized) + return(-1); + virLogInitialized = 1; + pthread_mutex_init(&virLogMutex, NULL); + virLogLock(); + virLogLen = 0; + virLogStart = 0; + virLogEnd = 0; + virLogDefaultPriority = VIR_LOG_WARN; + virLogUnlock(); + return(0); +} + +/** + * virLogReset: + * + * Reset the logging module to its default initial state + * + * Returns 0 if successful, and -1 in case or error + */ +int virLogReset(void) { + if (!virLogInitialized) + return(virLogStartup()); + + virLogLock(); + virLogResetFilters(); + virLogResetOutputs(); + virLogLen = 0; + virLogStart = 0; + virLogEnd = 0; + virLogDefaultPriority = VIR_LOG_WARN; + virLogUnlock(); + return(0); +} +/** + * virLogShutdown: + * + * Shutdown the logging module + */ +void virLogShutdown(void) { + if (!virLogInitialized) + return; + virLogLock(); + virLogResetFilters(); + virLogResetOutputs(); + virLogLen = 0; + virLogStart = 0; + virLogEnd = 0; + virLogUnlock(); + pthread_mutex_destroy(&virLogMutex); + virLogInitialized = 0; +} + +/* + * Store a string in the ring buffer + */ +static void virLogStr(const char *str, int len) { + int tmp; + + if (str == NULL) + return; + if (len <= 0) + len = strlen(str); + if (len > LOG_BUFFER_SIZE) + return; + virLogLock(); + + /* + * copy the data and reset the end, we cycle over the end of the buffer + */ + if (virLogEnd + len >= LOG_BUFFER_SIZE) { + tmp = LOG_BUFFER_SIZE - virLogEnd; + memcpy(&virLogBuffer[virLogEnd], str, tmp); + virLogBuffer[LOG_BUFFER_SIZE] = 0; + memcpy(&virLogBuffer[0], &str[len], len - tmp); + virLogEnd = len - tmp; + } else { + memcpy(&virLogBuffer[virLogEnd], str, len); + virLogEnd += len; + } + /* + * Update the log length, and if full move the start index + */ + virLogLen += len; + if (virLogLen > LOG_BUFFER_SIZE) { + tmp = virLogLen - LOG_BUFFER_SIZE; + virLogLen = LOG_BUFFER_SIZE; + virLogStart += tmp; + } + virLogUnlock(); +} + +#if 0 +/* + * Output the ring buffer + */ +static int virLogDump(void *data, virLogOutputFunc f) { + int ret = 0, tmp; + + if ((virLogLen == 0) || (f == NULL)) + return(0); + virLogLock(); + if (virLogStart + virLogLen < LOG_BUFFER_SIZE) { +push_end: + virLogBuffer[virLogStart + virLogLen] = 0; + tmp = f(data, &virLogBuffer[virLogStart], virLogLen); + if (tmp < 0) { + ret = -1; + goto error; + } + ret += tmp; + virLogStart += tmp; + virLogLen -= tmp; + } else { + tmp = LOG_BUFFER_SIZE - virLogStart; + ret = f(data, &virLogBuffer[virLogStart], tmp); + if (ret < 0) { + ret = -1; + goto error; + } + if (ret < tmp) { + virLogStart += ret; + virLogLen -= ret; + } else { + virLogStart = 0; + virLogLen -= tmp; + /* dump the second part */ + if (virLogLen > 0) + goto push_end; + } + } +error: + virLogUnlock(); + return(ret); +} #endif +/** + * virLogSetDefaultPriority: + * @priority: the default priority level + * + * Set the default priority level, i.e. any logged data of a priority + * equal or superior to this level will be logged, unless a specific rule + * was defined for the log category of the message. + * + * Returns 0 if successful, -1 in case of error. + */ +int virLogSetDefaultPriority(int priority) { + if ((priority < VIR_LOG_DEBUG) || (priority > VIR_LOG_ERROR)) + return(-1); + if (!virLogInitialized) + virLogStartup(); + virLogDefaultPriority = priority; + return(0); +} + +/** + * virLogResetFilters: + * + * Removes the set of logging filters defined. + * + * Returns the number of filters removed + */ +static int virLogResetFilters(void) { + int i; + + for (i = 0; i < virLogNbFilters;i++) + VIR_FREE(virLogFilters[i].match); + VIR_FREE(virLogFilters); + virLogNbFilters = 0; + return(i); +} + +/** + * virLogDefineFilter: + * @match: the pattern to match + * @priority: the priority to give to messages matching the pattern + * @flags: extra flag, currently unused + * + * Defines a pattern used for log filtering, it allow to select or + * reject messages independently of the default priority. + * The filter defines a rules that will apply only to messages matching + * the pattern (currently if @match is a substring of the message category) + * + * Returns -1 in case of failure or the filter number if successful + */ +int virLogDefineFilter(const char *match, int priority, + int flags ATTRIBUTE_UNUSED) { + int i; + char *mdup = NULL; + + if ((match == NULL) || (priority < VIR_LOG_DEBUG) || + (priority > VIR_LOG_ERROR)) + return(-1); + + virLogLock(); + for (i = 0;i < virLogNbFilters;i++) { + if (STREQ(virLogFilters[i].match, match)) { + virLogFilters[i].priority = priority; + goto cleanup; + } + } + + mdup = strdup(match); + if (dup == NULL) { + i = -1; + goto cleanup; + } + i = virLogNbFilters; + if (VIR_REALLOC_N(virLogFilters, virLogNbFilters + 1)) { + i = -1; + VIR_FREE(mdup); + goto cleanup; + } + virLogFilters[i].match = mdup; + virLogFilters[i].priority = priority; + virLogNbFilters++; +cleanup: + virLogUnlock(); + return(i); +} + +/** + * virLogFiltersCheck: + * @input: the input string + * + * Check the input of the message against the existing filters. Currently + * the match is just a substring check of the category used as the input + * string, a more subtle approach could be used instead + * + * Returns 0 if not matched or the new priority if found. + */ +static int virLogFiltersCheck(const char *input) { + int ret = 0; + int i; + + virLogLock(); + for (i = 0;i < virLogNbFilters;i++) { + if (strstr(input, virLogFilters[i].match)) { + ret = virLogFilters[i].priority; + break; + } + } + virLogUnlock(); + return(ret); +} + +/** + * virLogResetOutputs: + * + * Removes the set of logging output defined. + * + * Returns the number of output removed + */ +static int virLogResetOutputs(void) { + int i; + + for (i = 0;i < virLogNbOutputs;i++) { + if (virLogOutputs[i].c != NULL) + virLogOutputs[i].c(virLogOutputs[i].data); + } + VIR_FREE(virLogOutputs); + i = virLogNbOutputs; + virLogNbOutputs = 0; + return(i); +} + +/** + * virLogDefineOutput: + * @f: the function to call to output a message + * @f: the function to call to close the output (or NULL) + * @data: extra data passed as first arg to the function + * @priority: minimal priority for this filter, use 0 for none + * @flags: extra flag, currently unused + * + * Defines an output function for log messages. Each message once + * gone though filtering is emitted through each registered output. + * + * Returns -1 in case of failure or the output number if successful + */ +int virLogDefineOutput(virLogOutputFunc f, virLogCloseFunc c, void *data, + int priority, int flags ATTRIBUTE_UNUSED) { + int ret = -1; + + if (f == NULL) + return(-1); + + virLogLock(); + if (VIR_REALLOC_N(virLogOutputs, virLogNbOutputs + 1)) { + goto cleanup; + } + ret = virLogNbOutputs++; + virLogOutputs[ret].f = f; + virLogOutputs[ret].c = c; + virLogOutputs[ret].data = data; + virLogOutputs[ret].priority = priority; +cleanup: + virLogUnlock(); + return(ret); +} + +/** + * virLogMessage: + * @category: where is that message coming from + * @priority: the priority level + * @flags: extra flags, 1 if coming from the error handler + * @fmt: the string format + * @...: the arguments + * + * Call the libvirt logger with some informations. Based on the configuration + * the message may be stored, sent to output or just discarded + */ +void virLogMessage(const char *category, int priority, int flags, + const char *fmt, ...) { + char *str = NULL; + char *msg; + struct timeval cur_time; + struct tm time_info; + int len, fprio, i; + + if (!virLogInitialized) + virLogStartup(); + + if (fmt == NULL) + return; + + /* + * check against list of specific logging patterns + */ + fprio = virLogFiltersCheck(category); + if (fprio == 0) { + if (priority < virLogDefaultPriority) + return; + } else if (priority < fprio) + return; + + /* + * serialize the error message, add level and timestamp + */ + VIR_GET_VAR_STR(fmt, str); + if (str == NULL) + return; + gettimeofday(&cur_time, NULL); + localtime_r(&cur_time.tv_sec, &time_info); + + if (asprintf(&msg, "%02d:%02d:%02d.%03d: %s : %s\n", + time_info.tm_hour, time_info.tm_min, + time_info.tm_sec, (int) cur_time.tv_usec / 1000, + virLogPriorityString(priority), str) < 0) { + /* apparently we're running out of memory */ + VIR_FREE(str); + return; + } + VIR_FREE(str); + + /* + * Log based on defaults, first store in the history buffer + * then push the message on the outputs defined, if none + * use stderr. + * NOTE: the locking is a single point of contention for multiple + * threads, but avoid intermixing. Maybe set up locks per output + * to improve paralellism. + */ + len = strlen(msg); + virLogStr(msg, len); + virLogLock(); + for (i = 0; i < virLogNbOutputs;i++) { + if (priority >= virLogOutputs[i].priority) + virLogOutputs[i].f(virLogOutputs[i].data, category, priority, + msg, len); + } + if ((virLogNbOutputs == 0) && (flags != 1)) + safewrite(2, msg, len); + virLogUnlock(); + + VIR_FREE(msg); +} + +static int virLogOutputToFd(void *data, const char *category ATTRIBUTE_UNUSED, + int priority ATTRIBUTE_UNUSED, + const char *str, int len) { + int fd = (long) data; + int ret; + + if (fd < 0) + return(-1); + ret = safewrite(fd, str, len); + return(ret); +} + +static void virLogCloseFd(void *data) { + int fd = (long) data; + + if (fd >= 0) + close(fd); +} + +static int virLogAddOutputToStderr(int priority) { + if (virLogDefineOutput(virLogOutputToFd, NULL, (void *)2L, priority, 0) < 0) + return(-1); + return(0); +} + +static int virLogAddOutputToFile(int priority, const char *file) { + int fd; + + fd = open(file, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR); + if (fd < 0) + return(-1); + if (virLogDefineOutput(virLogOutputToFd, virLogCloseFd, (void *)(long)fd, + priority, 0) < 0) { + close(fd); + return(-1); + } + return(0); +} + +#if HAVE_SYSLOG_H +static int virLogOutputToSyslog(void *data ATTRIBUTE_UNUSED, + const char *category ATTRIBUTE_UNUSED, + int priority, const char *str, + int len ATTRIBUTE_UNUSED) { + int prio; + + switch (priority) { + case VIR_LOG_DEBUG: + prio = LOG_DEBUG; + break; + case VIR_LOG_INFO: + prio = LOG_INFO; + break; + case VIR_LOG_WARN: + prio = LOG_WARNING; + break; + case VIR_LOG_ERROR: + prio = LOG_ERR; + break; + default: + prio = LOG_ERR; + } + syslog(prio, "%s", str); + return(len); +} + +static void virLogCloseSyslog(void *data ATTRIBUTE_UNUSED) { + closelog(); +} + +static int virLogAddOutputToSyslog(int priority, const char *ident) { + openlog(ident, 0, 0); + if (virLogDefineOutput(virLogOutputToSyslog, virLogCloseSyslog, NULL, + priority, 0) < 0) { + closelog(); + return(-1); + } + return(0); +} +#endif /* HAVE_SYSLOG_H */ + +#define IS_SPACE(cur) \ + ((*cur == ' ') || (*cur == '\t') || (*cur == '\n') || \ + (*cur == '\r') || (*cur == '\\')) + +/** + * virLogParseOutputs: + * @outputs: string defining a (set of) output(s) + * + * The format for an output can be: + * x:stderr + * output goes to stderr + * x:syslog:name + * use syslog for the output and use the given name as the ident + * x:file:file_path + * output to a file, with the given filepath + * In all case the x prefix is the minimal level, acting as a filter + * 0: everything + * 1: DEBUG + * 2: INFO + * 3: WARNING + * 4: ERROR + * + * Multiple output can be defined in a single @output, they just need to be + * separated by spaces. + * + * Returns the number of output parsed and installed or -1 in case of error + */ +int virLogParseOutputs(const char *outputs) { + const char *cur = outputs, *str; + char *name; + int prio; + int ret = 0; + + if (cur == NULL) + return(-1); + + virSkipSpaces(&cur); + while (*cur != 0) { + prio= virParseNumber(&cur); + if ((prio < 0) || (prio > 4)) + return(-1); + if (*cur != ':') + return(-1); + cur++; + if (STREQLEN(cur, "stderr", 6)) { + cur += 6; + if (virLogAddOutputToStderr(prio) == 0) + ret++; + } else if (STREQLEN(cur, "syslog", 6)) { + cur += 6; + if (*cur != ':') + return(-1); + cur++; + str = cur; + while ((*cur != 0) && (!IS_SPACE(cur))) + cur++; + if (str == cur) + return(-1); +#if HAVE_SYSLOG_H + name = strndup(str, cur - str); + if (name == NULL) + return(-1); + if (virLogAddOutputToSyslog(prio, name) == 0) + ret++; + VIR_FREE(name); +#endif /* HAVE_SYSLOG_H */ + } else if (STREQLEN(cur, "file", 4)) { + cur += 4; + if (*cur != ':') + return(-1); + cur++; + str = cur; + while ((*cur != 0) && (!IS_SPACE(cur))) + cur++; + if (str == cur) + return(-1); + name = strndup(str, cur - str); + if (name == NULL) + return(-1); + if (virLogAddOutputToFile(prio, name) == 0) + ret++; + VIR_FREE(name); + } else { + return(-1); + } + virSkipSpaces(&cur); + } + return(ret); +} + +/** + * virLogParseFilters: + * @filters: string defining a (set of) filter(s) + * + * The format for a filter is: + * x:name + * where name is a match string + * the x prefix is the minimal level where the messages should be logged + * 1: DEBUG + * 2: INFO + * 3: WARNING + * 4: ERROR + * + * Multiple filter can be defined in a single @filters, they just need to be + * separated by spaces. + * + * Returns the number of filter parsed and installed or -1 in case of error + */ +int virLogParseFilters(const char *filters) { + const char *cur = filters, *str; + char *name; + int prio; + int ret = 0; + + if (cur == NULL) + return(-1); + + virSkipSpaces(&cur); + while (*cur != 0) { + prio= virParseNumber(&cur); + if ((prio < 0) || (prio > 4)) + return(-1); + if (*cur != ':') + return(-1); + cur++; + str = cur; + while ((*cur != 0) && (!IS_SPACE(cur))) + cur++; + if (str == cur) + return(-1); + name = strndup(str, cur - str); + if (name == NULL) + return(-1); + if (virLogDefineFilter(name, prio, 0) >= 0) + ret++; + VIR_FREE(name); + virSkipSpaces(&cur); + } + return(ret); +} +#endif /* ENABLE_DEBUG */ diff --git a/src/logging.h b/src/logging.h index 2d74adc669..f6aab41eac 100644 --- a/src/logging.h +++ b/src/logging.h @@ -30,16 +30,87 @@ * defined at runtime of from the libvirt daemon configuration file */ #ifdef ENABLE_DEBUG -extern int debugFlag; #define VIR_DEBUG(category, fmt,...) \ - do { if (debugFlag) fprintf (stderr, "DEBUG: %s: %s (" fmt ")\n", category, __func__, __VA_ARGS__); } while (0) + virLogMessage(category, VIR_LOG_DEBUG, 0, fmt, __VA_ARGS__) +#define VIR_INFO(category, fmt,...) \ + virLogMessage(category, VIR_LOG_INFO, 0, fmt, __VA_ARGS__) +#define VIR_WARN(category, fmt,...) \ + virLogMessage(category, VIR_LOG_WARN, 0, fmt, __VA_ARGS__) +#define VIR_ERROR(category, fmt,...) \ + virLogMessage(category, VIR_LOG_ERROR, 0, fmt, __VA_ARGS__) #else #define VIR_DEBUG(category, fmt,...) \ do { } while (0) +#define VIR_INFO(category, fmt,...) \ + do { } while (0) +#define VIR_WARN(category, fmt,...) \ + do { } while (0) +#define VIR_ERROR(category, fmt,...) \ + do { } while (0) #endif /* !ENABLE_DEBUG */ #define DEBUG(fmt,...) VIR_DEBUG(__FILE__, fmt, __VA_ARGS__) #define DEBUG0(msg) VIR_DEBUG(__FILE__, "%s", msg) +#define INFO(fmt,...) VIR_INFO(__FILE__, fmt, __VA_ARGS__) +#define INFO0(msg) VIR_INFO(__FILE__, "%s", msg) +#define WARN(fmt,...) VIR_WARN(__FILE__, fmt, __VA_ARGS__) +#define WARN0(msg) VIR_WARN(__FILE__, "%s", msg) +#define ERROR(fmt,...) VIR_ERROR(__FILE__, fmt, __VA_ARGS__) +#define ERROR0(msg) VIR_ERROR(__FILE__, "%s", msg) +/* + * To be made public + */ +typedef enum { + VIR_LOG_DEBUG = 1, + VIR_LOG_INFO, + VIR_LOG_WARN, + VIR_LOG_ERROR, +} virLogPriority; + +/** + * virLogOutputFunc: + * @data: extra output logging data + * @category: the category for the message + * @priority: the priority for the message + * @msg: the message to log, preformatted and zero terminated + * @len: the lenght of the message in bytes without the terminating zero + * + * Callback function used to output messages + * + * Returns the number of bytes written or -1 in case of error + */ +typedef int (*virLogOutputFunc) (void *data, const char *category, + int priority, const char *str, int len); + +/** + * virLogCloseFunc: + * @data: extra output logging data + * + * Callback function used to close a log output + */ +typedef void (*virLogCloseFunc) (void *data); + +extern int virLogSetDefaultPriority(int priority); +extern int virLogDefineFilter(const char *match, int priority, int flags); +extern int virLogDefineOutput(virLogOutputFunc f, virLogCloseFunc c, + void *data, int priority, int flags); + +#if 0 +extern char *virLogGetDump(int flags); +#endif + +/* + * Internal logging API + */ + +extern int virLogStartup(void); +extern int virLogReset(void); +extern void virLogShutdown(void); +extern int virLogParseFilters(const char *filters); +extern int virLogParseOutputs(const char *output); +extern void virLogMessage(const char *category, int priority, int flags, + const char *fmt, ...) ATTRIBUTE_FORMAT(printf, 4, 5); + #endif