diff options
| author | Garrett D'Amore <garrett@damore.org> | 2024-04-13 10:59:05 -0700 |
|---|---|---|
| committer | Garrett D'Amore <garrett@damore.org> | 2024-04-13 20:42:14 -0700 |
| commit | df371e0a77e5b30f5ebddd0902fc8dd46b349849 (patch) | |
| tree | 50c53add482002f79a2fd49d6881671ee3d7f3cd /src | |
| parent | d2ab8a8cf3a93621f853a41b029b40c61b79d0db (diff) | |
| download | nng-df371e0a77e5b30f5ebddd0902fc8dd46b349849.tar.gz nng-df371e0a77e5b30f5ebddd0902fc8dd46b349849.tar.bz2 nng-df371e0a77e5b30f5ebddd0902fc8dd46b349849.zip | |
fixes #543 Add logging support framework
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | src/core/log.c | 284 | ||||
| -rw-r--r-- | src/core/log_test.c | 188 |
3 files changed, 475 insertions, 0 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 009d6bb0..48b25265 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -14,6 +14,7 @@ nng_check_sym(strlcpy string.h NNG_HAVE_STRLCPY) nng_check_sym(strnlen string.h NNG_HAVE_STRNLEN) nng_check_sym(strcasecmp string.h NNG_HAVE_STRCASECMP) nng_check_sym(strncasecmp string.h NNG_HAVE_STRNCASECMP) +nng_check_sym(localtime_r time.h NNG_HAVE_LOCALTIME_R) nng_sources( defs.h @@ -38,6 +39,7 @@ nng_sources( listener.h lmq.c lmq.h + log.c message.c message.h msgqueue.c @@ -80,6 +82,7 @@ nng_test(errors_test) nng_test(id_test) nng_test(init_test) nng_test(list_test) +nng_test(log_test) nng_test(message_test) nng_test(reconnect_test) nng_test(sock_test) diff --git a/src/core/log.c b/src/core/log.c new file mode 100644 index 00000000..b560f6c7 --- /dev/null +++ b/src/core/log.c @@ -0,0 +1,284 @@ + +// Copyright 2024 Staysail Systems, Inc. <info@staysail.tech> +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +#include "nng/nng.h" +#include "nng_impl.h" +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#ifdef NNG_PLATFORM_WINDOWS +#include <io.h> +#endif +#ifdef NNG_PLATFORM_POSIX +#include <syslog.h> +#include <unistd.h> +#endif +#include <time.h> + +static nng_log_level log_level = NNG_LOG_NOTICE; +static nng_log_facility log_facility = NNG_LOG_USER; +static nng_logger log_logger = nng_null_logger; + +void +nng_log_set_facility(nng_log_facility facility) +{ + log_facility = facility; +} + +void +nng_log_set_level(nng_log_level level) +{ + log_level = level; +} + +void +nng_log_set_logger(nng_logger logger) +{ + if (logger == NULL) { + logger = nng_null_logger; + } + log_logger = logger; +} + +void +nng_null_logger(nng_log_level level, nng_log_facility facility, + const char *msgid, const char *msg) +{ + NNI_ARG_UNUSED(level); + NNI_ARG_UNUSED(facility); + NNI_ARG_UNUSED(msgid); + NNI_ARG_UNUSED(msg); + return; +} + +void +nng_stderr_logger(nng_log_level level, nng_log_facility facility, + const char *msgid, const char *msg) +{ + const char *sgr, *sgr0; + // Initial implementation. + bool colors = false; + const char *level_str; + time_t now; + char when[64]; + NNI_ARG_UNUSED(facility); + +#ifdef NNG_PLATFORM_WINDOWS + // NB: We are blithely assuming the user has a modern console. + colors = _isatty(_fileno(stderr)); +#elif defined(NNG_PLATFORM_POSIX) + // Only assuming we can use colors (and attributes) if stderr is a tty + // and $TERM is reasonable. We assume the terminal supports ECMA-48, + // which is true on every reasonable system these days. + colors = isatty(fileno(stderr)) && (getenv("TERM") != NULL) && + (getenv("TERM")[0] != 0); +#else + now = 0; + colors = false; +#endif + + // Escape hatch to prevent colorizing logs if we have to. Users on + // legacy Windows can set this, or on ancient HP terminals or + // something. Also in the same way that no-color.org proposes. + // The reason for both is to allow suppression *only* for NNG. There + // is no good reason to enable it to override the presence of NO_COLOR. + if ((getenv("NNG_LOG_NO_COLOR") != NULL) || + (getenv("NO_COLOR") != NULL)) { + colors = false; + } + now = time(NULL); +#ifdef NNG_HAVE_LOCALTIME_R + struct tm tm; + // No timezone offset, not strictly ISO8601 compliant + strftime(when, sizeof(when), "%Y-%m-%d %T", localtime_r(&now, &tm)); +#else + strftime(when, sizeof(when), "%Y-%m-%d %T", localtime(&now)); +#endif + + switch (level) { + case NNG_LOG_ERR: + sgr = "\x1b[31m"; // red + sgr0 = "\x1b[0m"; + level_str = "ERROR"; + break; + case NNG_LOG_WARN: + sgr = "\x1b[33m"; // yellow + sgr0 = "\x1b[0m"; + level_str = "WARN"; + break; + case NNG_LOG_NOTICE: + sgr = "\x1b[1m"; // bold + sgr0 = "\x1b[0m"; + level_str = "NOTICE"; + break; + case NNG_LOG_DEBUG: + sgr = "\x1b[36m"; // cyan + sgr0 = "\x1b[0m"; + level_str = "DEBUG"; + break; + case NNG_LOG_INFO: + sgr = ""; + sgr0 = ""; + level_str = "INFO"; + break; + default: + sgr = ""; + sgr0 = ""; + level_str = "NONE"; + break; + } + + if (!colors) { + sgr = ""; + sgr0 = ""; + } + + (void) fprintf(stderr, "%s[%-6s]: %s: %s%s%s%s\n", sgr, level_str, + when, msgid ? msgid : "", msgid ? ": " : "", msg, sgr0); +} + +void +nng_system_logger(nng_log_level level, nng_log_facility facility, + const char *msgid, const char *msg) +{ +#ifdef NNG_PLATFORM_POSIX + int pri; + switch (level) { + case NNG_LOG_ERR: + pri = LOG_ERR; + break; + case NNG_LOG_WARN: + pri = LOG_WARNING; + break; + case NNG_LOG_NOTICE: + pri = LOG_NOTICE; + break; + case NNG_LOG_INFO: + pri = LOG_INFO; + break; + case NNG_LOG_DEBUG: + pri = LOG_DEBUG; + break; + default: + pri = LOG_INFO; + break; + } + switch (facility) { + case NNG_LOG_DAEMON: + pri |= LOG_DAEMON; + break; + case NNG_LOG_USER: + pri |= LOG_USER; + break; + case NNG_LOG_AUTH: + pri |= LOG_AUTHPRIV; + break; + case NNG_LOG_LOCAL0: + pri |= LOG_LOCAL0; + break; + case NNG_LOG_LOCAL1: + pri |= LOG_LOCAL1; + break; + case NNG_LOG_LOCAL2: + pri |= LOG_LOCAL2; + break; + case NNG_LOG_LOCAL3: + pri |= LOG_LOCAL3; + break; + case NNG_LOG_LOCAL4: + pri |= LOG_LOCAL4; + break; + case NNG_LOG_LOCAL5: + pri |= LOG_LOCAL5; + break; + case NNG_LOG_LOCAL6: + pri |= LOG_LOCAL6; + break; + case NNG_LOG_LOCAL7: + pri |= LOG_LOCAL7; + break; + } + + if (msgid) { + syslog(pri, "%s: %s", msgid, msg); + } else { + syslog(pri, "%s", msg); + } +#else + // everyone else just goes to stderr for now + nng_stderr_logger(level, facility, msgid, msg); +#endif +} + +static void +nni_vlog(nng_log_level level, nng_log_facility facility, const char *msgid, + const char *msg, va_list ap) +{ + // nobody allowed to log at LOG_EMERG or using LOG_KERN + if (level > log_level || log_level == 0 || facility == 0) { + return; + } + char formatted[512]; + vsnprintf(formatted, sizeof(formatted), msg, ap); + log_logger(level, facility, msgid, formatted); +} + +void +nng_log_debug(const char *msgid, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + nni_vlog(NNG_LOG_DEBUG, log_facility, msgid, msg, ap); + va_end(ap); +} + +void +nng_log_info(const char *msgid, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + nni_vlog(NNG_LOG_INFO, log_facility, msgid, msg, ap); + va_end(ap); +} + +void +nng_log_notice(const char *msgid, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + nni_vlog(NNG_LOG_NOTICE, log_facility, msgid, msg, ap); + va_end(ap); +} + +void +nng_log_warn(const char *msgid, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + nni_vlog(NNG_LOG_WARN, log_facility, msgid, msg, ap); + va_end(ap); +} + +void +nng_log_err(const char *msgid, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + nni_vlog(NNG_LOG_ERR, log_facility, msgid, msg, ap); + va_end(ap); +} + +void +nng_log_auth(nng_log_level level, const char *msgid, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + nni_vlog(level, NNG_LOG_AUTH, msgid, msg, ap); + va_end(ap); +} diff --git a/src/core/log_test.c b/src/core/log_test.c new file mode 100644 index 00000000..6aa1dd53 --- /dev/null +++ b/src/core/log_test.c @@ -0,0 +1,188 @@ +// +// Copyright 2024 Staysail Systems, Inc. <info@staysail.tech> +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +#include <stdio.h> +#include <string.h> + +#include <nng/nng.h> +#include <nuts.h> + +#ifdef NNG_PLATFORM_POSIX +#include <stdlib.h> +#endif + +void +test_log_stderr(void) +{ + nng_log_set_logger(nng_stderr_logger); + nng_log_set_level(NNG_LOG_DEBUG); + nng_log_info(NULL, "something wicked"); + nng_log_err(NULL, "This is an error message"); + nng_log_warn(NULL, "This is a warning message"); + nng_log_notice(NULL, "This is a notice message"); + nng_log_info(NULL, "This is an info message"); + nng_log_debug(NULL, "This is a debug message"); + nng_log_notice("TESTMSG", "This notice has a msg id"); +#ifdef NNG_PLATFORM_POSIX + setenv("NO_COLOR", "", 1); + nng_log_err("MONO", "Uncolored messages"); + unsetenv("NO_COLOR"); + setenv("NNG_LOG_NO_COLOR", "", 1); + nng_log_err("MONO", "Also uncolored messages"); +#endif + // these are intentionally unreasonably large + nng_log_set_level((nng_log_level) 100); + nng_log_auth(99, "WTF", "This should be NONE"); +} + +typedef struct test_log_entry { + nng_log_level level; + nng_log_facility facility; + const char *msgid; + char msg[128]; +} test_log_entry; + +typedef struct { + test_log_entry entries[16]; + int count; +} test_logs; + +void +custom_logger_base(test_logs *logs, nng_log_level level, + nng_log_facility facility, const char *msgid, const char *msg) +{ + test_log_entry *entry; + + if (logs->count >= 16) { + return; + } + entry = &logs->entries[logs->count++]; + entry->level = level; + entry->facility = facility; + entry->msgid = msgid; // ok for constant strings + snprintf(entry->msg, sizeof(entry->msg), "%s", msg); +} + +static test_logs test_logs_priority; +void +test_log_priority_logger(nng_log_level level, nng_log_facility facility, + const char *msgid, const char *msg) +{ + custom_logger_base(&test_logs_priority, level, facility, msgid, msg); +} + +void +test_log_priority(void) +{ + nng_log_set_logger(test_log_priority_logger); + nng_log_set_level(NNG_LOG_WARN); + nng_log_debug(NULL, "This should be filtered"); + nng_log_err("ERR", "This gets through"); + nng_log_notice("NOT", "This gets filtered"); + nng_log_warn("WRN", "This makes it"); + nng_log_info("INF", "Filtered!"); + nng_log_err("ERR", "Another error message"); + nng_log_auth(NNG_LOG_ERR, "AUTH", "authentication err sample message"); + nng_log_set_level(NNG_LOG_NONE); + nng_log_err("ERR", "Yet Another error message - filtered"); + NUTS_ASSERT(test_logs_priority.count == 4); + NUTS_ASSERT(strcmp(test_logs_priority.entries[0].msgid, "ERR") == 0); + NUTS_ASSERT(test_logs_priority.entries[0].level == NNG_LOG_ERR); + NUTS_ASSERT(strcmp(test_logs_priority.entries[1].msgid, "WRN") == 0); + NUTS_ASSERT(test_logs_priority.entries[1].level == NNG_LOG_WARN); + NUTS_ASSERT(strcmp(test_logs_priority.entries[2].msgid, "ERR") == 0); + NUTS_ASSERT(test_logs_priority.entries[2].level == NNG_LOG_ERR); + NUTS_ASSERT(strcmp(test_logs_priority.entries[3].msgid, "AUTH") == 0); + NUTS_ASSERT(test_logs_priority.entries[3].level == NNG_LOG_ERR); + NUTS_ASSERT(test_logs_priority.entries[3].facility == NNG_LOG_AUTH); +} + +static test_logs test_logs_facility; +void +test_log_facility_logger(nng_log_level level, nng_log_facility facility, + const char *msgid, const char *msg) +{ + custom_logger_base(&test_logs_facility, level, facility, msgid, msg); +} + +void +test_log_facility(void) +{ + nng_log_set_logger(test_log_facility_logger); + nng_log_set_facility(NNG_LOG_LOCAL2); + nng_log_set_level(NNG_LOG_WARN); + nng_log_debug(NULL, "This should be filtered"); + nng_log_err("001", "This is local2"); + nng_log_set_facility(NNG_LOG_DAEMON); + nng_log_warn("002", "This is Daemon"); + + NUTS_ASSERT(test_logs_facility.count == 2); + NUTS_ASSERT(strcmp(test_logs_facility.entries[0].msgid, "001") == 0); + NUTS_ASSERT(test_logs_facility.entries[0].level == NNG_LOG_ERR); + NUTS_ASSERT(test_logs_facility.entries[0].facility == NNG_LOG_LOCAL2); + NUTS_ASSERT(strcmp(test_logs_facility.entries[1].msgid, "002") == 0); + NUTS_ASSERT(test_logs_facility.entries[1].facility == NNG_LOG_DAEMON); + NUTS_ASSERT(test_logs_facility.entries[1].level == NNG_LOG_WARN); +} + +void +test_log_null_logger(void) +{ + nng_log_set_logger(nng_null_logger); + nng_log_set_level(NNG_LOG_DEBUG); + nng_log_debug(NULL, "This should be dropped"); + nng_log_err("001", "This is local2"); + nng_log_warn("002", "This is also dropped"); + + // Lets also try setting it to NULL + nng_log_set_logger(nng_null_logger); + nng_log_warn("003", "This is also dropped"); +} + +void +test_log_system_logger(void) +{ + nng_log_set_logger(nng_system_logger); + nng_log_set_level(NNG_LOG_DEBUG); + nng_log_debug(NULL, "This is a test message, ignore me"); + nng_log_set_facility(NNG_LOG_DAEMON); + nng_log_debug(NULL, "This is a test message (DAEMON), ignore me"); + nng_log_set_facility(NNG_LOG_LOCAL0); + nng_log_debug(NULL, "This is a test message (LOCAL0), ignore me"); + nng_log_set_facility(NNG_LOG_LOCAL1); + nng_log_debug(NULL, "This is a test message (LOCAL1), ignore me"); + nng_log_set_facility(NNG_LOG_LOCAL2); + nng_log_debug(NULL, "This is a test message (LOCAL2), ignore me"); + nng_log_set_facility(NNG_LOG_LOCAL3); + nng_log_debug(NULL, "This is a test message (LOCAL3), ignore me"); + nng_log_set_facility(NNG_LOG_LOCAL4); + nng_log_debug(NULL, "This is a test message (LOCAL4), ignore me"); + nng_log_set_facility(NNG_LOG_LOCAL5); + nng_log_debug(NULL, "This is a test message (LOCAL5), ignore me"); + nng_log_set_facility(NNG_LOG_LOCAL6); + nng_log_debug(NULL, "This is a test message (LOCAL6), ignore me"); + nng_log_set_facility(NNG_LOG_LOCAL7); + nng_log_debug(NULL, "This is a test message (LOCAL7), ignore me"); + + nng_log_set_facility(NNG_LOG_USER); + nng_log_debug(NULL, "This is a test message (LOCAL7), ignore me"); + nng_log_err("TEST", "This is only a test (ERR). Ignore me."); + nng_log_warn("TEST", "This is only a test (WARN). Ignore me."); + nng_log_notice("TEST", "This is only a test (NOTICE). Ignore me."); + nng_log_info("TEST", "This is only a test (INFO). Ignore me."); +} + +TEST_LIST = { + { "log stderr", test_log_stderr }, + { "log priority", test_log_priority }, + { "log facility", test_log_facility }, + { "log null logger", test_log_null_logger }, + { "log system logger", test_log_system_logger }, + { NULL, NULL }, +}; |
