diff --git a/README.md b/README.md index 5e4863e..4086035 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Author: Attila Kovacs Last Updated: 18 September 2024 + ## Table of Contents - [Introduction](#introduction) @@ -39,6 +40,7 @@ Last Updated: 18 September 2024 - [Lazy pulling (high-frequency queries)](#lazy-pulling) - [Pipelined pulls (high volume queries)](#pipelined-pulls) - [Custom notification and update handling](#notifications) + - [Program status / error messages via SMA-X](#status-messages) - [Optional metadata](#optional-metadata) - [Error handling](#error-handling) - [Debug support](#debug-support) @@ -686,8 +688,6 @@ all previously sumbitted requests have been collected. You can do that with: } ``` - - ------------------------------------------------------------------------------ @@ -815,10 +815,111 @@ callback information on a queue, and then spawn or notify a separate thread to p background, including discarding the copied data if it's no longer needed. Alternatively, you can launch a decicated processor thread early on, and iside it wait for the updates before executing some complex action. The choice is yours. + + +------------------------------------------------------------------------------ + + +## Program status / error messages via SMA-X + + - [Broadcasting status messages from an application](#broadcasting-messages) + - [Processing program messages](#processing-messages) + +SMA-X also provides a standard for reporting porgram status, warning, and error messages via the Redis PUB/SUB +infrastructure. + + +### Broadcasting status messages from an application + +Broadcasting program messages to SMA-X is very simple using a set of dedicated messaging functions by message +type. These are: + + | __smax_clib__ function | Description | + | `smaxSendStatus(const char *msg, ...)` | sends a status message | + | `smaxSendInfo(const char *msg, ...)` | sends an informational message | + | `smaxSendDetail(const char *msg, ...)` | sends optional status/information detail | + | `smaxSendDebug(const char *msg, ...)` | sends a debugging messages | + | `smaxSendWarning(const char *msg, ...)` | sends a warning message | + | `smaxSendError(const char *msg, ...)` | sends an error message | + | `smaxSendProgress(double fraction, const char *msg, ...)` | sends a progress update and message | + +All the above methods work like `printf()`, and can take additional parameters corresponding to the format specifiers +contained in the `msg` argument. + +By default, the messages are sent under the canonocal program name (i.e. set by `_progname` on GNU/Linux systems) +that produced the message. You can override that, and define a custom sender ID for your status messages, by calling +`smaxSetMessageSenderID()` prior to broadcasting, e.g.: + +```c + // Set out sender ID to "my_program_id" + smaxSetMessageSenderID("my_program_id"); + ... + + // Broadcast a warning message for "my_program_id" + int status = smaxSendWarning("Something did not work" %s", explanation); +``` + + +### Processing program messages + +On the receiving end, other applications can process such program messages, for a selection of hosts, programs, and +message types. You need to prepare you message processor function(s) first, e.g.: + +```c + void my_message_processor(XMessage *m) { + printf("Received %s message from %s: %s\n", m->type, m->prog, m->text); + } -### Status / error messages +``` + +The processor function does not return any value, since it is called by a background thread, which does not check for +return status. The `XMessage` type, a pointer to which is the sole argument of the processor, is defined in `smax.h` +as: + + +```c + typedef struct { + char *host; // Host where message originated from + char *prog; // Originator program name + char *type; // Message type, e.g. "info", "detail", "warning", "error" + char *text; // Message body (with timestamp stripped). + double timestamp; // Message timestamp, if available (otherwise 0.0) + } XMessage; +``` + +Once you have your message consumer function, you can set it to be called for messages from select hosts, programs, and/or +select message types, using `smaxAddMessageProcessor()`, e.g.: +```c + // Will call my_message_procesor for all messages coming from "my_program_id" from all hosts. + // The return ID number (if > 0) can be used later to uniquely identify the processor with the set of selection + // parameters it is used with. So make sure to keep it handy for later. + int id = smaxAddMessageProcessor("*", "my_program_id", "*", my_message_processor); + if (id < 0) { + // Oops that did not work as planned. + ... + } +``` + +Each string argument (`host`, `prog`, and `type`) may take an asterisk (`"*"`) or `NULL` as the argument to indicate that +the processor function should be called for incoming messages for all values for the given parameter. + +The processor function can also inspect what type of message it received by comparing the `XMessage` `type` value against +one of the pre-defined constant expressions in `smax.h`: + + | `XMessage` `type` | Description | + | -------------------------- | ----------------------------------------------- | + | `SMAX_MSG_STATUS` | Program status update | + | `SMAX_MSG_INFO` | Informational program message | + | `SMAX_MSG_DETAIL` | Program detail (i.e. verbose messages) | + | `SMAX_MSG_PROGRESS` | Program detail (i.e. verbose messages) | + | `SMAX_MSG_DEBUG` | Program debug messages (also e.g. traces) | + | `SMAX_MSG_WARNING` | Program warnings | + | `SMAX_MSG_ERROR` | Program errors | + +Once you no longer need to process messages by the given processor function, you can remove it from the call list by +passing its ID number (<0) to `smaxRemoveMessageProcessor()`. ------------------------------------------------------------------------------ @@ -832,9 +933,6 @@ yours. ### Physical units -#### Coordinate systems - - ----------------------------------------------------------------------------- diff --git a/include/smax.h b/include/smax.h index f94b1a8..6be554b 100644 --- a/include/smax.h +++ b/include/smax.h @@ -296,13 +296,13 @@ int smaxAddSubscriber(const char *stem, RedisSubscriberCall f); int smaxRemoveSubscribers(RedisSubscriberCall f); // Messages ---------------------------------------------------> -int smaxSendStatus(const char *msg); -int smaxSendInfo(const char *msg); -int smaxSendDetail(const char *msg); -int smaxSendDebug(const char *msg); -int smaxSendWarning(const char *msg); -int smaxSendError(const char *msg); -int smaxSendProgress(double fraction, const char *msg); +int smaxSendStatus(const char *msg, ...); +int smaxSendInfo(const char *msg, ...); +int smaxSendDetail(const char *msg, ...); +int smaxSendDebug(const char *msg, ...); +int smaxSendWarning(const char *msg, ...); +int smaxSendError(const char *msg, ...); +int smaxSendProgress(double fraction, const char *msg, ...); int smaxAddMessageProcessor(const char *host, const char *prog, const char *type, void (*f)(XMessage *)); int smaxAddDefaultMessageProcessor(const char *host, const char *prog, const char *type); int smaxRemoveMessageProcessor(int id); diff --git a/src/smax-messages.c b/src/smax-messages.c index 85db178..f9caf90 100644 --- a/src/smax-messages.c +++ b/src/smax-messages.c @@ -8,11 +8,22 @@ * Simple API for sending and receiving program broadcast messages through SMA-X. */ + +// We'll use gcc major version as a proy for the glibc library to decide which feature macro to use. +// gcc 5.1 was released 2015-04-22... +#if __GNUC__ >= 5 +# define _ISOC99_SOURCE ///< vsnprintf() feature macro starting glibc 2.20 (2014-09-08) +#else +# define _BSD_SOURCE ///< vsnprinf() feature macro for glibc <= 2.19 +#endif + + #include #include #include #include #include +#include #include "smax-private.h" @@ -40,37 +51,41 @@ static int nextID; static void ProcessMessage(const char *pattern, const char *channel, const char *msg, long length); -static int SendMessage(const char *type, const char *text) { +static int SendMessage(const char *type, const char *text, va_list varg) { static const char *fn = "SendMessage"; + char *msg; + const char *id = senderID ? senderID : smaxGetProgramID(); char *channel; - char *tsmsg; int n; if(!type) return x_error(X_NULL, EINVAL, fn, "type parameter is NULL"); if(!text) return x_error(X_NULL, EINVAL, fn, "text parameter is NULL"); + if(strlen(text) > sizeof(msg) - X_TIMESTAMP_LENGTH - 1) x_error(X_NULL, EINVAL, fn, "text message is too long: >= %d bytes", strlen(text)); + n = sizeof(MESSAGES_PREFIX) + strlen(id) + X_SEP_LENGTH + strlen(type); channel = malloc(n); - if(!channel) return x_error(X_NULL, errno, fn, "malloc() error (%d bytes)", n); + if(!channel) return x_error(X_NULL, errno, fn, "malloc() error (channel: %d bytes)", n); + + sprintf(channel, MESSAGES_PREFIX "%s" X_SEP "%s", id, type); - n = strlen(text) + X_TIMESTAMP_LENGTH + 3; - tsmsg = malloc(n); - if(!tsmsg) { + n = vsnprintf(NULL, 0, text, varg); + msg = (char *) malloc(n + X_TIMESTAMP_LENGTH + 1); + if(!msg) { free(channel); - return x_error(X_NULL, errno, fn, "malloc() error (%d bytes)", n); + return x_error(X_NULL, errno, fn, "malloc() error (msg: %d bytes)", n); } - sprintf(channel, MESSAGES_PREFIX "%s" X_SEP "%s", id, type); - n = sprintf(tsmsg, "%s @", text); - smaxTimestamp(&tsmsg[n]); + n = vsnprintf(msg, n, text, varg); + smaxTimestamp(&msg[n]); - n = redisxNotify(smaxGetRedis(), channel, tsmsg); + n = redisxNotify(smaxGetRedis(), channel, msg); if(n > 0) n = X_FAILURE; + free(msg); free(channel); - free(tsmsg); prop_error(fn, n); @@ -98,98 +113,144 @@ void smaxSetMessageSenderID(const char *id) { } /** - * Broadcast a program status update via SMA-X. + * Broadcast a program status update via SMA-X. Works just like `printf()`. * - * @param msg Message text + * @param msg Message text (may include format specifications for additional vararg parameters) * @return X_SUCCESS (0), or else an X error. * * @sa sendInfo() */ -int smaxSendStatus(const char *msg) { - prop_error("smaxSendStatus", SendMessage(SMAX_MSG_STATUS, msg)); + +int smaxSendStatus(const char *msg, ...) { + va_list varg; + int status; + + va_start(varg, msg); + status = SendMessage(SMAX_MSG_STATUS, msg, varg); + va_end(varg); + + prop_error("smaxSendDetail", status); return X_SUCCESS; } /** * Broadcast an informational message via SMA-X. These should be confirmations or essential * information reported back to users. Non-essential information should be sent with - * sendDetail() instead. + * sendDetail() instead. Works just like `printf()`. * - * @param msg Message text + * @param msg Message text (may include format specifications for additional vararg parameters) * @return X_SUCCESS (0), or else an X error. * * @sa sendDetail() * @sa sendStatus() */ -int smaxSendInfo(const char *msg) { - prop_error("smaxSendInfo", SendMessage(SMAX_MSG_INFO, msg)); +int smaxSendInfo(const char *msg, ...) { + va_list varg; + int status; + + va_start(varg, msg); + status = SendMessage(SMAX_MSG_INFO, msg, varg); + va_end(varg); + + prop_error("smaxSendDetail", status); return X_SUCCESS; } /** - * Broadcast non-essential verbose informational detail via SMA-X. + * Broadcast non-essential verbose informational detail via SMA-X. Works just like `printf()`. * - * @param msg Message text + * @param msg Message text (may include format specifications for additional vararg parameters) * @return X_SUCCESS (0), or else an X error. */ -int smaxSendDetail(const char *msg) { - prop_error("smaxSendDetail", SendMessage(SMAX_MSG_DETAIL, msg)); +int smaxSendDetail(const char *msg, ...) { + va_list varg; + int status; + + va_start(varg, msg); + status = SendMessage(SMAX_MSG_DETAIL, msg, varg); + va_end(varg); + + prop_error("smaxSendDetail", status); return X_SUCCESS; } /** - * Broadcast a debugging message via SMA-X (e.g. program traces). + * Broadcast a debugging message via SMA-X (e.g. program traces). Works just like `printf()`. * - * @param msg Message text + * @param msg Message text (may include format specifications for additional vararg parameters) * @return X_SUCCESS (0), or else an X error. */ -int smaxSendDebug(const char *msg) { - prop_error("smaxSendDEbug", SendMessage(SMAX_MSG_DEBUG, msg)); +int smaxSendDebug(const char *msg, ...) { + va_list varg; + int status; + + va_start(varg, msg); + status = SendMessage(SMAX_MSG_DEBUG, msg, varg); + va_end(varg); + + prop_error("smaxSendDetail", status); return X_SUCCESS; } /** * Broadcast a warning message via SMA-X. Warnings should be used for any potentially * problematic issues that nonetheless do not impair program functionality. + * Works just like `printf()`. * - * @param msg Message text + * @param msg Message text (may include format specifications for additional vararg parameters) * @return X_SUCCESS (0), or else an X error. * * @sa smaxSendError(); * @sa smaxSendDebug(); * */ -int smaxSendWarning(const char *msg) { - prop_error("smaxSendWarning", SendMessage(SMAX_MSG_WARNING, msg)); +int smaxSendWarning(const char *msg, ...) { + va_list varg; + int status; + + va_start(varg, msg); + status = SendMessage(SMAX_MSG_WARNING, msg, varg); + va_end(varg); + + prop_error("smaxSendDetail", status); return X_SUCCESS; } /** * Broadcast an error message via SMA-X. Errors should be used for an issues - * that impair program functionality. + * that impair program functionality. Works just like `printf()`. * - * @param msg Message text + * @param msg Message text (may include format specifications for additional vararg parameters) * @return X_SUCCESS (0), or else an X error. * * @sa smaxSendWarning(); * @sa smaxSendDebug(); * */ -int smaxSendError(const char *msg) { - prop_error("smaxSendError", SendMessage(SMAX_MSG_ERROR, msg)); +int smaxSendError(const char *msg, ...) { + va_list varg; + int status; + + va_start(varg, msg); + status = SendMessage(SMAX_MSG_ERROR, msg, varg); + va_end(varg); + + prop_error("smaxSendDetail", status); return X_SUCCESS; } /** - * Broadcast a progress update over SMA-X. + * Broadcast a progress update over SMA-X. Apart from the progress fraction argument, it works just like + * `printf()`. * * @param fraction (0.0:1.0) Completion fraction. - * @param msg Message text + * @param msg Message text (may include format specifications for additional vararg parameters) * @return X_SUCCESS (0), or else an X error. */ -int smaxSendProgress(double fraction, const char *msg) { +int smaxSendProgress(double fraction, const char *msg, ...) { static const char *fn = "smaxSendProgress"; + va_list varg; char *progress; int result; @@ -203,7 +264,10 @@ int smaxSendProgress(double fraction, const char *msg) { sprintf(progress, "%.1f %s", (100.0 * fraction), msg); - result = SendMessage(SMAX_MSG_DETAIL, progress); + va_start(varg, msg); + result = SendMessage(SMAX_MSG_DETAIL, progress, varg); + va_end(varg); + free(progress); prop_error(fn, result);