/* log.c - logging funtions Copyright (C) 2002, 2003, 2008, 2010, 2011, 2012, 2013 Arthur de Jong This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "log.h" /* set the logname */ #undef PACKAGE #define PACKAGE "nslcd" /* storage for logging modes */ static struct log_cfg { int loglevel; const char *scheme; FILE *fp; /* NULL == syslog */ struct log_cfg *next; } *loglist = NULL; /* default loglevel when no logging is configured */ static int prelogging_loglevel = LOG_INFO; #define MAX_REQUESTID_LENGTH 40 #ifdef TLS /* the session id that is set for this thread */ static TLS char *sessionid = NULL; /* the request identifier that is set for this thread */ static TLS char *requestid = NULL; #else /* no TLS, use pthreads */ static pthread_once_t tls_init_once = PTHREAD_ONCE_INIT; static pthread_key_t sessionid_key; static pthread_key_t requestid_key; static void tls_init_keys(void) { pthread_key_create(&sessionid_key, NULL); pthread_key_create(&requestid_key, NULL); } #endif /* no TLS, use pthreads */ /* set loglevel when no logging is configured */ void log_setdefaultloglevel(int loglevel) { prelogging_loglevel = loglevel; } /* add logging method to configuration list */ static void addlogging(int loglevel, const char *scheme, FILE *fp) { struct log_cfg *tmp, *lst; /* create new logstruct */ tmp = (struct log_cfg *)malloc(sizeof(struct log_cfg)); if (tmp == NULL) { log_log(LOG_CRIT, "malloc() returned NULL"); exit(EXIT_FAILURE); } tmp->loglevel = loglevel; tmp->scheme = scheme; tmp->fp = fp; tmp->next = NULL; /* save the struct in the list */ if (loglist == NULL) loglist = tmp; else { for (lst = loglist; lst->next != NULL; lst = lst->next); lst->next = tmp; } } /* configure logging to a file */ void log_addlogging_file(int loglevel, const char *filename) { FILE *fp; filename = strdup(filename); if (filename == NULL) { log_log(LOG_CRIT, "strdup() returned NULL"); exit(EXIT_FAILURE); } fp = fopen(filename, "a"); if (fp == NULL) { log_log(LOG_ERR, "cannot open logfile (%s) for appending: %s", filename, strerror(errno)); exit(1); } addlogging(loglevel, filename, fp); } /* configure logging to syslog */ void log_addlogging_syslog(int loglevel) { openlog(PACKAGE, LOG_PID, LOG_DAEMON); addlogging(loglevel, "syslog", NULL); } /* configure a null logging mode (no logging) */ void log_addlogging_none() { /* this is a hack, but it's so easy */ addlogging(LOG_EMERG, "none", NULL); } /* start the logging with the configured logging methods if no method is configured yet, logging is done to syslog */ void log_startlogging(void) { if (loglist == NULL) log_addlogging_syslog(LOG_INFO); prelogging_loglevel = -1; } /* indicate that we should clear any session identifiers set by log_newsession */ void log_clearsession(void) { #ifndef TLS char *sessionid, *requestid; pthread_once(&tls_init_once, tls_init_keys); sessionid = pthread_getspecific(sessionid_key); requestid = pthread_getspecific(requestid_key); #endif /* no TLS */ /* set the session id to empty */ if (sessionid != NULL) sessionid[0] = '\0'; /* set the request id to empty */ if (requestid != NULL) requestid[0] = '\0'; } /* indicate that a session id should be included in the output and set it to a new value */ void log_newsession(void) { #ifndef TLS char *sessionid, *requestid; pthread_once(&tls_init_once, tls_init_keys); sessionid = pthread_getspecific(sessionid_key); requestid = pthread_getspecific(requestid_key); #endif /* no TLS */ /* ensure that sessionid can hold a string */ if (sessionid == NULL) { sessionid = (char *)malloc(7); if (sessionid == NULL) { fprintf(stderr, "malloc() failed: %s", strerror(errno)); return; /* silently fail */ } #ifndef TLS pthread_setspecific(sessionid_key, sessionid); #endif /* no TLS */ } sprintf(sessionid, "%06x", (int)(rand() & 0xffffff)); /* set the request id to empty */ if (requestid != NULL) requestid[0] = '\0'; } /* indicate that a request identifier should be included in the output from this point on, until log_newsession() is called */ void log_setrequest(const char *format, ...) { va_list ap; #ifndef TLS char *requestid; pthread_once(&tls_init_once, tls_init_keys); requestid = pthread_getspecific(requestid_key); #endif /* no TLS */ /* ensure that requestid can hold a string */ if (requestid == NULL) { requestid = (char *)malloc(MAX_REQUESTID_LENGTH); if (requestid == NULL) { fprintf(stderr, "malloc() failed: %s", strerror(errno)); return; /* silently fail */ } #ifndef TLS pthread_setspecific(requestid_key, requestid); #endif /* no TLS */ } /* make the message */ va_start(ap, format); vsnprintf(requestid, MAX_REQUESTID_LENGTH, format, ap); requestid[MAX_REQUESTID_LENGTH - 1] = '\0'; va_end(ap); } /* log the given message using the configured logging method */ void log_log(int pri, const char *format, ...) { int res; struct log_cfg *lst; char buffer[200]; va_list ap; #ifndef TLS char *sessionid, *requestid; pthread_once(&tls_init_once, tls_init_keys); sessionid = pthread_getspecific(sessionid_key); requestid = pthread_getspecific(requestid_key); #endif /* no TLS */ /* make the message */ va_start(ap, format); res = vsnprintf(buffer, sizeof(buffer), format, ap); if ((res < 0) || (res >= (int)sizeof(buffer))) { /* truncate with "..." */ buffer[sizeof(buffer) - 2] = '.'; buffer[sizeof(buffer) - 3] = '.'; buffer[sizeof(buffer) - 4] = '.'; } buffer[sizeof(buffer) - 1] = '\0'; va_end(ap); /* do the logging */ if (prelogging_loglevel >= 0) { /* if logging is not yet defined, log to stderr */ if (pri <= prelogging_loglevel) { if ((requestid != NULL) && (requestid[0] != '\0')) fprintf(stderr, "%s: [%s] <%s> %s%s\n", PACKAGE, sessionid, requestid, pri == LOG_DEBUG ? "DEBUG: " : "", buffer); else if ((sessionid != NULL) && (sessionid[0] != '\0')) fprintf(stderr, "%s: [%s] %s%s\n", PACKAGE, sessionid, pri == LOG_DEBUG ? "DEBUG: " : "", buffer); else fprintf(stderr, "%s: %s%s\n", PACKAGE, pri == LOG_DEBUG ? "DEBUG: " : "", buffer); } } else { for (lst = loglist; lst != NULL; lst = lst->next) { if (pri <= lst->loglevel) { if (lst->fp == NULL) { if ((requestid != NULL) && (requestid[0] != '\0')) syslog(pri, "[%s] <%s> %s%s", sessionid, requestid, pri == LOG_DEBUG ? "DEBUG: " : "", buffer); else if ((sessionid != NULL) && (sessionid[0] != '\0')) syslog(pri, "[%s] %s%s", sessionid, pri == LOG_DEBUG ? "DEBUG: " : "", buffer); else syslog(pri, "%s%s", pri == LOG_DEBUG ? "DEBUG: " : "", buffer); } else { if ((requestid != NULL) && (requestid[0] != '\0')) fprintf(lst->fp, "%s: [%s] <%s> %s%s\n", PACKAGE, sessionid, requestid, pri == LOG_DEBUG ? "DEBUG: " : "", buffer); else if ((sessionid != NULL) && (sessionid[0] != '\0')) fprintf(lst->fp, "%s: [%s] %s%s\n", PACKAGE, sessionid, pri == LOG_DEBUG ? "DEBUG: " : "", buffer); else fprintf(lst->fp, "%s: %s%s\n", PACKAGE, pri == LOG_DEBUG ? "DEBUG: " : "", buffer); fflush(lst->fp); } } } } } static const char *loglevel2str(int loglevel) { switch (loglevel) { case LOG_CRIT: return "crit"; case LOG_ERR: return "error"; case LOG_WARNING: return "warning"; case LOG_NOTICE: return "notice"; case LOG_INFO: return "info"; case LOG_DEBUG: return "debug"; default: return "???"; } } /* log the logging configuration on DEBUG loglevel */ void log_log_config(void) { struct log_cfg *lst; for (lst = loglist; lst != NULL; lst = lst->next) { if (lst->loglevel == LOG_EMERG) log_log(LOG_DEBUG, "CFG: log %s", lst->scheme); else log_log(LOG_DEBUG, "CFG: log %s %s", lst->scheme, loglevel2str(lst->loglevel)); } }