349 lines
9.8 KiB
C
349 lines
9.8 KiB
C
/*
|
|
*
|
|
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
|
|
|
This file is part of CLIXON.
|
|
|
|
CLIXON is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
CLIXON 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with CLIXON; see the file LICENSE. If not, see
|
|
<http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
* Regular logging and debugging. Syslog using levels.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "clixon_config.h" /* generated by config & autoconf */
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <syslog.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
|
|
/* clicon */
|
|
#include "clixon_err.h"
|
|
#include "clixon_log.h"
|
|
|
|
/* The global debug level. 0 means no debug */
|
|
int debug = 0;
|
|
|
|
/* Bitmask whether to log to syslog or stderr: CLICON_LOG_STDERR | CLICON_LOG_SYSLOG */
|
|
static int _logflags = 0x0;
|
|
|
|
/* Function pointer to log notify callback */
|
|
static clicon_log_notify_t *_log_notify_cb = NULL;
|
|
static void *_log_notify_arg = NULL;
|
|
|
|
/* Set to open file to bypass logging and write debug messages directly to file */
|
|
static FILE *_debugfile = NULL;
|
|
|
|
/*! Initialize system logger.
|
|
*
|
|
* Make syslog(3) calls with specified ident and gates calls of level upto specified level (upto).
|
|
* May also print to stderr, if err is set.
|
|
* Applies to clicon_err() and clicon_debug too
|
|
*
|
|
* @param[in] ident prefix that appears on syslog (eg 'cli')
|
|
* @param[in] upto log priority, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG (see syslog(3)).
|
|
* @param[in] flags bitmask: if CLICON_LOG_STDERR, then print logs to stderr
|
|
* if CLICON_LOG_SYSLOG, then print logs to syslog
|
|
* You can do a combination of both
|
|
* @code
|
|
* clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR);
|
|
* @endcode
|
|
*/
|
|
int
|
|
clicon_log_init(char *ident,
|
|
int upto,
|
|
int flags)
|
|
{
|
|
if (setlogmask(LOG_UPTO(upto)) < 0)
|
|
/* Cant syslog here */
|
|
fprintf(stderr, "%s: setlogmask: %s\n", __FUNCTION__, strerror(errno));
|
|
_logflags = flags;
|
|
openlog(ident, LOG_PID, LOG_USER); /* LOG_PUSER is achieved by direct stderr logs in clicon_log */
|
|
return 0;
|
|
}
|
|
|
|
/*! Register log callback, return old setting
|
|
*/
|
|
clicon_log_notify_t *
|
|
clicon_log_register_callback(clicon_log_notify_t *cb,
|
|
void *arg)
|
|
{
|
|
clicon_log_notify_t *old = _log_notify_cb;
|
|
_log_notify_cb = cb;
|
|
_log_notify_arg = arg;
|
|
return old;
|
|
}
|
|
|
|
/*! Mimic syslog and print a time on file f
|
|
*/
|
|
static int
|
|
flogtime(FILE *f)
|
|
{
|
|
struct timeval tv;
|
|
struct tm *tm;
|
|
|
|
gettimeofday(&tv, NULL);
|
|
tm = localtime((time_t*)&tv.tv_sec);
|
|
fprintf(f, "%s %2d %02d:%02d:%02d: ",
|
|
mon2name(tm->tm_mon), tm->tm_mday,
|
|
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Mimic syslog and print a time on string s
|
|
* String returned needs to be freed.
|
|
*/
|
|
static char *
|
|
slogtime(void)
|
|
{
|
|
struct timeval tv;
|
|
struct tm *tm;
|
|
char *str;
|
|
|
|
/* Example: "Apr 14 11:30:52: " len=17+1 */
|
|
if ((str = malloc(18)) == NULL){
|
|
fprintf(stderr, "%s: malloc: %s\n", __FUNCTION__, strerror(errno));
|
|
return NULL;
|
|
}
|
|
gettimeofday(&tv, NULL);
|
|
tm = localtime((time_t*)&tv.tv_sec);
|
|
snprintf(str, 18, "%s %2d %02d:%02d:%02d: ",
|
|
mon2name(tm->tm_mon), tm->tm_mday,
|
|
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
|
return str;
|
|
}
|
|
|
|
|
|
/*! Make a logging call to syslog (or stderr).
|
|
*
|
|
* This is the _only_ place the actual syslog (or stderr) logging is made in clicon,..
|
|
* @note yslog makes itw own filtering, but if log to stderr we do it here
|
|
* @see clicon_debug()
|
|
*
|
|
* @param[in] level log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG. Thisis OR:d with facility == LOG_USER
|
|
* @param[in] msg Message to print as argv.
|
|
*/
|
|
int
|
|
clicon_log_str(int level,
|
|
char *msg)
|
|
{
|
|
if (_logflags & CLICON_LOG_SYSLOG)
|
|
syslog(LOG_MAKEPRI(LOG_USER, level), "%s", msg);
|
|
/* syslog makes own filtering, we do it here:
|
|
* if normal (not debug) then filter loglevels >= debug
|
|
*/
|
|
if (debug == 0 && level >= LOG_DEBUG)
|
|
goto done;
|
|
if (_logflags & CLICON_LOG_STDERR){
|
|
flogtime(stderr);
|
|
fprintf(stderr, "%s\n", msg);
|
|
}
|
|
if (_logflags & CLICON_LOG_STDOUT){
|
|
flogtime(stdout);
|
|
fprintf(stdout, "%s\n", msg);
|
|
}
|
|
if (_log_notify_cb){
|
|
static int cb = 0;
|
|
char *d, *msg2;
|
|
int len;
|
|
|
|
if (cb++ == 0){
|
|
/* Here there is danger of recursion: if callback in turn logs, therefore
|
|
make static check (should be stack-based - now global)
|
|
*/
|
|
if ((d = slogtime()) == NULL)
|
|
return -1;
|
|
len = strlen(d) + strlen(msg) + 1;
|
|
if ((msg2 = malloc(len)) == NULL){
|
|
fprintf(stderr, "%s: malloc: %s\n", __FUNCTION__, strerror(errno));
|
|
return -1;
|
|
}
|
|
snprintf(msg2, len, "%s%s", d, msg);
|
|
assert(_log_notify_arg);
|
|
_log_notify_cb(level, msg2, _log_notify_arg);
|
|
free(d);
|
|
free(msg2);
|
|
}
|
|
cb--;
|
|
}
|
|
done:
|
|
return 0;
|
|
}
|
|
|
|
/*! Make a logging call to syslog using variable arg syntax.
|
|
*
|
|
* See also clicon_log_init() and clicon_log_str()
|
|
*
|
|
* @code
|
|
clicon_log(LOG_NOTICE, "%s: dump to dtd not supported", __PROGRAM__);
|
|
* @endcode
|
|
* @param[in] level log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG. Thisis OR:d with facility == LOG_USER
|
|
* @param[in] format Message to print as argv.
|
|
*/
|
|
int
|
|
clicon_log(int level,
|
|
char *format, ...)
|
|
{
|
|
va_list args;
|
|
int len;
|
|
char *msg = NULL;
|
|
int retval = -1;
|
|
|
|
/* first round: compute length of debug message */
|
|
va_start(args, format);
|
|
len = vsnprintf(NULL, 0, format, args);
|
|
va_end(args);
|
|
|
|
/* allocate a message string exactly fitting the message length */
|
|
if ((msg = malloc(len+1)) == NULL){
|
|
fprintf(stderr, "malloc: %s\n", strerror(errno)); /* dont use clicon_err here due to recursion */
|
|
goto done;
|
|
}
|
|
|
|
/* second round: compute write message from format and args */
|
|
va_start(args, format);
|
|
if (vsnprintf(msg, len+1, format, args) < 0){
|
|
va_end(args);
|
|
fprintf(stderr, "vsnprintf: %s\n", strerror(errno)); /* dont use clicon_err here due to recursion */
|
|
goto done;
|
|
}
|
|
va_end(args);
|
|
/* Actually log it */
|
|
clicon_log_str(level, msg);
|
|
|
|
retval = 0;
|
|
done:
|
|
if (msg)
|
|
free(msg);
|
|
return retval;
|
|
}
|
|
|
|
|
|
/*! Initialize debug messages. Set debug level.
|
|
*
|
|
* Initialize debug module. The level is used together with clicon_debug(dbglevel) calls as follows:
|
|
* print message if level >= dbglevel.
|
|
* Example: clicon_debug_init(1) -> debug(1) is printed, but not debug(2).
|
|
* Normally, debug messages are sent to clicon_log() which in turn can be sent to syslog and/or stderr.
|
|
* But you can also override this with a specific debug file so that debug messages are written on the file
|
|
* independently of log or errors. This is to ensure that a syslog of normal logs is unpolluted by extensive
|
|
* debugging.
|
|
*
|
|
* @param[in] dbglevel 0 is show no debug messages, 1 is normal, 2.. is high debug.
|
|
Note this is _not_ level from syslog(3)
|
|
* @param[in] f Debug-file. Open file where debug messages are directed.
|
|
This overrides the clicon_log settings which is otherwise where
|
|
debug messages are directed.
|
|
*/
|
|
int
|
|
clicon_debug_init(int dbglevel,
|
|
FILE *f)
|
|
{
|
|
debug = dbglevel; /* Global variable */
|
|
return 0;
|
|
}
|
|
|
|
/*! Print a debug message with debug-level. Settings determine where msg appears.
|
|
*
|
|
* If the dbglevel passed in the function is equal to or lower than the one set by
|
|
* clicon_debug_init(level). That is, only print debug messages <= than what you want:
|
|
* print message if level >= dbglevel.
|
|
* The message is sent to clicon_log. EIther to syslog, stderr or both, depending on
|
|
* clicon_log_init() setting
|
|
*
|
|
* @param[in] dbglevel 0 always called (dont do this: not really a dbg message)
|
|
* 1 default level if passed -D
|
|
* 2.. Higher debug levels
|
|
* @param[in] format Message to print as argv.
|
|
*/
|
|
int
|
|
clicon_debug(int dbglevel,
|
|
char *format, ...)
|
|
{
|
|
va_list args;
|
|
int len;
|
|
char *msg = NULL;
|
|
int retval = -1;
|
|
|
|
if (dbglevel > debug) /* debug mask */
|
|
return 0;
|
|
/* first round: compute length of debug message */
|
|
va_start(args, format);
|
|
len = vsnprintf(NULL, 0, format, args);
|
|
va_end(args);
|
|
|
|
/* allocate a message string exactly fitting the messgae length */
|
|
if ((msg = malloc(len+1)) == NULL){
|
|
clicon_err(OE_UNIX, errno, "malloc");
|
|
goto done;
|
|
}
|
|
/* second round: compute write message from format and args */
|
|
va_start(args, format);
|
|
if (vsnprintf(msg, len+1, format, args) < 0){
|
|
va_end(args);
|
|
clicon_err(OE_UNIX, errno, "vsnprintf");
|
|
goto done;
|
|
}
|
|
va_end(args);
|
|
if (_debugfile != NULL){ /* Bypass syslog altogether */
|
|
/* XXX: Here use date sub-routine as found in err_print1 */
|
|
flogtime(_debugfile);
|
|
fprintf(_debugfile, "%s\n", msg);
|
|
}
|
|
else
|
|
clicon_log_str(LOG_DEBUG, msg);
|
|
retval = 0;
|
|
done:
|
|
if (msg)
|
|
free(msg);
|
|
return retval;
|
|
}
|
|
|
|
/*! Translate month number (0..11) to a three letter month name
|
|
* @param[in] md month number, where 0 is january
|
|
*/
|
|
char *
|
|
mon2name(int md)
|
|
{
|
|
switch(md){
|
|
case 0: return "Jan";
|
|
case 1: return "Feb";
|
|
case 2: return "Mar";
|
|
case 3: return "Apr";
|
|
case 4: return "May";
|
|
case 5: return "Jun";
|
|
case 6: return "Jul";
|
|
case 7: return "Aug";
|
|
case 8: return "Sep";
|
|
case 9: return "Oct";
|
|
case 10: return "Nov";
|
|
case 11: return "Dec";
|
|
default:
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|