/* ====================================================================
* 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