Main Page | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals

log.c

Go to the documentation of this file.
00001 /* ==================================================================== 
00002  * The Kannel Software License, Version 1.0 
00003  * 
00004  * Copyright (c) 2001-2008 Kannel Group  
00005  * Copyright (c) 1998-2001 WapIT Ltd.   
00006  * All rights reserved. 
00007  * 
00008  * Redistribution and use in source and binary forms, with or without 
00009  * modification, are permitted provided that the following conditions 
00010  * are met: 
00011  * 
00012  * 1. Redistributions of source code must retain the above copyright 
00013  *    notice, this list of conditions and the following disclaimer. 
00014  * 
00015  * 2. Redistributions in binary form must reproduce the above copyright 
00016  *    notice, this list of conditions and the following disclaimer in 
00017  *    the documentation and/or other materials provided with the 
00018  *    distribution. 
00019  * 
00020  * 3. The end-user documentation included with the redistribution, 
00021  *    if any, must include the following acknowledgment: 
00022  *       "This product includes software developed by the 
00023  *        Kannel Group (http://www.kannel.org/)." 
00024  *    Alternately, this acknowledgment may appear in the software itself, 
00025  *    if and wherever such third-party acknowledgments normally appear. 
00026  * 
00027  * 4. The names "Kannel" and "Kannel Group" must not be used to 
00028  *    endorse or promote products derived from this software without 
00029  *    prior written permission. For written permission, please  
00030  *    contact org@kannel.org. 
00031  * 
00032  * 5. Products derived from this software may not be called "Kannel", 
00033  *    nor may "Kannel" appear in their name, without prior written 
00034  *    permission of the Kannel Group. 
00035  * 
00036  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 
00037  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
00038  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
00039  * DISCLAIMED.  IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS 
00040  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,  
00041  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT  
00042  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR  
00043  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
00044  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE  
00045  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  
00046  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
00047  * ==================================================================== 
00048  * 
00049  * This software consists of voluntary contributions made by many 
00050  * individuals on behalf of the Kannel Group.  For more information on  
00051  * the Kannel Group, please see <http://www.kannel.org/>. 
00052  * 
00053  * Portions of this software are based upon software originally written at  
00054  * WapIT Ltd., Helsinki, Finland for the Kannel project.  
00055  */ 
00056 
00057 /*
00058  * log.c - implement logging functions
00059  */
00060 
00061 #include "gwlib.h"
00062 #include <limits.h>
00063 #include <stdio.h>
00064 #include <stdlib.h>
00065 #include <errno.h>
00066 #include <time.h>
00067 #include <stdarg.h>
00068 #include <string.h>
00069 
00070 #ifdef HAVE_EXECINFO_H
00071 #include <execinfo.h>
00072 #endif
00073 
00074 #if HAVE_SYSLOG_H
00075 #include <syslog.h>
00076 #else
00077 
00078 /*
00079  * If we don't have syslog.h, then we'll use the following dummy definitions
00080  * to avoid writing #if HAVE_SYSLOG_H everywhere.
00081  */
00082 
00083 enum {
00084     LOG_PID, LOG_DAEMON, LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERR, LOG_ALERT
00085 };
00086 
00087 static void openlog(const char *ident, int option, int facility)
00088 {
00089 }
00090 
00091 static void syslog(int translog, const char *buf)
00092 {
00093 }
00094 
00095 static void closelog(void)
00096 {
00097 }
00098 #endif
00099 
00100 
00101 /*
00102  * List of currently open log files.
00103  */
00104 #define MAX_LOGFILES 128
00105 static struct {
00106     FILE *file;
00107     int minimum_output_level;
00108     char filename[FILENAME_MAX + 1]; /* to allow re-open */
00109     enum excl_state exclusive;
00110 } logfiles[MAX_LOGFILES];
00111 static int num_logfiles = 0;
00112 
00113 
00114 /*
00115  * Mapping array between thread id and logfiles[] index.
00116  * This is used for smsc specific logging.
00117  */
00118 #define THREADTABLE_SIZE 1024
00119 static unsigned int thread_to[(long)THREADTABLE_SIZE];
00120 
00121 
00122 /*
00123  * Ensure we use the real threadtable slot number to map the thread id
00124  * instead of the thread id reported by gwthread_self()
00125  */
00126 #define thread_slot() \
00127     (gwthread_self() % THREADTABLE_SIZE)
00128 
00129 
00130 /*
00131  * List of places that should be logged at debug-level.
00132  */
00133 #define MAX_LOGGABLE_PLACES (10*1000)
00134 static char *loggable_places[MAX_LOGGABLE_PLACES];
00135 static int num_places = 0;
00136 
00137 
00138 /*
00139  * Reopen/rotate locking things.
00140  */
00141 static RWLock rwlock;
00142 
00143 /*
00144  * Syslog support.
00145  */
00146 static int sysloglevel;
00147 static int dosyslog = 0;
00148 
00149 
00150 /*
00151  * Make sure stderr is included in the list.
00152  */
00153 static void add_stderr(void)
00154 {
00155     int i;
00156 
00157     for (i = 0; i < num_logfiles; ++i)
00158     if (logfiles[i].file == stderr)
00159         return;
00160     logfiles[num_logfiles].file = stderr;
00161     logfiles[num_logfiles].minimum_output_level = GW_DEBUG;
00162     logfiles[num_logfiles].exclusive = GW_NON_EXCL;
00163     ++num_logfiles;
00164 }
00165 
00166 
00167 void log_init(void)
00168 {
00169     unsigned long i;
00170 
00171     /* Initialize rwlock */
00172     gw_rwlock_init_static(&rwlock);
00173 
00174     /* default all possible thread to logging index 0, stderr */
00175     for (i = 0; i <= THREADTABLE_SIZE; i++) {
00176         thread_to[i] = 0;
00177     }
00178 
00179     add_stderr();
00180 }
00181 
00182 void log_shutdown(void)
00183 {
00184     log_close_all();
00185     /* destroy rwlock */
00186     gw_rwlock_destroy(&rwlock);
00187 }
00188 
00189 
00190 void log_set_output_level(enum output_level level)
00191 {
00192     int i;
00193 
00194     for (i = 0; i < num_logfiles; ++i) {
00195     if (logfiles[i].file == stderr) {
00196         logfiles[i].minimum_output_level = level;
00197         break;
00198     }
00199     }
00200 }
00201 
00202 void log_set_log_level(enum output_level level)
00203 {
00204     int i;
00205 
00206     /* change everything but stderr */
00207     for (i = 0; i < num_logfiles; ++i) {
00208         if (logfiles[i].file != stderr) {
00209             logfiles[i].minimum_output_level = level;
00210             info(0, "Changed logfile `%s' to level `%d'.", logfiles[i].filename, level);
00211         }
00212     }
00213 }
00214 
00215 
00216 void log_set_syslog(const char *ident, int syslog_level)
00217 {
00218     if (ident == NULL)
00219     dosyslog = 0;
00220     else {
00221     dosyslog = 1;
00222     sysloglevel = syslog_level;
00223     openlog(ident, LOG_PID, LOG_DAEMON);
00224     debug("gwlib.log", 0, "Syslog logging enabled.");
00225     }
00226 }
00227 
00228 
00229 void log_reopen(void)
00230 {
00231     int i, j, found;
00232 
00233     /*
00234      * Writer lock.
00235      */
00236     gw_rwlock_wrlock(&rwlock);
00237 
00238     for (i = 0; i < num_logfiles; ++i) {
00239         if (logfiles[i].file != stderr) {
00240             found = 0;
00241 
00242             /*
00243              * Reverse seek for allready reopened logfile.
00244              * If we find a previous file descriptor for the same file
00245              * name, then don't reopen that duplicate, but assign the
00246              * file pointer to it.
00247              */
00248             for (j = i-1; j >= 0 && found == 0; j--) {
00249                 if (strcmp(logfiles[i].filename, logfiles[j].filename) == 0) {
00250                     logfiles[i].file = logfiles[j].file;
00251                     found = 1;
00252                 }
00253             }
00254             if (found)
00255                 continue;
00256             if (logfiles[i].file != NULL)
00257                 fclose(logfiles[i].file);
00258             logfiles[i].file = fopen(logfiles[i].filename, "a");
00259             if (logfiles[i].file == NULL) {
00260                 error(errno, "Couldn't re-open logfile `%s'.",
00261                       logfiles[i].filename);
00262             }
00263         }
00264     }
00265 
00266     /*
00267      * Unlock writer.
00268      */
00269     gw_rwlock_unlock(&rwlock);
00270 }
00271 
00272 
00273 void log_close_all(void)
00274 {
00275     /*
00276      * Writer lock.
00277      */
00278     gw_rwlock_wrlock(&rwlock);
00279 
00280     while (num_logfiles > 0) {
00281         --num_logfiles;
00282         if (logfiles[num_logfiles].file != stderr &&
00283             logfiles[num_logfiles].file != NULL)
00284             fclose(logfiles[num_logfiles].file);
00285         logfiles[num_logfiles].file = NULL;
00286     }
00287 
00288     /*
00289      * Unlock writer.
00290      */
00291     gw_rwlock_unlock(&rwlock);
00292 
00293     /* close syslog if used */
00294     if (dosyslog) {
00295         closelog();
00296         dosyslog = 0;
00297     }
00298 }
00299 
00300 
00301 int log_open(char *filename, int level, enum excl_state excl)
00302 {
00303     FILE *f = NULL;
00304     int i;
00305     
00306     gw_rwlock_wrlock(&rwlock);
00307 
00308     if (num_logfiles == MAX_LOGFILES) {
00309         gw_rwlock_unlock(&rwlock);
00310         error(0, "Too many log files already open, not adding `%s'",
00311               filename);
00312         return -1;
00313     }
00314 
00315     if (strlen(filename) > FILENAME_MAX) {
00316         gw_rwlock_unlock(&rwlock);
00317         error(0, "Log filename too long: `%s'.", filename);
00318         return -1;
00319     }
00320 
00321     /*
00322      * Check if the file is already opened for logging.
00323      * If there is an open file, then assign the file descriptor
00324      * that is already existing for this log file.
00325      */
00326     for (i = 0; i < num_logfiles && f == NULL; ++i) {
00327         if (strcmp(logfiles[i].filename, filename) == 0)
00328             f = logfiles[i].file;
00329     }
00330 
00331     /* if not previously opened, then open it now */
00332     if (f == NULL) {
00333         f = fopen(filename, "a");
00334         if (f == NULL) {
00335             gw_rwlock_unlock(&rwlock);
00336             error(errno, "Couldn't open logfile `%s'.", filename);
00337             return -1;
00338         }
00339     }
00340     
00341     logfiles[num_logfiles].file = f;
00342     logfiles[num_logfiles].minimum_output_level = level;
00343     logfiles[num_logfiles].exclusive = excl;
00344     strcpy(logfiles[num_logfiles].filename, filename);
00345     ++num_logfiles;
00346     i = num_logfiles - 1;
00347     gw_rwlock_unlock(&rwlock);
00348 
00349     info(0, "Added logfile `%s' with level `%d'.", filename, level);
00350 
00351     return i;
00352 }
00353 
00354 
00355 #define FORMAT_SIZE (1024)
00356 static void format(char *buf, int level, const char *place, int e,
00357            const char *fmt, int with_timestamp)
00358 {
00359     static char *tab[] = {
00360     "DEBUG: ",
00361     "INFO: ",
00362     "WARNING: ",
00363     "ERROR: ",
00364     "PANIC: ",
00365     "LOG: "
00366     };
00367     static int tab_size = sizeof(tab) / sizeof(tab[0]);
00368     time_t t;
00369     struct tm tm;
00370     char *p, prefix[1024];
00371     long tid, pid;
00372     
00373     p = prefix;
00374 
00375     if (with_timestamp) {
00376         time(&t);
00377 #if LOG_TIMESTAMP_LOCALTIME
00378         tm = gw_localtime(t);
00379 #else
00380         tm = gw_gmtime(t);
00381 #endif
00382         sprintf(p, "%04d-%02d-%02d %02d:%02d:%02d ",
00383         tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
00384         tm.tm_hour, tm.tm_min, tm.tm_sec);
00385     
00386         p = strchr(p, '\0');
00387     }
00388 
00389     gwthread_self_ids(&tid, &pid);
00390     sprintf(p, "[%ld] [%ld] ", pid, tid);
00391     
00392     p = strchr(p, '\0');
00393     if (level < 0 || level >= tab_size)
00394     sprintf(p, "UNKNOWN: ");
00395     else
00396     sprintf(p, "%s", tab[level]);
00397 
00398     p = strchr(p, '\0');
00399     if (place != NULL && *place != '\0')
00400     sprintf(p, "%s: ", place);
00401     
00402     if (strlen(prefix) + strlen(fmt) > FORMAT_SIZE / 2) {
00403     sprintf(buf, "%s <OUTPUT message too long>\n", prefix);
00404     return;
00405     }
00406     
00407     if (e == 0)
00408     sprintf(buf, "%s%s\n", prefix, fmt);
00409     else
00410     sprintf(buf, "%s%s\n%sSystem error %d: %s\n",
00411         prefix, fmt, prefix, e, strerror(e));
00412 }
00413 
00414 
00415 static void PRINTFLIKE(2,0) output(FILE *f, char *buf, va_list args) 
00416 {
00417     vfprintf(f, buf, args);
00418     fflush(f);
00419 }
00420 
00421 
00422 static void PRINTFLIKE(1,0) kannel_syslog(char *format, va_list args, int level)
00423 {
00424     char buf[4096]; /* Trying to syslog more than 4K could be bad */
00425     int translog;
00426     
00427     if (level >= sysloglevel && dosyslog) {
00428     if (args == NULL) {
00429         strncpy(buf, format, sizeof(buf));
00430         buf[sizeof(buf) - 1] = '\0';
00431     } else {
00432         vsnprintf(buf, sizeof(buf), format, args);
00433         /* XXX vsnprint not 100% portable */
00434     }
00435 
00436     switch(level) {
00437     case GW_DEBUG:
00438         translog = LOG_DEBUG;
00439         break;
00440     case GW_INFO:
00441         translog = LOG_INFO;
00442         break;
00443     case GW_WARNING:
00444         translog = LOG_WARNING;
00445         break;
00446     case GW_ERROR:
00447         translog = LOG_ERR;
00448         break;
00449     case GW_PANIC:
00450         translog = LOG_ALERT;
00451         break;
00452     default:
00453         translog = LOG_INFO;
00454         break;
00455     }
00456     syslog(translog, "%s", buf);
00457     }
00458 }
00459 
00460 
00461 /*
00462  * Almost all of the message printing functions are identical, except for
00463  * the output level they use. This macro contains the identical parts of
00464  * the functions so that the code needs to exist only once. It's a bit
00465  * more awkward to edit, but that can't be helped. The "do {} while (0)"
00466  * construct is a gimmick to be more like a function call in all syntactic
00467  * situation.
00468  */
00469 
00470 #define FUNCTION_GUTS(level, place) \
00471     do { \
00472         int i; \
00473         char buf[FORMAT_SIZE]; \
00474         va_list args; \
00475         \
00476         format(buf, level, place, err, fmt, 1); \
00477             gw_rwlock_rdlock(&rwlock); \
00478         for (i = 0; i < num_logfiles; ++i) { \
00479         if (logfiles[i].exclusive == GW_NON_EXCL && \
00480                     level >= logfiles[i].minimum_output_level && \
00481                     logfiles[i].file != NULL) { \
00482                 va_start(args, fmt); \
00483                 output(logfiles[i].file, buf, args); \
00484                 va_end(args); \
00485         } \
00486         } \
00487             gw_rwlock_unlock(&rwlock); \
00488         if (dosyslog) { \
00489             format(buf, level, place, err, fmt, 0); \
00490         va_start(args, fmt); \
00491         kannel_syslog(buf,args,level); \
00492         va_end(args); \
00493         } \
00494     } while (0)
00495 
00496 #define FUNCTION_GUTS_EXCL(level, place) \
00497     do { \
00498         char buf[FORMAT_SIZE]; \
00499         va_list args; \
00500         \
00501         format(buf, level, place, err, fmt, 1); \
00502             gw_rwlock_rdlock(&rwlock); \
00503             if (logfiles[e].exclusive == GW_EXCL && \
00504                 level >= logfiles[e].minimum_output_level && \
00505                 logfiles[e].file != NULL) { \
00506                 va_start(args, fmt); \
00507                 output(logfiles[e].file, buf, args); \
00508                 va_end(args); \
00509             } \
00510             gw_rwlock_unlock(&rwlock); \
00511     } while (0)
00512 
00513 
00514 #ifdef HAVE_BACKTRACE
00515 static void PRINTFLIKE(2,3) gw_panic_output(int err, const char *fmt, ...)
00516 {
00517     FUNCTION_GUTS(GW_PANIC, "");
00518 }
00519 #endif
00520 
00521 void gw_panic(int err, const char *fmt, ...)
00522 {
00523     /*
00524      * we don't want PANICs to spread accross smsc logs, so
00525      * this will be always within the main core log.
00526      */
00527     FUNCTION_GUTS(GW_PANIC, "");
00528 
00529 #ifdef HAVE_BACKTRACE
00530     {
00531         void *stack_frames[50];
00532         size_t size, i;
00533         char **strings;
00534 
00535         size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
00536         strings = backtrace_symbols(stack_frames, size);
00537 
00538         if (strings) {
00539             for (i = 0; i < size; i++)
00540                 gw_panic_output(0, "%s", strings[i]);
00541         }
00542         else { /* hmm, no memory available */
00543             for (i = 0; i < size; i++)
00544                 gw_panic_output(0, "%p", stack_frames[i]);
00545         }
00546 
00547         /*
00548          * Note: we don't free 'strings' array because gw_free could panic's and we
00549          *       have endless loop with SEGFAULT at the end. And this doesn't care
00550          *       us in any case, because we are panic's and exiting immediately. (alex)
00551          */
00552     }
00553 #endif
00554 
00555 #ifdef SEGFAULT_PANIC
00556     *((char*)0) = 0;
00557 #endif
00558 
00559     exit(EXIT_FAILURE);
00560 }
00561 
00562 
00563 void error(int err, const char *fmt, ...) 
00564 {
00565     int e;
00566     
00567     if ((e = thread_to[thread_slot()])) {
00568         FUNCTION_GUTS_EXCL(GW_ERROR, "");
00569     } else {
00570         FUNCTION_GUTS(GW_ERROR, "");
00571     }
00572 }
00573 
00574 
00575 void warning(int err, const char *fmt, ...) 
00576 {
00577     int e;
00578     
00579     if ((e = thread_to[thread_slot()])) {
00580         FUNCTION_GUTS_EXCL(GW_WARNING, "");
00581     } else {
00582         FUNCTION_GUTS(GW_WARNING, "");
00583     }
00584 }
00585 
00586 
00587 void info(int err, const char *fmt, ...) 
00588 {
00589     int e;
00590     
00591     if ((e = thread_to[thread_slot()])) {
00592         FUNCTION_GUTS_EXCL(GW_INFO, "");
00593     } else {
00594         FUNCTION_GUTS(GW_INFO, "");
00595     }
00596 }
00597 
00598 
00599 static int place_matches(const char *place, const char *pat) 
00600 {
00601     size_t len;
00602     
00603     len = strlen(pat);
00604     if (pat[len-1] == '*')
00605     return (strncasecmp(place, pat, len - 1) == 0);
00606     return (strcasecmp(place, pat) == 0);
00607 }
00608 
00609 
00610 static int place_should_be_logged(const char *place) 
00611 {
00612     int i;
00613     
00614     if (num_places == 0)
00615     return 1;
00616     for (i = 0; i < num_places; ++i) {
00617     if (*loggable_places[i] != '-' && 
00618         place_matches(place, loggable_places[i]))
00619         return 1;
00620     }
00621     return 0;
00622 }
00623 
00624 
00625 static int place_is_not_logged(const char *place) 
00626 {
00627     int i;
00628     
00629     if (num_places == 0)
00630     return 0;
00631     for (i = 0; i < num_places; ++i) {
00632     if (*loggable_places[i] == '-' &&
00633         place_matches(place, loggable_places[i]+1))
00634         return 1;
00635     }
00636     return 0;
00637 }
00638 
00639 
00640 void debug(const char *place, int err, const char *fmt, ...) 
00641 {
00642     int e;
00643     
00644     if (place_should_be_logged(place) && place_is_not_logged(place) == 0) {
00645     /*
00646      * Note: giving `place' to FUNCTION_GUTS makes log lines
00647          * too long and hard to follow. We'll rely on an external
00648          * list of what places are used instead of reading them
00649          * from the log file.
00650      */
00651         if ((e = thread_to[thread_slot()])) {
00652             FUNCTION_GUTS_EXCL(GW_DEBUG, "");
00653         } else {
00654             FUNCTION_GUTS(GW_DEBUG, "");
00655         }
00656     }
00657 }
00658 
00659 
00660 void log_set_debug_places(const char *places) 
00661 {
00662     char *p;
00663     
00664     p = strtok(gw_strdup(places), " ,");
00665     num_places = 0;
00666     while (p != NULL && num_places < MAX_LOGGABLE_PLACES) {
00667     loggable_places[num_places++] = p;
00668     p = strtok(NULL, " ,");
00669     }
00670 }
00671 
00672 
00673 void log_thread_to(unsigned int idx)
00674 {
00675     long thread_id = thread_slot();
00676 
00677     if (idx > 0) 
00678         info(0, "Logging thread `%ld' to logfile `%s' with level `%d'.", 
00679              thread_id, logfiles[idx].filename, logfiles[idx].minimum_output_level);
00680     thread_to[thread_id] = idx;
00681 }
00682 
See file LICENSE for details about the license agreement for using, modifying, copying or deriving work from this software.