/* ==================================================================== * The Kannel Software License, Version 1.0 * * Copyright (c) 2001-2005 Kannel Group * Copyright (c) 1998-2001 WapIT Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Kannel Group (http://www.kannel.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Kannel" and "Kannel Group" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please * contact org@kannel.org. * * 5. Products derived from this software may not be called "Kannel", * nor may "Kannel" appear in their name, without prior written * permission of the Kannel Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Kannel Group. For more information on * the Kannel Group, please see . * * Portions of this software are based upon software originally written at * WapIT Ltd., Helsinki, Finland for the Kannel project. */ /* * log.c - implement logging functions */ #include "gwlib.h" #include #include #include #include #include #include #include #ifdef HAVE_EXECINFO_H #include #endif #if HAVE_SYSLOG_H #include #else /* * If we don't have syslog.h, then we'll use the following dummy definitions * to avoid writing #if HAVE_SYSLOG_H everywhere. */ enum { LOG_PID, LOG_DAEMON, LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERR, LOG_ALERT }; static void openlog(const char *ident, int option, int facility) { } static void syslog(int translog, const char *buf) { } static void closelog(void) { } #endif /* * List of currently open log files. */ #define MAX_LOGFILES 128 static struct { FILE *file; int minimum_output_level; char filename[FILENAME_MAX + 1]; /* to allow re-open */ enum excl_state exclusive; } logfiles[MAX_LOGFILES]; static int num_logfiles = 0; /* * Mapping array between thread id and logfiles[] index. * This is used for smsc specific logging. */ #define THREADTABLE_SIZE 1024 static unsigned int thread_to[(long)THREADTABLE_SIZE]; /* * Ensure we use the real threadtable slot number to map the thread id * instead of the thread id reported by gwthread_self() */ #define thread_slot() \ (gwthread_self() % THREADTABLE_SIZE) /* * List of places that should be logged at debug-level. */ #define MAX_LOGGABLE_PLACES (10*1000) static char *loggable_places[MAX_LOGGABLE_PLACES]; static int num_places = 0; /* * Reopen/rotate locking things. */ static List *writers = NULL; /* * Syslog support. */ static int sysloglevel; static int dosyslog = 0; /* * Make sure stderr is included in the list. */ static void add_stderr(void) { int i; for (i = 0; i < num_logfiles; ++i) if (logfiles[i].file == stderr) return; logfiles[num_logfiles].file = stderr; logfiles[num_logfiles].minimum_output_level = GW_DEBUG; logfiles[num_logfiles].exclusive = GW_NON_EXCL; ++num_logfiles; } void log_init(void) { unsigned long i; /* default all possible thread to logging index 0, stderr */ for (i = 0; i <= THREADTABLE_SIZE; i++) { thread_to[i] = 0; } add_stderr(); /* initialize rw lock */ if (writers == NULL); writers = gwlist_create(); } void log_shutdown(void) { log_close_all(); if (writers != NULL) gwlist_destroy(writers, NULL); writers = NULL; } void log_set_output_level(enum output_level level) { int i; for (i = 0; i < num_logfiles; ++i) { if (logfiles[i].file == stderr) { logfiles[i].minimum_output_level = level; break; } } } void log_set_log_level(enum output_level level) { int i; /* change everything but stderr */ for (i = 0; i < num_logfiles; ++i) { if (logfiles[i].file != stderr) { logfiles[i].minimum_output_level = level; info(0, "Changed logfile `%s' to level `%d'.", logfiles[i].filename, level); } } } void log_set_syslog(const char *ident, int syslog_level) { if (ident == NULL) dosyslog = 0; else { dosyslog = 1; sysloglevel = syslog_level; openlog(ident, LOG_PID, LOG_DAEMON); debug("gwlib.log", 0, "Syslog logging enabled."); } } void log_reopen(void) { int i, j, found; /* * Writer lock. */ if (writers != NULL) { gwlist_lock(writers); /* wait for writers complete */ gwlist_consume(writers); } for (i = 0; i < num_logfiles; ++i) { if (logfiles[i].file != stderr) { found = 0; /* * Reverse seek for allready reopened logfile. * If we find a previous file descriptor for the same file * name, then don't reopen that duplicate, but assign the * file pointer to it. */ for (j = i-1; j >= 0 && found == 0; j--) { if (strcmp(logfiles[i].filename, logfiles[j].filename) == 0) { logfiles[i].file = logfiles[j].file; found = 1; } } if (found) continue; if (logfiles[i].file != NULL) fclose(logfiles[i].file); logfiles[i].file = fopen(logfiles[i].filename, "a"); if (logfiles[i].file == NULL) { error(errno, "Couldn't re-open logfile `%s'.", logfiles[i].filename); } } } /* * Unlock writer. */ if (writers != NULL) gwlist_unlock(writers); } void log_close_all(void) { /* * Writer lock. */ if (writers != NULL) { gwlist_lock(writers); /* wait for writers */ gwlist_consume(writers); } while (num_logfiles > 0) { --num_logfiles; if (logfiles[num_logfiles].file != stderr && logfiles[num_logfiles].file != NULL) fclose(logfiles[num_logfiles].file); logfiles[num_logfiles].file = NULL; } /* * Unlock writer. */ if (writers != NULL) gwlist_unlock(writers); /* close syslog if used */ if (dosyslog) { closelog(); dosyslog = 0; } } int log_open(char *filename, int level, enum excl_state excl) { FILE *f = NULL; int i; if (writers == NULL) writers = gwlist_create(); if (num_logfiles == MAX_LOGFILES) { error(0, "Too many log files already open, not adding `%s'", filename); return -1; } if (strlen(filename) > FILENAME_MAX) { error(0, "Log filename too long: `%s'.", filename); return -1; } /* * Check if the file is already opened for logging. * If there is an open file, then assign the file descriptor * that is already existing for this log file. */ for (i = 0; i < num_logfiles && f == NULL; ++i) { if (strcmp(logfiles[i].filename, filename) == 0) f = logfiles[i].file; } /* if not previously opened, then open it now */ if (f == NULL) { f = fopen(filename, "a"); if (f == NULL) { error(errno, "Couldn't open logfile `%s'.", filename); return -1; } } logfiles[num_logfiles].file = f; logfiles[num_logfiles].minimum_output_level = level; logfiles[num_logfiles].exclusive = excl; strcpy(logfiles[num_logfiles].filename, filename); ++num_logfiles; info(0, "Added logfile `%s' with level `%d'.", filename, level); return (num_logfiles - 1); } #define FORMAT_SIZE (1024) static void format(char *buf, int level, const char *place, int e, const char *fmt, int with_timestamp) { static char *tab[] = { "DEBUG: ", "INFO: ", "WARNING: ", "ERROR: ", "PANIC: ", "LOG: " }; static int tab_size = sizeof(tab) / sizeof(tab[0]); time_t t; struct tm tm; char *p, prefix[1024]; long tid, pid; p = prefix; if (with_timestamp) { time(&t); #if LOG_TIMESTAMP_LOCALTIME tm = gw_localtime(t); #else tm = gw_gmtime(t); #endif sprintf(p, "%04d-%02d-%02d %02d:%02d:%02d ", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); p = strchr(p, '\0'); } gwthread_self_ids(&tid, &pid); sprintf(p, "[%ld] [%ld] ", pid, tid); p = strchr(p, '\0'); if (level < 0 || level >= tab_size) sprintf(p, "UNKNOWN: "); else sprintf(p, "%s", tab[level]); p = strchr(p, '\0'); if (place != NULL && *place != '\0') sprintf(p, "%s: ", place); if (strlen(prefix) + strlen(fmt) > FORMAT_SIZE / 2) { sprintf(buf, "%s \n", prefix); return; } if (e == 0) sprintf(buf, "%s%s\n", prefix, fmt); else sprintf(buf, "%s%s\n%sSystem error %d: %s\n", prefix, fmt, prefix, e, strerror(e)); } static void PRINTFLIKE(2,0) output(FILE *f, char *buf, va_list args) { vfprintf(f, buf, args); fflush(f); } static void PRINTFLIKE(1,0) kannel_syslog(char *format, va_list args, int level) { char buf[4096]; /* Trying to syslog more than 4K could be bad */ int translog; if (level >= sysloglevel && dosyslog) { if (args == NULL) { strncpy(buf, format, sizeof(buf)); buf[sizeof(buf) - 1] = '\0'; } else { vsnprintf(buf, sizeof(buf), format, args); /* XXX vsnprint not 100% portable */ } switch(level) { case GW_DEBUG: translog = LOG_DEBUG; break; case GW_INFO: translog = LOG_INFO; break; case GW_WARNING: translog = LOG_WARNING; break; case GW_ERROR: translog = LOG_ERR; break; case GW_PANIC: translog = LOG_ALERT; break; default: translog = LOG_INFO; break; } syslog(translog, "%s", buf); } } /* * Almost all of the message printing functions are identical, except for * the output level they use. This macro contains the identical parts of * the functions so that the code needs to exist only once. It's a bit * more awkward to edit, but that can't be helped. The "do {} while (0)" * construct is a gimmick to be more like a function call in all syntactic * situation. */ #define FUNCTION_GUTS(level, place) \ do { \ int i; \ char buf[FORMAT_SIZE]; \ va_list args; \ \ format(buf, level, place, err, fmt, 1); \ if (writers != NULL) { \ gwlist_lock(writers); \ gwlist_add_producer(writers); \ gwlist_unlock(writers); \ } \ for (i = 0; i < num_logfiles; ++i) { \ if (logfiles[i].exclusive == GW_NON_EXCL && \ level >= logfiles[i].minimum_output_level && \ logfiles[i].file != NULL) { \ va_start(args, fmt); \ output(logfiles[i].file, buf, args); \ va_end(args); \ } \ } \ if (writers != NULL) \ gwlist_remove_producer(writers); \ if (dosyslog) { \ format(buf, level, place, err, fmt, 0); \ va_start(args, fmt); \ kannel_syslog(buf,args,level); \ va_end(args); \ } \ } while (0) #define FUNCTION_GUTS_EXCL(level, place) \ do { \ char buf[FORMAT_SIZE]; \ va_list args; \ \ format(buf, level, place, err, fmt, 1); \ if (writers != NULL) { \ gwlist_lock(writers); \ gwlist_add_producer(writers); \ gwlist_unlock(writers); \ } \ if (logfiles[e].exclusive == GW_EXCL && \ level >= logfiles[e].minimum_output_level && \ logfiles[e].file != NULL) { \ va_start(args, fmt); \ output(logfiles[e].file, buf, args); \ va_end(args); \ } \ if (writers != NULL) \ gwlist_remove_producer(writers); \ } while (0) static void PRINTFLIKE(2,3) gw_panic_output(int err, const char *fmt, ...) { FUNCTION_GUTS(GW_PANIC, ""); } void gw_panic(int err, const char *fmt, ...) { /* * we don't want PANICs to spread accross smsc logs, so * this will be always within the main core log. */ FUNCTION_GUTS(GW_PANIC, ""); #ifdef HAVE_BACKTRACE { void *stack_frames[50]; size_t size, i; char **strings; size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*)); strings = backtrace_symbols(stack_frames, size); if (strings) { for (i = 0; i < size; i++) gw_panic_output(0, "%s", strings[i]); } else { /* hmm, no memory available */ for (i = 0; i < size; i++) gw_panic_output(0, "%p", stack_frames[i]); } /* * Note: we don't free 'strings' array because gw_free could panic's and we * have endless loop with SEGFAULT at the end. And this doesn't care * us in any case, because we are panic's and exiting immediately. (alex) */ } #endif #ifdef SEGFAULT_PANIC *((char*)0) = 0; #endif exit(EXIT_FAILURE); } void error(int err, const char *fmt, ...) { int e; if ((e = thread_to[thread_slot()])) { FUNCTION_GUTS_EXCL(GW_ERROR, ""); } else { FUNCTION_GUTS(GW_ERROR, ""); } } void warning(int err, const char *fmt, ...) { int e; if ((e = thread_to[thread_slot()])) { FUNCTION_GUTS_EXCL(GW_WARNING, ""); } else { FUNCTION_GUTS(GW_WARNING, ""); } } void info(int err, const char *fmt, ...) { int e; if ((e = thread_to[thread_slot()])) { FUNCTION_GUTS_EXCL(GW_INFO, ""); } else { FUNCTION_GUTS(GW_INFO, ""); } } static int place_matches(const char *place, const char *pat) { size_t len; len = strlen(pat); if (pat[len-1] == '*') return (strncasecmp(place, pat, len - 1) == 0); return (strcasecmp(place, pat) == 0); } static int place_should_be_logged(const char *place) { int i; if (num_places == 0) return 1; for (i = 0; i < num_places; ++i) { if (*loggable_places[i] != '-' && place_matches(place, loggable_places[i])) return 1; } return 0; } static int place_is_not_logged(const char *place) { int i; if (num_places == 0) return 0; for (i = 0; i < num_places; ++i) { if (*loggable_places[i] == '-' && place_matches(place, loggable_places[i]+1)) return 1; } return 0; } void debug(const char *place, int err, const char *fmt, ...) { int e; if (place_should_be_logged(place) && place_is_not_logged(place) == 0) { /* * Note: giving `place' to FUNCTION_GUTS makes log lines * too long and hard to follow. We'll rely on an external * list of what places are used instead of reading them * from the log file. */ if ((e = thread_to[thread_slot()])) { FUNCTION_GUTS_EXCL(GW_DEBUG, ""); } else { FUNCTION_GUTS(GW_DEBUG, ""); } } } void log_set_debug_places(const char *places) { char *p; p = strtok(gw_strdup(places), " ,"); num_places = 0; while (p != NULL && num_places < MAX_LOGGABLE_PLACES) { loggable_places[num_places++] = p; p = strtok(NULL, " ,"); } } void log_thread_to(unsigned int idx) { long thread_id = thread_slot(); if (idx > 0) info(0, "Logging thread `%ld' to logfile `%s' with level `%d'.", thread_id, logfiles[idx].filename, logfiles[idx].minimum_output_level); thread_to[thread_id] = idx; }