[Nagiosplug-devel] Plugin output format

Holger Weiss holger at CIS.FU-Berlin.DE
Wed Aug 1 02:39:11 CEST 2007


* Holger Weiss <holger at CIS.FU-Berlin.DE> [2007-07-26 17:10]:
> * Ton Voon <ton.voon at altinity.com> [2007-07-26 15:49]:
> > My feeling is that if we can get a single function to return the
> > output, used by all our plugins, then it is easy to change the
> > removal of the status part on a global basis (configure time
> > option?). That's where the hard work is.
>
> Yes, that's how I would/will implement such a change, I just wanted to
> ask whether you're fine with changing the output format (by default or
> optionally) before discussing implementation details :-)

The attached patch provides two new ./configure options:

| $ ./configure --help | grep -A5 output-format
| --with-standard-output-format=FORMAT
|                         specify the standard plugin output FORMAT; %p, %s,
|                         %x, and %i will be replaced by the plugin name, the
|                         service name, the status string, and the information
|                         text, respectively; tabs or newlines can be inserted
|                         using \t or \n [default="%s %x: %i\n"]
| --with-verbose-output-format=FORMAT
|                         specify the verbose plugin output FORMAT; %p, %s,
|                         %x, and %i will be replaced by the plugin name, the
|                         service name, the status string, and the information
|                         text, respectively; tabs or newlines can be inserted
|                         using \t or \n [default="%s %x: %i\n"]

And it provides four new functions:

| void np_init_output(const char *progname,
|                     const char *servname,
|                     int verbosity,
|                     int flags);
| void np_die(int state, const char *fmt, ...);
| void np_verbose(int verbosity, const char *fmt, ...);
| int np_increment_verbosity(int by);

The idea is that on startup, every plugin calls something like

	np_init_output(argv[0], "HTTP", 0, 0);

which saves the program and service names as well as the verbosity level
into static variables within utils_base.c so that they can be used by
output functions later on.  The flags argument is currently unused.

The new np_die() function has the same interface as the old die(), but
it'll print the plugin output depending on the format string specified
via ./configure and, if a different format was specified for verbose
output, depending on the verbosity level.  By default, all output would
currently use the format specified in the development guidelines, that
is, "SERVICE STATUS: Information text."  If we leave it that way, the
only visible change would be that the plugins then actually _use_ the
documented format consistently :-)  On the other hand, you (Thomas,
Matthias, Ton) seemed to prefer removing the SERVICE from the output,
too?  Should we do that by default?  I don't really care.  In any case,
we could do so easily in the future.

Apart from that,

	np_verbose(2, "foo %s", "bar");

can be used instead of something like:

	if (verbose >= 2)
		printf("foo %s\n", "bar");

np_verbose() would currently append a '\n' to the output, not sure yet
whether that's a good idea.  Optionally,

	np_increment_verbosity(1);

can be called within the 'v' case of the plugin's getopt(3) loop (or
anywhere else, of course) in order to increment the verbosity level by 1
for every "-v" flag the user specified.  np_increment_verbosity(0) would
return the current verbosity level without changing it, in case a plugin
is interested in the current level.

A "%m" within the format string for both np_die() or np_verbose() will
be replaced by the current strerror(errno), just as syslog(3) does it.
Usage example:

	if ((fp = fopen(file, "r")) == NULL)
		np_die(STATE_CRITICAL, "Cannot open %s: %m", file);

If nobody objects, I'll commit the changes and start converting the
plugins to use the new functions this weekend.  IMO, the main advantages
would be that the plugin output is easily changeable/configurable and
that both np_verbose() and np_die() can be called from within library
functions.

Any comments?

Holger

-- 
PGP fingerprint:  F1F0 9071 8084 A426 DD59  9839 59D3 F3A1 B8B5 D3DE
-------------- next part --------------
Index: lib/utils_base.h
===================================================================
--- lib/utils_base.h	(revision 1769)
+++ lib/utils_base.h	(working copy)
@@ -37,6 +37,14 @@
 
 char *np_escaped_string (const char *);
 
+void np_init_output(const char *, const char *, int, int);
+int np_increment_verbosity(int);
+void np_verbose(int, const char *, ...)
+	__attribute__((format(printf, 2, 3)));
+void np_die(int, const char *, ...)
+	__attribute__((noreturn, format(printf, 2, 3)));
+
+/* TODO: die() can be removed as soon as all plugins use np_die() instead. */
 void die (int, const char *, ...) __attribute__((noreturn,format(printf, 2, 3)));
 
 /* Return codes for _set_thresholds */
@@ -50,4 +58,6 @@
  * code from the above function, in case it's helpful for testing */
 int np_warn_if_not_root(void);
 
+const char *state_text(int);
+
 #endif /* _UTILS_BASE_ */
Index: lib/utils_base.c
===================================================================
--- lib/utils_base.c	(revision 1769)
+++ lib/utils_base.c	(working copy)
@@ -12,11 +12,133 @@
  * $Date$
  ****************************************************************************/
 
+#if HAVE_LIBGEN_H
+#include <libgen.h>	/* basename(3) */
+#endif
 #include <stdarg.h>
 #include "common.h"
 #include "utils_base.h"
 
+static char *np_insert_syserr(const char *);
+
+extern int errno;
+static int np_verbosity = -1;
+static const char *np_progname = NULL;
+static const char *np_servname = NULL;
+
+/*
+ * Set static variables for use in output functions.  Usually, argv[0] may be
+ * used as progname, since we call basename(3) ourselves.  If a verbosity value
+ * of -1 is specified, the np_verbosity won't be set.  This allows for setting
+ * the verbosity via np_increment_verbosity() before calling np_init_output().
+ * Currently, no flags are implemented.
+ */
 void
+np_init_output(const char *progname, const char *servname, int verbosity,
+               int flags __attribute__((unused)))
+{
+	static char pathbuf[128], progbuf[128], servbuf[32];
+
+#if HAVE_BASENAME
+	/*
+	 * Copy the progname into a temporary buffer in order to cope with
+	 * basename(3) implementations which modify their argument.  TODO: Maybe
+	 * we should implement an np_basename()?  Gnulib's base_name() dies on
+	 * error, writing a message to stderr, which is probably not what we
+	 * want?  Once we have some replacement, the libgen-/basename(3)-related
+	 * checks can be removed from configure.in.
+	 */
+	strncpy(pathbuf, progname, sizeof(progbuf) - 1);
+	pathbuf[sizeof(pathbuf) - 1] = '\0';
+	progname = basename(pathbuf);
+#endif
+	strncpy(progbuf, progname, sizeof(progbuf) - 1);
+	progbuf[sizeof(progbuf) - 1] = '\0';
+	strncpy(servbuf, servname, sizeof(servbuf) - 1);
+	servbuf[sizeof(servbuf) - 1] = '\0';
+
+	/* Set the static variables. */
+	np_progname = progbuf;
+	np_servname = servbuf;
+	if (verbosity != -1)
+		np_verbosity = verbosity;
+}
+
+int
+np_increment_verbosity(int by)
+{
+	if (np_verbosity == -1)
+		np_verbosity = by;
+	else
+		np_verbosity += by;
+
+	return np_verbosity;
+}
+
+void
+np_verbose(int verbosity, const char *fmt, ...)
+{
+	va_list ap;
+
+	if (np_verbosity != -1 && verbosity >= np_verbosity) {
+		fmt = np_insert_syserr(fmt);
+		va_start(ap, fmt);
+		vprintf(fmt, ap);
+		va_end(ap);
+		putchar('\n');
+	}
+}
+
+void
+np_die(int status, const char *fmt, ...)
+{
+	va_list ap;
+	const char *p;
+
+	for (p = (np_verbosity > 0) ?
+	    VERBOSE_OUTPUT_FORMAT : STANDARD_OUTPUT_FORMAT;
+	    *p != '\0'; p++) {
+		if (*p == '%') {
+			if (*++p == '\0')
+				break;
+			switch (*p) {
+			case 'i':
+				fmt = np_insert_syserr(fmt);
+				va_start(ap, fmt);
+				vprintf(fmt, ap);
+				va_end(ap);
+				continue;
+			case 'p':
+				if (np_progname != NULL)
+					fputs(np_progname, stdout);
+				continue;
+			case 's':
+				if (np_servname != NULL)
+					fputs(np_servname, stdout);
+				continue;
+			case 'x':
+				fputs(state_text(status), stdout);
+				continue;
+			}
+		} else if (*p == '\\') {
+			if (*++p == '\0')
+				break;
+			switch (*p) {
+			case 'n':
+				putchar('\n');
+				continue;
+			case 't':
+				putchar('\t');
+				continue;
+			}
+		}
+		putchar(*p);
+	}
+	exit(status);
+}
+
+/* TODO: die() can be removed as soon as all plugins use np_die() instead. */
+void
 die (int result, const char *fmt, ...)
 {
 	va_list ap;
@@ -243,3 +365,65 @@
 	}
 	return status;
 }
+
+const char *
+state_text(int result)
+{
+	switch (result) {
+	case STATE_OK:
+		return "OK";
+	case STATE_WARNING:
+		return "WARNING";
+	case STATE_CRITICAL:
+		return "CRITICAL";
+	case STATE_DEPENDENT:
+		return "DEPENDENT";
+	default:
+		return "UNKNOWN";
+	}
+}
+
+/*
+ * Replace occurrences of "%m" by strerror(errno).  Other printf(3)-style
+ * conversion specifications will be copied verbatim, including "%%", even if
+ * followed by an "m".  Returns a static buffer in order to not fail on memory
+ * allocation error.
+ */
+static char *
+np_insert_syserr(const char *buf)
+{
+	static char newbuf[1024];
+	char *errstr = strerror(errno);
+	size_t errlen = strlen(errstr);
+	size_t copylen;
+	unsigned i, j;
+
+	for (i = 0, j = 0; buf[i] != '\0' && j < sizeof(newbuf) - 2; i++, j++) {
+		if (buf[i] == '%') {
+			if (buf[++i] == 'm') {
+				copylen = (errlen > sizeof(newbuf) - j - 1) ?
+				    sizeof(newbuf) - j - 1: errlen;
+				memcpy(&newbuf[j], errstr, copylen);
+				/*
+				 * As we'll increment j by 1 after the iteration
+				 * anyway, we only increment j by the number of
+				 * copied bytes - 1.
+				 */
+				j += copylen - 1;
+				continue;
+			} else {
+				/*
+				 * The possibility to run into this block is the
+				 * reason we checked for j < sizeof(newbuf) - 2
+				 * instead of j < sizeof(newbuf) - 1.
+				 */
+				newbuf[j++] = '%';
+				if (buf[i] == '\0')
+					break;
+			}
+		}
+		newbuf[j] = buf[i];
+	}
+	newbuf[j] = '\0';
+	return newbuf;
+}
Index: plugins/utils.c
===================================================================
--- plugins/utils.c	(revision 1769)
+++ plugins/utils.c	(working copy)
@@ -121,23 +121,6 @@
 	         command_name, clean_revstring(revision_string), PACKAGE, VERSION);
 }
 
-const char *
-state_text (int result)
-{
-	switch (result) {
-	case STATE_OK:
-		return "OK";
-	case STATE_WARNING:
-		return "WARNING";
-	case STATE_CRITICAL:
-		return "CRITICAL";
-	case STATE_DEPENDENT:
-		return "DEPENDENT";
-	default:
-		return "UNKNOWN";
-	}
-}
-
 void
 timeout_alarm_handler (int signo)
 {
Index: plugins/utils.h
===================================================================
--- plugins/utils.h	(revision 1769)
+++ plugins/utils.h	(working copy)
@@ -84,8 +84,6 @@
 void usage5(void) __attribute__((noreturn));
 void usage_va(const char *fmt, ...) __attribute__((noreturn));
 
-const char *state_text (int);
-
 #define max(a,b) (((a)>(b))?(a):(b))
 #define min(a,b) (((a)<(b))?(a):(b))
 
Index: configure.in
===================================================================
--- configure.in	(revision 1769)
+++ configure.in	(working copy)
@@ -40,6 +40,39 @@
 INSTALL_STRIP_PROGRAM="$INSTALL_STRIP_PROGRAM $extra_install_args"
 AC_SUBST(INSTALL)
 
+dnl Configure the plugin output format
+default_output_format="%s %x: %i\n"
+AC_ARG_WITH([standard_output_format],
+	[AS_HELP_STRING([--with-standard-output-format=FORMAT],
+		[specify the standard plugin output FORMAT; %p, %s, %x, and %i
+		 will be replaced by the plugin name, the service name, the
+		 status string, and the information text, respectively; tabs or
+		 newlines can be inserted using \t or \n
+		 @<:@default="%s %x: %i\n"@:>@])],
+	[standard_output_format=$withval],
+	[standard_output_format="yes"])
+AC_ARG_WITH([verbose_output_format],
+	[AS_HELP_STRING([--with-verbose-output-format=FORMAT],
+		[specify the verbose plugin output FORMAT; %p, %s, %x, and %i
+		 will be replaced by the plugin name, the service name, the
+		 status string, and the information text, respectively; tabs or
+		 newlines can be inserted using \t or \n
+		 @<:@default="%s %x: %i\n"@:>@])],
+	[verbose_output_format=$withval],
+	[verbose_output_format="yes"])
+AS_IF([test "$standard_output_format" = yes],
+		[standard_output_format=$default_output_format],
+	[test "$standard_output_format" = no],
+		[standard_output_format=""],
+	[test "$verbose_output_format" = yes],
+		[verbose_output_format=$default_output_format],
+	[test "$verbose_output_format" = no],
+		[verbose_output_format=""])
+AC_DEFINE_UNQUOTED([STANDARD_OUTPUT_FORMAT], ["$standard_output_format"],
+	[Define the standard plugin output format.])
+AC_DEFINE_UNQUOTED([VERBOSE_OUTPUT_FORMAT], ["$standard_output_format"],
+	[Define the verbose plugin output format.])
+
 AC_PROG_CC
 gl_EARLY
 AC_PROG_GCC_TRADITIONAL
@@ -147,6 +180,11 @@
 AC_CHECK_LIB(resolv,main,SOCKETLIBS="$SOCKETLIBS -lresolv")
 AC_SUBST(SOCKETLIBS)
 
+dnl check for basename(3) which needs -lgen on some systems (e.g. IRIX)
+AC_CHECK_HEADERS([libgen.h])
+AC_SEARCH_LIBS([basename], [gen])
+AC_CHECK_FUNCS([basename])
+
 dnl
 dnl check for math-related functions needing -lm
 AC_CHECK_HEADERS(math.h)


More information about the Devel mailing list