/*************************************************************************** * Common logging facility. * * This file is part of the miniSEED Library. * * Copyright (c) 2023 Chad Trabant, EarthScope Data Services * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ***************************************************************************/ #include #include #include #include #include "libmseed.h" void rloginit_int (MSLogParam *logp, void (*log_print) (const char *), const char *logprefix, void (*diag_print) (const char *), const char *errprefix, int maxmessages); int rlog_int (MSLogParam *logp, const char *function, int level, const char *format, va_list *varlist); int add_message_int (MSLogRegistry *logreg, const char *function, int level, const char *message); void print_message_int (MSLogParam *logp, int level, const char *message, char *terminator); /* Initialize the global logging parameters * * If not disabled by a defined LIBMSEED_NO_THREADING, use options for * thread-local storage. In this default case each thread will have * it's own "global" logging parameters initialized to the library * default settings. * * Windows has its own designation for TLS. * Otherwise, C11 defines the standardized _Thread_local storage-class. * Otherwise fallback to the commonly supported __thread keyword. */ #if !defined(LIBMSEED_NO_THREADING) #if defined(LMP_WIN) #define lm_thread_local __declspec( thread ) #elif __STDC_VERSION__ >= 201112L #define lm_thread_local _Thread_local #else #define lm_thread_local __thread #endif lm_thread_local MSLogParam gMSLogParam = MSLogParam_INITIALIZER; #else MSLogParam gMSLogParam = MSLogParam_INITIALIZER; #endif /**********************************************************************/ /** * @brief Initialize the global logging parameters. * * Any message printing functions indicated must except a single * argument, namely a string \c (const char *) that will contain the log * message. If the function pointers are NULL, defaults will be used. * * If the log and error prefixes have been set they will be pre-pended * to the message. If the prefixes are NULL, defaults will be used. * * If \a maxmessages is greater than zero, warning and error (level >= * 1) messages will be accumulated in a message registry. Once the * maximum number of messages have accumulated, the oldest messages * are discarded. Messages in the registry can be printed using * ms_rlog_emit() or cleared using ms_rlog_free(). * * @param[in] log_print Function to print log messages * @param[in] logprefix Prefix to add to log and diagnostic messages * @param[in] diag_print Function to print diagnostic and error messages * @param[in] errprefix Prefix to add to error messages * @param[in] maxmessages Maximum number of error/warning messages to store in registry * * \sa ms_rlog() * \sa ms_rlog_emit() * \sa ms_rlog_free() ***************************************************************************/ void ms_rloginit (void (*log_print) (const char *), const char *logprefix, void (*diag_print) (const char *), const char *errprefix, int maxmessages) { rloginit_int (&gMSLogParam, log_print, logprefix, diag_print, errprefix, maxmessages); } /* End of ms_rloginit() */ /**********************************************************************/ /** * @brief Initialize specified ::MSLogParam logging parameters. * * If the logging parameters have not been initialized (logp == NULL), * new parameter space will be allocated. * * Any message printing functions indicated must except a single * argument, namely a string \c (const char *) that will contain the log * message. If the function pointers are NULL, defaults will be used. * * If the log and error prefixes have been set they will be pre-pended * to the message. If the prefixes are NULL, defaults will be used. * * If \a maxmessages is greater than zero, warning and error (level >= * 1) messages will be accumulated in a message registry. Once the * maximum number of messages have accumulated, the oldest messages * are discarded. Messages in the registry can be printed using * ms_rlog_emit() or cleared using ms_rlog_free(). * * @param[in] logp ::MSLogParam logging parameters * @param[in] log_print Function to print log messages * @param[in] logprefix Prefix to add to log and diagnostic messages * @param[in] diag_print Function to print diagnostic and error messages * @param[in] errprefix Prefix to add to error messages * @param[in] maxmessages Maximum number of error/warning messages to store in registry * * @returns a pointer to the created/re-initialized MSLogParam struct * on success and NULL on error. * * \sa ms_rlog() * \sa ms_rlog_emit() * \sa ms_rlog_free() ***************************************************************************/ MSLogParam * ms_rloginit_l (MSLogParam *logp, void (*log_print) (const char *), const char *logprefix, void (*diag_print) (const char *), const char *errprefix, int maxmessages) { MSLogParam *llog; if (logp == NULL) { llog = (MSLogParam *)libmseed_memory.malloc (sizeof (MSLogParam)); if (llog == NULL) { ms_log (2, "Cannot allocate memory"); return NULL; } llog->log_print = NULL; llog->logprefix = NULL; llog->diag_print = NULL; llog->errprefix = NULL; llog->registry.maxmessages = 0; llog->registry.messagecnt = 0; llog->registry.messages = NULL; } else { llog = logp; } rloginit_int (llog, log_print, logprefix, diag_print, errprefix, maxmessages); return llog; } /* End of ms_rloginit_l() */ /**********************************************************************/ /** * @brief Initialize the logging subsystem. * * This function modifies the logging parameters in the supplied * ::MSLogParam. Use NULL for the function pointers or the prefixes if * they should not be changed from previously set or default values. * ***************************************************************************/ void rloginit_int (MSLogParam *logp, void (*log_print) (const char *), const char *logprefix, void (*diag_print) (const char *), const char *errprefix, int maxmessages) { if (!logp) return; if (log_print) logp->log_print = log_print; if (logprefix) { if (strlen (logprefix) >= MAX_LOG_MSG_LENGTH) { ms_log_l (logp, 2, "%s", "log message prefix is too large"); } else { logp->logprefix = logprefix; } } if (diag_print) logp->diag_print = diag_print; if (errprefix) { if (strlen (errprefix) >= MAX_LOG_MSG_LENGTH) { ms_log_l (logp, 2, "%s", "error message prefix is too large"); } else { logp->errprefix = errprefix; } } if (maxmessages >= 0) { logp->registry.maxmessages = maxmessages; } return; } /* End of rloginit_int() */ /**********************************************************************/ /** * @brief Register log message using global logging parameters. * * It is convenient to call this function via the ms_log() macro, * which sets the calling function automatically. * * Three message levels are recognized, see @ref logging-levels for * more information. * * This function builds the log/error message and passes to it to the * appropriate print function. If custom printing functions have not * been defined, messages will be printed with \c fprintf(), log * messages to \c stdout and error messages to \c stderr. * * If the log/error prefixes have been set they will be pre-pended to * the message. If no custom log prefix is set none will be included. * If no custom error prefix is set \c "Error: " will be included. * * A trailing newline character is for error messages is removed if * the message is added to the log registry. * * All messages will be truncated at the ::MAX_LOG_MSG_LENGTH, this * includes any prefix. * * @param[in] function Name of function registering log message * @param[in] level Message level * @param[in] format Message format in printf() style * @param[in] ... Message format variables * * @returns The number of characters formatted on success, and a * negative value on error.. ***************************************************************************/ int ms_rlog (const char *function, int level, const char *format, ...) { int retval; va_list varlist; va_start (varlist, format); retval = rlog_int (&gMSLogParam, function, level, format, &varlist); va_end (varlist); return retval; } /* End of ms_rlog() */ /**********************************************************************/ /** * @brief Register log message using specified logging parameters. * * It is convenient to call this function via the ms_log_l() macro, * which sets the calling function automatically. * * The function uses logging parameters specified in the supplied * ::MSLogParam. This reentrant capability allows using different * parameters in different parts of a program or different threads. * * Three message levels are recognized, see @ref logging-levels for * more information. * * This function builds the log/error message and passes to it to the * appropriate print function. If custom printing functions have not * been defined, messages will be printed with \c fprintf(), log * messages to \c stdout and error messages to \c stderr. * * If the log/error prefixes have been set they will be pre-pended to * the message. If no custom log prefix is set none will be included. * If no custom error prefix is set \c "Error: " will be included. * * A trailing newline character is for error messages is removed if * the message is added to the log registry. * * All messages will be truncated at the ::MAX_LOG_MSG_LENGTH, this * includes any prefix. * * @param[in] logp Pointer to ::MSLogParam to use for this message * @param[in] function Name of function registering log message * @param[in] level Message level * @param[in] format Message format in printf() style * @param[in] ... Message format variables * * @returns The number of characters formatted on success, and a * negative value on error. ***************************************************************************/ int ms_rlog_l (MSLogParam *logp, const char *function, int level, const char *format, ...) { int retval; va_list varlist; if (!logp) logp = &gMSLogParam; va_start (varlist, format); retval = rlog_int (logp, function, level, format, &varlist); va_end (varlist); return retval; } /* End of ms_rlog_l() */ /**********************************************************************/ /** * @brief Log message using specified logging parameters and \c va_list * * Trailing newline character is removed when added messages to the * registry. * * @param[in] logp Pointer to ::MSLogParam to use for this message * @param[in] function Name of function registering log message * @param[in] level Message level * @param[in] varlist Message in a \c va_list in printf() style * * @returns The number of characters formatted on success, and a * negative value on error. ***************************************************************************/ int rlog_int (MSLogParam *logp, const char *function, int level, const char *format, va_list *varlist) { char message[MAX_LOG_MSG_LENGTH]; int presize = 0; int printed = 0; if (!logp) { fprintf (stderr, "%s() called without specifying log parameters", __func__); return -1; } message[0] = '\0'; if (level >= 2) /* Error message */ { if (logp->errprefix != NULL) { strncpy (message, logp->errprefix, MAX_LOG_MSG_LENGTH); message[MAX_LOG_MSG_LENGTH - 1] = '\0'; } else { strncpy (message, "Error: ", MAX_LOG_MSG_LENGTH); } presize = strlen (message); printed = vsnprintf (&message[presize], MAX_LOG_MSG_LENGTH - presize, format, *varlist); message[MAX_LOG_MSG_LENGTH - 1] = '\0'; } else if (level == 1) /* Diagnostic message */ { if (logp->logprefix != NULL) { strncpy (message, logp->logprefix, MAX_LOG_MSG_LENGTH); message[MAX_LOG_MSG_LENGTH - 1] = '\0'; } presize = strlen (message); printed = vsnprintf (&message[presize], MAX_LOG_MSG_LENGTH - presize, format, *varlist); message[MAX_LOG_MSG_LENGTH - 1] = '\0'; } else if (level == 0) /* Normal log message */ { if (logp->logprefix != NULL) { strncpy (message, logp->logprefix, MAX_LOG_MSG_LENGTH); message[MAX_LOG_MSG_LENGTH - 1] = '\0'; } presize = strlen (message); printed = vsnprintf (&message[presize], MAX_LOG_MSG_LENGTH - presize, format, *varlist); message[MAX_LOG_MSG_LENGTH - 1] = '\0'; } printed += presize; /* Add warning/error message to registry if enabled */ if (level >= 1 && logp->registry.maxmessages > 0) { /* Remove trailing newline if present */ if (message[printed - 1] == '\n') { message[printed - 1] = '\0'; printed -= 1; } add_message_int (&logp->registry, function, level, message); } else { print_message_int (logp, level, message, NULL); } return printed; } /* End of rlog_int() */ /**********************************************************************/ /** * @brief Add message to registry * * Add a message to the specified log registry. Earliest entries are * removed to remain within the specified maximum number of messsages. * * @param[in] logreg Pointer to ::MSLogRegistry to use for this message * @param[in] function Name of function generating the message * @param[in] level Message level * @param[in] message Message text * * @returns Zero on sucess and non-zero on error ***************************************************************************/ int add_message_int (MSLogRegistry *logreg, const char *function, int level, const char *message) { MSLogEntry *logentry = NULL; MSLogEntry *lognext = NULL; int count; if (!logreg || !message) return -1; /* Allocate new entry */ logentry = (MSLogEntry *)libmseed_memory.malloc (sizeof (MSLogEntry)); if (logentry == NULL) { fprintf (stderr, "%s(): Cannot allocate memory for log message\n", __func__); return -1; } /* Populate new entry */ logentry->level = level; if (function) { strncpy (logentry->function, function, sizeof (logentry->function)); logentry->function[sizeof (logentry->function) - 1] = '\0'; } else { logentry->function[0] = '\0'; } strncpy (logentry->message, message, sizeof (logentry->message)); logentry->message[sizeof (logentry->message) - 1] = '\0'; /* Add entry to registry */ logentry->next = logreg->messages; logreg->messages = logentry; logreg->messagecnt += 1; /* Remove earliest messages if more than maximum allowed */ if (logreg->messagecnt > logreg->maxmessages) { count = 0; logentry = logreg->messages; while (logentry) { lognext = logentry->next; count++; if (count == logreg->maxmessages) logentry->next = NULL; if (count > logreg->maxmessages) free (logentry); logentry = lognext; } } return 0; } /* End of add_message_int() */ /**********************************************************************/ /** * @brief Send message to print functions * * @param[in] logp Pointer to ::MSLogParam appropriate for this message * @param[in] level Message level * @param[in] message Message to print * * @returns Zero on success, and a negative value on error. ***************************************************************************/ void print_message_int (MSLogParam *logp, int level, const char *message, char *terminator) { if (!logp || !message) return; if (level >= 1) /* Error or warning message */ { if (logp->diag_print != NULL) { logp->diag_print (message); } else { fprintf (stderr, "%s%s", message, (terminator) ? terminator : ""); } } else if (level == 0) /* Normal log message */ { if (logp->log_print != NULL) { logp->log_print (message); } else { fprintf (stdout, "%s%s", message, (terminator) ? terminator : ""); } } } /* End of print_message_int() */ /**********************************************************************/ /** * @brief Emit, aka send to print functions, messages from log registry * * Emit messages from the log registry, using the printing functions * identified by the ::MSLogParam. * * Messages are printed in order from earliest to latest. * * The maximum number messages to emit, from most recent to earliest, * can be limited using \a count. If the value is 0 all messages are * emitted. If this limit is used and messages are left in the * registry, it is highly recommended to either emit them soon or * clear them with ms_rlog_free(). A common pattern would be to emit * the last message (e.g. \a count of 1) and immediately free * (discard) any remaining messages. * * @param[in] logp ::MSLogParam for this message or NULL for global parameters * @param[in] count Number of messages to emit, 0 to emit all messages * @param[in] context If non-zero include context by prefixing the function name (if available) * * @returns The number of message emitted on success, and a negative * value on error. * * \sa ms_rloginit() * \sa ms_rlog_free() ***************************************************************************/ int ms_rlog_emit (MSLogParam *logp, int count, int context) { MSLogEntry *logentry = NULL; MSLogEntry *logprint = NULL; char local_message[MAX_LOG_MSG_LENGTH]; char *message = NULL; int emit = (count > 0) ? count : -1; if (!logp) logp = &gMSLogParam; /* Pop off count entries (or all if count <= 0), and invert into print list */ logentry = logp->registry.messages; while (logentry && emit) { logp->registry.messages = logentry->next; logentry->next = logprint; logprint = logentry; if (emit > 0) emit--; logentry = logp->registry.messages; } /* Print and free entries */ logentry = logprint; while (logprint) { /* Add function name to message if requested and present */ if (context && logprint->function[0] != '\0') { snprintf (local_message, sizeof(local_message), "%s() %.*s", logprint->function, (int)(MAX_LOG_MSG_LENGTH - sizeof(logprint->function) - 3), logprint->message); message = local_message; } else { message = logprint->message; } print_message_int (logp, logprint->level, message, "\n"); logentry = logprint->next; free (logprint); logprint = logentry; } return 0; } /* End of ms_rlog_emit() */ /**********************************************************************/ /** * @brief Free, without emitting, all messages from log registry * * @param[in] logp ::MSLogParam for this message or NULL for global parameters * * @returns The number of message freed on success, and a negative * value on error. ***************************************************************************/ int ms_rlog_free (MSLogParam *logp) { MSLogEntry *logentry = NULL; int freed = 0; if (!logp) logp = &gMSLogParam; logentry = logp->registry.messages; while (logentry) { freed++; logp->registry.messages = logentry->next; free (logentry); logentry = logp->registry.messages; } return freed; } /* End of ms_rlog_free() */