blob: c9656fb1fd87b94758d86041286d5809c885eaad [file] [log] [blame]
/**
* @file logger.cpp
*
* Functions to do logging.
*
* If a log statement ends in a newline, the current log is ended.
* When the log severity changes, an implicit newline is inserted.
*
* @author Ben Gardner
* @license GPL v2+
*/
#include "logger.h"
#include <cstdio>
#include <deque>
#include <stdarg.h>
#include "unc_ctype.h"
#include "log_levels.h"
struct log_fcn_info
{
log_fcn_info(const char *name, int line) : name(name), line(line)
{
}
const char *name;
int line;
};
static std::deque<log_fcn_info> g_fq;
/** Private log structure */
struct log_buf
{
log_buf() : log_file(0), sev(LSYS), in_log(0), buf_len(0), show_hdr(false)
{
}
FILE *log_file;
log_sev_t sev;
int in_log;
char buf[256];
int buf_len;
log_mask_t mask;
bool show_hdr;
};
static struct log_buf g_log;
/**
* Initializes the log subsystem - call this first.
* This function sets the log stream and enables the top 3 sevs (0-2).
*
* @param log_file NULL for stderr or the FILE stream for logs.
*/
void log_init(FILE *log_file)
{
/* set the top 3 severities */
logmask_set_all(g_log.mask, false);
log_set_sev(LSYS, true);
log_set_sev(LERR, true);
log_set_sev(LWARN, true);
g_log.log_file = (log_file != NULL) ? log_file : stderr;
}
/**
* Show or hide the severity prefix "<1>"
*
* @param true=show false=hide
*/
void log_show_sev(bool show)
{
g_log.show_hdr = show;
}
/**
* Returns whether a log severity is active.
*
* @param sev The severity
* @return true/false
*/
bool log_sev_on(log_sev_t sev)
{
return(logmask_test(g_log.mask, sev));
}
/**
* Sets a log sev on or off
*
* @param sev The severity
* @return true/false
*/
void log_set_sev(log_sev_t sev, bool value)
{
logmask_set_sev(g_log.mask, sev, value);
}
/**
* Sets the log mask
*
* @param mask The mask to copy
*/
void log_set_mask(const log_mask_t& mask)
{
g_log.mask = mask;
}
/**
* Gets the log mask
*
* @param mask Where to copy the mask
*/
void log_get_mask(log_mask_t& mask)
{
mask = g_log.mask;
}
/**
* Flushes the cached log text to the stream
*
* @param force_nl Append NL if not present
*/
static void log_flush(bool force_nl)
{
if (g_log.buf_len > 0)
{
if (force_nl && (g_log.buf[g_log.buf_len - 1] != '\n'))
{
g_log.buf[g_log.buf_len++] = '\n';
g_log.buf[g_log.buf_len] = 0;
}
if (fwrite(g_log.buf, g_log.buf_len, 1, g_log.log_file) != 1)
{
/* maybe we should log something to complain... =) */
}
g_log.buf_len = 0;
}
}
/**
* Starts the log statement by flushing if needed and printing the header
*
* @param sev The log severity
* @return The number of bytes available
*/
static size_t log_start(log_sev_t sev)
{
if (sev != g_log.sev)
{
if (g_log.buf_len > 0)
{
log_flush(true);
}
g_log.sev = sev;
g_log.in_log = false;
}
/* If not in a log, the buffer is empty. Add the header, if enabled. */
if (!g_log.in_log && g_log.show_hdr)
{
g_log.buf_len = snprintf(g_log.buf, sizeof(g_log.buf), "<%d>", sev);
}
int cap = ((int)sizeof(g_log.buf) - 2) - g_log.buf_len;
return((cap > 0) ? (size_t)cap : 0);
}
/**
* Cleans up after a log statement by detecting whether the log is done,
* (it ends in a newline) and possibly flushing the log.
*/
static void log_end(void)
{
g_log.in_log = (g_log.buf[g_log.buf_len - 1] != '\n');
if (!g_log.in_log || (g_log.buf_len > (int)(sizeof(g_log.buf) / 2)))
{
log_flush(false);
}
}
/**
* Logs a string of known length
*
* @param sev The severity
* @param str The pointer to the string
* @param len The length of the string from strlen(str)
*/
void log_str(log_sev_t sev, const char *str, int len)
{
if ((str == NULL) || (len <= 0) || !log_sev_on(sev))
{
return;
}
size_t cap = log_start(sev);
if (cap > 0)
{
if (len > (int)cap)
{
len = cap;
}
memcpy(&g_log.buf[g_log.buf_len], str, len);
g_log.buf_len += len;
g_log.buf[g_log.buf_len] = 0;
}
log_end();
}
/**
* Logs a formatted string -- similiar to printf()
*
* @param sev The severity
* @param fmt The format string
* @param ... Additional arguments
*/
void log_fmt(log_sev_t sev, const char *fmt, ...)
{
va_list args;
int len;
size_t cap;
if ((fmt == NULL) || !log_sev_on(sev))
{
return;
}
/* Some implementation of vsnprintf() return the number of characters
* that would have been stored if the buffer was large enough instead of
* the number of characters actually stored.
*/
cap = log_start(sev);
/* Add on the variable log parameters to the log string */
va_start(args, fmt);
len = vsnprintf(&g_log.buf[g_log.buf_len], cap, fmt, args);
va_end(args);
if (len > 0)
{
if (len > (int)cap)
{
len = cap;
}
g_log.buf_len += len;
g_log.buf[g_log.buf_len] = 0;
}
log_end();
}
/**
* Dumps hex characters inline, no newlines inserted
*
* @param sev The severity
* @param data The data to log
* @param len The number of bytes to log
*/
void log_hex(log_sev_t sev, const void *vdata, int len)
{
const UINT8 *dat = (const UINT8 *)vdata;
int idx;
char buf[80];
if ((vdata == NULL) || !log_sev_on(sev))
{
return;
}
idx = 0;
while (len-- > 0)
{
buf[idx++] = to_hex_char(*dat >> 4);
buf[idx++] = to_hex_char(*dat);
dat++;
if (idx >= (int)(sizeof(buf) - 3))
{
buf[idx] = 0;
log_str(sev, buf, idx);
idx = 0;
}
}
if (idx > 0)
{
buf[idx] = 0;
log_str(sev, buf, idx);
}
}
/**
* Logs a block of data in a pretty hex format
* Numbers on the left, characters on the right, just like I like it.
*
* @param sev The severity
* @param data The data to log
* @param len The number of bytes to log
*/
void log_hex_blk(log_sev_t sev, const void *data, int len)
{
static char buf[80] = "nnn | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | cccccccccccccccc\n";
const UINT8 *dat = (const UINT8 *)data;
int idx;
int count;
int str_idx = 0;
int chr_idx = 0;
int tmp;
int total;
if ((data == NULL) || (len <= 0) || !log_sev_on(sev))
{
return;
}
/*
* Dump the specified number of bytes in hex, 16 byte per line by
* creating a string and then calling log_str()
*/
/* Loop through the data of the current iov */
count = 0;
total = 0;
for (idx = 0; idx < len; idx++)
{
if (count == 0)
{
str_idx = 6;
chr_idx = 56;
buf[0] = to_hex_char(total >> 12);
buf[1] = to_hex_char(total >> 8);
buf[2] = to_hex_char(total >> 4);
}
tmp = dat[idx];
buf[str_idx] = to_hex_char(tmp >> 4);
buf[str_idx + 1] = to_hex_char(tmp);
str_idx += 3;
buf[chr_idx++] = unc_isprint(tmp) ? tmp : '.';
total++;
count++;
if (count >= 16)
{
count = 0;
log_str(sev, buf, 73);
}
}
/*
** Print partial line if any
*/
if (count != 0)
{
/* Clear out any junk */
while (count < 16)
{
buf[str_idx] = ' '; /* MSB hex */
buf[str_idx + 1] = ' '; /* LSB hex */
str_idx += 3;
buf[chr_idx++] = ' ';
count++;
}
log_str(sev, buf, 73);
}
}
log_func::log_func(const char *name, int line)
{
g_fq.push_back(log_fcn_info(name, line));
}
log_func::~log_func()
{
g_fq.pop_back();
}
void log_func_call(int line)
{
/* REVISIT: pass the __func__ and verify it matches the top entry? */
if (!g_fq.empty())
{
g_fq.back().line = line;
}
}
void log_func_stack(log_sev_t sev, const char *prefix, const char *suffix, int skip_cnt)
{
if (prefix)
{
LOG_FMT(sev, "%s", prefix);
}
if (skip_cnt < 0)
{
skip_cnt = 0;
}
#ifdef DEBUG
const char *sep = "";
for (int idx = (int)g_fq.size() - (skip_cnt + 1); idx >= 0; idx--)
{
LOG_FMT(sev, "%s %s:%d", sep, g_fq[idx].name, g_fq[idx].line);
sep = ",";
}
#else
LOG_FMT(sev, "-DEBUG NOT SET-");
#endif
if (suffix)
{
LOG_FMT(sev, "%s", suffix);
}
}