aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2024-04-13 10:59:05 -0700
committerGarrett D'Amore <garrett@damore.org>2024-04-13 20:42:14 -0700
commitdf371e0a77e5b30f5ebddd0902fc8dd46b349849 (patch)
tree50c53add482002f79a2fd49d6881671ee3d7f3cd
parentd2ab8a8cf3a93621f853a41b029b40c61b79d0db (diff)
downloadnng-df371e0a77e5b30f5ebddd0902fc8dd46b349849.tar.gz
nng-df371e0a77e5b30f5ebddd0902fc8dd46b349849.tar.bz2
nng-df371e0a77e5b30f5ebddd0902fc8dd46b349849.zip
fixes #543 Add logging support framework
-rw-r--r--docs/man/CMakeLists.txt4
-rw-r--r--docs/man/libnng.3.adoc10
-rw-r--r--docs/man/nng_log.3.adoc62
-rw-r--r--docs/man/nng_log_set_facility.3.adoc47
-rw-r--r--docs/man/nng_log_set_level.adoc42
-rw-r--r--docs/man/nng_log_set_logger.adoc52
-rw-r--r--include/nng/nng.h89
-rw-r--r--src/core/CMakeLists.txt3
-rw-r--r--src/core/log.c284
-rw-r--r--src/core/log_test.c188
10 files changed, 779 insertions, 2 deletions
diff --git a/docs/man/CMakeLists.txt b/docs/man/CMakeLists.txt
index 9e3e8128..ba3508d2 100644
--- a/docs/man/CMakeLists.txt
+++ b/docs/man/CMakeLists.txt
@@ -115,6 +115,10 @@ if (NNG_ENABLE_DOC)
nng_listener_set
nng_listener_setopt
nng_listener_start
+ nng_log
+ nng_log_set_facility
+ nng_log_set_loevel
+ nng_log_set_logger
nng_msg_alloc
nng_msg_append
nng_msg_body
diff --git a/docs/man/libnng.3.adoc b/docs/man/libnng.3.adoc
index 7153b14f..3e2900aa 100644
--- a/docs/man/libnng.3.adoc
+++ b/docs/man/libnng.3.adoc
@@ -283,6 +283,16 @@ universal resource locators (URLS).
|xref:nng_url_parse.3.adoc[nng_url_parse()]|create URL structure from string
|===
+=== Logging Support
+
+Common functionality for message logging.
+
+|===
+|xref:nng_log.3.adoc[nng_log()]|log a message
+|xref:nng_log_facility.3.adoc[nng_log_set_facility()]|set log facility
+|xref:nng_log_level.3.adoc[nng_log_set_level()]|set log level
+|xref:nng_log_logger.3.adoc[nng_log_set_logger()]|set logging handler
+|===
=== Supplemental API
diff --git a/docs/man/nng_log.3.adoc b/docs/man/nng_log.3.adoc
new file mode 100644
index 00000000..852f765b
--- /dev/null
+++ b/docs/man/nng_log.3.adoc
@@ -0,0 +1,62 @@
+= nng_log(3)
+//
+// Copyright 2024 Staysail Systems, Inc. <info@staysail.tech>
+//
+// This document 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.
+//
+
+== NAME
+
+nng_log - log messages
+
+== SYNOPSIS
+
+[source, c]
+----
+#include <nng/nng.h>
+
+void nng_log_err(const char *msgid, const char *msg, ...);
+void nng_log_warn(const char *msgid, const char *msg, ...);
+void nng_log_notice(const char *msgid, const char *msg, ...);
+void nng_log_info(const char *msgid, const char *msg, ...);
+void nng_log_debug(const char *msgid, const char *msg, ...);
+
+void nng_log_auth(nng_log_level level, const char *msgid, const char *msg, ...);
+----
+
+== DESCRIPTION
+
+These functions are used to post a message to system or application logs.
+
+The first five forms all post a message at the severity indicated by the function name.
+The _msgid_ should be a short message identifier that should indicate the message in question.
+A `NULL` value for _msgid_ canbe used as well.
+
+Message identifiers can be used to assist in filtering logs.
+These should uniquely identify the nature of the problem, whe possible, to assist in trouble-shooting.
+They should also be short.
+Eight characters or less is ideal, and more than sixteen is strongly discouraged.
+
+The message is formatting as if by `sprintf`, using `msg` as the format, and remaining arguments as arguments to the format.
+
+The final function, `nng_log_auth`, is used for posting authentication related messages which might be treated specially, such as be storing them in a separate secured log file.
+It takes the severity as a level in _level_.
+The severity can be one of the following values:
+
+* `NNG_LOG_ERR`
+* `NNG_LOG_WARN`
+* `NNG_LOG_NOTICE`
+* `NNG_LOG_INFO`
+* `NNG_LOG_DEBUG`
+
+The message itself is handled according to the logging facility set up with xref:nng_mg_set_logger.3.adoc[`nng_log_set_logger`].
+Message delivery is best effort, and messages may be suppressed based on the priority set with xref:nng_log_set_level.3.adoc[`nng_log_set_level`].
+
+== SEE ALSO
+
+xref:nng_log_set_facility.3.adoc[nng_log_set_facility(3)],
+xref:nng_log_set_level.3.adoc[nng_log_set_level(3)],
+xref:nng_log_set_logger.3.adoc[nng_log_set_logger(3)]
diff --git a/docs/man/nng_log_set_facility.3.adoc b/docs/man/nng_log_set_facility.3.adoc
new file mode 100644
index 00000000..ecd6ab76
--- /dev/null
+++ b/docs/man/nng_log_set_facility.3.adoc
@@ -0,0 +1,47 @@
+= nng_log_set_facility(3)
+//
+// Copyright 2024 Staysail Systems, Inc. <info@staysail.tech>
+//
+// This document 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.
+//
+
+== NAME
+
+nng_log_set_facility - set facility used for log messages
+
+== SYNOPSIS
+
+[source, c]
+----
+#include <nng/nng.h>
+
+typedef enum nng_log_facility {
+ NNG_LOG_USER = 1,
+ NNG_LOG_DAEMON = 3,
+ NNG_LOG_AUTH = 10,
+ NNG_LOG_LOCAL0 = 16,
+ NNG_LOG_LOCAL1 = 17,
+ NNG_LOG_LOCAL2 = 18,
+ NNG_LOG_LOCAL3 = 19,
+ NNG_LOG_LOCAL4 = 20,
+ NNG_LOG_LOCAL5 = 21,
+ NNG_LOG_LOCAL6 = 22,
+ NNG_LOG_LOCAL7 = 23,
+} nng_log_facility;
+
+void nng_log_set_facility(nng_log_facility facility);
+----
+
+== DESCRIPTION
+
+The `nng_log_set_facility` is used to set the _facility_ of the application posting logs.
+This is used to assist with directing log content when handled by services such as `syslog`.
+
+Note that while the log levels used here overlap with common levels used by the syslog facility on POSIX systems, applications should not rely on this.
+
+== SEE ALSO
+
+xref:nng_log_set_level.3.adoc[nng_log_set_level(3)]
diff --git a/docs/man/nng_log_set_level.adoc b/docs/man/nng_log_set_level.adoc
new file mode 100644
index 00000000..012f93f8
--- /dev/null
+++ b/docs/man/nng_log_set_level.adoc
@@ -0,0 +1,42 @@
+= nng_log_set_level(3)
+//
+// Copyright 2024 Staysail Systems, Inc. <info@staysail.tech>
+//
+// This document 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.
+//
+
+== NAME
+
+nng_log_set_level - set minimum level for logging messagse
+
+== SYNOPSIS
+
+[source, c]
+----
+#include <nng/nng.h>
+
+typedef enum nng_log_level {
+ NNG_LOG_NONE = 0, // used for filters only, NNG suppresses these
+ NNG_LOG_ERR = 3,
+ NNG_LOG_WARN = 4,
+ NNG_LOG_NOTICE = 5,
+ NNG_LOG_INFO = 6,
+ NNG_LOG_DEBUG = 7
+} nng_log_level;
+
+void nng_log_set_level(nng_log_level level);
+----
+
+== DESCRIPTION
+
+The `nng_log_set_level` function is used to set the minimum severity to _level_ for processing log messages.
+Any messages with a less severe rating are not processed and simply are discarded.
+Use `NNG_LOG_NONE` to suppress all log messages.
+Use `NNG_LOG_DEBUG` to receive all log messages.
+
+== SEE ALSO
+
+xref:nng_log_set_facility.3.adoc[nng_log_set_facility(3)]
diff --git a/docs/man/nng_log_set_logger.adoc b/docs/man/nng_log_set_logger.adoc
new file mode 100644
index 00000000..4f3cafc8
--- /dev/null
+++ b/docs/man/nng_log_set_logger.adoc
@@ -0,0 +1,52 @@
+= nng_log_set_logger(3)
+//
+// Copyright 2024 Staysail Systems, Inc. <info@staysail.tech>
+//
+// This document 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.
+//
+
+== NAME
+
+nng_log_set_logger - set logging handler
+
+== SYNOPSIS
+
+[source, c]
+----
+#include <nng/nng.h>
+
+typedef void (*nng_logger)(nng_log_level level, nng_log_facility facility,
+ const char *msgid, const char *msg);
+
+void nng_null_logger(nng_log_level, nng_log_facility, const char *, const char *);
+void nng_stderr_logger(nng_log_level, nng_log_facility, const char *, const char *);
+void nng_system_logger(nng_log_level, nng_log_facility, const char *, const char *);
+
+void nng_log_set_logger(nng_logger logger);
+----
+
+== DESCRIPTION
+
+The `nng_log_set_logger` is used to set the base logging function to _logger_.
+The _logger_ may be a user defined function to process log messages.
+Only a single logger may be registered at a time.
+If needed, the logger should make copies of either _msgid_ or _msg_, as those may not be valid once the logger function returns.
+
+The `nng_null_logger` function is an implementation of `nng_logger` that simply discards the content.
+
+The `nng_stderr_logger` function is an implementation that logs messages to the standard error stream.
+It will attempt to colorize messages by the severity, if the standard error is a terminal device.
+This can be supressed by setting either the `NO_COLOR` or `NNG_LOG_NO_COLOR` environment variables.
+
+The `nng_system_logger` attempts to use an appropriate system facility to log messages.
+For POSIX systems, this means using `syslog` to process the messages.
+For other the `nng_stderr_log` may be used as a fallback.
+
+== SEE ALSO
+
+xref:nng_log_set_facility.3.adoc[nng_log_set_facility(3)],
+xref:nng_log_set_level.3.adoc[nng_log_set_level(3)],
+xref:nng_log.3.adoc[nng_log(3)]
diff --git a/include/nng/nng.h b/include/nng/nng.h
index babb2eb8..7b196e66 100644
--- a/include/nng/nng.h
+++ b/include/nng/nng.h
@@ -72,7 +72,7 @@ extern "C" {
// NNG_PROTOCOL_NUMBER is used by protocol headers to calculate their
// protocol number from a major and minor number. Applications should
// probably not need to use this.
-#define NNG_PROTOCOL_NUMBER(maj, min) (((x) * 16) + (y))
+#define NNG_PROTOCOL_NUMBER(maj, min) (((x) *16) + (y))
// Types common to nng.
@@ -617,7 +617,7 @@ NNG_DECL void nng_aio_finish(nng_aio *, int);
// final argument is passed to the cancelfn. The final argument of the
// cancellation function is the error number (will not be zero) corresponding
// to the reason for cancellation, e.g. NNG_ETIMEDOUT or NNG_ECANCELED.
-typedef void (*nng_aio_cancelfn)(nng_aio *, void *, int);
+typedef void (*nng_aio_cancelfn)(nng_aio *, void *, int);
NNG_DECL void nng_aio_defer(nng_aio *, nng_aio_cancelfn, void *);
// nng_aio_sleep does a "sleeping" operation, basically does nothing
@@ -1466,6 +1466,91 @@ enum {
NNG_INIT_MAX_POLLER_THREADS,
};
+// Logging support.
+
+// Log levels. These correspond to RFC 5424 (syslog) levels.
+// NNG never only uses priorities 3 - 7.
+//
+// Note that LOG_EMER is 0, but we don't let applications submit'
+// such messages, so this is a useful value to prevent logging altogether.
+typedef enum nng_log_level {
+ NNG_LOG_NONE = 0, // used for filters only, NNG suppresses these
+ NNG_LOG_ERR = 3,
+ NNG_LOG_WARN = 4,
+ NNG_LOG_NOTICE = 5,
+ NNG_LOG_INFO = 6,
+ NNG_LOG_DEBUG = 7
+} nng_log_level;
+
+// Facilities. Also from RFC 5424.
+// Not all values are enumerated here. Values not enumerated here
+// should be assumed reserved for system use, and not available for
+// NNG or general applications.
+typedef enum nng_log_facility {
+ NNG_LOG_USER = 1,
+ NNG_LOG_DAEMON = 3,
+ NNG_LOG_AUTH = 10, // actually AUTHPRIV, for sensitive logs
+ NNG_LOG_LOCAL0 = 16,
+ NNG_LOG_LOCAL1 = 17,
+ NNG_LOG_LOCAL2 = 18,
+ NNG_LOG_LOCAL3 = 19,
+ NNG_LOG_LOCAL4 = 20,
+ NNG_LOG_LOCAL5 = 21,
+ NNG_LOG_LOCAL6 = 22,
+ NNG_LOG_LOCAL7 = 23,
+} nng_log_facility;
+
+// Logging function, which may be supplied by application code. Only
+// one logging function may be registered. The level and facility are
+// as above. The message ID is chosen by the submitter - internal NNG
+// messages will have MSGIDs starting with "NNG-". The MSGID should be
+// not more than 8 characters, though this is not a hard requirement.
+// Loggers are required ot make a copy of the msgid and message if required,
+// because the values will not be valid once the logger returns.
+typedef void (*nng_logger)(nng_log_level level, nng_log_facility facility,
+ const char *msgid, const char *msg);
+
+// Discard logger, simply throws logs away.
+extern void nng_null_logger(
+ nng_log_level, nng_log_facility, const char *, const char *);
+
+// Very simple, prints formatted messages to stderr.
+extern void nng_stderr_logger(
+ nng_log_level, nng_log_facility, const char *, const char *);
+
+// Performs an appropriate logging function for the system. On
+// POSIX systems it uses syslog(3). Details vary by system, and the
+// logging may be influenced by other APIs not provided by NNG, such as
+// openlog() for POSIX systems. This may be nng_stderr_logger on
+// other systems.
+extern void nng_system_logger(
+ nng_log_level, nng_log_facility, const char *, const char *);
+
+// Set the default facility to use when logging. NNG uses NNG_LOG_USER by
+// default.
+extern void nng_log_set_facility(nng_log_facility facility);
+
+// Set the default logging level. Use NNG_LOG_DEBUG to get everything.
+// Use NNG_LOG_NONE to prevent logging altogether. Logs that are less
+// severe (numeric level is higher) will be discarded.
+extern void nng_log_set_level(nng_log_level level);
+
+// Register a logger.
+extern void nng_log_set_logger(nng_logger logger);
+
+// Log a message. The msg is formatted using following arguments as per
+// sprintf. The msgid may be NULL.
+extern void nng_log_err(const char *msgid, const char *msg, ...);
+extern void nng_log_warn(const char *msgid, const char *msg, ...);
+extern void nng_log_notice(const char *msgid, const char *msg, ...);
+extern void nng_log_info(const char *msgid, const char *msg, ...);
+extern void nng_log_debug(const char *msgid, const char *msg, ...);
+
+// Log an authentication related message. These will use the NNG_LOG_AUTH
+// facility.
+extern void nng_log_auth(
+ nng_log_level level, const char *msgid, const char *msg, ...);
+
#ifdef __cplusplus
}
#endif
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 },
+};