diff options
author | Yorhel <git@yorhel.nl> | 2013-04-14 13:13:19 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2013-04-14 13:16:16 +0200 |
commit | dd99f9dddd89d671c0eec8be86d4eae80aa53059 (patch) | |
tree | 0fe1897b90a82a56e729f576bef1562f12f36788 | |
parent | b54959df98d29dd3aec1725c239db261d4f28d7c (diff) |
Add util/logfile abstraction and -l, --log-file option
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | doc/globster.pod | 8 | ||||
-rw-r--r-- | src/global.h | 1 | ||||
-rw-r--r-- | src/main.c | 20 | ||||
-rw-r--r-- | src/util/logfile.c | 135 | ||||
-rw-r--r-- | src/util/logfile.h | 59 |
6 files changed, 223 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am index 022c30c..3eb5677 100644 --- a/Makefile.am +++ b/Makefile.am @@ -109,6 +109,7 @@ globster_SOURCES=\ src/main.c\ src/util/adc.c\ src/util/base32.c\ + src/util/logfile.c\ src/util/netstream.c\ src/util/netutil.c\ src/util/nmdc.c @@ -133,6 +134,7 @@ noinst_HEADERS=\ src/main.h\ src/util/adc.h\ src/util/base32.h\ + src/util/logfile.h\ src/util/netstream.h\ src/util/netutil.h\ src/util/nmdc.h diff --git a/doc/globster.pod b/doc/globster.pod index 66c41c2..2c8dbdf 100644 --- a/doc/globster.pod +++ b/doc/globster.pod @@ -37,7 +37,13 @@ Connect to the system D-Bus. Connect to the session D-Bus (default). -=item --log-level I<level> +=item --log-file I<file> + +Write the logs to the specified file. The special values C<stdout> and +C<stderr> can be used to log to standard output or standard error, +respectively. + +=item -l, --log-level I<level> The log level. Can be set separately for each source file and supports wildcard matching. For example C<hub/*:3,*:2> sets the log level for all hub-related diff --git a/src/global.h b/src/global.h index 4bbdf36..c6ba8c2 100644 --- a/src/global.h +++ b/src/global.h @@ -72,6 +72,7 @@ #include <compat.h> #include <util/adc.h> #include <util/base32.h> +#include <util/logfile.h> #include <util/netstream.h> #include <util/netutil.h> #include <util/nmdc.h> @@ -35,8 +35,11 @@ main_state_t main_state; static bool conf_system = false; static bool conf_autoconnect = true; +static char *conf_log_file = "stderr"; static char *conf_log_level = NULL; static char *conf_session_dir = NULL; + +static logfile_t *log_file; static ev_signal termsig, intsig; @@ -100,8 +103,13 @@ static void log_handler(const char *file, int line, int level, const char *messa case YLOG_TRACE: strcpy(lvl, "trace"); break; default: sprintf(lvl, "%5d", level); } + pthread_mutex_lock(&m); - fprintf(stderr, "%s -%s- %s:%d: %s\n", tstr, lvl, file, line, message); + if(log_file) + logfile_logf(log_file, "%s -%s- %s:%d: %s\n", tstr, lvl, file, line, message); + else + fprintf(strcmp(conf_log_file, "stderr") == 0 ? stderr : stdout, + "%s -%s- %s:%d: %s\n", tstr, lvl, file, line, message); pthread_mutex_unlock(&m); } @@ -130,6 +138,7 @@ static void print_help() { " -h, --help This help message\n" " --system Connect to the D-Bus system bus\n" " --session Connect to the D-Bus session bus (default)\n" + " --log-file FILE Log to the given file, `stdout' or `stderr'\n" " --log-level Set the log level\n" " -c, --session-dir PATH Set the session directory\n" " -n Disable autoconnect"); @@ -146,6 +155,7 @@ static void argv_parse(int argc, char **argv) { { 'h', 0, "-h,--help" }, { 'Y', 0, "--system" }, { 'S', 0, "--session" }, + { 'l', 1, "-l,--log-file"}, { 'L', 1, "--log-level" }, { 'c', 1, "-c,--session-dir" }, { 'n', 0, "-n" }, @@ -163,6 +173,7 @@ static void argv_parse(int argc, char **argv) { exit(0); case 'Y': conf_system = true; break; case 'S': conf_system = false; break; + case 'l': conf_log_file = val; break; case 'L': conf_log_level = val; break; case 'c': conf_session_dir = val; break; case 'n': conf_autoconnect = false; break; @@ -205,6 +216,9 @@ int main(int argc, char **argv) { argv_parse(argc, argv); main_state = MAIN_INIT; + + if(strcmp(conf_log_file, "stderr") != 0 && strcmp(conf_log_file, "stdout") != 0) + log_file = logfile_open(conf_log_file); if(!conf_log_level) conf_log_level = getenv("YLOG_LEVEL"); ylog_set_level(YLOG_DEFAULT, conf_log_level); @@ -244,6 +258,10 @@ int main(int argc, char **argv) { db_destroy(); yinfo("Clean shutdown"); + + if(log_file) + logfile_close(log_file); + return 0; } diff --git a/src/util/logfile.c b/src/util/logfile.c new file mode 100644 index 0000000..3e79b1f --- /dev/null +++ b/src/util/logfile.c @@ -0,0 +1,135 @@ +/* Copyright (c) 2012-2013 Yoran Heling + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include <global.h> + + +struct logfile_t { + logfile_t *next, *prev; + char *fn; + int fd; + struct stat st; +}; + +static logfile_t *logfile_list; + + +/* (Re)opens the log file and checks for inode and file size changes. */ +static void logfile_checkfile(logfile_t *l) { + struct stat st; + + if(l->fd >= 0 && (stat(l->fn, &st) < 0 + || (l->st.st_ino != st.st_ino || l->st.st_size > st.st_size))) { + /* If we were unable to stat our log file, assume it has been rotated + * and recreate it. If our log file has either been truncated or + * replaced with a different file, let's reopen it as well. */ + close(l->fd); + l->fd = -1; + } + + if(l->fd >= 0) { + l->st = st; + return; + } + + l->fd = open(l->fn, O_WRONLY|O_APPEND|O_CREAT, 0666); + + /* XXX: What do we do if open() failed? */ + if(l->fd < 0) + return; + + /* Stat the fd in order to fill out l->st with meaningful data for the next + * check. */ + if(fstat(l->fd, &st) == 0) + l->st = st; + else { + /* Let's... just try again later. */ + close(l->fd); + l->fd = -1; + } +} + + +logfile_t *logfile_open(const char *fn) { + logfile_t *l = malloc(sizeof(logfile_t)); + l->fn = strdup(fn); + l->fd = -1; + + l->next = logfile_list; + l->prev = NULL; + if(logfile_list) + logfile_list->prev = l; + logfile_list = l; + + logfile_checkfile(l); + + return l; +} + + +void logfile_close(logfile_t *l) { + if(l->next) + l->next->prev = l->prev; + if(l->prev) + l->prev->next = l->next; + if(logfile_list == l) + logfile_list = l->next; + + if(l->fd >= 0) + close(l->fd); + + free(l->fn); + free(l); +} + + +void logfile_log(logfile_t *l, const char *str) { + logfile_checkfile(l); + if(l->fd < 0) + return; + + int len = strlen(str); + int wr = 0; + int r; + /* XXX: What to do if write() failed? */ + while(wr < len && (r = write(l->fd, str+wr, len-wr)) > 0) + wr += r; +} + + +static void logfile_reopen(logfile_t *l) { + if(l->fd >= 0) { + close(l->fd); + l->fd = -1; + } + logfile_checkfile(l); +} + + +void logfile_global_reopen() { + logfile_t *l; + for(l=logfile_list; l; l=l->next) + logfile_reopen(l); +} + + +/* vim: set noet sw=4 ts=4: */ diff --git a/src/util/logfile.h b/src/util/logfile.h new file mode 100644 index 0000000..ca0c564 --- /dev/null +++ b/src/util/logfile.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2012-2013 Yoran Heling + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTIL_LOGFILE_H +#define UTIL_LOGFILE_H + +/* This abstraction allows logging to files. The lines are logged as they are + * given, without timestamp or other information. A global list of all log + * files is kept in order to provide a mechanism to re-open all logs (on + * SIGHUP, for example). + * + * These functions are not thread-safe. I/O errors are silently ignored. :-( + * + * TODO: Currently, logfile_log() flushes each line to the OS before returning. + * This is required if you don't want to lose lines in the case of a crash, but + * hinders performance if losing the very last entries is acceptable, for + * example with chat logs. An interface should be added to allow deferred + * flushing of the log entries. + */ + +typedef struct logfile_t logfile_t; + +logfile_t *logfile_open(const char *fn); + +void logfile_close(logfile_t *); + +void logfile_log(logfile_t *, const char *); + +#define logfile_logf(logfile_l, ...) do {\ + kstring_t logfile_fmt = {};\ + ksprintf(&logfile_fmt, __VA_ARGS__);\ + logfile_log(logfile_l, logfile_fmt.s);\ + free(logfile_fmt.s);\ + } while(0) + +void logfile_global_reopen(); + +#endif +/* vim: set noet sw=4 ts=4: */ |