You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

337 lines
7.6 KiB

/**
* @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 "compat.h"
#include <cstdarg> // to get va_start, va_end
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(nullptr)
, sev(LSYS)
, in_log(0)
, buf_len(0)
, show_hdr(false)
{
bufX.clear();
bufX.resize(256);
}
FILE *log_file; //! file where the log messages are stored into
log_sev_t sev; //! log level determines which messages are logged
int in_log; //! flag indicates if a log operation is going on
size_t buf_len; //! number of characters currently stored in buffer
std::vector<char> bufX; //! buffer holds the log message
log_mask_t mask;
bool show_hdr; //! flag determine if a header gets added to log message
};
static struct log_buf g_log;
/**
* 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);
/**
* 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();
/**
* 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 != nullptr) ? log_file : stderr;
}
void log_show_sev(bool show)
{
g_log.show_hdr = show;
}
bool log_sev_on(log_sev_t sev)
{
return(logmask_test(g_log.mask, sev));
}
void log_set_sev(log_sev_t sev, bool value)
{
logmask_set_sev(g_log.mask, sev, value);
}
void log_set_mask(const log_mask_t &mask)
{
g_log.mask = mask;
}
void log_get_mask(log_mask_t &mask)
{
mask = g_log.mask;
}
void log_flush(bool force_nl)
{
if (g_log.buf_len > 0)
{
if ( force_nl
&& g_log.bufX[g_log.buf_len - 1] != '\n')
{
g_log.bufX[g_log.buf_len++] = '\n';
g_log.bufX[g_log.buf_len] = 0;
}
size_t retlength = fwrite(&g_log.bufX[0], g_log.buf_len, 1, g_log.log_file);
if (retlength != 1)
{
// maybe we should log something to complain... =)
}
g_log.buf_len = 0;
}
}
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 = static_cast<size_t>(snprintf(&g_log.bufX[0], g_log.bufX.size(), "<%d>", sev));
}
size_t cap = (g_log.bufX.size() - 2) - g_log.buf_len;
return((cap > 0) ? cap : 0);
}
static void log_end()
{
g_log.in_log = (g_log.bufX[g_log.buf_len - 1] != '\n');
if ( !g_log.in_log
|| (g_log.buf_len > (g_log.bufX.size() / 2)))
{
log_flush(false);
}
}
void log_fmt(log_sev_t sev, const char *fmt, ...)
{
if ( fmt == nullptr
|| !log_sev_on(sev))
{
return;
}
// Issue #1203
unsigned int length = strlen(fmt);
if (length == 0)
{
return;
}
// the value of buffer_length is experimental
const int buffer_length = 40000;
char buf[buffer_length];
// it MUST be a 'unsigned int' variable to be runable under windows
if (length >= buffer_length)
{
fprintf(stderr, "FATAL(1): The variable 'buf' is not big enough:\n");
fprintf(stderr, " it should be bigger as %u\n", length);
fprintf(stderr, "Please make a report.\n");
fprintf(stderr, "For the buffer: %s\n", fmt);
exit(EX_SOFTWARE);
}
memcpy(buf, fmt, length);
buf[length] = 0;
convert_log_zu2lu(buf);
while (true)
{
/* 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.
*
* this gets the number of characters that fit into the log buffer
*/
size_t cap = log_start(sev);
// Add on the variable log parameters to the log string
va_list args; // determine list of arguments ...
va_start(args, fmt);
size_t which = g_log.buf_len;
char *where = &g_log.bufX[which];
size_t lenX = static_cast<size_t>(vsnprintf(where, cap, buf, args));
va_end(args);
if (lenX > 0)
{
// The functions snprintf() and vsnprintf() do not write more than size bytes
// (including the terminating null byte ('\0')). If the output was truncated due
// to this limit, then the return value is the number of characters (excluding the
// terminating null byte) which would have been written to the final string if
// enough space had been available. Thus, a return value of size or more means
// that the output was truncated.
if (lenX > cap)
{
size_t bufXLength = g_log.bufX.size();
size_t X = bufXLength * 2;
if (X >= buffer_length)
{
fprintf(stderr, "FATAL(2): The variable 'buf' is not big enough:\n");
fprintf(stderr, " it should be bigger as %zu\n", X);
fprintf(stderr, "Please make a report.\n");
fprintf(stderr, "For the buffer: %s\n", fmt);
exit(EX_SOFTWARE);
}
g_log.bufX.resize(X);
}
else
{
g_log.buf_len += lenX;
g_log.bufX[g_log.buf_len] = 0;
break;
}
}
}
log_end();
} // log_fmt
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_stack(log_sev_t sev, const char *prefix, const char *suffix, size_t skip_cnt)
{
UNUSED(skip_cnt);
if (prefix != nullptr)
{
LOG_FMT(sev, "%s", prefix);
}
#ifdef DEBUG
const char *sep = "";
size_t g_fq_size = g_fq.size();
size_t begin_with;
if (g_fq_size > (skip_cnt + 1))
{
begin_with = g_fq_size - (skip_cnt + 1);
for (size_t idx = begin_with; idx != 0; idx--)
{
LOG_FMT(sev, "%s %s:%d", sep, g_fq[idx].name, g_fq[idx].line);
sep = ",";
}
LOG_FMT(sev, "%s %s:%d", sep, g_fq[0].name, g_fq[0].line);
}
#else
LOG_FMT(sev, "-DEBUG NOT SET-");
#endif
if (suffix != nullptr)
{
LOG_FMT(sev, "%s", suffix);
}
}
const char *get_unqualified_func_name(const char *func)
{
/**
* we look for the last ':' character;
*/
for (auto i = strlen(func); i > 0; --i)
{
if (func[i - 1] == ':')
{
/**
* function name is qualified, so return the
* unqualified portion
*/
return(func + i);
}
}
/**
* input function name is unqualified
*/
return(func);
}