Rewrite logging system

Rename the logging primitive functions and utility functions, prefixing
all with 'serval_log', eg: logMessage() -> serval_logf() etc.

Add an XPRINTF xhexdump() function and use it to implement the
serval_log_hexdump() utility, renamed from dump().  Add macros
WHY_dump(), WARN_dump(), HINT_dump() and DEBUG_dump(), and use them
everywhere.

Remove the 'log.console.dump_config' and 'log.file.dump_config'
configuration options; configuration is now dumped in every log prolog.

The logging system now constructs the log prolog by invoking the new
'log_prolog' trigger, so that it no longer depends on the version string
and configuration system.  Any system that wants to present a message in
the log prolog can define its own trigger, which calls standard log
primitives to print the message.

Split the logging system into a front-end (log.c) that provides the
logging primitives and is independent of the configuration system, and a
set of back-end "outputters" (log_output_console.c, log_output_file.c,
log_output_android.c) that may depend on the configuration system and
are decoupled from the front-end using the 'logoutput' link section.

These log outputters are explicitly linked into executables by the
Makefile rules, but could also be linked in using USE_FEATURE().  The
USE_FEATURE() calls have _not_ been added to servald_features.c, so that
different daemon executables can be built with the same feature set but
different log outputs.
This commit is contained in:
Andrew Bettison 2017-11-30 00:04:54 +10:30
parent 71e654a615
commit 92fa6c196a
43 changed files with 1369 additions and 758 deletions

View File

@ -302,17 +302,26 @@ libservaldaemon.a: _servald.a $(LIBSODIUM_A)
libservaldaemon.so: \ libservaldaemon.so: \
$(OBJSDIR_SERVALD)/servald_features.o \ $(OBJSDIR_SERVALD)/servald_features.o \
$(OBJSDIR_SERVALD)/log_output_console.o \
$(OBJSDIR_SERVALD)/log_output_file.o \
$(SERVAL_DAEMON_JNI_OBJS) \ $(SERVAL_DAEMON_JNI_OBJS) \
_servald.a \ _servald.a \
$(LIBSODIUM_A) $(LIBSODIUM_A)
@echo LINK $@ @echo LINK $@
@$(CC) -Wall -shared -o $@ $(LDFLAGS) $(filter %.o, $^) $(filter %.a, $^) @$(CC) -Wall -shared -o $@ $(LDFLAGS) $(filter %.o, $^) $(filter %.a, $^)
servald: $(OBJSDIR_SERVALD)/servald_features.o libservaldaemon.a # The daemon executable. There is no main.o mentioned in this link line, so
# the link brings it in from libservaldaemon.a in order to resolve the 'main'
# symbol. The servald_features.o object causes all the other .o object files
# to be pulled into the link.
servald: $(OBJSDIR_SERVALD)/servald_features.o \
$(OBJSDIR_SERVALD)/log_output_console.o \
$(OBJSDIR_SERVALD)/log_output_file.o \
libservaldaemon.a
@echo LINK $@ @echo LINK $@
@$(CC) -Wall -o $@ $^ $(LDFLAGS) @$(CC) -Wall -o $@ $^ $(LDFLAGS)
servaldwrap: $(OBJSDIR_SERVALD)/servalwrap.o $(OBJSDIR_TOOLS)/version.o servaldwrap: $(OBJSDIR_SERVALD)/servalwrap.o
@echo LINK $@ @echo LINK $@
@$(CC) -Wall -o $@ $^ $(LDFLAGS) @$(CC) -Wall -o $@ $^ $(LDFLAGS)

View File

@ -22,6 +22,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#define __SERVAL_DNA__COMMANDLINE_H #define __SERVAL_DNA__COMMANDLINE_H
#include <stdio.h> // for FILE #include <stdio.h> // for FILE
#include "lang.h" // for _APPEND() macro
#include "section.h" #include "section.h"
#include "trigger.h" #include "trigger.h"
#include "cli.h" #include "cli.h"
@ -30,10 +31,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#define KEYRING_ENTRY_PIN_OPTION ,"[--entry-pin=<pin>]" #define KEYRING_ENTRY_PIN_OPTION ,"[--entry-pin=<pin>]"
#define KEYRING_PIN_OPTIONS KEYRING_PIN_OPTION KEYRING_ENTRY_PIN_OPTION "..." #define KEYRING_PIN_OPTIONS KEYRING_PIN_OPTION KEYRING_ENTRY_PIN_OPTION "..."
// macros are weird sometimes ....
#define __APPEND_(X,Y) X ## Y
#define _APPEND(X,Y) __APPEND_(X,Y)
#define DEFINE_CMD(FUNC, FLAGS, HELP, WORD1, ...) \ #define DEFINE_CMD(FUNC, FLAGS, HELP, WORD1, ...) \
static int FUNC(const struct cli_parsed *parsed, struct cli_context *context); \ static int FUNC(const struct cli_parsed *parsed, struct cli_context *context); \
static struct cli_schema _APPEND(FUNC, __LINE__) IN_SECTION(commands) = {\ static struct cli_schema _APPEND(FUNC, __LINE__) IN_SECTION(commands) = {\

55
conf.c
View File

@ -25,6 +25,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "conf.h" #include "conf.h"
#include "instance.h" #include "instance.h"
#include "log.h" #include "log.h"
#include "log_prolog.h"
#include "debug.h" #include "debug.h"
#include "str.h" #include "str.h"
#include "mem.h" #include "mem.h"
@ -233,27 +234,28 @@ static int reload_and_parse(int permissive, int strict)
WARNF("config file %s loaded despite defects -- %s", conffile_path(), strbuf_str(b)); WARNF("config file %s loaded despite defects -- %s", conffile_path(), strbuf_str(b));
} }
} }
// Dump the log file before setting cf_limbo=0, so that:
// - if we are currently in limbo this will print no dump, then as soon as limbo goes off the
// very next log message will emit the prolog at the top of the log, which includes a dump of
// the newly loaded configuration;
// - if not currently in limbo, then will print a dump, which may differ from the dump that
// already appears in the log's prolog.
cf_dump_to_log("Configuration reloaded");
cf_limbo = 0; // let log messages out cf_limbo = 0; // let log messages out
logFlush(); serval_log_flush();
if (changed) { if (changed) {
CALL_TRIGGER(conf_log);
CALL_TRIGGER(conf_change); CALL_TRIGGER(conf_change);
} }
return ret; return ret;
} }
// Put a dummy no-op trigger callback into the "config_change" and "config_log" trigger sections, // Put a dummy no-op trigger callback into the "config_change" trigger section, otherwise if no
// otherwise if no other object provides one, the link will fail with errors like: // other object provides one, the link will fail with errors like:
// undefined reference to `__start_tr_config_change' // undefined reference to `__start_tr_config_change'
// undefined reference to `__stop_tr_config_change' // undefined reference to `__stop_tr_config_change'
static void __dummy_on_config_log();
DEFINE_TRIGGER(conf_log, __dummy_on_config_log);
static void __dummy_on_config_log() {}
static void __dummy_on_config_change();
DEFINE_TRIGGER(conf_change, __dummy_on_config_change);
static void __dummy_on_config_change() {} static void __dummy_on_config_change() {}
DEFINE_TRIGGER(conf_change, __dummy_on_config_change);
// The configuration API entry points. // The configuration API entry points.
@ -289,3 +291,36 @@ int cf_reload_permissive()
{ {
return reload_and_parse(1, 0); return reload_and_parse(1, 0);
} }
void cf_dump_to_log(const char *heading)
{
if (cf_limbo)
return;
struct cf_om_node *root = NULL;
int ret = cf_fmt_config_main(&root, &config);
if (ret == CFERROR) {
WHY("cannot dump current configuration: cf_fmt_config_main() returned CFERROR");
} else {
NOWHENCE(INFOF("%s:", heading));
struct cf_om_iterator oit;
int empty = 1;
for (cf_om_iter_start(&oit, root); oit.node; cf_om_iter_next(&oit)) {
if (oit.node->text && oit.node->line_number) {
empty = 0;
NOWHENCE(INFOF(" %s=%s", oit.node->fullkey, oit.node->text));
}
}
if (empty)
NOWHENCE(INFO(" (empty)"));
}
cf_om_free_node(&root);
}
/* Dump the configuration in the prolog of every log file.
*/
static void log_prolog_dump_config() {
cf_dump_to_log("Current configuration");
}
DEFINE_TRIGGER(log_prolog, log_prolog_dump_config);

2
conf.h
View File

@ -722,8 +722,8 @@ int cf_load_permissive(void);
int cf_reload(void); int cf_reload(void);
int cf_reload_strict(void); int cf_reload_strict(void);
int cf_reload_permissive(void); int cf_reload_permissive(void);
void cf_dump_to_log(const char *heading);
DECLARE_TRIGGER(conf_log);
DECLARE_TRIGGER(conf_change); DECLARE_TRIGGER(conf_change);
#endif //__SERVAL_DNA__CONF_H #endif //__SERVAL_DNA__CONF_H

View File

@ -278,8 +278,7 @@ END_STRUCT
#define LOG_FORMAT_OPTIONS \ #define LOG_FORMAT_OPTIONS \
ATOM(bool_t, show_pid, 1, boolean,, "If true, all log lines contain PID of logging process") \ ATOM(bool_t, show_pid, 1, boolean,, "If true, all log lines contain PID of logging process") \
ATOM(bool_t, show_time, 1, boolean,, "If true, all log lines contain time stamp") \ ATOM(bool_t, show_time, 1, boolean,, "If true, all log lines contain time stamp") \
ATOM(int, level, LOG_LEVEL_DEBUG, log_level,, "Only log messages at and above this level of severity") \ ATOM(int, level, LOG_LEVEL_DEBUG, log_level,, "Only log messages at and above this level of severity")
ATOM(bool_t, dump_config, 1, boolean,, "If true, current configuration is dumped into start of log")
STRUCT(log_format) STRUCT(log_format)
LOG_FORMAT_OPTIONS LOG_FORMAT_OPTIONS
@ -307,7 +306,6 @@ STRUCT_DEFAULT(log_format, console)
ATOM_DEFAULT(show_pid, 0) ATOM_DEFAULT(show_pid, 0)
ATOM_DEFAULT(show_time, 0) ATOM_DEFAULT(show_time, 0)
ATOM_DEFAULT(level, LOG_LEVEL_HINT) ATOM_DEFAULT(level, LOG_LEVEL_HINT)
ATOM_DEFAULT(dump_config, 0)
END_STRUCT_DEFAULT END_STRUCT_DEFAULT
STRUCT_DEFAULT(log_format, android) STRUCT_DEFAULT(log_format, android)

View File

@ -41,7 +41,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#define DEBUG(FLAG,X) DEBUGF(FLAG, "%s", (X)) #define DEBUG(FLAG,X) DEBUGF(FLAG, "%s", (X))
#define DEBUGF_perror(FLAG,F,...) do { if (IF_DEBUG(FLAG)) _DEBUGF_TAG_perror(#FLAG, F, ##__VA_ARGS__); } while (0) #define DEBUGF_perror(FLAG,F,...) do { if (IF_DEBUG(FLAG)) _DEBUGF_TAG_perror(#FLAG, F, ##__VA_ARGS__); } while (0)
#define DEBUG_perror(FLAG,X) DEBUGF_perror(FLAG, "%s", (X)) #define DEBUG_perror(FLAG,X) DEBUGF_perror(FLAG, "%s", (X))
#define DEBUG_argv(FLAG,X,ARGC,ARGV) do { if (IF_DEBUG(FLAG)) _DEBUGF_TAG_argv(#FLAG, X, (ARGC), (ARGV)); } while (0) #define DEBUG_argv(FLAG,X,ARGC,ARGV) do { if (IF_DEBUG(FLAG)) _DEBUG_TAG_argv(#FLAG, X, (ARGC), (ARGV)); } while (0)
#define DEBUG_dump(FLAG,X,ADDR,LEN) do { if (IF_DEBUG(FLAG)) _DEBUG_TAG_dump(#FLAG, X, (ADDR), (LEN)); } while (0)
#define D(FLAG) DEBUG(FLAG, "D") #define D(FLAG) DEBUG(FLAG, "D")
#define T DEBUG(trace, "T") #define T DEBUG(trace, "T")

View File

@ -217,7 +217,7 @@ dna_helper_start()
switch (dna_helper_pid = fork()) { switch (dna_helper_pid = fork()) {
case 0: case 0:
/* Child, should exec() to become helper after installing file descriptors. */ /* Child, should exec() to become helper after installing file descriptors. */
close_log_file(); serval_log_close();
setenv("MYSID", mysid, 1); setenv("MYSID", mysid, 1);
signal(SIGTERM, SIG_DFL); signal(SIGTERM, SIG_DFL);
close(stdin_fds[1]); close(stdin_fds[1]);

View File

@ -35,6 +35,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <sys/time.h> #include <sys/time.h>
#include <unistd.h> #include <unistd.h>
#include "os.h" #include "os.h"
#include "xprintf.h"
#define PACKET_SIZE 255 #define PACKET_SIZE 255
int chars_per_ms=1; int chars_per_ms=1;
@ -143,7 +144,7 @@ int read_bytes(struct radio_state *s)
return bytes; return bytes;
log_time(); log_time();
fprintf(stderr, "Read from %s\n", s->name); fprintf(stderr, "Read from %s\n", s->name);
dump(NULL,buff,bytes); xhexdump(XPRINTF_STDIO(stderr), buff, bytes, "");
s->last_char_ms = gettime_ms(); s->last_char_ms = gettime_ms();
// process incoming bytes // process incoming bytes
@ -190,7 +191,7 @@ void write_bytes(struct radio_state *s)
if (wrote != -1){ if (wrote != -1){
log_time(); log_time();
fprintf(stderr, "Wrote to %s\n", s->name); fprintf(stderr, "Wrote to %s\n", s->name);
dump(NULL, s->rxbuffer, (size_t)wrote); xhexdump(XPRINTF_STDIO(stderr), s->rxbuffer, (size_t)wrote, "");
if ((size_t)wrote < s->rxb_len) if ((size_t)wrote < s->rxb_len)
bcopy(&s->rxbuffer[(size_t)wrote], s->rxbuffer, s->rxb_len - (size_t)wrote); bcopy(&s->rxbuffer[(size_t)wrote], s->rxbuffer, s->rxb_len - (size_t)wrote);
s->rxb_len -= (size_t)wrote; s->rxb_len -= (size_t)wrote;
@ -318,7 +319,7 @@ void transfer_bytes(struct radio_state *radios)
if (bytes < PACKET_SIZE && t->wait_count++ <5){ if (bytes < PACKET_SIZE && t->wait_count++ <5){
log_time(); log_time();
fprintf(stderr,"Waiting for more bytes for %s\n", t->name); fprintf(stderr,"Waiting for more bytes for %s\n", t->name);
dump(NULL, t->txbuffer, bytes); xhexdump(XPRINTF_STDIO(stderr), t->txbuffer, bytes, "");
}else }else
send = bytes; send = bytes;
} }

View File

@ -39,6 +39,7 @@ HDRS= fifo.h \
crypto.h \ crypto.h \
dataformats.h \ dataformats.h \
log.h \ log.h \
log_output.h \
debug.h \ debug.h \
net.h \ net.h \
fdqueue.h \ fdqueue.h \

View File

@ -1258,10 +1258,8 @@ static int keyring_decrypt_pkr(keyring_file *k, const char *pin, int slot_number
/* compare hash to record */ /* compare hash to record */
if (memcmp(hash, &slot[PKR_SALT_BYTES], crypto_hash_sha512_BYTES)) { if (memcmp(hash, &slot[PKR_SALT_BYTES], crypto_hash_sha512_BYTES)) {
DEBUGF(keyring, "slot %u is not valid (MAC mismatch)", slot_number); DEBUGF(keyring, "slot %u is not valid (MAC mismatch)", slot_number);
if (IF_DEBUG(keyring)){ DEBUG_dump(keyring, "computed",hash,crypto_hash_sha512_BYTES);
dump("computed",hash,crypto_hash_sha512_BYTES); DEBUG_dump(keyring, "stored",&slot[PKR_SALT_BYTES],crypto_hash_sha512_BYTES);
dump("stored",&slot[PKR_SALT_BYTES],crypto_hash_sha512_BYTES);
}
goto kdp_safeexit; goto kdp_safeexit;
} }
@ -1648,10 +1646,8 @@ int keyring_set_did_name(keyring_identity *id, const char *did, const char *name
bzero(&kp->public_key[len], kp->public_key_len - len); bzero(&kp->public_key[len], kp->public_key_len - len);
} }
if (IF_DEBUG(keyring)) { DEBUG_dump(keyring, "storing DID",&kp->private_key[0],32);
dump("{keyring} storing DID",&kp->private_key[0],32); DEBUG_dump(keyring, "storing Name",&kp->public_key[0],64);
dump("{keyring} storing Name",&kp->public_key[0],64);
}
return 0; return 0;
} }
@ -1731,8 +1727,7 @@ int keyring_set_public_tag(keyring_identity *id, const char *name, const unsigne
if (keyring_pack_tag(kp->public_key, &kp->public_key_len, name, value, length)) if (keyring_pack_tag(kp->public_key, &kp->public_key_len, name, value, length))
return -1; return -1;
if (IF_DEBUG(keyring)) DEBUG_dump(keyring, "New tag", kp->public_key, kp->public_key_len);
dump("{keyring} New tag", kp->public_key, kp->public_key_len);
return 0; return 0;
} }

10
lang.h
View File

@ -24,10 +24,20 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "config.h" #include "config.h"
#endif #endif
/* Conveniences to assist readability.
*/
typedef char bool_t;
/* Useful macros not specific to Serval DNA that assist with using the C /* Useful macros not specific to Serval DNA that assist with using the C
* language. * language.
*/ */
// Concatenate two preprocessor symbols, eg, _APPEND(__FUNC__, __LINE__).
#define __APPEND_(X,Y) X ## Y
#define _APPEND(X,Y) __APPEND_(X,Y)
// Number of elements in an array (Warning: does not work if A is a pointer!). // Number of elements in an array (Warning: does not work if A is a pointer!).
#define NELS(A) (sizeof (A) / sizeof *(A)) #define NELS(A) (sizeof (A) / sizeof *(A))

756
log.c
View File

@ -1,7 +1,7 @@
/* /*
Serval DNA logging Serval DNA logging
Copyright (C) 2013-2015 Serval Project Inc. Copyright (C) 2013-2015 Serval Project Inc.
Copyright (C) 2016 Flinders University Copyright (C) 2016-2017 Flinders University
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License modify it under the terms of the GNU General Public License
@ -22,197 +22,77 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <ctype.h> #include <ctype.h>
#include <sys/time.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/param.h> #include <sys/param.h>
#include <time.h>
#include <unistd.h> #include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <dirent.h>
#include <assert.h> #include <assert.h>
#include "log.h"
#include "log_output.h"
#include "log_prolog.h"
#include "version_servald.h" #include "version_servald.h"
#include "instance.h" #include "instance.h"
#include "log.h"
#include "net.h" #include "net.h"
#include "os.h" #include "os.h"
#include "conf.h"
#include "str.h" #include "str.h"
#include "strbuf.h" #include "strbuf.h"
#include "strbuf_helpers.h" #include "strbuf_helpers.h"
#include "xprintf.h" #include "xprintf.h"
#include "trigger.h"
int logLevel_NoLogFileConfigured = LOG_LEVEL_INFO; /* 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
#define NO_FILE ((FILE *)1) * the output whose iterator they are currently servicing.
/* The _log_state structure records the persistent state associated with a single log output
* destination. The state is only persistent for the lifetime of the process (eg, while the daemon
* is running), and is not stored anywhere else. It is initially zerofilled.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/ */
struct _log_state { static __thread struct log_output_iterator *current_iterator = NULL;
/* Whether the software version has been logged in the current file yet. */
bool_t version_logged;
/* The time stamp of the last logged message, used to detect when the date advances so that
* the date can be logged. */
struct tm last_tm;
/* Whether the current configuration has been logged in the current file yet. */
bool_t config_logged;
};
/* The _log_iterator structure is a transient structure that is used to iterate over all the /* By default, if there is no log file configured, this is logged as an INFO message, but a server
* supported log output destinations. Generally, one of these is created (as an auto variable) * can set this to log it at a higher level.
* every time a log message is generated, and destroyed immediately after the message has been sent
* to all the log outputs.
*
* The log iterator is controlled using various _log_iterator_xxx() functions.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/ */
typedef struct _log_iterator { int serval_log_level_NoLogFileConfigured = LOG_LEVEL_INFO;
const struct config_log_format *config;
struct _log_state *state;
struct timeval tv;
struct tm tm;
XPRINTF xpf;
time_t file_start_time;
} _log_iterator;
/* Static variables for sending log output to a file. /* Primitive operations for log_output_iterator.
*
* The _log_file_strbuf is used to accumulate log messages before the log file is open and ready for
* writing.
*/
const char *_log_file_path;
char _log_file_path_buf[400];
static FILE *_log_file = NULL;
static void _open_log_file(_log_iterator *);
static void _rotate_log_file(_log_iterator *it);
static void _flush_log_file();
static struct _log_state state_file;
static struct config_log_format config_file;
static struct { size_t len; mode_t mode; } mkdir_trace[10];
static mode_t mkdir_trace_latest_mode;
static unsigned mkdir_count;
static time_t _log_file_start_time;
static char _log_file_buf[8192];
static struct strbuf _log_file_strbuf = STRUCT_STRBUF_EMPTY;
#ifdef ANDROID
/* Static variables for sending log output to the Android log.
*
* The _android_strbuf is used to accumulate a single log line before printing to Android's logging
* API.
*/
#include <android/log.h>
struct _log_state state_android;
static char _log_android_buf[1024];
static struct strbuf _android_strbuf;
#endif // ANDROID
/* Static variables for sending log output to standard error.
*/
static FILE *logfile_stderr = NULL;
struct _log_state state_stderr;
static void _open_log_stderr();
static void _flush_log_stderr();
/* Primitive operations for _log_iterator structures.
*/ */
static void _log_iterator_start(_log_iterator *it) static void log_iterator_start(struct log_output_iterator *it)
{ {
it->output = NULL;
memset(it, 0, sizeof *it); memset(it, 0, sizeof *it);
gettimeofday(&it->tv, NULL); gettimeofday(&it->tv, NULL);
localtime_r(&it->tv.tv_sec, &it->tm); localtime_r(&it->tv.tv_sec, &it->tm);
it->xpf = XPRINTF_NULL;
} }
static void _log_iterator_rewind(_log_iterator *it) static int log_iterator_advance(struct log_output_iterator *it)
{ {
it->config = NULL; assert(it->output != SECTION_END(logoutput));
it->state = NULL; 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);
} }
static void _log_iterator_advance_to_file(_log_iterator *it) /* 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:
cf_dfl_config_log_format(&config_file); * log.c:193: error: undefined reference to '__stop_logoutput'
cf_cpy_config_log_format(&config_file, &config.log.file); */
it->config = &config_file;
it->state = &state_file;
}
#ifdef ANDROID DEFINE_LOG_OUTPUT(NULL);
static void _log_iterator_advance_to_android(_log_iterator *it)
{
it->config = &config.log.android;
it->state = &state_android;
}
#endif // ANDROID
static void _log_iterator_advance_to_stderr(_log_iterator *it) /* Functions for formatting log messages.
{ */
it->config = &config.log.console;
it->state = &state_stderr;
}
static int _log_iterator_advance(_log_iterator *it) static void print_line_prefix(struct log_output_iterator *it)
{ {
if (it->config == NULL) { assert(it->output);
_log_iterator_advance_to_file(it); struct log_output *out = *it->output;
return 1; assert(out);
}
if (it->config == &config_file) {
#ifdef ANDROID
_log_iterator_advance_to_android(it);
return 1;
}
if (it->config == &config.log.android) {
#endif // ANDROID
_log_iterator_advance_to_stderr(it);
return 1;
}
return 0;
}
static int _log_enabled(_log_iterator *it) if (out->show_pid(out))
{
if (it->config == &config_file) {
_open_log_file(it); // puts initial INFO message(s) at head of log file
if (_log_file == NO_FILE)
return 0;
}
else if (it->config == &config.log.console) {
_open_log_stderr();
if (logfile_stderr == NULL || logfile_stderr == NO_FILE)
return 0;
}
return 1;
}
static void _log_prefix_level(_log_iterator *it, int level)
{
const char *levelstr = "UNKWN:";
switch (level) {
case LOG_LEVEL_FATAL: levelstr = "FATAL:"; break;
case LOG_LEVEL_ERROR: levelstr = "ERROR:"; break;
case LOG_LEVEL_WARN: levelstr = "WARN:"; break;
case LOG_LEVEL_HINT: levelstr = "HINT:"; break;
case LOG_LEVEL_INFO: levelstr = "INFO:"; break;
case LOG_LEVEL_DEBUG: levelstr = "DEBUG:"; break;
}
xprintf(it->xpf, "%-6.6s", levelstr);
}
static void _log_prefix_context(_log_iterator *it)
{
if (it->config->show_pid)
xprintf(it->xpf, "[%5u] ", getpid()); xprintf(it->xpf, "[%5u] ", getpid());
if (it->config->show_time) {
if (out->show_time(out)) {
if (it->tv.tv_sec == 0) { if (it->tv.tv_sec == 0) {
xputs("NOTIME______ ", it->xpf); xputs("NOTIME______ ", it->xpf);
} else { } else {
@ -223,6 +103,7 @@ static void _log_prefix_context(_log_iterator *it)
xprintf(it->xpf, "%s.%03u ", buf, (unsigned int)it->tv.tv_usec / 1000); xprintf(it->xpf, "%s.%03u ", buf, (unsigned int)it->tv.tv_usec / 1000);
} }
} }
if (strbuf_len(&log_context)) { if (strbuf_len(&log_context)) {
xputs("[", it->xpf); xputs("[", it->xpf);
xputs(strbuf_str(&log_context), it->xpf); xputs(strbuf_str(&log_context), it->xpf);
@ -230,354 +111,198 @@ static void _log_prefix_context(_log_iterator *it)
} }
} }
static void _log_prefix(_log_iterator *it, int level) static void whence_prefix(struct log_output_iterator *it, struct __sourceloc whence)
{
if (it->config == &config_file) {
if (strbuf_is_empty(&_log_file_strbuf))
strbuf_init(&_log_file_strbuf, _log_file_buf, sizeof _log_file_buf);
else if (strbuf_len(&_log_file_strbuf))
strbuf_putc(&_log_file_strbuf, '\n');
it->xpf = XPRINTF_STRBUF(&_log_file_strbuf);
_log_prefix_level(it, level);
}
#ifdef ANDROID
else if (it->config == &config.log.android) {
strbuf_init(&_android_strbuf, _log_android_buf, sizeof _log_android_buf);
it->xpf = XPRINTF_STRBUF(&_android_strbuf);
}
#endif // ANDROID
else if (it->config == &config.log.console) {
it->xpf = XPRINTF_STDIO(logfile_stderr);
_log_prefix_level(it, level);
}
else
abort();
_log_prefix_context(it);
}
static void _log_prefix_whence(_log_iterator *it, struct __sourceloc whence)
{ {
assert(!XPRINTF_IS_NULL(it->xpf));
if ((whence.file && whence.file[0]) || (whence.function && whence.function[0])) { if ((whence.file && whence.file[0]) || (whence.function && whence.function[0])) {
xprint_sourceloc(it->xpf, whence); xprint_sourceloc(it->xpf, whence);
xputs(" ", it->xpf); xputs(" ", it->xpf);
} }
} }
static void _log_end_line(_log_iterator *it, int UNUSED(level)) /* Log output operations.
*/
static void log_open(struct log_output_iterator *it)
{ {
#ifdef ANDROID assert(it->output);
if (it->config == &config.log.android) { assert(*it->output);
int alevel = ANDROID_LOG_UNKNOWN; if ((*it->output)->open)
switch (level) { (*it->output)->open(it);
case LOG_LEVEL_FATAL: alevel = ANDROID_LOG_FATAL; break;
case LOG_LEVEL_ERROR: alevel = ANDROID_LOG_ERROR; break;
case LOG_LEVEL_WARN: alevel = ANDROID_LOG_WARN; break;
case LOG_LEVEL_HINT:
case LOG_LEVEL_INFO: alevel = ANDROID_LOG_INFO; break;
case LOG_LEVEL_DEBUG: alevel = ANDROID_LOG_DEBUG; break;
default: abort();
}
__android_log_print(alevel, "servald", "%s", _log_android_buf);
}
else
#endif // ANDROID
if (it->config == &config.log.console) {
fputc('\n', logfile_stderr);
}
} }
static void _log_flush(_log_iterator *it) static bool_t is_log_available(struct log_output_iterator *it)
{ {
if (it->config == &config_file) { assert(it->output);
_flush_log_file(); assert(*it->output);
} return !(*it->output)->is_available || (*it->output)->is_available(it);
else if (it->config == &config.log.console) {
_flush_log_stderr();
}
} }
static void _log_vprintf_nl(_log_iterator *it, int level, const char *fmt, va_list ap) static void log_start_line(struct log_output_iterator *it, int level)
{
_log_prefix(it, level);
vxprintf(it->xpf, fmt, ap);
_log_end_line(it, level);
}
static void _log_printf_nl(_log_iterator *it, int level, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
_log_vprintf_nl(it, level, fmt, ap);
va_end(ap);
}
static void _log_current_datetime(_log_iterator *it, int level)
{
char buf[50];
if (strftime(buf, sizeof buf, "%F %T %z", &it->tm)) {
_log_printf_nl(it, level, "Local date/time: %s", buf);
it->state->last_tm = it->tm;
}
}
static void _log_software_version(_log_iterator *it, int level)
{
_log_printf_nl(it, level, "Serval DNA version: %s", version_servald);
it->state->version_logged = 1;
}
static int _log_current_config(_log_iterator *it, int level)
{
if (!cf_limbo) {
// if formatting config causes anything else to be logged, ignore it.
cf_limbo=1;
struct cf_om_node *root = NULL;
int ret = cf_fmt_config_main(&root, &config);
if (ret == CFERROR) {
_log_printf_nl(it, level, "Cannot dump current configuration: cf_fmt_config_main() returned CFERROR");
} else {
_log_printf_nl(it, level, "Current configuration:");
struct cf_om_iterator oit;
int empty = 1;
for (cf_om_iter_start(&oit, root); oit.node; cf_om_iter_next(&oit)) {
if (oit.node->text && oit.node->line_number) {
empty = 0;
_log_printf_nl(it, level, " %s=%s", oit.node->fullkey, oit.node->text);
}
}
if (empty)
_log_printf_nl(it, level, " (empty)");
}
cf_om_free_node(&root);
it->state->config_logged = 1;
cf_limbo=0;
}
return 1;
}
static void _log_update(_log_iterator *it)
{
if ( it->tm.tm_mday != it->state->last_tm.tm_mday
|| it->tm.tm_mon != it->state->last_tm.tm_mon
|| it->tm.tm_year != it->state->last_tm.tm_year
)
_log_current_datetime(it, LOG_LEVEL_INFO);
if (!it->state->version_logged)
_log_software_version(it, LOG_LEVEL_INFO);
if (it->config->dump_config && !it->state->config_logged)
_log_current_config(it, LOG_LEVEL_INFO);
}
static int _log_iterator_next(_log_iterator *it, int level)
{ {
assert(level >= LOG_LEVEL_SILENT); assert(level >= LOG_LEVEL_SILENT);
assert(level <= LOG_LEVEL_FATAL); assert(level <= LOG_LEVEL_FATAL);
_log_end_line(it, level); assert(XPRINTF_IS_NULL(it->xpf));
_log_flush(it); assert(it->output);
while (_log_iterator_advance(it)) { assert(*it->output);
if (level >= it->config->level && _log_enabled(it)) { assert((*it->output)->start_line);
_log_update(it); (*it->output)->start_line(it, level);
_log_prefix(it, level); assert(!XPRINTF_IS_NULL(it->xpf));
return 1; print_line_prefix(it);
}
}
return 0;
} }
static void _log_iterator_vprintf_nl(_log_iterator *it, int level, struct __sourceloc whence, const char *fmt, va_list ap) static void log_end_line(struct log_output_iterator *it, int level)
{ {
_log_iterator_rewind(it); assert(level >= LOG_LEVEL_SILENT);
while (_log_iterator_next(it, level)) { assert(level <= LOG_LEVEL_FATAL);
_log_prefix_whence(it, whence); assert(!XPRINTF_IS_NULL(it->xpf));
va_list ap1; assert(it->output);
va_copy(ap1, ap); assert(*it->output);
vxprintf(it->xpf, fmt, ap1); if ((*it->output)->end_line)
va_end(ap1); (*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:";
} }
} }
static void _logs_vprintf_nl(int level, struct __sourceloc whence, const char *fmt, va_list ap) void serval_log_output_iterator_vprintf_nl(struct log_output_iterator *it, int level, const char *fmt, va_list ap)
{ {
_log_iterator it; log_start_line(it, level);
_log_iterator_start(&it); vxprintf(it->xpf, fmt, ap);
_log_iterator_vprintf_nl(&it, level, whence, fmt, ap); log_end_line(it, level);
} }
static void _logs_printf_nl(int level, struct __sourceloc whence, const char *fmt, ...) void serval_log_output_iterator_printf_nl(struct log_output_iterator *it, int level, const char *fmt, ...)
{ {
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
_logs_vprintf_nl(level, whence, fmt, ap); serval_log_output_iterator_vprintf_nl(it, level, fmt, ap);
va_end(ap); va_end(ap);
} }
static void _compute_file_start_time(_log_iterator *it) static void print_datetime(struct log_output_iterator *it, int level)
{ {
if (it->file_start_time == 0) { char buf[50];
assert(!cf_limbo); if (strftime(buf, sizeof buf, "%F %T %z", &it->tm)) {
assert(it->tv.tv_sec != 0); serval_log_output_iterator_printf_nl(it, level, "Local date/time: %s", buf);
it->file_start_time = it->tv.tv_sec; (*it->output)->last_tm = it->tm;
if (config.log.file.duration)
it->file_start_time -= it->file_start_time % config.log.file.duration;
} }
} }
static void trace_mkdir(struct __sourceloc UNUSED(whence), const char *path, mode_t mode) static void print_newdate(struct log_output_iterator *it)
{ {
if (mkdir_count < NELS(mkdir_trace)) { struct log_output *out = *it->output;
mkdir_trace[mkdir_count].len = strlen(path); if ( it->tm.tm_mday != out->last_tm.tm_mday
mkdir_trace[mkdir_count].mode = mode; || it->tm.tm_mon != out->last_tm.tm_mon
} || it->tm.tm_year != out->last_tm.tm_year
++mkdir_count; )
mkdir_trace_latest_mode = mode; print_datetime(it, LOG_LEVEL_INFO);
} }
static void log_mkdir_trace(const char *dir) void serval_log_print_prolog(struct log_output_iterator *it)
{ {
unsigned i; assert(current_iterator == NULL || current_iterator == it);
for (i = 0; i < mkdir_count && i < NELS(mkdir_trace); ++i) struct log_output_iterator *save_current_iterator = current_iterator;
_logs_printf_nl(LOG_LEVEL_INFO, __NOWHERE__, "Created %s (mode %04o)", alloca_toprint(-1, dir, mkdir_trace[i].len), mkdir_trace[i].mode); current_iterator = it;
if (mkdir_count > NELS(mkdir_trace) + 1) print_datetime(it, LOG_LEVEL_INFO);
_logs_printf_nl(LOG_LEVEL_INFO, __NOWHERE__, "Created ..."); CALL_TRIGGER(log_prolog);
if (mkdir_count > NELS(mkdir_trace)) current_iterator = save_current_iterator;
_logs_printf_nl(LOG_LEVEL_INFO, __NOWHERE__, "Created %s (mode %04o)", alloca_str_toprint(dir), mkdir_trace_latest_mode);
} }
static void _open_log_file(_log_iterator *it) // 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(it->state == &state_file); assert(current_iterator);
if (_log_file != NO_FILE) { log_start_line(it, level);
if (_log_file_path == NULL) whence_prefix(it, whence);
_log_file_path = getenv("SERVALD_LOG_FILE"); va_list ap1;
if (_log_file_path == NULL && !cf_limbo) { va_copy(ap1, ap);
strbuf sbfile = strbuf_local_buf(_log_file_path_buf); vxprintf(it->xpf, fmt, ap1);
strbuf_serval_log_path(sbfile); va_end(ap1);
strbuf_path_join(sbfile, config.log.file.directory_path, "", NULL); // with trailing '/' log_end_line(it, level);
_compute_file_start_time(it); }
if (config.log.file.path[0]) {
strbuf_path_join(sbfile, config.log.file.path, NULL); /* Logging primitives. This is the "public" API of the logging system, as described in "log.h".
} else { */
struct tm tm;
(void)localtime_r(&it->file_start_time, &tm); void serval_vlogf(int level, struct __sourceloc whence, const char *fmt, va_list ap)
strbuf_append_strftime(sbfile, "serval-%Y%m%d%H%M%S.log", &tm); {
} if (level != LOG_LEVEL_SILENT) {
if (strbuf_overrun(sbfile)) { if (current_iterator) {
_log_file = NO_FILE; // This occurs if a log primitive is invoked recursively from within a log output, which is
_logs_printf_nl(LOG_LEVEL_ERROR, __HERE__, "Cannot form log file name - buffer overrun"); // typically an open() operation calling serval_log_print_prolog(it). In this case, the log
} else { // output only goes to the single output in question, not to all outputs.
_log_file_start_time = it->file_start_time; iterator_vprintf_nl(current_iterator, level, whence, fmt, ap);
_log_file_path = strbuf_str(sbfile);
}
} }
if (!_log_file) { else {
if (_log_file_path == NULL) { struct log_output_iterator it;
if (cf_limbo) log_iterator_start(&it);
return; while (log_iterator_advance(&it)) {
_log_file = NO_FILE; struct log_output *out = *it.output;
_logs_printf_nl(logLevel_NoLogFileConfigured, __NOWHERE__, "No log file configured"); if (level >= out->minimum_level(out)) {
} else { // If the open() operation recurses by invoking a log primitive directly, then print that
// Create the new log file. // message to all available outputs but avoid recursing into open() operations that are
size_t dirsiz = strlen(_log_file_path) + 1; // already being called.
char _dir[dirsiz]; if (!out->opening) {
strcpy(_dir, _log_file_path); out->opening = 1;
const char *dir = dirname(_dir); // modifies _dir[] log_open(&it);
mkdir_count = 0; out->opening = 0;
if (mkdirs_log(dir, 0700, trace_mkdir) == -1) {
_log_file = NO_FILE;
log_mkdir_trace(dir);
_logs_printf_nl(LOG_LEVEL_WARN, __HERE__, "Cannot mkdir %s - %s [errno=%d]", alloca_str_toprint(dir), strerror(errno), errno);
} else if ((_log_file = fopen(_log_file_path, "a")) == NULL) {
_log_file = NO_FILE;
log_mkdir_trace(dir);
_logs_printf_nl(LOG_LEVEL_WARN, __HERE__, "Cannot create-append %s - %s [errno=%d]", _log_file_path, strerror(errno), errno);
} else {
setlinebuf(_log_file);
memset(it->state, 0, sizeof *it->state);
// The first line in every log file must be the starting time stamp. (After that, it is up
// to _log_update() to insert other mandatory messages in any suitable order.)
_log_current_datetime(it, LOG_LEVEL_INFO);
log_mkdir_trace(dir);
_logs_printf_nl(LOG_LEVEL_INFO, __NOWHERE__, "Logging to %s (fd %d)", _log_file_path, fileno(_log_file));
// if stderr should be redirected
if (logfile_stderr == NO_FILE)
dup2(fileno(_log_file),STDERR_FILENO);
// Update the log symlink to point to the latest log file.
strbuf sbsymlink = strbuf_alloca(400);
strbuf_system_log_path(sbsymlink);
strbuf_path_join(sbsymlink, "serval.log", NULL);
if (strbuf_overrun(sbsymlink))
_logs_printf_nl(LOG_LEVEL_ERROR, __HERE__, "Cannot form log symlink name - buffer overrun");
else {
const char *f = _log_file_path;
const char *s = strbuf_str(sbsymlink);
const char *relpath = f;
for (; *f && *f == *s; ++f, ++s)
if (*f == '/')
relpath = f;
while (*relpath == '/')
++relpath;
while (*s == '/')
++s;
if (strchr(s, '/'))
relpath = _log_file_path;
// If racing with another process at this exact same point, then the symlink(2) call may
// fail with EEXIST, in which case log a warning, not an error.
unlink(strbuf_str(sbsymlink));
if (symlink(relpath, strbuf_str(sbsymlink)) == -1)
_logs_printf_nl(errno == EEXIST ? LOG_LEVEL_WARN : LOG_LEVEL_ERROR,
__HERE__,
"Cannot symlink %s -> %s - %s [errno=%d]",
strbuf_str(sbsymlink), relpath, strerror(errno), errno);
} }
// Expire old log files. if (is_log_available(&it)) {
size_t pathsiz = strlen(_log_file_path) + 1; current_iterator = &it;
char path[pathsiz]; print_newdate(&it);
while (1) { iterator_vprintf_nl(&it, level, whence, fmt, ap);
strcpy(path, _log_file_path); log_flush(&it);
const char *base = basename(path); // modifies path[] current_iterator = NULL;
DIR *d = opendir(dir);
if (!d) {
_logs_printf_nl(LOG_LEVEL_ERROR, __HERE__, "Cannot expire log files: opendir(%s) - %s [errno=%d]", dir, strerror(errno), errno);
break;
}
struct dirent oldest;
memset(&oldest, 0, sizeof oldest);
unsigned count = 0;
while (1) {
errno = 0;
struct dirent *ent = readdir(d);
if (ent == NULL) {
if (errno)
_logs_printf_nl(LOG_LEVEL_ERROR, __HERE__, "Cannot expire log files: readdir(%s) - %s [errno=%d]", dir, strerror(errno), errno);
break;
}
const char *e;
if ( str_startswith(ent->d_name, "serval-", &e)
&& isdigit(e[0]) && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3]) // YYYY
&& isdigit(e[4]) && isdigit(e[5]) // MM
&& isdigit(e[6]) && isdigit(e[7]) // DD
&& isdigit(e[8]) && isdigit(e[9]) // HH
&& isdigit(e[10]) && isdigit(e[11]) // MM
&& isdigit(e[12]) && isdigit(e[13]) // SS
&& strcmp(&e[14], ".log") == 0
) {
++count;
if ( strcmp(ent->d_name, base) != 0
&& (!oldest.d_name[0] || strcmp(ent->d_name, oldest.d_name) < 0)
)
oldest = *ent;
}
}
closedir(d);
if (count <= config.log.file.rotate || !oldest.d_name[0])
break;
strbuf b = strbuf_local(path, pathsiz);
strbuf_path_join(b, dir, oldest.d_name, NULL);
assert(!strbuf_overrun(b));
_logs_printf_nl(LOG_LEVEL_INFO, __NOWHERE__, "Unlink %s", path);
unlink(path);
} }
} }
} }
@ -585,101 +310,30 @@ static void _open_log_file(_log_iterator *it)
} }
} }
static void _rotate_log_file(_log_iterator *it) void serval_log_flush()
{ {
if (!cf_limbo && _log_file != NO_FILE && _log_file_path == _log_file_path_buf) { assert(!current_iterator);
if (!config.log.file.path[0] && config.log.file.duration) { struct log_output_iterator it;
_compute_file_start_time(it); log_iterator_start(&it);
if (it->file_start_time != _log_file_start_time) { while (log_iterator_advance(&it))
// Close the current log file, which will cause _open_log_file() to open the next one. log_flush(&it);
if (_log_file)
fclose(_log_file);
_log_file = NULL;
_log_file_path = NULL;
}
}
}
} }
static void _flush_log_file() void serval_log_close()
{ {
if (_log_file && _log_file != NO_FILE && strbuf_len(&_log_file_strbuf) != 0) { assert(!current_iterator);
fprintf(_log_file, "%s%s%s", struct log_output_iterator it;
strbuf_len(&_log_file_strbuf) ? strbuf_str(&_log_file_strbuf) : "", log_iterator_start(&it);
strbuf_len(&_log_file_strbuf) ? "\n" : "", while (log_iterator_advance(&it))
strbuf_overrun(&_log_file_strbuf) ? "LOG OVERRUN\n" : "" log_close(&it);
);
strbuf_reset(&_log_file_strbuf);
}
} }
/* Discard any unwritten log messages and close the log file immediately. This should be called in bool_t serval_log_capture_fd(int fd) {
* any child process immediately after fork() to prevent any buffered log messages from being assert(!current_iterator);
* written twice into the log file. struct log_output_iterator it;
* log_iterator_start(&it);
* @author Andrew Bettison <andrew@servalproject.com> bool_t captured = 0;
*/ while (log_iterator_advance(&it))
void close_log_file() log_capture_fd(&it, fd, &captured);
{ return captured;
strbuf_reset(&_log_file_strbuf);
if (_log_file && _log_file != NO_FILE)
fclose(_log_file);
_log_file = NULL;
}
static void _open_log_stderr()
{
if (!logfile_stderr) {
logfile_stderr = stderr;
setlinebuf(logfile_stderr);
}
}
static void _flush_log_stderr()
{
if (logfile_stderr && logfile_stderr != NO_FILE)
fflush(logfile_stderr);
}
void redirect_stderr_to_log()
{
if (logfile_stderr && logfile_stderr != NO_FILE) {
fflush(logfile_stderr);
logfile_stderr = NO_FILE;
}
}
void logFlush()
{
_log_iterator it;
_log_iterator_start(&it);
while (_log_iterator_advance(&it))
_log_flush(&it);
}
void vlogMessage(int level, struct __sourceloc whence, const char *fmt, va_list ap)
{
if (level != LOG_LEVEL_SILENT) {
_log_iterator it;
_log_iterator_start(&it);
_rotate_log_file(&it);
while (_log_iterator_next(&it, level)) {
_log_prefix_whence(&it, whence);
va_list ap1;
va_copy(ap1, ap);
vxprintf(it.xpf, fmt, ap1);
va_end(ap1);
}
}
}
static void logConfigChanged();
DEFINE_TRIGGER(conf_log, logConfigChanged);
static void logConfigChanged()
{
_log_iterator it;
_log_iterator_start(&it);
while (_log_iterator_advance(&it))
it.state->config_logged = 0;
logFlush();
} }

120
log.h
View File

@ -49,87 +49,111 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
const char *log_level_as_string(int level); const char *log_level_as_string(int level);
int string_to_log_level(const char *text); int string_to_log_level(const char *text);
// Log output control. // Logging primitives:
extern int logLevel_NoLogFileConfigured;
void close_log_file();
void redirect_stderr_to_log();
void logFlush();
// Logging context string. // Log a message via all available log outputs.
void serval_vlogf(int level, struct __sourceloc whence, const char *fmt, va_list ap);
// Flush all log outputs.
void serval_log_flush();
// Close all log outputs without flushing; they will be re-opened on the next
// vLogMessage(). This is mainly used in forked child processes, to ensure
// that the child process does not share open file descriptors with the parent
// process.
void serval_log_close();
// If possible, capture all output written to the given file descriptor in a
// persistent log format, such as a file or an operating system log. Any
// output that produces a persistent log and is able to redirect the file
// descriptor into that log will do so. If any output is already writing to
// the file descriptor (eg, the console output, which writes to fd 2) then that
// output will cease writing to that file descriptor. Returns true if the file
// descriptor has been redirected successfully.
bool_t serval_log_capture_fd(int fd);
// Logging context string -- if non-empty, then is prefixed to all log messages
// inside square brackets.
struct strbuf; struct strbuf;
extern struct strbuf log_context; extern struct strbuf log_context;
// Logging primitives. // The log level to use for the message that no log file output is configured.
void vlogMessage(int level, struct __sourceloc whence, const char *fmt, va_list); extern int serval_log_level_NoLogFileConfigured;
int logBacktrace(int level, struct __sourceloc whence);
__SERVAL_LOG_INLINE void logMessage(int level, struct __sourceloc whence, const char *fmt, ...) // The following logging utilities are implemented entirely in terms of the
// above primitives.
__SERVAL_LOG_INLINE void serval_logf(int level, struct __sourceloc whence, const char *fmt, ...)
{ {
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
vlogMessage(level, whence, fmt, ap); serval_vlogf(level, whence, fmt, ap);
va_end(ap); va_end(ap);
} }
__SERVAL_LOG_INLINE int logErrorAndReturnNegativeOne(struct __sourceloc whence, const char *fmt, ...) __SERVAL_LOG_INLINE int serval_logf_and_return(int retval, struct __sourceloc whence, const char *fmt, ...)
{ {
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
vlogMessage(LOG_LEVEL_ERROR, whence, fmt, ap); serval_vlogf(LOG_LEVEL_ERROR, whence, fmt, ap);
va_end(ap); va_end(ap);
return -1; return retval;
} }
// Useful logging primitive macros. int serval_log_backtrace(int level, struct __sourceloc whence);
#define LOGF(L,F,...) logMessage(L, __WHENCE__, F, ##__VA_ARGS__) void serval_log_argv(int level, struct __sourceloc whence, const char *label, int argc, const char *const *argv);
#define LOGF_perror(L,F,...) logMessage(L, __WHENCE__, F ": %s [errno=%d]", ##__VA_ARGS__, strerror(errno), errno) int serval_log_hexdump(int level, struct __sourceloc whence, char *name, const unsigned char *addr, size_t len);
#define LOG_perror(L,X) LOGF_perror(L, "%s", (X)) void serval_log_multiline(int level, struct __sourceloc whence, const char *str);
#define NOWHENCE(LOGSTMT) do { const struct __sourceloc __whence = __NOWHENCE__; LOGSTMT; } while (0) // Convenient logging macros.
#define BACKTRACE logBacktrace(LOG_LEVEL_FATAL, __WHENCE__) #define LOGF(L,F,...) serval_logf(L, __WHENCE__, F, ##__VA_ARGS__)
#define LOGF_perror(L,F,...) serval_logf(L, __WHENCE__, F ": %s [errno=%d]", ##__VA_ARGS__, strerror(errno), errno)
#define LOG_perror(L,X) LOGF_perror(L, "%s", (X))
#define FATALF(F,...) do { LOGF(LOG_LEVEL_FATAL, F, ##__VA_ARGS__); abort(); exit(-1); } while (1) #define NOWHENCE(LOGSTMT) do { const struct __sourceloc __whence = __NOWHENCE__; LOGSTMT; } while (0)
#define FATAL(X) FATALF("%s", (X))
#define FATALF_perror(F,...) FATALF(F ": %s [errno=%d]", ##__VA_ARGS__, strerror(errno), errno)
#define FATAL_perror(X) FATALF_perror("%s", (X))
#define WHYF(F,...) logErrorAndReturnNegativeOne(__WHENCE__, F, ##__VA_ARGS__) #define BACKTRACE serval_log_backtrace(LOG_LEVEL_FATAL, __WHENCE__)
#define WHY(X) WHYF("%s", (X))
#define WHYF_perror(F,...) WHYF(F ": %s [errno=%d]", ##__VA_ARGS__, strerror(errno), errno)
#define WHY_perror(X) WHYF_perror("%s", (X))
#define WHY_argv(X,ARGC,ARGV) logArgv(LOG_LEVEL_ERROR, __WHENCE__, (X), (ARGC), (ARGV))
#define WARNF(F,...) LOGF(LOG_LEVEL_WARN, F, ##__VA_ARGS__) #define FATALF(F,...) do { LOGF(LOG_LEVEL_FATAL, F, ##__VA_ARGS__); abort(); exit(-1); } while (1)
#define WARN(X) WARNF("%s", (X)) #define FATAL(X) FATALF("%s", (X))
#define WARNF_perror(F,...) LOGF_perror(LOG_LEVEL_WARN, F, ##__VA_ARGS__) #define FATALF_perror(F,...) FATALF(F ": %s [errno=%d]", ##__VA_ARGS__, strerror(errno), errno)
#define WARN_perror(X) WARNF_perror("%s", (X)) #define FATAL_perror(X) FATALF_perror("%s", (X))
#define HINTF(F,...) LOGF(LOG_LEVEL_HINT, F, ##__VA_ARGS__) #define WHYF(F,...) serval_logf_and_return(-1, __WHENCE__, F, ##__VA_ARGS__)
#define HINT(X) HINTF("%s", (X)) #define WHY(X) WHYF("%s", (X))
#define HINT_argv(X,ARGC,ARGV) logArgv(LOG_LEVEL_HINT, __WHENCE__, (X), (ARGC), (ARGV)) #define WHYF_perror(F,...) WHYF(F ": %s [errno=%d]", ##__VA_ARGS__, strerror(errno), errno)
#define WHY_perror(X) WHYF_perror("%s", (X))
#define WHY_argv(X,ARGC,ARGV) serval_log_argv(LOG_LEVEL_ERROR, __WHENCE__, (X), (ARGC), (ARGV))
#define WHY_dump(X,ADDR,LEN) serval_log_hexdump(LOG_LEVEL_ERROR, __WHENCE__, (X), (const unsigned char *)(ADDR), (size_t)(LEN))
#define INFOF(F,...) LOGF(LOG_LEVEL_INFO, F, ##__VA_ARGS__) #define WARNF(F,...) LOGF(LOG_LEVEL_WARN, F, ##__VA_ARGS__)
#define INFO(X) INFOF("%s", (X)) #define WARN(X) WARNF("%s", (X))
#define WARNF_perror(F,...) LOGF_perror(LOG_LEVEL_WARN, F, ##__VA_ARGS__)
#define WARN_perror(X) WARNF_perror("%s", (X))
#define WARN_dump(X,ADDR,LEN) serval_log_hexdump(LOG_LEVEL_WARN, __WHENCE__, (X), (const unsigned char *)(ADDR), (size_t)(LEN))
// log.h provides these macros for writing messages at DEBUG level, so applications can #define HINTF(F,...) LOGF(LOG_LEVEL_HINT, F, ##__VA_ARGS__)
// define their own DEBUG() and DEBUGF() macros; see "debug.h" as the prime example. #define HINT(X) HINTF("%s", (X))
#define HINT_argv(X,ARGC,ARGV) serval_log_argv(LOG_LEVEL_HINT, __WHENCE__, (X), (ARGC), (ARGV))
#define HINT_dump(X,ADDR,LEN) serval_log_hexdump(LOG_LEVEL_HINT, __WHENCE__, (X), (const unsigned char *)(ADDR), (size_t)(LEN))
#define INFOF(F,...) LOGF(LOG_LEVEL_INFO, F, ##__VA_ARGS__)
#define INFO(X) INFOF("%s", (X))
// These macros are useful for implementing other macros that conditionally log
// at DEBUG level; DEBUG() and DEBUGF() in "debug.h" are the prime example.
#define _DEBUGF(F,...) LOGF(LOG_LEVEL_DEBUG, F, ##__VA_ARGS__) #define _DEBUGF(F,...) LOGF(LOG_LEVEL_DEBUG, F, ##__VA_ARGS__)
#define _DEBUG(X) _DEBUGF("%s", (X)) #define _DEBUG(X) _DEBUGF("%s", (X))
#define _DEBUGF_perror(F,...) LOGF_perror(LOG_LEVEL_DEBUG, F, ##__VA_ARGS__) #define _DEBUGF_perror(F,...) LOGF_perror(LOG_LEVEL_DEBUG, F, ##__VA_ARGS__)
#define _DEBUG_perror(X) _DEBUGF_perror("%s", (X)) #define _DEBUG_perror(X) _DEBUGF_perror("%s", (X))
#define _DEBUG_argv(X,ARGC,ARGV) logArgv(LOG_LEVEL_DEBUG, __WHENCE__, (X), (ARGC), (ARGV)) #define _DEBUG_argv(X,ARGC,ARGV) serval_log_argv(LOG_LEVEL_DEBUG, __WHENCE__, (X), (ARGC), (ARGV))
#define _DEBUG_dump(X,ADDR,LEN) serval_log_hexdump(LOG_LEVEL_DEBUG, __WHENCE__, (X), (const unsigned char *)(ADDR), (size_t)(LEN))
#define _DEBUGF_TAG(TAG,F,...) _DEBUGF("{%s} " F, (TAG), ##__VA_ARGS__) #define _DEBUGF_TAG(TAG,F,...) _DEBUGF("{%s} " F, (TAG), ##__VA_ARGS__)
#define _DEBUGF_TAG_perror(TAG,F,...) _DEBUGF_perror("{%s} " F, (TAG), ##__VA_ARGS__) #define _DEBUGF_TAG_perror(TAG,F,...) _DEBUGF_perror("{%s} " F, (TAG), ##__VA_ARGS__)
#define _DEBUG_TAG_argv(TAG,X,ARGC,ARGV) _DEBUG_argv("{" TAG "} " X, (ARGC), (ARGV)) #define _DEBUG_TAG_argv(TAG,X,ARGC,ARGV) _DEBUG_argv("{" TAG "} " X, (ARGC), (ARGV))
#define _DEBUG_TAG_dump(TAG,X,ADDR,LEN) _DEBUG_dump("{" TAG "} " X, (ADDR), (LEN))
#define dump(X,A,N) logDump(LOG_LEVEL_DEBUG, __WHENCE__, (X), (const unsigned char *)(A), (size_t)(N))
// Utility functions, defined in terms of above primitives.
void logArgv(int level, struct __sourceloc whence, const char *label, int argc, const char *const *argv);
int logDump(int level, struct __sourceloc whence, char *name, const unsigned char *addr, size_t len);
void logString(int level, struct __sourceloc whence, const char *str);
#endif // __SERVAL_DNA__LOG_H #endif // __SERVAL_DNA__LOG_H

View File

@ -37,6 +37,6 @@ static int app_log(const struct cli_parsed *parsed, struct cli_context *UNUSED(c
int level = string_to_log_level(lvl); int level = string_to_log_level(lvl);
if (level == LOG_LEVEL_INVALID) if (level == LOG_LEVEL_INVALID)
return WHYF("invalid log level: %s", lvl); return WHYF("invalid log level: %s", lvl);
logMessage(level, __NOWHERE__, "%s", msg); NOWHENCE(LOGF(level, "%s", msg));
return 0; return 0;
} }

View File

@ -1,5 +1,5 @@
/* /*
Serval DNA logging Serval DNA logging inline functions
Copyright 2013-2014 Serval Project Inc. Copyright 2013-2014 Serval Project Inc.
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
@ -17,8 +17,8 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
// This compilation unit simply provides a statically linkable logMessage() function for those other // This compilation unit simply provides statically linkable functions, eg, serval_logf(), for those
// units that did not inline it. // other units that did not inline it.
#define __SERVAL_LOG_INLINE #define __SERVAL_LOG_INLINE
#include "log.h" #include "log.h"

144
log_output.h Normal file
View File

@ -0,0 +1,144 @@
/*
Serval DNA log output
Copyright (C) 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.
*/
#ifndef __SERVAL_DNA__LOG_OUTPUT_H
#define __SERVAL_DNA__LOG_OUTPUT_H
#include <sys/time.h> // for struct timeval
#include <time.h> // for struct tm
#include <stdarg.h> // for va_list
#include "lang.h" // for bool_t and _APPEND()
#include "section.h"
#include "xprintf.h"
struct log_output_iterator;
/* The log_output structure represents a single log output. The logging
* primitives defined in "log.h" iterate over these, so every log message is
* sent to all available outputs.
*
* Each log output is represented by an instance of this struct. The instance
* persists for the lifetime of the process (eg, while the daemon is running),
* and is not stored anywhere else.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
struct log_output {
// Configuration:
// the minimum level logged by this output:
int (*minimum_level)(const struct log_output *out);
// whether to include the Process ID in each output line:
bool_t (*show_pid)(const struct log_output *out);
// whether to include the time in each output line:
bool_t (*show_time)(const struct log_output *out);
// State:
// pointer to output-specific state:
void *state;
// the time stamp of the most recently logged message, used to detect when
// the date advances so that the date can be logged:
struct tm last_tm;
// a flag to prevent recursion into the open() operation
bool_t opening;
// Operations:
//
// Perform any setup necessary to commence outputting log messages, eg,
// set/update the config items above, create/rename/open files, allocate
// buffers, connect to server etc.; may invoke vlogMessage(), so must
// handle re-entry; open() gets called at the start of every call to the
// vlogMessage() primitive, before start_line() is called.
void (*open)(struct log_output_iterator *it);
// If *capture is 0 and the output is of a persistent nature (eg, a file or
// system log) and is able to redirect data written to the given file
// descriptor to its output, then do so and set *capture to 1.
void (*capture_fd)(struct log_output_iterator *it, int fd, bool_t *capture);
// Test whether output is able to handle messages; if it returns false then
// start_line() and end_line() will not be invoked.
bool_t (*is_available)(const struct log_output_iterator *it);
// Start outputting a new line at the given level, which may involve
// printing a prefix to indicate the level; return an XPRINTF which can be
// used to print the rest of the line.
void (*start_line)(struct log_output_iterator *it, int level);
// Finish outputting a line already started with start_line(), which may involve
// writing a suffix, but flushing should only be done by flush(), so that
// multi-line messages can be output with only a single flush at the end
void (*end_line)(struct log_output_iterator *it, int level);
// Ensure that any buffered log output has been output; if the process were to
// terminate after this, the last line logged would not be lost.
void (*flush)(struct log_output_iterator *it);
// Release any resources currently held by the logger without flushing, so
// that the next open() called in the child will re-acquire resources; this
// is called in a newly-forked child process to ensure that buffered log
// messages do not get output twice and that the child and parent do not
// fight over file descriptors or sockets etc.
void (*close)(struct log_output_iterator *it);
};
/* log_output_iterator is a transient structure that is used to iterate over all the
* log outputs. Generally, one of these is created (as an auto variable) every
* time a log message is generated, and destroyed immediately after the message
* has been sent to all the log outputs.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
struct log_output_iterator {
// State:
struct log_output *const *output;
// The time at which the log message is generated, in two formats:
struct timeval tv;
struct tm tm;
// The xprintf() handle used to construct the log message:
XPRINTF xpf;
};
/* Log outputters are included into an executable just by linking them in;
* "section.h" allows the logger to iterate through them all.
*
* There are several ways of linking an outputter into an executable: (1) the
* log_output_xxx.o object files can be explicitly listed on the link
* command-line, or (2) they can be put into a library and dragged into the
* executable by referencing them explicitly somehow, such as the "feature.h"
* mechanism.
*/
DECLARE_SECTION(struct log_output *, logoutput);
#define DEFINE_LOG_OUTPUT(LOG_OUTPUT) \
static struct log_output * _APPEND(__log_output, __LINE__) IN_SECTION(logoutput) = (LOG_OUTPUT)
#define LOG_OUTPUT_COUNT (SECTION_START(logoutput) - SECTION_END(logoutput))
/* Functions for use by log outputters. These form the "private" or "backend" API of
* the logging system.
*/
const char *serval_log_level_prefix_string(int level);
void serval_log_output_iterator_printf_nl(struct log_output_iterator *it, int level, const char *fmt, ...);
void serval_log_output_iterator_vprintf_nl(struct log_output_iterator *it, int level, const char *fmt, va_list ap);
void serval_log_print_prolog(struct log_output_iterator *it);
#endif // __SERVAL_DNA__LOG_OUTPUT_H

119
log_output_android.c Normal file
View File

@ -0,0 +1,119 @@
/*
Serval DNA logging output to Android log
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 <android/log.h>
#include "log_output.h"
#include "feature.h"
#include "conf.h"
/* To enable logging to the Android system log, include USE_FEATURE(log_output_android) somewhere in
* a source file that is always linked.
*/
DEFINE_FEATURE(log_output_android);
/* Private state for Android log output.
*/
struct log_output_android_state {
char buf[1024];
struct strbuf strbuf;
};
#define DISABLED ((FILE *)1)
static struct log_output_android_state static_state = {
.strbuf = STRUCT_STRBUF_EMPTY
};
static inline struct log_output_android_state *_state(struct log_output *out)
{
assert(out->state);
return (struct log_output_android_state *)(out->state);
}
/* Functions for querying configuration.
*/
static bool_t log_android_dump_config(const struct log_output *UNUSED(out))
{
return config.log.android.dump_config;
}
static int log_android_minimum_level(const struct log_output *UNUSED(out))
{
return config.log.android.level;
}
static bool_t log_android_show_pid(const struct log_output *UNUSED(out))
{
return config.log.android.show_pid;
}
static bool_t log_android_show_time(const struct log_output *UNUSED(out))
{
return config.log.android.show_time;
}
/* Log output operations.
*/
static void log_android_start_line(struct log_output_iterator *it, int level)
{
struct log_output *out = *it->output;
struct log_output_android_state *state = _state(out);
strbuf_init(&state->strbuf, state->buf, sizeof state->buf);
it->xpf = XPRINTF_STRBUF(&state->strbuf);
}
static void log_android_end_line(struct log_output_iterator *it, int level)
{
int alevel = ANDROID_LOG_UNKNOWN;
switch (level) {
case LOG_LEVEL_FATAL: alevel = ANDROID_LOG_FATAL; break;
case LOG_LEVEL_ERROR: alevel = ANDROID_LOG_ERROR; break;
case LOG_LEVEL_WARN: alevel = ANDROID_LOG_WARN; break;
case LOG_LEVEL_HINT:
case LOG_LEVEL_INFO: alevel = ANDROID_LOG_INFO; break;
case LOG_LEVEL_DEBUG: alevel = ANDROID_LOG_DEBUG; break;
default: abort();
}
__android_log_print(alevel, "servald", "%s", _state(*it->output)->buf);
}
static struct log_output static_log_output = {
.dump_config = log_android_dump_config,
.minimum_level = log_android_minimum_level,
.show_pid = log_android_show_pid,
.show_time = log_android_show_time,
.state = &static_state,
.open = NULL,
.capture_fd = NULL,
.is_available = NULL,
.start_line = log_android_start_line,
.end_line = log_android_end_line,
.flush = NULL,
.close = NULL
};
DEFINE_LOG_OUTPUT(&static_log_output);

148
log_output_console.c Normal file
View File

@ -0,0 +1,148 @@
/*
Serval DNA logging output to console (stderr)
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 <stdio.h> // for fopen(), fileno()
#include <assert.h>
#include "log_output.h"
#include "feature.h"
#include "conf.h"
/* To enable logging to console (standard error), include USE_FEATURE(log_output_console) somewhere
* in a source file that is always linked.
*/
DEFINE_FEATURE(log_output_console);
/* Private state for console log output.
*/
struct log_output_console_state {
FILE *fp;
};
#define DISABLED ((FILE *)1)
static struct log_output_console_state static_state = {
.fp = NULL,
};
static inline struct log_output_console_state *_state(struct log_output *out)
{
assert(out->state);
return (struct log_output_console_state *)(out->state);
}
/* Functions for querying configuration.
*/
static int log_console_minimum_level(const struct log_output *UNUSED(out))
{
return config.log.console.level;
}
static bool_t log_console_show_pid(const struct log_output *UNUSED(out))
{
return config.log.console.show_pid;
}
static bool_t log_console_show_time(const struct log_output *UNUSED(out))
{
return config.log.console.show_time;
}
/* The open() operation. This gets called once before each line is logged.
*/
static void open_log_console(struct log_output_iterator *it)
{
struct log_output_console_state *state = _state(*it->output);
if (!state->fp && state->fp != DISABLED) {
state->fp = stderr;
setlinebuf(state->fp);
serval_log_print_prolog(it);
}
}
static void capture_fd_log_console(struct log_output_iterator *it, int fd, bool_t *UNUSED(capture))
{
struct log_output_console_state *state = _state(*it->output);
if (state->fp && state->fp != DISABLED && fileno(state->fp) == fd) {
fflush(state->fp);
state->fp = DISABLED;
}
}
static bool_t is_log_console_available(const struct log_output_iterator *it)
{
return _state(*it->output)->fp != DISABLED;
}
static void log_console_start_line(struct log_output_iterator *it, int level)
{
struct log_output_console_state *state = _state(*it->output);
if (state->fp && state->fp != DISABLED) {
it->xpf = XPRINTF_STDIO(state->fp);
xputs(serval_log_level_prefix_string(level), it->xpf);
}
}
static void log_console_end_line(struct log_output_iterator *it, int UNUSED(level))
{
struct log_output_console_state *state = _state(*it->output);
if (state->fp && state->fp != DISABLED)
fputc('\n', state->fp);
}
void flush_log_console(struct log_output_iterator *it)
{
struct log_output_console_state *state = _state(*it->output);
if (state->fp && state->fp != DISABLED)
fflush(state->fp);
}
void close_log_console(struct log_output_iterator *it)
{
struct log_output_console_state *state = _state(*it->output);
if (state->fp && state->fp != DISABLED) {
// If stderr were ever made buffered, then to avoid duplicates of buffered log messages being
// flushed from a child process, the child must close file descriptor 2 before calling
// logClose(), and re-open it again afterwards.
fclose(state->fp);
state->fp = NULL;
}
}
static struct log_output static_log_output = {
.minimum_level = log_console_minimum_level,
.show_pid = log_console_show_pid,
.show_time = log_console_show_time,
.state = &static_state,
.open = open_log_console,
.capture_fd = capture_fd_log_console,
.is_available = is_log_console_available,
.start_line = log_console_start_line,
.end_line = log_console_end_line,
.flush = flush_log_console,
.close = close_log_console
};
DEFINE_LOG_OUTPUT(&static_log_output);

387
log_output_file.c Normal file
View File

@ -0,0 +1,387 @@
/*
Serval DNA logging output to files
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> // for dirname()
#include <time.h> // for time_t and struct tm
#include <fcntl.h> // for open(), O_RDWR
#include <stdio.h> // for fopen(), fileno()
#include <ctype.h> // for isdigit()
#include <dirent.h> // for readdir() etc.
#include <string.h> // for strcpy()
#include <assert.h>
//#include <unistd.h> // for dup2()
#include "log_output.h"
#include "feature.h"
#include "conf.h"
#include "instance.h"
#include "strbuf.h"
#include "strbuf_helpers.h"
/* To enable logging to files, include USE_FEATURE(log_output_file) somewhere in a source file.
*/
DEFINE_FEATURE(log_output_file);
/* Private state for file log output.
*/
struct log_output_file_state {
// The full pathname of the currently open log file:
const char *path;
char path_buf[400];
// The currently open log file:
// - NULL means open() has not been called yet, but will buffer messages until the file is opened,
// so is ready to accept messages
// - OPEN_FAILED means a prior open() failed, so is not ready to accept messages
// - otherwise, open() has created/opened a file, and is ready to accept messages
FILE *fp;
// The time that the currently open log file was started, used for file rotation logic:
time_t start_time;
// Buffer to hold log messages before the log file is open and ready for writing:
char buf[8192];
struct strbuf strbuf;
// File descriptor to redirect to the open log file.
int capture_fd;
};
#define OPEN_FAILED ((FILE *)1)
static struct log_output_file_state static_state = {
.path = NULL,
.fp = NULL,
.start_time = 0,
.strbuf = STRUCT_STRBUF_EMPTY,
.capture_fd = -1
};
static inline struct log_output_file_state *_state(struct log_output *out)
{
assert(out->state);
return (struct log_output_file_state *)(out->state);
}
/* Functions for querying configuration.
*/
static int log_file_minimum_level(const struct log_output *UNUSED(out))
{
return config.log.file.level;
}
static bool_t log_file_show_pid(const struct log_output *UNUSED(out))
{
return config.log.file.show_pid;
}
static bool_t log_file_show_time(const struct log_output *UNUSED(out))
{
return config.log.file.show_time;
}
/* Functions for tracing and then logging the actions of multi-directory mkdir().
*/
struct mkdir_trace {
struct {
size_t len;
mode_t mode;
} created[10];
size_t created_count;
mode_t latest_mode;
};
static void trace_mkdir(struct __sourceloc UNUSED(whence), const char *path, mode_t mode, void *context)
{
struct mkdir_trace *trace = context;
trace->latest_mode = mode;
if (trace->created_count < NELS(trace->created)) {
trace->created[trace->created_count].len = strlen(path);
trace->created[trace->created_count].mode = mode;
}
++trace->created_count;
}
static void log_mkdir_trace(const char *dir, struct mkdir_trace *trace)
{
unsigned i;
for (i = 0; i < trace->created_count && i < NELS(trace->created); ++i)
NOWHENCE(INFOF("Created %s (mode %04o)", alloca_toprint(-1, dir, trace->created[i].len), trace->created[i].mode));
if (trace->created_count > NELS(trace->created) + 1)
NOWHENCE(INFO("Created ..."));
if (trace->created_count > NELS(trace->created))
NOWHENCE(INFOF("Created %s (mode %04o)", alloca_str_toprint(dir), trace->latest_mode));
}
/* The open() operation. This gets called once before each line is logged.
*
* This method writes some initial INFO message(s) at head of the log, which causes it to call
* serval_vlogf(), but the logging system will not recurse back into this function, instead will
* just write the log messages by calling log_file_start_line() and log_file_end_line() in this and
* in other outputters.
*/
static void open_log_file(struct log_output_iterator *it)
{
struct log_output *out = *it->output;
struct log_output_file_state *state = _state(out);
// Once an attempt to open has failed, don't try any more.
if (state->fp == OPEN_FAILED)
return;
time_t start_time = 0;
// Use the path given by the environment variable if set, mainly to support test scripts.
// Otherwise work out the log file path from the configuration, if available.
const char *env_path = getenv("SERVALD_LOG_FILE");
if (env_path)
state->path = env_path;
else if (!cf_limbo) {
// Rotate the log file name if the configuration does not specify a fixed name.
if (!config.log.file.path[0]) {
assert(it->tv.tv_sec != 0);
start_time = it->tv.tv_sec;
if (config.log.file.duration) {
// Compute the desired start time of the log file.
start_time -= start_time % config.log.file.duration;
// If the desired start time has advanced from the current open file's start time, then
// close the current log file, which will cause the logic below to open the next one.
if (state->path == state->path_buf && start_time != state->start_time) {
if (state->fp)
fclose(state->fp);
state->fp = NULL;
state->path = NULL;
}
}
}
// (Re-)compute the file path.
if (state->path == NULL) {
strbuf sbfile = strbuf_local_buf(state->path_buf);
strbuf_serval_log_path(sbfile);
strbuf_path_join(sbfile, config.log.file.directory_path, "", NULL); // ensure trailing '/'
if (config.log.file.path[0]) {
strbuf_path_join(sbfile, config.log.file.path, NULL);
} else {
assert(start_time != 0);
struct tm tm;
(void)localtime_r(&start_time, &tm);
strbuf_append_strftime(sbfile, "serval-%Y%m%d%H%M%S.log", &tm);
}
if (strbuf_overrun(sbfile)) {
state->fp = OPEN_FAILED;
WHY("Cannot form log file name - buffer overrun");
} else {
state->path = state->path_buf;
state->start_time = start_time;
}
}
}
// Create the log file and append to it.
if (state->fp == NULL) {
if (state->path == NULL) {
if (!cf_limbo) {
state->fp = OPEN_FAILED;
NOWHENCE(LOGF(serval_log_level_NoLogFileConfigured, "No log file configured"));
}
} else {
// Create the new log file.
size_t dirsiz = strlen(state->path) + 1;
char _dir[dirsiz];
struct mkdir_trace _trace;
bzero(&_trace, sizeof _trace);
strcpy(_dir, state->path);
const char *dir = dirname(_dir); // modifies _dir[]
if (mkdirs_log(dir, 0700, trace_mkdir, &_trace) == -1) {
state->fp = OPEN_FAILED;
log_mkdir_trace(dir, &_trace);
WARNF("Cannot mkdir %s - %s [errno=%d]", alloca_str_toprint(dir), strerror(errno), errno);
} else if ((state->fp = fopen(state->path, "a")) == NULL) {
state->fp = OPEN_FAILED;
log_mkdir_trace(dir, &_trace);
WARNF("Cannot create-append %s - %s [errno=%d]", state->path, strerror(errno), errno);
} else {
setlinebuf(state->fp);
serval_log_print_prolog(it);
log_mkdir_trace(dir, &_trace);
NOWHENCE(INFOF("Logging to %s (fd %d)", state->path, fileno(state->fp)));
// If the output has been instructed to redirect a given file descriptor to its log file,
// then do so now.
if (state->capture_fd != -1)
dup2(fileno(state->fp), state->capture_fd);
// Update the log symlink to point to the latest log file.
strbuf sbsymlink = strbuf_alloca(400);
strbuf_system_log_path(sbsymlink);
strbuf_path_join(sbsymlink, "serval.log", NULL);
if (strbuf_overrun(sbsymlink))
WHY("Cannot form log symlink name - buffer overrun");
else {
const char *f = state->path;
const char *s = strbuf_str(sbsymlink);
const char *relpath = f;
for (; *f && *f == *s; ++f, ++s)
if (*f == '/')
relpath = f;
while (*relpath == '/')
++relpath;
while (*s == '/')
++s;
if (strchr(s, '/'))
relpath = state->path;
// If racing with another process at this exact same point, then the symlink(2) call may
// fail with EEXIST, in which case log a warning, not an error.
unlink(strbuf_str(sbsymlink));
if (symlink(relpath, strbuf_str(sbsymlink)) == -1)
LOGF(errno == EEXIST ? LOG_LEVEL_WARN : LOG_LEVEL_ERROR,
"Cannot symlink %s -> %s - %s [errno=%d]",
strbuf_str(sbsymlink), relpath, strerror(errno), errno);
else
NOWHENCE(INFOF("Created symlink %s -> %s", strbuf_str(sbsymlink), relpath));
}
// Expire old log files.
size_t pathsiz = strlen(state->path) + 1;
char path[pathsiz];
while (1) {
strcpy(path, state->path);
const char *base = basename(path); // modifies path[]
DIR *d = opendir(dir);
if (!d) {
WHYF("Cannot expire log files: opendir(%s) - %s [errno=%d]", dir, strerror(errno), errno);
break;
}
struct dirent oldest;
memset(&oldest, 0, sizeof oldest);
unsigned count = 0;
while (1) {
errno = 0;
struct dirent *ent = readdir(d);
if (ent == NULL) {
if (errno)
WHYF("Cannot expire log files: readdir(%s) - %s [errno=%d]", dir, strerror(errno), errno);
break;
}
const char *e;
if ( str_startswith(ent->d_name, "serval-", &e)
&& isdigit(e[0]) && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3]) // YYYY
&& isdigit(e[4]) && isdigit(e[5]) // MM
&& isdigit(e[6]) && isdigit(e[7]) // DD
&& isdigit(e[8]) && isdigit(e[9]) // HH
&& isdigit(e[10]) && isdigit(e[11]) // MM
&& isdigit(e[12]) && isdigit(e[13]) // SS
&& strcmp(&e[14], ".log") == 0
) {
++count;
if ( strcmp(ent->d_name, base) != 0
&& (!oldest.d_name[0] || strcmp(ent->d_name, oldest.d_name) < 0)
)
oldest = *ent;
}
}
closedir(d);
if (count <= config.log.file.rotate || !oldest.d_name[0])
break;
strbuf b = strbuf_local(path, pathsiz);
strbuf_path_join(b, dir, oldest.d_name, NULL);
assert(!strbuf_overrun(b));
NOWHENCE(INFOF("Delete %s", path));
unlink(path);
}
}
}
}
}
static void capture_fd_log_file(struct log_output_iterator *it, int fd, bool_t *capture)
{
if (!*capture) {
// This outputter does not connect the file descriptor to an output file until the next log
// message is output.
_state(*it->output)->capture_fd = fd;
// Ensure that the file descriptor is occupied, so that no other open() call can occupy it in
// the meantime.
int devnull;
if ((devnull = open("/dev/null", O_RDWR, 0)) == -1)
WHY_perror("open(\"/dev/null\")");
else {
if (devnull != fd && dup2(devnull, fd) == -1)
WHYF_perror("dup2(%d, %d)", devnull, fd);
else
*capture = 1;
if (devnull != fd)
close(devnull);
}
}
}
static bool_t is_log_file_available(const struct log_output_iterator *it)
{
return _state(*it->output)->fp != OPEN_FAILED;
}
static void log_file_start_line(struct log_output_iterator *it, int level)
{
struct log_output_file_state *state = _state(*it->output);
strbuf sb = &state->strbuf;
if (strbuf_is_empty(sb))
strbuf_init(sb, state->buf, sizeof state->buf);
else if (strbuf_len(sb))
strbuf_putc(sb, '\n');
it->xpf = XPRINTF_STRBUF(sb);
xputs(serval_log_level_prefix_string(level), it->xpf);
}
static void flush_log_file(struct log_output_iterator *it)
{
struct log_output_file_state *state = _state(*it->output);
FILE *fp = state->fp;
strbuf sb = &state->strbuf;
if (fp && fp != OPEN_FAILED && strbuf_len(sb) != 0) {
fprintf(fp, "%s\n%s", strbuf_str(sb), strbuf_overrun(sb) ? "LOG OVERRUN\n" : "");
strbuf_reset(sb);
}
}
void close_log_file(struct log_output_iterator *it)
{
struct log_output_file_state *state = _state(*it->output);
FILE *fp = state->fp;
strbuf_reset(&state->strbuf);
if (fp && fp != OPEN_FAILED)
fclose(fp);
state->fp = NULL; // next open() will try again
}
static struct log_output static_log_output = {
.minimum_level = log_file_minimum_level,
.show_pid = log_file_show_pid,
.show_time = log_file_show_time,
.state = &static_state,
.open = open_log_file,
.capture_fd = capture_fd_log_file,
.is_available = is_log_file_available,
.start_line = log_file_start_line,
.end_line = NULL,
.flush = flush_log_file,
.close = close_log_file
};
DEFINE_LOG_OUTPUT(&static_log_output);

42
log_prolog.h Normal file
View File

@ -0,0 +1,42 @@
/*
Serval DNA log prolog
Copyright (C) 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.
*/
#ifndef __SERVAL_DNA__LOG_PROLOG_H
#define __SERVAL_DNA__LOG_PROLOG_H
#include "trigger.h"
/* Whenever a log output starts a new log, such as opening the console for the
* first time or rotating to a new log file, or even at a regular interval, it
* emits a "prolog" that starts with the current date/time, then a series of
* log messages generated by invoking all the log_prolog triggers.
*
* The log_prolog trigger functions call the standard logging API to produce
* their output, but the logging system uses an internal trick to ensure that
* the their messages only go to the log output that invoked them, so for
* example, when a log file rotates, the prolog only gets written into the new
* file, and not into the console log as well.
*/
DECLARE_TRIGGER(log_prolog);
struct log_output_iterator;
void serval_log_print_prolog(struct log_output_iterator *it);
#endif // __SERVAL_DNA__LOG_PROLOG_H

View File

@ -29,31 +29,25 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "str.h" #include "str.h"
#include "net.h" #include "net.h"
int logDump(int level, struct __sourceloc whence, char *name, const unsigned char *addr, size_t len) int serval_log_hexdump(int level, struct __sourceloc whence, char *name, const unsigned char *addr, size_t len)
{ {
if (level != LOG_LEVEL_SILENT) { if (level != LOG_LEVEL_SILENT) {
char buf[100];
size_t i;
if (name) if (name)
logMessage(level, whence, "Dump of %s", name); serval_logf(level, whence, "Dump of %s", name);
for(i = 0; i < len; i += 16) { size_t off = 0;
while (off < len) {
char buf[100];
strbuf b = strbuf_local_buf(buf); strbuf b = strbuf_local_buf(buf);
strbuf_sprintf(b, " %04zx :", i); size_t skip = xhexdump_line(XPRINTF_STRBUF(b), addr, len, off);
int j; addr += skip;
for (j = 0; j < 16 && i + j < len; j++) off += skip;
strbuf_sprintf(b, " %02x", addr[i + j]); serval_logf(level, whence, " %s", strbuf_str(b));
for (; j < 16; j++)
strbuf_puts(b, " ");
strbuf_puts(b, " ");
for (j = 0; j < 16 && i + j < len; j++)
strbuf_sprintf(b, "%c", addr[i+j] >= ' ' && addr[i+j] < 0x7f ? addr[i+j] : '.');
logMessage(level, whence, "%s", strbuf_str(b));
} }
} }
return 0; return 0;
} }
void logArgv(int level, struct __sourceloc whence, const char *label, int argc, const char *const *argv) void serval_log_argv(int level, struct __sourceloc whence, const char *label, int argc, const char *const *argv)
{ {
if (level != LOG_LEVEL_SILENT) { if (level != LOG_LEVEL_SILENT) {
struct strbuf b; struct strbuf b;
@ -63,25 +57,25 @@ void logArgv(int level, struct __sourceloc whence, const char *label, int argc,
strbuf_init(&b, alloca(len + 1), len + 1); strbuf_init(&b, alloca(len + 1), len + 1);
strbuf_append_argv(&b, argc, argv); strbuf_append_argv(&b, argc, argv);
if (label) if (label)
logMessage(level, whence, "%s %s", label, strbuf_str(&b)); serval_logf(level, whence, "%s %s", label, strbuf_str(&b));
else else
logMessage(level, whence, "%s", strbuf_str(&b)); serval_logf(level, whence, "%s", strbuf_str(&b));
} }
} }
void logString(int level, struct __sourceloc whence, const char *str) void serval_log_multiline(int level, struct __sourceloc whence, const char *str)
{ {
if (level != LOG_LEVEL_SILENT) { if (level != LOG_LEVEL_SILENT) {
const char *s = str; const char *s = str;
const char *p; const char *p;
for (p = str; *p; ++p) { for (p = str; *p; ++p) {
if (*p == '\n') { if (*p == '\n') {
logMessage(level, whence, "%.*s", (int)(p - s), s); serval_logf(level, whence, "%.*s", (int)(p - s), s);
s = p + 1; s = p + 1;
} }
} }
if (p > s) if (p > s)
logMessage(level, whence, "%.*s", (int)(p - s), s); serval_logf(level, whence, "%.*s", (int)(p - s), s);
} }
} }
@ -113,7 +107,7 @@ int string_to_log_level(const char *text)
return LOG_LEVEL_INVALID; return LOG_LEVEL_INVALID;
} }
int logBacktrace(int level, struct __sourceloc whence) int serval_log_backtrace(int level, struct __sourceloc whence)
{ {
#ifndef NO_BACKTRACE #ifndef NO_BACKTRACE
char execpath[MAXPATHLEN]; char execpath[MAXPATHLEN];
@ -167,7 +161,7 @@ int logBacktrace(int level, struct __sourceloc whence)
} }
// parent // parent
close(stdout_fds[1]); close(stdout_fds[1]);
logMessage(level, whence, "GDB BACKTRACE"); serval_logf(level, whence, "GDB BACKTRACE");
char buf[1024]; char buf[1024];
char *const bufe = buf + sizeof buf; char *const bufe = buf + sizeof buf;
char *linep = buf; char *linep = buf;
@ -179,14 +173,14 @@ int logBacktrace(int level, struct __sourceloc whence)
for (; p < readp; ++p) for (; p < readp; ++p)
if (*p == '\n' || *p == '\0') { if (*p == '\n' || *p == '\0') {
*p = '\0'; *p = '\0';
logMessage(level, __NOWHERE__, "GDB %s", linep); serval_logf(level, __NOWHERE__, "GDB %s", linep);
linep = p + 1; linep = p + 1;
} }
if (readp >= bufe && linep == buf) { if (readp >= bufe && linep == buf) {
// Line does not fit into buffer. // Line does not fit into buffer.
char t = bufe[-1]; char t = bufe[-1];
bufe[-1] = '\0'; bufe[-1] = '\0';
logMessage(level, __NOWHERE__, "GDB %s", buf); serval_logf(level, __NOWHERE__, "GDB %s", buf);
buf[0] = t; buf[0] = t;
readp = buf + 1; readp = buf + 1;
} else if (readp + 120 >= bufe && linep != buf) { } else if (readp + 120 >= bufe && linep != buf) {
@ -202,7 +196,7 @@ int logBacktrace(int level, struct __sourceloc whence)
WHY_perror("read"); WHY_perror("read");
if (readp > linep) { if (readp > linep) {
*readp = '\0'; *readp = '\0';
logMessage(level, __NOWHERE__, "GDB %s", linep); serval_logf(level, __NOWHERE__, "GDB %s", linep);
} }
close(stdout_fds[0]); close(stdout_fds[0]);
int status = 0; int status = 0;
@ -210,7 +204,7 @@ int logBacktrace(int level, struct __sourceloc whence)
WHY_perror("waitpid"); WHY_perror("waitpid");
strbuf b = strbuf_local_buf(buf); strbuf b = strbuf_local_buf(buf);
strbuf_append_exit_status(b, status); strbuf_append_exit_status(b, status);
logMessage(level, __NOWHERE__, "gdb %s", buf); serval_logf(level, __NOWHERE__, "gdb %s", buf);
unlink(tempfile); unlink(tempfile);
#endif #endif
return 0; return 0;

View File

@ -35,6 +35,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "cli.h" #include "cli.h"
#include "monitor-client.h" #include "monitor-client.h"
#include "commandline.h" #include "commandline.h"
#include "xprintf.h"
DEFINE_FEATURE(cli_monitor); DEFINE_FEATURE(cli_monitor);
@ -47,7 +48,7 @@ static int remote_print(char *cmd, int argc, char **argv, unsigned char *data, i
} }
printf("\n"); printf("\n");
if (dataLen){ if (dataLen){
dump(NULL,data,dataLen); xhexdump(XPRINTF_STDIO(stdout), data, dataLen, "");
} }
return 1; return 1;
} }

View File

@ -107,8 +107,7 @@ int monitor_client_writeline(int fd,char *fmt, ...)
n=vsnprintf(msg, sizeof(msg), fmt, ap); n=vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap); va_end(ap);
if (IF_DEBUG(monitor)) DEBUG_dump(monitor, "Writing to monitor", msg, n);
dump("{monitor} Writing to monitor", msg, n);
return write(fd,msg,n); return write(fd,msg,n);
} }
@ -131,8 +130,7 @@ int monitor_client_writeline_and_data(int fd,unsigned char *data,int bytes,char
bcopy(data,out+n,bytes); bcopy(data,out+n,bytes);
n+=bytes; n+=bytes;
if (IF_DEBUG(monitor)) DEBUG_dump(monitor, "Writing to monitor", out, n);
dump("{monitor} Writing to monitor", out, n);
return write(fd,out,n); return write(fd,out,n);
} }
@ -157,8 +155,7 @@ int monitor_client_read(int fd, struct monitor_state *res, struct monitor_comman
return -1; return -1;
} }
if (IF_DEBUG(monitor)) DEBUG_dump(monitor, "Read from monitor", res->buffer + oldOffset, bytesRead);
dump("{monitor} Read from monitor", res->buffer + oldOffset, bytesRead);
res->bufferBytes+=bytesRead; res->bufferBytes+=bytesRead;

26
os.c
View File

@ -38,26 +38,26 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <mach-o/dyld.h> #include <mach-o/dyld.h>
#endif #endif
void log_info_mkdir(struct __sourceloc __whence, const char *path, mode_t mode) void log_info_mkdir(struct __sourceloc __whence, const char *path, mode_t mode, void *UNUSED(context))
{ {
INFOF("mkdir %s (mode %04o)", alloca_str_toprint(path), mode); INFOF("mkdir %s (mode %04o)", alloca_str_toprint(path), mode);
} }
int _mkdirs(struct __sourceloc __whence, const char *path, mode_t mode, MKDIR_LOG_FUNC *logger) int _mkdirs(struct __sourceloc __whence, const char *path, mode_t mode, MKDIR_LOG_FUNC *logger, void *log_context)
{ {
return _mkdirsn(__whence, path, strlen(path), mode, logger); return _mkdirsn(__whence, path, strlen(path), mode, logger, log_context);
} }
int _emkdirs(struct __sourceloc __whence, const char *path, mode_t mode, MKDIR_LOG_FUNC *logger) int _emkdirs(struct __sourceloc __whence, const char *path, mode_t mode, MKDIR_LOG_FUNC *logger, void *log_context)
{ {
if (_mkdirs(__whence, path, mode, logger) == -1) if (_mkdirs(__whence, path, mode, logger, log_context) == -1)
return WHYF_perror("mkdirs(%s,%o)", alloca_str_toprint(path), mode); return WHYF_perror("mkdirs(%s,%o)", alloca_str_toprint(path), mode);
return 0; return 0;
} }
int _emkdirsn(struct __sourceloc __whence, const char *path, size_t len, mode_t mode, MKDIR_LOG_FUNC *logger) int _emkdirsn(struct __sourceloc __whence, const char *path, size_t len, mode_t mode, MKDIR_LOG_FUNC *logger, void *log_context)
{ {
if (_mkdirsn(__whence, path, len, mode, logger) == -1) if (_mkdirsn(__whence, path, len, mode, logger, log_context) == -1)
return WHYF_perror("mkdirsn(%s,%lu,%o)", alloca_toprint(-1, path, len), (unsigned long)len, mode); return WHYF_perror("mkdirsn(%s,%lu,%o)", alloca_toprint(-1, path, len), (unsigned long)len, mode);
return 0; return 0;
} }
@ -72,7 +72,7 @@ int _emkdirsn(struct __sourceloc __whence, const char *path, size_t len, mode_t
* *
* @author Andrew Bettison <andrew@servalproject.com> * @author Andrew Bettison <andrew@servalproject.com>
*/ */
int _mkdirsn(struct __sourceloc whence, const char *path, size_t len, mode_t mode, MKDIR_LOG_FUNC *logger) int _mkdirsn(struct __sourceloc whence, const char *path, size_t len, mode_t mode, MKDIR_LOG_FUNC *logger, void *log_context)
{ {
if (len == 0) if (len == 0)
errno = EINVAL; errno = EINVAL;
@ -81,7 +81,7 @@ int _mkdirsn(struct __sourceloc whence, const char *path, size_t len, mode_t mod
strncpy(pathfrag, path, len)[len] = '\0'; strncpy(pathfrag, path, len)[len] = '\0';
if (mkdir(pathfrag, mode) != -1) { if (mkdir(pathfrag, mode) != -1) {
if (logger) if (logger)
logger(whence, pathfrag, mode); logger(whence, pathfrag, mode, log_context);
return 0; return 0;
} }
if (errno == EEXIST) { if (errno == EEXIST) {
@ -98,17 +98,15 @@ int _mkdirsn(struct __sourceloc whence, const char *path, size_t len, mode_t mod
while (lastsep != path && *--lastsep == '/') while (lastsep != path && *--lastsep == '/')
; ;
if (lastsep != path) { if (lastsep != path) {
if (_mkdirsn(whence, path, lastsep - path + 1, mode, logger) == -1) if (_mkdirsn(whence, path, lastsep - path + 1, mode, logger, log_context) == -1)
return -1; return -1;
if (mkdir(pathfrag, mode) == -1) { if (mkdir(pathfrag, mode) == -1) {
if (errno==EEXIST) if (errno == EEXIST)
return 0; return 0;
return -1; return -1;
} }
if (logger) if (logger)
logger(whence, pathfrag, mode); logger(whence, pathfrag, mode, log_context);
return 0; return 0;
} }
} }

34
os.h
View File

@ -108,27 +108,27 @@ __SERVAL_DNA__OS_INLINE off64_t lseek64(int fd, off64_t offset, int whence) {
/* The "e" variants log the error before returning -1. /* The "e" variants log the error before returning -1.
*/ */
typedef void MKDIR_LOG_FUNC(struct __sourceloc, const char *, mode_t); typedef void MKDIR_LOG_FUNC(struct __sourceloc, const char *, mode_t, void *);
MKDIR_LOG_FUNC log_info_mkdir; MKDIR_LOG_FUNC log_info_mkdir;
int _mkdirs(struct __sourceloc, const char *path, mode_t mode, MKDIR_LOG_FUNC *); int _mkdirs(struct __sourceloc, const char *path, mode_t mode, MKDIR_LOG_FUNC *, void *);
int _mkdirsn(struct __sourceloc, const char *path, size_t len, mode_t mode, MKDIR_LOG_FUNC *); int _mkdirsn(struct __sourceloc, const char *path, size_t len, mode_t mode, MKDIR_LOG_FUNC *, void *);
int _emkdirs(struct __sourceloc, const char *path, mode_t mode, MKDIR_LOG_FUNC *); int _emkdirs(struct __sourceloc, const char *path, mode_t mode, MKDIR_LOG_FUNC *, void *);
int _emkdirsn(struct __sourceloc, const char *path, size_t len, mode_t mode, MKDIR_LOG_FUNC *); int _emkdirsn(struct __sourceloc, const char *path, size_t len, mode_t mode, MKDIR_LOG_FUNC *, void *);
#define mkdirs_log(path, mode, func) _mkdirs(__WHENCE__, (path), (mode), (func)) #define mkdirs_log(path, mode, func, ctx) _mkdirs(__WHENCE__, (path), (mode), (func), (ctx))
#define mkdirsn_log(path, len, mode, func) _mkdirsn(__WHENCE__, (path), (len), (mode), (func)) #define mkdirsn_log(path, len, mode, func, ctx) _mkdirsn(__WHENCE__, (path), (len), (mode), (func), (ctx))
#define emkdirs_log(path, mode, func) _emkdirs(__WHENCE__, (path), (mode), (func)) #define emkdirs_log(path, mode, func, ctx) _emkdirs(__WHENCE__, (path), (mode), (func), (ctx))
#define emkdirsn_log(path, len, mode, func) _emkdirsn(__WHENCE__, (path), (len), (mode), (func)) #define emkdirsn_log(path, len, mode, func, ctx) _emkdirsn(__WHENCE__, (path), (len), (mode), (func), (ctx))
#define mkdirs(path, mode) mkdirs_log((path), (mode), NULL) #define mkdirs(path, mode) mkdirs_log((path), (mode), NULL, NULL)
#define mkdirsn(path, len, mode) mkdirsn_log((path), (len), (mode), NULL) #define mkdirsn(path, len, mode) mkdirsn_log((path), (len), (mode), NULL, NULL)
#define emkdirs(path, mode) emkdirs_log((path), (mode), NULL) #define emkdirs(path, mode) emkdirs_log((path), (mode), NULL, NULL)
#define emkdirsn(path, len, mode) emkdirsn_log((path), (len), (mode), NULL) #define emkdirsn(path, len, mode) emkdirsn_log((path), (len), (mode), NULL, NULL)
#define mkdirs_info(path, mode) mkdirs_log((path), (mode), log_info_mkdir) #define mkdirs_info(path, mode) mkdirs_log((path), (mode), log_info_mkdir, NULL)
#define mkdirsn_info(path, len, mode) mkdirsn_log((path), (len), (mode), log_info_mkdir) #define mkdirsn_info(path, len, mode) mkdirsn_log((path), (len), (mode), log_info_mkdir, NULL)
#define emkdirs_info(path, mode) emkdirs_log((path), (mode), log_info_mkdir) #define emkdirs_info(path, mode) emkdirs_log((path), (mode), log_info_mkdir, NULL)
#define emkdirsn_info(path, len, mode) emkdirsn_log((path), (len), (mode), log_info_mkdir) #define emkdirsn_info(path, len, mode) emkdirsn_log((path), (len), (mode), log_info_mkdir, NULL)
/* Read the symbolic link into the supplied buffer and add a terminating nul. /* Read the symbolic link into the supplied buffer and add a terminating nul.
* Logs an ERROR and returns -1 if the buffer is too short to hold the link * Logs an ERROR and returns -1 if the buffer is too short to hold the link

View File

@ -239,8 +239,7 @@ void _ob_append_bytes(struct __sourceloc __whence, struct overlay_buffer *b, con
} else { } else {
DEBUGF(overlaybuffer, "ob_append_bytes(b=%p, bytes=%p, count=%zu) OVERRUN position=%zu return NULL", b, bytes, count, b->position + count); DEBUGF(overlaybuffer, "ob_append_bytes(b=%p, bytes=%p, count=%zu) OVERRUN position=%zu return NULL", b, bytes, count, b->position + count);
} }
if (IF_DEBUG(overlaybuffer)) DEBUG_dump(overlaybuffer, "ob_append_bytes", bytes, count);
dump("{overlaybuffer} ob_append_bytes", bytes, count);
b->position += count; b->position += count;
} }
@ -655,9 +654,9 @@ int ob_dump(struct overlay_buffer *b, char *desc)
if (b->bytes) { if (b->bytes) {
if (b->sizeLimit != SIZE_MAX && b->sizeLimit > 0) { if (b->sizeLimit != SIZE_MAX && b->sizeLimit > 0) {
assert(b->position <= b->sizeLimit); assert(b->position <= b->sizeLimit);
dump(desc, b->bytes, b->sizeLimit); _DEBUG_dump(desc, b->bytes, b->sizeLimit);
} else if (b->position > 0) } else if (b->position > 0)
dump(desc, b->bytes, b->position); _DEBUG_dump(desc, b->bytes, b->position);
} }
return 0; return 0;
} }

View File

@ -1531,7 +1531,7 @@ void logServalPacket(int level, struct __sourceloc __whence, const char *message
else if (mb.buffer == NULL) else if (mb.buffer == NULL)
WHYF("serval_packetvisualise() output buffer missing, message=%s packet=%p len=%lu", alloca_toprint(-1, message, strlen(message)), packet, (long unsigned int)len); WHYF("serval_packetvisualise() output buffer missing, message=%s packet=%p len=%lu", alloca_toprint(-1, message, strlen(message)), packet, (long unsigned int)len);
else else
logString(level, __whence, mb.buffer); serval_log_multiline(level, __whence, mb.buffer);
if (mb.buffer) if (mb.buffer)
free(mb.buffer); free(mb.buffer);
} }

View File

@ -878,8 +878,8 @@ int _overlay_send_frame(struct __sourceloc whence, struct internal_mdp_header *h
return -1; return -1;
} }
DEBUGF(mdprequests, "Send frame %zu bytes", ob_position(plaintext)); DEBUGF(mdprequests, "Send frame %zu bytes", ob_position(plaintext));
if (IF_DEBUG(mdprequests) && IF_DEBUG(verbose)) if (IF_DEBUG(verbose))
dump("Frame plaintext", ob_ptr(plaintext), ob_position(plaintext)); DEBUG_dump(mdprequests, "Frame plaintext", ob_ptr(plaintext), ob_position(plaintext));
/* Work out the disposition of the frame-> For now we are only worried /* Work out the disposition of the frame-> For now we are only worried
about the crypto matters, and not compression that may be applied about the crypto matters, and not compression that may be applied

View File

@ -420,11 +420,9 @@ int packetOkOverlay(struct overlay_interface *interface,unsigned char *packet, s
payload_len = ob_get_ui16(b); payload_len = ob_get_ui16(b);
if (payload_len > ob_remaining(b)){ if (payload_len > ob_remaining(b)){
unsigned char *current = ob_ptr(b)+ob_position(b); unsigned char *current = ob_ptr(b)+ob_position(b);
if (IF_DEBUG(overlayframes)) DEBUG_dump(overlayframes, "Payload Header", header_start, current - header_start);
dump("Payload Header", header_start, current - header_start); ret = WHYF("Payload length %zd suggests frame should be %zd bytes, but was only %zd",
ret = WHYF("Payload length %zd suggests frame should be %zd bytes, but was only %zd",
payload_len, ob_position(b)+payload_len, len); payload_len, ob_position(b)+payload_len, len);
// TODO signal reduced MTU? // TODO signal reduced MTU?
goto end; goto end;
} }

View File

@ -514,8 +514,8 @@ int radio_link_decode(struct overlay_interface *interface, uint8_t c)
// Since we know we've synced with the remote party, // Since we know we've synced with the remote party,
// and there's nothing we can do about any earlier data // and there's nothing we can do about any earlier data
// throw away everything before the end of this packet // throw away everything before the end of this packet
if (state->payload_start && IF_DEBUG(radio_link)) if (state->payload_start)
dump("{radio_link} Skipped", state->payload, state->payload_start); DEBUG_dump(radio_link, "Skipped", state->payload, state->payload_start);
// If the packet is truncated by less than 16 bytes, RS protection should be enough to recover the packet, // If the packet is truncated by less than 16 bytes, RS protection should be enough to recover the packet,
// but we may need to examine the last few bytes to find the start of the next packet. // but we may need to examine the last few bytes to find the start of the next packet.

View File

@ -99,7 +99,7 @@ static int sqlite_trace_callback(unsigned UNUSED(mask), void *UNUSED(context), v
break; break;
} }
if (rendered_sql) { if (rendered_sql) {
logMessage(LOG_LEVEL_DEBUG, sqlite_trace_whence ? *sqlite_trace_whence : __HERE__, "%s", rendered_sql); serval_logf(LOG_LEVEL_DEBUG, sqlite_trace_whence ? *sqlite_trace_whence : __HERE__, "%s", rendered_sql);
++sqlite_trace_done; ++sqlite_trace_done;
} }
if (expanded_sql) if (expanded_sql)

View File

@ -469,8 +469,7 @@ static int rhizome_import_received_bundle(struct rhizome_manifest *m)
return 0; return 0;
DEBUGF(rhizome_rx, "manifest len=%zu has %u signatories. Associated filesize=%"PRIu64" bytes", DEBUGF(rhizome_rx, "manifest len=%zu has %u signatories. Associated filesize=%"PRIu64" bytes",
m->manifest_all_bytes, m->sig_count, m->filesize); m->manifest_all_bytes, m->sig_count, m->filesize);
if (IF_DEBUG(rhizome_rx)) DEBUG_dump(rhizome_rx, "manifest", m->manifestdata, m->manifest_all_bytes);
dump("manifest", m->manifestdata, m->manifest_all_bytes);
enum rhizome_bundle_status status = rhizome_add_manifest_to_store(m, NULL); enum rhizome_bundle_status status = rhizome_add_manifest_to_store(m, NULL);
switch (status) { switch (status) {
case RHIZOME_BUNDLE_STATUS_NEW: case RHIZOME_BUNDLE_STATUS_NEW:

View File

@ -67,13 +67,6 @@ enum status_codes{
//CODE_CRYPTO_ERROR = 12, //CODE_CRYPTO_ERROR = 12,
}; };
/* Conveniences to assist readability
*/
typedef char bool_t;
/* Serval ID (aka Subscriber ID) /* Serval ID (aka Subscriber ID)
*/ */

View File

@ -229,7 +229,7 @@ int server_bind()
serverMode = SERVER_RUNNING; serverMode = SERVER_RUNNING;
// Warn, not merely Info, if there is no configured log file. // Warn, not merely Info, if there is no configured log file.
logLevel_NoLogFileConfigured = LOG_LEVEL_WARN; serval_log_level_NoLogFileConfigured = LOG_LEVEL_WARN;
/* Catch SIGHUP etc so that we can respond to requests to do things, eg, shut down. */ /* Catch SIGHUP etc so that we can respond to requests to do things, eg, shut down. */
struct sigaction sig; struct sigaction sig;
@ -551,7 +551,7 @@ void server_watchdog(struct sched_ent *alarm)
switch (watchdog_pid = fork()) { switch (watchdog_pid = fork()) {
case 0: case 0:
/* Grandchild, should exec() watchdog. */ /* Grandchild, should exec() watchdog. */
close_log_file(); serval_log_close();
signal(SIGTERM, SIG_DFL); signal(SIGTERM, SIG_DFL);
close(0); close(0);
close(1); close(1);
@ -882,7 +882,7 @@ static int app_server_start(const struct cli_parsed *parsed, struct cli_context
on an Android device, then we don't receive a SIGHUP when the adb shell process ends. on an Android device, then we don't receive a SIGHUP when the adb shell process ends.
*/ */
DEBUG(verbose, "Grand-Child Process, reopening log"); DEBUG(verbose, "Grand-Child Process, reopening log");
close_log_file(); serval_log_close();
int fd; int fd;
if ((fd = open("/dev/null", O_RDWR, 0)) == -1) if ((fd = open("/dev/null", O_RDWR, 0)) == -1)
exit(WHY_perror("open(\"/dev/null\")")); exit(WHY_perror("open(\"/dev/null\")"));
@ -894,11 +894,15 @@ static int app_server_start(const struct cli_parsed *parsed, struct cli_context
exit(WHYF_perror("dup2(%d,stdin)", fd)); exit(WHYF_perror("dup2(%d,stdin)", fd));
if (dup2(fd, STDOUT_FILENO) == -1) if (dup2(fd, STDOUT_FILENO) == -1)
exit(WHYF_perror("dup2(%d,stdout)", fd)); exit(WHYF_perror("dup2(%d,stdout)", fd));
if (dup2(fd, STDERR_FILENO) == -1) /* Redirect standard error to the current log file, so that any diagnostic messages
* printed directly to stderr by libraries or child processes will end up being captured
* in a log file. If standard error is not redirected, then simply direct it to
* /dev/null.
*/
if (!serval_log_capture_fd(STDERR_FILENO) && dup2(fd, STDERR_FILENO) == -1)
exit(WHYF_perror("dup2(%d,stderr)", fd)); exit(WHYF_perror("dup2(%d,stderr)", fd));
if (fd > 2) if (fd > STDERR_FILENO)
(void)close(fd); (void)close(fd);
redirect_stderr_to_log();
/* The execpath option is provided so that a JNI call to "start" can be made which /* The execpath option is provided so that a JNI call to "start" can be made which
creates a new server daemon process with the correct argv[0]. Otherwise, the servald creates a new server daemon process with the correct argv[0]. Otherwise, the servald
process appears as a process with argv[0] = "org.servalproject". */ process appears as a process with argv[0] = "org.servalproject". */

View File

@ -16,7 +16,8 @@ SERVAL_CLIENT_SOURCES = \
fdqueue.c \ fdqueue.c \
instance.c \ instance.c \
limit.c \ limit.c \
logMessage.c \ log.c \
log_inline.c \
log_cli.c \ log_cli.c \
log_context.c \ log_context.c \
log_util.c \ log_util.c \
@ -59,7 +60,8 @@ SERVAL_DAEMON_SOURCES = \
keyring.c \ keyring.c \
keyring_cli.c \ keyring_cli.c \
keyring_restful.c \ keyring_restful.c \
log.c \ log_output_console.c \
log_output_file.c \
lsif.c \ lsif.c \
radio_link.c \ radio_link.c \
meshms.c \ meshms.c \
@ -146,7 +148,8 @@ CLIENT_ONLY_SOURCES = \
log_stderr.c log_stderr.c
ANDROID_SOURCES = \ ANDROID_SOURCES = \
android.c android.c \
log_output_android.c
ALL_SOURCES = \ ALL_SOURCES = \
$(SERVAL_CLIENT_SOURCES) \ $(SERVAL_CLIENT_SOURCES) \

View File

@ -138,8 +138,8 @@ static int app_crypt_test(const struct cli_parsed *parsed, struct cli_context *c
if (memcmp(&sign1_sk[32], sign1_pk, crypto_sign_PUBLICKEYBYTES)) { if (memcmp(&sign1_sk[32], sign1_pk, crypto_sign_PUBLICKEYBYTES)) {
WHY("Could not calculate public key from private key.\n"); WHY("Could not calculate public key from private key.\n");
dump("calculated",&pk,sizeof(pk)); WHY_dump("calculated",&pk,sizeof(pk));
dump("original",&sign1_pk,sizeof(sign1_pk)); WHY_dump("original",&sign1_pk,sizeof(sign1_pk));
} else } else
cli_printf(context, "Public key is contained in private key.\n"); cli_printf(context, "Public key is contained in private key.\n");
@ -162,12 +162,12 @@ static int app_crypt_test(const struct cli_parsed *parsed, struct cli_context *c
bzero(plainText,1024); bzero(plainText,1024);
snprintf((char *)&plainText[0],sizeof plainText,"%s","No casaba melons allowed in the lab."); snprintf((char *)&plainText[0],sizeof plainText,"%s","No casaba melons allowed in the lab.");
int plainLenIn=64; int plainLenIn=64;
dump("plaintext", plainText, 64); WHY_dump("plaintext", plainText, 64);
if (crypto_sign_detached(sig, NULL, plainText, plainLenIn, key)) if (crypto_sign_detached(sig, NULL, plainText, plainLenIn, key))
return WHY("crypto_sign_detached() failed.\n"); return WHY("crypto_sign_detached() failed.\n");
dump("signature", sig, sizeof sig); WHY_dump("signature", sig, sizeof sig);
unsigned char casabamelons[crypto_sign_BYTES]={ unsigned char casabamelons[crypto_sign_BYTES]={
0xa4,0xea,0xd0,0x7f,0x11,0x65,0x28,0x3f, 0xa4,0xea,0xd0,0x7f,0x11,0x65,0x28,0x3f,
@ -182,7 +182,7 @@ static int app_crypt_test(const struct cli_parsed *parsed, struct cli_context *c
if (memcmp(casabamelons, sig, 64)) { if (memcmp(casabamelons, sig, 64)) {
WHY("Computed signature for stored key+message does not match expected value.\n"); WHY("Computed signature for stored key+message does not match expected value.\n");
dump("expected signature",casabamelons,sizeof(casabamelons)); WHY_dump("expected signature",casabamelons,sizeof(casabamelons));
} }
if (crypto_sign_verify_detached(sig, plainText, plainLenIn, &key[32])) if (crypto_sign_verify_detached(sig, plainText, plainLenIn, &key[32]))

View File

@ -268,7 +268,7 @@ test_KeyringNoAutoCreate() {
wait_until has_sent_mdp_tick_to "dummy1/$instance_name" wait_until has_sent_mdp_tick_to "dummy1/$instance_name"
executeOk_servald keyring list executeOk_servald keyring list
assert_keyring_list 0 assert_keyring_list 0
executeOk_servald id self executeOk_servald --stderr id self
assertStdoutLineCount == 3 assertStdoutLineCount == 3
} }
finally_KeyringNoAutoCreate() { finally_KeyringNoAutoCreate() {

View File

@ -131,31 +131,37 @@ test_LogFileStderrFile() {
} }
doc_LogFileRotation="Log file rotation and deletion" doc_LogFileRotation="Log file rotation and deletion"
setup_LogFileRotation() {
setup
executeOk_servald config set log.console.level debug \
set log.console.show_pid true \
set log.file.level warn
}
test_LogFileRotation() { test_LogFileRotation() {
executeOk_servald config \ executeOk_servald config \
set log.file.directory_path "$PWD" \ set log.file.directory_path "$PWD" \
set log.file.rotate 3 \ set log.file.rotate 3 \
set log.file.duration 2s set log.file.duration 2s
assert --message="no log files yet" [ $(ls *.log | wc -l) -eq 0 ] assert --stderr --message="no log files yet" [ $(ls *.log 2>/dev/null | wc -l) -eq 0 ]
executeOk_servald log info one executeOk_servald log warn one
assert --message="one log file" [ $(ls *.log | wc -l) -eq 1 ] assert --stderr --message="one log file" [ $(ls *.log | wc -l) -eq 1 ]
log1=*.log log1=$(ls *.log | tail -n 1)
sleep 2.1 sleep 2.1
executeOk_servald log info two executeOk_servald log warn two
assert --message="two log files" [ $(ls *.log | wc -l) -eq 2 ] assert --stderr --message="two log files" [ $(ls *.log | wc -l) -eq 2 ]
log2=$(ls *.log | tail -n 1) log2=$(ls *.log | tail -n 1)
assert --message="ascending log file name" [ "$log2" != "$log1" ] assert --stderr --message="ascending log file name" [ "$log2" != "$log1" ]
sleep 2.1 sleep 2.1
executeOk_servald log info three executeOk_servald log warn three
assert --message="three log files" [ $(ls *.log | wc -l) -eq 3 ] assert --stderr --message="three log files" [ $(ls *.log | wc -l) -eq 3 ]
log3=$(ls *.log | tail -n 1) log3=$(ls *.log | tail -n 1)
assert --message="ascending log file name" [ "$log3" != "$log1" -a "$log3" != "$log2" ] assert --stderr --message="ascending log file name" [ "$log3" != "$log1" -a "$log3" != "$log2" ]
sleep 2.1 sleep 2.1
executeOk_servald log info four executeOk_servald log warn four
assert --message="three log files" [ $(ls *.log | wc -l) -eq 3 ] assert --stderr --message="three log files" [ $(ls *.log | wc -l) -eq 3 ]
assert --message="first log file gone" ! [ -e $log1 ] assert --stderr --message="first log file gone" ! [ -e $log1 ]
log4=$(ls *.log | tail -n 1) log4=$(ls *.log | tail -n 1)
assert --message="ascending log file name" [ "$log4" != "$log2" -a "$log4" != "$log3" -a "$log4" != "$log1" ] assert --stderr --message="ascending log file name" [ "$log4" != "$log2" -a "$log4" != "$log3" -a "$log4" != "$log1" ]
} }
doc_LogFileDirectoryAbsolute="Absolute log file directory path" doc_LogFileDirectoryAbsolute="Absolute log file directory path"

View File

@ -1,6 +1,7 @@
/* /*
Serval DNA version and copyright strings Serval DNA version and copyright strings
Copyright (C) 2013 Serval Project Inc. Copyright (C) 2013 Serval Project Inc.
Copyright (C) 2017 Flinders University
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License modify it under the terms of the GNU General Public License
@ -18,6 +19,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
#include "version_servald.h" #include "version_servald.h"
#include "log_prolog.h"
#include "debug.h"
#ifndef SERVALD_VERSION #ifndef SERVALD_VERSION
#error "SERVALD_VERSION is not defined" #error "SERVALD_VERSION is not defined"
@ -29,3 +32,13 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
const char version_servald[] = SERVALD_VERSION; const char version_servald[] = SERVALD_VERSION;
const char copyright_servald[] = SERVALD_COPYRIGHT; const char copyright_servald[] = SERVALD_COPYRIGHT;
/* Print the version in the prolog of every log file.
*/
static void log_version()
{
NOWHENCE(INFOF("Serval DNA version: %s", version_servald));
}
DEFINE_TRIGGER(log_prolog, log_version);

View File

@ -69,6 +69,7 @@
#include "strbuf.h" #include "strbuf.h"
#include "strbuf_helpers.h" #include "strbuf_helpers.h"
#include "commandline.h" #include "commandline.h"
#include "xprintf.h"
static int console_dial(const struct cli_parsed *parsed, struct cli_context *context); static int console_dial(const struct cli_parsed *parsed, struct cli_context *context);
static int console_answer(const struct cli_parsed *parsed, struct cli_context *context); static int console_answer(const struct cli_parsed *parsed, struct cli_context *context);
@ -256,7 +257,7 @@ static int remote_print(char *cmd, int argc, char **argv, unsigned char *data, i
} }
printf("\n"); printf("\n");
if (dataLen){ if (dataLen){
dump(NULL,data,dataLen); xhexdump(XPRINTF_STDIO(stdout), data, dataLen, "");
} }
fflush(stdout); fflush(stdout);
return 1; return 1;

View File

@ -26,12 +26,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
/* /*
* Every log message identifies the location in the source code at which the * Every log message identifies the location in the source code at which the
* message was produced. This location is represented by a struct __sourceloc, * message was produced. This location is represented by a struct __sourceloc,
* which is passed by value to the logMessage() function and its ilk. * which is passed by value to the serval_logf() function and its ilk.
* *
* A struct __sourceloc value is generated by the __HERE__ macro, which uses * A struct __sourceloc value is generated by the __HERE__ macro, which uses
* the cpp(1) built-in macros __FILE__, __LINE__ and __FUNCTION__ to generate * the cpp(1) built-in macros __FILE__, __LINE__ and __FUNCTION__ to generate
* its elements. The __NOWHERE__ macro creates a struct __sourceloc with NULL * its elements. The __NOWHERE__ macro creates a struct __sourceloc with NULL
* and zero fields. If you pass __NOWHERE__ to logMessage(), it will omit * and zero fields. If you pass __NOWHERE__ to serval_logf(), it will omit
* location information from the log line. The __NOWHENCE__ macro creates a * location information from the log line. The __NOWHENCE__ macro creates a
* special source __sourceloc that logging primitives should interpret to * special source __sourceloc that logging primitives should interpret to
* suppress the output of the usual source-code location information. * suppress the output of the usual source-code location information.

View File

@ -22,6 +22,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "strbuf.h" #include "strbuf.h"
#include "xprintf.h" #include "xprintf.h"
/* Primitives.
*/
void xprintf(XPRINTF xfp, const char *fmt, ...) void xprintf(XPRINTF xfp, const char *fmt, ...)
{ {
va_list ap; va_list ap;
@ -45,6 +48,37 @@ void xputc(char c, XPRINTF xpf)
xprintf(xpf, "%c", c); xprintf(xpf, "%c", c);
} }
size_t xhexdump_line(XPRINTF xpf, const unsigned char *addr, const size_t len, const size_t off)
{
xprintf(xpf, "%04zx :", off);
size_t i;
for (i = 0; i < 16 && off + i < len; i++)
xprintf(xpf, " %02x", addr[off + i]);
for (; i < 16; i++)
xputs(" ", xpf);
xputs(" ", xpf);
for (i = 0; i < 16 && off + i < len; ++i) {
unsigned char c = addr[off + i];
xprintf(xpf, "%c", c >= ' ' && c < 0x7f ? c : '.');
}
return i;
}
void xhexdump(XPRINTF xpf, const unsigned char *addr, const size_t len, const char *line_prefix)
{
size_t off = 0;
while (off < len) {
xputs(line_prefix, xpf);
size_t skip = xhexdump_line(xpf, addr, len, off);
off += skip;
addr += skip;
xputc('\n', xpf);
}
}
/* Implementations for various destinations.
*/
void _cx_vprintf_stdio(void *context, const char *fmt, va_list ap) void _cx_vprintf_stdio(void *context, const char *fmt, va_list ap)
{ {
vfprintf((FILE *)context, fmt, ap); vfprintf((FILE *)context, fmt, ap);

View File

@ -73,6 +73,7 @@ typedef struct _xprintf {
void *context; void *context;
} XPRINTF; } XPRINTF;
#define XPRINTF_IS_NULL(X) ((X).func == NULL)
#define _XPRINTF(F,C) ((XPRINTF){(F),(C)}) #define _XPRINTF(F,C) ((XPRINTF){(F),(C)})
void xprintf(XPRINTF xpf, const char *fmt, ...) __attribute__ ((__ATTRIBUTE_format(printf,2,3))); void xprintf(XPRINTF xpf, const char *fmt, ...) __attribute__ ((__ATTRIBUTE_format(printf,2,3)));
@ -80,8 +81,13 @@ void vxprintf(XPRINTF xpf, const char *fmt, va_list);
void xputs(const char *str, XPRINTF xpf); void xputs(const char *str, XPRINTF xpf);
void xputc(char c, XPRINTF xpf); void xputc(char c, XPRINTF xpf);
size_t xhexdump_line(XPRINTF xpf, const unsigned char *addr, const size_t len, const size_t off);
void xhexdump(XPRINTF xpf, const unsigned char *addr, size_t len, const char *line_prefix);
#define XPRINTF_GEN(F,C) _XPRINTF((F),(void *)(C)) #define XPRINTF_GEN(F,C) _XPRINTF((F),(void *)(C))
#define XPRINTF_NULL _XPRINTF(NULL, NULL)
/* Standard i/o adapter. An XPRINTF constructed with XPRINTF_STDIO(stream) /* Standard i/o adapter. An XPRINTF constructed with XPRINTF_STDIO(stream)
* will write all its output to stream using fprintf(stream,...). * will write all its output to stream using fprintf(stream,...).
*/ */