serval-dna/log.c

340 lines
9.5 KiB
C
Raw Normal View History

/*
Serval DNA logging
Copyright (C) 2013-2015 Serval Project Inc.
Copyright (C) 2016-2017 Flinders University
This program 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 2
of the License, or (at your option) any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <libgen.h>
2012-07-25 07:32:15 +00:00
#include <stdlib.h>
2012-07-19 08:29:45 +00:00
#include <stdio.h>
2012-05-20 14:38:31 +00:00
#include <ctype.h>
2012-07-25 07:32:15 +00:00
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <unistd.h>
#include <assert.h>
#include "log.h"
#include "log_output.h"
#include "log_prolog.h"
Switch to feature-driven linking This introduces a new way of linking Serval executables and dynamic libraries from static libraries like libservald.a -- called "feature-driven" linking. The Makefile now links servald and serval-tests from libservald.a, rather than from an explicit list of object (.o) files. Thanks to the section-based method for registering functions such as HTTP handlers, CLI commands and MDP handlers, these object files had become "stand-alone" and hence were no longer included in the link because there was no unresolved reference that required them to be linked in. The new "feature.h" provides the DECLARE_FEATURE(name) macro that each stand-alone source file uses to declare the named feature(s) it provides. Each executable can call the USE_FEATURE(name) macro in any of its explicitly-linked source files to cause the corresponding object(s) to be included in the link, eg, servald_features.c. The DEFINE_BINDING() macro has been extended so that every individual MDP binding is given a feature name based on its port number macro, eg, "mdp_binding_MDP_PORT_ECHO". Some features have been factored into their own separate source files so they can be omitted or included in a build independently of each other: - the MDP bindings for MDP_PORT_DNALOOKUP, MDP_PORT_ECHO, MDP_PORT_TRACE, MDP_PORT_KEYMAPREQUEST, MDP_PORT_RHIZOME_xxx, MDP_PORT_PROBE, MDP_PORT_STUN, MDP_PORT_STUNREQ - the CLI "log" and "echo" commands - the CLI "rhizome direct" command The JNI source files are only compiled if the <jni.h> header is present, otherwise they are omitted from libservald.so.
2016-10-13 02:58:23 +00:00
#include "version_servald.h"
#include "instance.h"
#include "net.h"
#include "os.h"
#include "str.h"
#include "strbuf.h"
#include "strbuf_helpers.h"
#include "xprintf.h"
#include "trigger.h"
/* This thread-local variable is used to support recursion, so that log output operations like
* open() that invoke log primitives will not cause infinite recursion, but instead will log only to
* the output whose iterator they are currently servicing.
*/
static __thread struct log_output_iterator *current_iterator = NULL;
/* By default, if there is no log file configured, this is logged as an INFO message, but a server
* can set this to log it at a higher level.
*/
int serval_log_level_NoLogFileConfigured = LOG_LEVEL_INFO;
/* Primitive operations for log_output_iterator.
*/
static void log_iterator_start(struct log_output_iterator *it)
{
it->output = NULL;
memset(it, 0, sizeof *it);
gettimeofday(&it->tv, NULL);
localtime_r(&it->tv.tv_sec, &it->tm);
it->xpf = XPRINTF_NULL;
}
static int log_iterator_advance(struct log_output_iterator *it)
{
assert(it->output != SECTION_END(logoutput));
do {
it->output = it->output ? it->output + 1 : SECTION_START(logoutput);
} while (it->output != SECTION_END(logoutput) && !*it->output); // skip NULL entries
return it->output != SECTION_END(logoutput);
}
/* In case no outputters are linked, ensure that the logoutput section exists, by adding a NULL
* entry to it. Otherwise you get link errors like:
* log.c:193: error: undefined reference to '__stop_logoutput'
*/
DEFINE_LOG_OUTPUT(NULL);
/* Functions for formatting log messages.
*/
static void print_line_prefix(struct log_output_iterator *it)
{
assert(it->output);
struct log_output *out = *it->output;
assert(out);
if (out->show_pid(out))
2015-06-22 08:37:50 +00:00
xprintf(it->xpf, "[%5u] ", getpid());
if (out->show_time(out)) {
2015-06-22 08:37:50 +00:00
if (it->tv.tv_sec == 0) {
xputs("NOTIME______ ", it->xpf);
} else {
char buf[50];
if (strftime(buf, sizeof buf, "%T", &it->tm) == 0)
xputs("EMPTYTIME___ ", it->xpf);
else
xprintf(it->xpf, "%s.%03u ", buf, (unsigned int)it->tv.tv_usec / 1000);
}
}
Switch to feature-driven linking This introduces a new way of linking Serval executables and dynamic libraries from static libraries like libservald.a -- called "feature-driven" linking. The Makefile now links servald and serval-tests from libservald.a, rather than from an explicit list of object (.o) files. Thanks to the section-based method for registering functions such as HTTP handlers, CLI commands and MDP handlers, these object files had become "stand-alone" and hence were no longer included in the link because there was no unresolved reference that required them to be linked in. The new "feature.h" provides the DECLARE_FEATURE(name) macro that each stand-alone source file uses to declare the named feature(s) it provides. Each executable can call the USE_FEATURE(name) macro in any of its explicitly-linked source files to cause the corresponding object(s) to be included in the link, eg, servald_features.c. The DEFINE_BINDING() macro has been extended so that every individual MDP binding is given a feature name based on its port number macro, eg, "mdp_binding_MDP_PORT_ECHO". Some features have been factored into their own separate source files so they can be omitted or included in a build independently of each other: - the MDP bindings for MDP_PORT_DNALOOKUP, MDP_PORT_ECHO, MDP_PORT_TRACE, MDP_PORT_KEYMAPREQUEST, MDP_PORT_RHIZOME_xxx, MDP_PORT_PROBE, MDP_PORT_STUN, MDP_PORT_STUNREQ - the CLI "log" and "echo" commands - the CLI "rhizome direct" command The JNI source files are only compiled if the <jni.h> header is present, otherwise they are omitted from libservald.so.
2016-10-13 02:58:23 +00:00
if (strbuf_len(&log_context)) {
2015-06-22 08:37:50 +00:00
xputs("[", it->xpf);
Switch to feature-driven linking This introduces a new way of linking Serval executables and dynamic libraries from static libraries like libservald.a -- called "feature-driven" linking. The Makefile now links servald and serval-tests from libservald.a, rather than from an explicit list of object (.o) files. Thanks to the section-based method for registering functions such as HTTP handlers, CLI commands and MDP handlers, these object files had become "stand-alone" and hence were no longer included in the link because there was no unresolved reference that required them to be linked in. The new "feature.h" provides the DECLARE_FEATURE(name) macro that each stand-alone source file uses to declare the named feature(s) it provides. Each executable can call the USE_FEATURE(name) macro in any of its explicitly-linked source files to cause the corresponding object(s) to be included in the link, eg, servald_features.c. The DEFINE_BINDING() macro has been extended so that every individual MDP binding is given a feature name based on its port number macro, eg, "mdp_binding_MDP_PORT_ECHO". Some features have been factored into their own separate source files so they can be omitted or included in a build independently of each other: - the MDP bindings for MDP_PORT_DNALOOKUP, MDP_PORT_ECHO, MDP_PORT_TRACE, MDP_PORT_KEYMAPREQUEST, MDP_PORT_RHIZOME_xxx, MDP_PORT_PROBE, MDP_PORT_STUN, MDP_PORT_STUNREQ - the CLI "log" and "echo" commands - the CLI "rhizome direct" command The JNI source files are only compiled if the <jni.h> header is present, otherwise they are omitted from libservald.so.
2016-10-13 02:58:23 +00:00
xputs(strbuf_str(&log_context), it->xpf);
2015-06-22 08:37:50 +00:00
xputs("] ", it->xpf);
}
}
static void whence_prefix(struct log_output_iterator *it, struct __sourceloc whence)
{
assert(!XPRINTF_IS_NULL(it->xpf));
if ((whence.file && whence.file[0]) || (whence.function && whence.function[0])) {
xprint_sourceloc(it->xpf, whence);
xputs(" ", it->xpf);
}
}
/* Log output operations.
*/
static void log_open(struct log_output_iterator *it)
{
assert(it->output);
assert(*it->output);
if ((*it->output)->open)
(*it->output)->open(it);
}
static bool_t is_log_available(struct log_output_iterator *it)
{
assert(it->output);
assert(*it->output);
return !(*it->output)->is_available || (*it->output)->is_available(it);
}
static void log_start_line(struct log_output_iterator *it, int level)
{
assert(level >= LOG_LEVEL_SILENT);
assert(level <= LOG_LEVEL_FATAL);
assert(XPRINTF_IS_NULL(it->xpf));
assert(it->output);
assert(*it->output);
assert((*it->output)->start_line);
(*it->output)->start_line(it, level);
assert(!XPRINTF_IS_NULL(it->xpf));
print_line_prefix(it);
}
static void log_end_line(struct log_output_iterator *it, int level)
{
assert(level >= LOG_LEVEL_SILENT);
assert(level <= LOG_LEVEL_FATAL);
assert(!XPRINTF_IS_NULL(it->xpf));
assert(it->output);
assert(*it->output);
if ((*it->output)->end_line)
(*it->output)->end_line(it, level);
it->xpf = XPRINTF_NULL;
}
static void log_flush(struct log_output_iterator *it)
{
assert(it->output);
assert(*it->output);
if ((*it->output)->flush)
(*it->output)->flush(it);
}
static void log_close(struct log_output_iterator *it)
{
assert(it->output);
assert(*it->output);
if ((*it->output)->close)
(*it->output)->close(it);
}
static void log_capture_fd(struct log_output_iterator *it, int fd, bool_t *captured)
{
assert(it->output);
assert(*it->output);
if ((*it->output)->capture_fd)
(*it->output)->capture_fd(it, fd, captured);
}
/* Functions for use by log outputters. This is the "private" API of the logging system, as
* described in "log_output.h".
*/
const char *serval_log_level_prefix_string(int level)
{
switch (level) {
case LOG_LEVEL_FATAL: return "FATAL:";
case LOG_LEVEL_ERROR: return "ERROR:";
case LOG_LEVEL_WARN: return "WARN: ";
case LOG_LEVEL_HINT: return "HINT: ";
case LOG_LEVEL_INFO: return "INFO: ";
case LOG_LEVEL_DEBUG: return "DEBUG:";
default: return "UNKWN:";
}
}
void serval_log_output_iterator_vprintf_nl(struct log_output_iterator *it, int level, const char *fmt, va_list ap)
{
log_start_line(it, level);
vxprintf(it->xpf, fmt, ap);
log_end_line(it, level);
}
void serval_log_output_iterator_printf_nl(struct log_output_iterator *it, int level, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
serval_log_output_iterator_vprintf_nl(it, level, fmt, ap);
va_end(ap);
}
static void print_datetime(struct log_output_iterator *it, int level)
{
char buf[50];
if (strftime(buf, sizeof buf, "%F %T %z", &it->tm)) {
serval_log_output_iterator_printf_nl(it, level, "Local date/time: %s", buf);
(*it->output)->last_tm = it->tm;
}
}
static void print_newdate(struct log_output_iterator *it)
{
struct log_output *out = *it->output;
if ( it->tm.tm_mday != out->last_tm.tm_mday
|| it->tm.tm_mon != out->last_tm.tm_mon
|| it->tm.tm_year != out->last_tm.tm_year
)
print_datetime(it, LOG_LEVEL_INFO);
}
void serval_log_print_prolog(struct log_output_iterator *it)
{
assert(current_iterator == NULL || current_iterator == it);
struct log_output_iterator *save_current_iterator = current_iterator;
current_iterator = it;
print_datetime(it, LOG_LEVEL_INFO);
CALL_TRIGGER(log_prolog);
current_iterator = save_current_iterator;
}
// Put a dummy no-op trigger callback into the "log_prolog" trigger section, otherwise if no other
// object provides one, the link will fail with errors like:
// undefined reference to `__start_tr_log_prolog'
// undefined reference to `__stop_tr_log_prolog'
static void __dummy_on_log_prolog() {}
DEFINE_TRIGGER(log_prolog, __dummy_on_log_prolog);
/* Private helper functions for formatting and emitting log messages.
*/
static void iterator_vprintf_nl(struct log_output_iterator *it, int level, struct __sourceloc whence, const char *fmt, va_list ap)
{
assert(current_iterator);
log_start_line(it, level);
whence_prefix(it, whence);
va_list ap1;
va_copy(ap1, ap);
vxprintf(it->xpf, fmt, ap1);
va_end(ap1);
log_end_line(it, level);
}
/* Logging primitives. This is the "public" API of the logging system, as described in "log.h".
*/
void serval_vlogf(int level, struct __sourceloc whence, const char *fmt, va_list ap)
{
if (level != LOG_LEVEL_SILENT) {
if (current_iterator) {
// This occurs if a log primitive is invoked recursively from within a log output, which is
// typically an open() operation calling serval_log_print_prolog(it). In this case, the log
// output only goes to the single output in question, not to all outputs.
iterator_vprintf_nl(current_iterator, level, whence, fmt, ap);
}
else {
struct log_output_iterator it;
log_iterator_start(&it);
while (log_iterator_advance(&it)) {
struct log_output *out = *it.output;
if (level >= out->minimum_level(out)) {
// If the open() operation recurses by invoking a log primitive directly, then print that
// message to all available outputs but avoid recursing into open() operations that are
// already being called.
if (!out->opening) {
out->opening = 1;
log_open(&it);
out->opening = 0;
}
if (is_log_available(&it)) {
current_iterator = &it;
print_newdate(&it);
iterator_vprintf_nl(&it, level, whence, fmt, ap);
log_flush(&it);
current_iterator = NULL;
}
}
}
}
}
}
void serval_log_flush()
{
assert(!current_iterator);
struct log_output_iterator it;
log_iterator_start(&it);
while (log_iterator_advance(&it))
log_flush(&it);
}
void serval_log_close()
{
assert(!current_iterator);
struct log_output_iterator it;
log_iterator_start(&it);
while (log_iterator_advance(&it))
log_close(&it);
}
bool_t serval_log_capture_fd(int fd) {
assert(!current_iterator);
struct log_output_iterator it;
log_iterator_start(&it);
bool_t captured = 0;
while (log_iterator_advance(&it))
log_capture_fd(&it, fd, &captured);
return captured;
}