/* * 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 . * * Regular logging and debugging. Syslog using levels. */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ #endif #include #include #include #include #include #include #include #include #include #include #include #include /* 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; }