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
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() */
|