diff --git a/BACKLOG.md b/BACKLOG.md index e52b60b..9332394 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -7,7 +7,7 @@ ## bs_arg -* Support "extra" arguments of no type, eg. for `--help` or `--version`. +* Support "extra" arguments of no type, eg. for `--help` or `--version` ("switch"). ## bs_file diff --git a/include/libbase/arg.h b/include/libbase/arg.h index ade49c2..b5f1e4e 100644 --- a/include/libbase/arg.h +++ b/include/libbase/arg.h @@ -22,7 +22,6 @@ #ifndef __LIBBASE_ARG_H__ #define __LIBBASE_ARG_H__ -#include "log.h" #include "test.h" #include @@ -206,17 +205,6 @@ void bs_arg_cleanup(const bs_arg_t *arg_ptr); */ int bs_arg_print_usage(FILE *stream_ptr, const bs_arg_t *arg_ptr); -/** Severity level names, for logging. Used for bs_arg_log_level. */ -extern const bs_arg_enum_table_t bs_arg_log_level_names[]; - -/** Common commandline arg for `--log_level`. Sets @ref bs_log_severity. */ -static const bs_arg_t bs_arg_log_level = BS_ARG_ENUM( - "log_level", - "Log level to apply. One of DEBUG, INFO, WARNING, ERROR.", - "INFO", - &bs_arg_log_level_names[0], - (int*)&bs_log_severity); - /** Unit test set. */ extern const bs_test_set_t bs_arg_test_set; diff --git a/include/libbase/log.h b/include/libbase/log.h index 08173dd..0395350 100644 --- a/include/libbase/log.h +++ b/include/libbase/log.h @@ -21,12 +21,13 @@ #ifndef __LIBBASE_LOG_H__ #define __LIBBASE_LOG_H__ -#include "def.h" -#include "test.h" - #include #include +#include "arg.h" +#include "def.h" +#include "test.h" + #ifdef __cplusplus extern "C" { #endif // __cplusplus @@ -47,8 +48,16 @@ typedef enum { BS_ERRNO = 0x80 } bs_log_severity_t; -/** Current severity for logging. Only equal-or-higher severity is logged. */ -extern bs_log_severity_t bs_log_severity; +/** Defines the settings used for the logger. */ +struct bs_log_settings { + /** Configured log severity. */ + bs_log_severity_t severity; + /** Override: Log to stderr, even after bs_log_init_file(). */ + bool log_to_stderr; +}; + +/** The logger's settings. */ +extern struct bs_log_settings bs_log_settings; /** Actually write a log message. Helper to bs_log. */ void bs_log_write(bs_log_severity_t severity, @@ -69,19 +78,17 @@ void bs_log_vwrite(bs_log_severity_t severity, * * @param dirname_ptr Directory to log into. Uses "./" if NULL. * @param filebase_ptr - * @param severity * * @return true on success. This function will fail if called more than once. */ -bool bs_log_init_file( +bool bs_log_init( const char *dirname_ptr, - const char *filebase_ptr, - bs_log_severity_t severity); + const char *filebase_ptr); /** Returns whether log outut will happen for `severity`. */ static inline bool bs_will_log(bs_log_severity_t severity) { - return ((severity & 0x7f) >= bs_log_severity || + return ((severity & 0x7f) >= bs_log_settings.severity || (severity & 0x7f) == BS_FATAL); } @@ -94,6 +101,30 @@ static inline bool bs_will_log(bs_log_severity_t severity) } \ } +/** Severity level names, for logging. Used for bs_arg_log_level. */ +extern const bs_arg_enum_table_t bs_log_level_names[]; + +/** + * Common definition for commandline argument `--log_level`. Sets + * @ref bs_log_settings.severity. + */ +static const bs_arg_t bs_arg_log_level = BS_ARG_ENUM( + "log_level", + "Log level to apply. One of DEBUG, INFO, WARNING, ERROR.", + "INFO", + &bs_log_level_names[0], + (int*)&bs_log_settings.severity); + +/** + * Common definition for commandline argument `--log_to_stderr`. Sets + * @ref bs_log_settings.log_to_stderr. + */ +static const bs_arg_t bs_arg_log_to_stderr = BS_ARG_BOOL( + "log_to_stderr", + "Overrules log output to be written to STDERR.", + false, + &bs_log_settings.log_to_stderr); + /** Unit testset. */ extern const bs_test_set_t bs_log_test_set; diff --git a/include/libbase/log_wrappers.h b/include/libbase/log_wrappers.h index c604a9c..90f42a5 100644 --- a/include/libbase/log_wrappers.h +++ b/include/libbase/log_wrappers.h @@ -34,7 +34,7 @@ static inline void *_logged_calloc( const char *filename_ptr, int line_no, size_t nmemb, size_t size) { void *ptr = calloc(nmemb, size); - if ((NULL == ptr) && (BS_ERROR >= bs_log_severity)) { + if ((NULL == ptr) && bs_will_log(BS_ERROR)) { bs_log_write((bs_log_severity_t)(BS_ERROR | BS_ERRNO), filename_ptr, line_no, "Failed calloc(%zu, %zu)", nmemb, size); } @@ -50,7 +50,7 @@ static inline void *_logged_malloc( const char *filename_ptr, int line_no, size_t size) { void *ptr = malloc(size); - if ((NULL == ptr) && (BS_ERROR >= bs_log_severity)) { + if ((NULL == ptr) && bs_will_log(BS_ERROR)) { bs_log_write((bs_log_severity_t)(BS_ERROR | BS_ERRNO), filename_ptr, line_no, "Failed malloc(%zu)", size); } @@ -69,7 +69,7 @@ static inline char *_logged_strdup( char *new_str = malloc(len); if (NULL != new_str) { memcpy(new_str, str, len); - } else if (BS_ERROR >= bs_log_severity) { + } else if (bs_will_log(BS_ERROR)) { bs_log_write((bs_log_severity_t)(BS_ERROR | BS_ERRNO), filename_ptr, line_no, "Failed strdup(%s)", str); } diff --git a/src/arg.c b/src/arg.c index fa4c575..b1bd0d3 100644 --- a/src/arg.c +++ b/src/arg.c @@ -90,14 +90,6 @@ static bool lookup_enum(const bs_arg_enum_table_t *lookup_table, static const char *bs_arg_bool_value_true = "true"; static const char *bs_arg_bool_value_false = "false"; -const bs_arg_enum_table_t bs_arg_log_level_names[] = { - { .name_ptr = "DEBUG", BS_DEBUG }, - { .name_ptr = "INFO", BS_INFO }, - { .name_ptr = "WARNING", BS_WARNING }, - { .name_ptr = "ERROR", BS_ERROR }, - { .name_ptr = NULL }, -}; - /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ @@ -681,7 +673,6 @@ static void bs_arg_test_parse_arg_for_enum(bs_test_t *test_ptr); static void bs_arg_test_set_defaults(bs_test_t *test_ptr); static void bs_arg_test_parse(bs_test_t *test_ptr); static void bs_arg_test_check_arg(bs_test_t *test_ptr); -static void bs_arg_test_log_level(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_arg_test_cases[] = { @@ -696,7 +687,6 @@ static const bs_test_case_t bs_arg_test_cases[] = { { true, "set_defaults", bs_arg_test_set_defaults }, { true, "parse", bs_arg_test_parse }, { true, "check_arg", bs_arg_test_check_arg }, - { true, "log_level", bs_arg_test_log_level }, { false, NULL, NULL } }; @@ -1040,24 +1030,4 @@ static void bs_arg_test_check_arg(bs_test_t *test_ptr) BS_TEST_VERIFY_TRUE(test_ptr, check_arg(valid_args)); } -/* ------------------------------------------------------------------------- */ -void bs_arg_test_log_level(bs_test_t *test_ptr) -{ - static const bs_arg_t args[] = { - bs_arg_log_level, - BS_ARG_SENTINEL() - }; - - const char *argv[] = { "program", "--log_level", "DEBUG" }; - int argc = sizeof(argv) / sizeof(const char*); - - bs_log_severity_t log_level = bs_log_severity; - bs_log_severity = BS_INFO; - BS_TEST_VERIFY_TRUE( - test_ptr, - bs_arg_parse(args, BS_ARG_MODE_NO_EXTRA, &argc, argv)); - BS_TEST_VERIFY_EQ(test_ptr, BS_DEBUG, bs_log_severity); - bs_log_severity = log_level; -} - /* == End of arg.c ========================================================= */ diff --git a/src/log.c b/src/log.c index 30efbe9..32c6f48 100644 --- a/src/log.c +++ b/src/log.c @@ -20,6 +20,8 @@ #include #include +#include +#include #include #include #include @@ -40,55 +42,57 @@ /* == Data ================================================================= */ -bs_log_severity_t bs_log_severity = BS_WARNING; +struct bs_log_settings bs_log_settings = { + .severity = BS_WARNING, + .log_to_stderr = false +}; -static const char *_severity_names[5] = { - "DEBUG", "INFO", "WARNING", "ERROR", "FATAL"}; +const bs_arg_enum_table_t bs_log_level_names[] = { + { .name_ptr = "DEBUG", BS_DEBUG }, + { .name_ptr = "INFO", BS_INFO }, + { .name_ptr = "WARNING", BS_WARNING }, + { .name_ptr = "ERROR", BS_ERROR }, + { .name_ptr = NULL }, +}; -static int _log_fd = 2; +static int _log_fd = 2; -static const char *_bs_log_extension_ptr = ".log"; +static const char *_bs_log_extension_ptr = ".log"; +static int _bs_log_prepare_file(const char *fmt_ptr, ...); static const char *_strip_prefix(const char *path_ptr); /* == Functions ============================================================ */ /* ------------------------------------------------------------------------- */ -bool bs_log_init_file( +bool bs_log_init( const char *dirname_ptr, - const char *filebase_ptr, - bs_log_severity_t severity) + const char *filebase_ptr) { static bool initialized = false; if (initialized) return false; + _log_fd = 2; + if (bs_log_settings.log_to_stderr) return true; + if (NULL == dirname_ptr) dirname_ptr = "./"; // Attempt to create the directory, unless it exists. if (!bs_file_realpath_is(dirname_ptr, S_IFDIR) && !bs_file_mkdir_p(dirname_ptr, 0700)) return false; - // Construct the log file's name. - char path[PATH_MAX + 1]; - int len = snprintf(path, PATH_MAX + 1, "%s/%s%s", - dirname_ptr, filebase_ptr, _bs_log_extension_ptr); - if (PATH_MAX <= len) { - bs_log(BS_ERROR, "Failed snprintf(%p, %d, \"%s/%s%s\"): %d", - path, PATH_MAX + 1, dirname_ptr, filebase_ptr, - _bs_log_extension_ptr, len); - return false; - } - - // Now, actually create it. - int fd = open(path, O_CREAT|O_WRONLY|O_TRUNC, S_IWUSR|S_IRUSR); + // Prepare the log file. Include the PID, if the file with the primary + // name cannot be prepared. + int fd = _bs_log_prepare_file( + "%s/%s%s", dirname_ptr, filebase_ptr, _bs_log_extension_ptr); if (0 > fd) { - bs_log(BS_ERROR | BS_ERRNO, - "Failed open(%s, O_CREATE|O_WRONLY|O_TRUNC, S_IWUSR|S_IRUSR)", - path); - return false; + fd = _bs_log_prepare_file( + "%s/%s.%"PRIdMAX"%s-FIXME", dirname_ptr, filebase_ptr, + (intmax_t)getpid(), _bs_log_extension_ptr); + if (0 > fd) return false; } + _log_fd = fd; - bs_log_severity = severity; initialized = true; return true; } @@ -139,7 +143,7 @@ void bs_log_vwrite(bs_log_severity_t severity, tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec, (int)(tv.tv_usec / 1000), color_attr_ptr, - _severity_names[severity & 0x7f], + bs_log_level_names[severity & 0x7f].name_ptr, reset_ptr, _strip_prefix(file_name_ptr), line_num); @@ -161,8 +165,9 @@ void bs_log_vwrite(bs_log_severity_t severity, size_t written_bytes = 0; while (written_bytes < pos) { - ssize_t more_bytes = write(_log_fd, &buf[written_bytes], - pos - written_bytes); + int fd = bs_log_settings.log_to_stderr ? 2 : _log_fd; + ssize_t more_bytes = write( + fd, &buf[written_bytes], pos - written_bytes); if (0 > more_bytes) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { continue; @@ -175,9 +180,49 @@ void bs_log_vwrite(bs_log_severity_t severity, if (severity == BS_FATAL) BS_ABORT(); } - /* == Local (static) methods =============================================== */ +/* ------------------------------------------------------------------------- */ +/** Prepares a log file: Opens it, obtains a write lock, and truncates it. */ +int _bs_log_prepare_file(const char *fmt_ptr, ...) +{ + // Construct the file name. + char path[PATH_MAX + 1]; + va_list ap; + va_start(ap, fmt_ptr); + int len = vsnprintf(path, PATH_MAX + 1, fmt_ptr, ap); + va_end(ap); + if (PATH_MAX <= len) { + bs_log(BS_ERROR, "Failed vsnprintf(%p, %d, \"%s\", ...): %d", + path, PATH_MAX + 1, fmt_ptr, len); + return -1; + } + + // Now, actually create it. + int fd = open(path, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR); + if (0 > fd) { + bs_log(BS_ERROR | BS_ERRNO, + "Failed open(%s, O_CREATE | O_WRONLY, S_IWUSR | S_IRUSR)", + path); + return -1; + } + + // File may already exist and be in use. Attempt to lock entire file. + struct flock file_lock = { .l_type = F_WRLCK, .l_whence = SEEK_SET }; + if (0 > fcntl(fd, F_SETLK, &file_lock)) { + bs_log(BS_INFO | BS_ERRNO, "fcntl(%d, F_SETLK, %p) for \"%s\" failed", + fd, &file_lock, path); + close(fd); + return -1; + } + + if (0 > ftruncate(fd, 0)) { + bs_log(BS_WARNING | BS_ERRNO, + "Failed truncate(%d, 0) for \"%s\"", fd, path); + } + return fd; +} + /** Strips leading relative (or absolute) path prefix. */ const char *_strip_prefix(const char *path_ptr) { @@ -191,12 +236,14 @@ const char *_strip_prefix(const char *path_ptr) static void test_file(bs_test_t *test_ptr); static void test_strip_prefix(bs_test_t *test_ptr); static void test_log(bs_test_t *test_ptr); +static void _bs_log_test_parse_args(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_log_test_cases[] = { { true, "file", test_file }, { true, "basename", test_strip_prefix }, { true, "log", test_log }, + { true, "parse_args", _bs_log_test_parse_args }, BS_TEST_CASE_SENTINEL(), }; @@ -271,7 +318,7 @@ void test_file(bs_test_t *test_ptr) { BS_TEST_VERIFY_TRUE( test_ptr, - bs_log_init_file(bs_test_temp_path(test_ptr, "sub"), "base", BS_INFO)); + bs_log_init(bs_test_temp_path(test_ptr, "sub"), "base")); const char *log_name_ptr = bs_test_temp_path(test_ptr, "sub/base.log"); BS_TEST_VERIFY_TRUE(test_ptr, bs_file_realpath_is(log_name_ptr, S_IFREG)); @@ -283,7 +330,7 @@ void test_file(bs_test_t *test_ptr) // Must fail on further initialization attempt(s). BS_TEST_VERIFY_FALSE( test_ptr, - bs_log_init_file(bs_test_temp_path(test_ptr, "sub"), "other", BS_INFO)); + bs_log_init(bs_test_temp_path(test_ptr, "sub"), "other")); } /* ------------------------------------------------------------------------- */ @@ -304,8 +351,8 @@ void test_log(bs_test_t *test_ptr) char expected_output[BS_LOG_MAX_BUF_SIZE + 1]; bs_log_severity_t backup_severity; - backup_severity = bs_log_severity; - bs_log_severity = BS_WARNING; + backup_severity = bs_log_settings.severity; + bs_log_settings.severity = BS_WARNING; int fds[2]; if (0 != pipe(fds)) { @@ -342,7 +389,7 @@ void test_log(bs_test_t *test_ptr) verify_log_output_equals_at( test_ptr, __FILE__, __LINE__, fds[0], NULL); - bs_log_severity = BS_INFO; + bs_log_settings.severity = BS_INFO; snprintf(expected_output, sizeof(expected_output), "(\e[37mINFO\e[0m) \e[90mlog.c:%d\e[0m \e[37mtest 45\e[0m\n", __LINE__ + 1); @@ -354,7 +401,34 @@ void test_log(bs_test_t *test_ptr) close(fds[0]); close(fds[1]); - bs_log_severity = backup_severity; + bs_log_settings.severity = backup_severity; +} + +/* ------------------------------------------------------------------------- */ +/** Verifies that the common commandline args are parsed correctly. */ +void _bs_log_test_parse_args(bs_test_t *test_ptr) +{ + static const bs_arg_t args[] = { + bs_arg_log_level, + bs_arg_log_to_stderr, + BS_ARG_SENTINEL(), + }; + + const char *a1[] = { "", "--log_level", "ERROR", "--log_to_stderr", 0 }; + int l = sizeof(a1) / sizeof(const char*) - 1; + BS_TEST_VERIFY_TRUE( + test_ptr, + bs_arg_parse(args, BS_ARG_MODE_NO_EXTRA, &l, a1)); + BS_TEST_VERIFY_EQ(test_ptr, BS_ERROR, bs_log_settings.severity); + BS_TEST_VERIFY_TRUE(test_ptr, bs_log_settings.log_to_stderr); + + const char *a2[] = { "", "--log_level", "DEBUG", "--nolog_to_stderr", 0 }; + l = sizeof(a1) / sizeof(const char*) - 1; + BS_TEST_VERIFY_TRUE( + test_ptr, + bs_arg_parse(args, BS_ARG_MODE_NO_EXTRA, &l, a2)); + BS_TEST_VERIFY_EQ(test_ptr, BS_DEBUG, bs_log_settings.severity); + BS_TEST_VERIFY_FALSE(test_ptr, bs_log_settings.log_to_stderr); } /* == End of log.c ========================================================= */