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);