You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

658 lines
20 KiB
C

/***************************************************************************
* 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 <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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() */