]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
logger: add --sd-id and -sd-param
authorKarel Zak <kzak@redhat.com>
Thu, 1 Oct 2015 12:48:15 +0000 (14:48 +0200)
committerKarel Zak <kzak@redhat.com>
Thu, 1 Oct 2015 12:48:15 +0000 (14:48 +0200)
This patch add support for RFC 5424 structured data elements. For
example:

     logger --rfc5424 --sd-id zoo@123                \
                      --sd-param tiger=\"hungry\"    \
                      --sd-param zebra=\"running\"   \
                      --sd-id manager@123            \
                      --sd-param onMeeting=\"yes\"   \
                      "this is message"

produces:

     <13>1 2015-10-01T14:07:59.168662+02:00 ws kzak - - [timeQuality tzKnown="1" isSynced="1" syncAccuracy="218616"][zoo@123 tiger="hungry" zebra="running"][manager@123 onMeeting="yes"] this is message

Signed-off-by: Karel Zak <kzak@redhat.com>
misc-utils/Makemodule.am
misc-utils/logger.1
misc-utils/logger.c

index e801611721a93915bcdbe0966251cf1318353058..e017952a89fb88999c1260b083e543d218181e60 100644 (file)
@@ -21,7 +21,7 @@ endif # BUILD_CAL
 if BUILD_LOGGER
 usrbin_exec_PROGRAMS += logger
 dist_man_MANS += misc-utils/logger.1
-logger_SOURCES = misc-utils/logger.c lib/strutils.c
+logger_SOURCES = misc-utils/logger.c lib/strutils.c lib/strv.c
 if HAVE_SYSTEMD
 logger_LDADD = $(SYSTEMD_LIBS) $(SYSTEMD_DAEMON_LIBS) $(SYSTEMD_JOURNAL_LIBS)
 logger_CFLAGS = $(SYSTEMD_CFLAGS) $(SYSTEMD_DAEMON_CFLAGS) $(SYSTEMD_JOURNAL_CFLAGS)
index 5deb3f0a1f6f8610427531d1ceec9d3448e01ecd..fe525042f775552906a2e717a51608631c50833e 100644 (file)
@@ -171,12 +171,16 @@ Use the RFC 3164 BSD syslog protocol to submit messages to a remote server.
 Use the RFC 5424 syslog protocol to submit messages to a remote server.
 The optional \fIwithout\fR argument can be a comma-separated list of
 the following values: \fBnotq\fR, \fBnotime\fR, \fBnohost\fR.
+
 The \fBnotq\fR value suppresses the time-quality structured data
-from the submitted message.  (The time-quality information shows whether
+from the submitted message.  The time-quality information shows whether
 the local clock was synchronized plus the maximum number of microseconds
-the timestamp might be off.)  The \fBnotime\fR value (which implies
-\fBnotq\fR) suppresses the complete sender timestamp that is in
+the timestamp might be off. The time-quality is also automatically suppressed when
+\fB\-\-sd\-id timeQuality\fR is specified.
+
+The \fBnotime\fR value (which implies \fBnotq\fR) suppresses the complete sender timestamp that is in
 ISO-8601 format, including microseconds and timezone.
+
 The \fBnohost\fR value suppresses
 .BR gethostname (2)
 information from the message header.
@@ -185,6 +189,42 @@ The RFC 5424 protocol has been the default for
 .B logger
 since version 2.26.
 .TP
+.BR "\-\-sd\-id " \fIname[@digits]
+Specifies structured data element ID for RFC 5424 message header. The option
+has to be used before \fB\-\-sd\-param\fR to introduce a new element. The
+number of structured data elements is unlimited. ID is case-sensitive and
+uniquely identify the type and purpose of the element. The same ID must not
+exist more than once in a message. The '@digit' is required for user defined non-standardized
+IDs.
+
+\fBlogger\fR currently generates \fBtimeQuality\fR standardized element only. RFC
+5424 also describes elements \fBorigin\fR (with params: ip, enterpriseId, software
+and swVersion) and \fBmeta\fR (with params: sequenceId, sysUpTime and language).
+These IDs may be specified without the @digit suffix.
+
+.TP
+.BR "\-\-sd\-param " \fIname="value"
+Specifies structured data element paramameter name and value. The option has to
+be used after \fB\-\-sd\-id\fR and may be specified more than once for the same
+element. Note that quotation marks are required and must be escaped on command
+line.
+.IP
+.nf
+\fB    logger --rfc5424 --sd-id zoo@123                \\
+\fB                     --sd-param tiger=\\"hungry\\"    \\
+\fB                     --sd-param zebra=\\"running\\"   \\
+\fB                     --sd-id manager@123            \\
+\fB                     --sd-param onMeeting=\\"yes\\"   \\
+\fB                     "this is message"
+.fi
+.IP
+produces:
+.IP
+.nf
+\fB  <13>1 2015-10-01T14:07:59.168662+02:00 ws kzak - - [timeQuality tzKnown="1" isSynced="1" syncAccuracy="218616"][zoo@123 tiger="hungry" zebra="running"][manager@123 onMeeting="yes"] this is message
+.fi
+.IP
+.TP
 .B \-\-octet\-count
 Use the RFC 6587 octet counting framing method for sending messages. When
 this option is not used, the default is no framing on UDP, and RFC6587
index e4a6c5aa125525ca5f02d73be2a7d54e9eae8f5b..c8ec8fa2dd67543a2da33a90255956e8c34df572 100644 (file)
@@ -59,6 +59,8 @@
 #include "pathnames.h"
 #include "strutils.h"
 #include "xalloc.h"
+#include "strv.h"
+#include "list.h"
 
 #define        SYSLOG_NAMES
 #include <syslog.h>
@@ -93,9 +95,19 @@ enum {
        OPT_MSGID,
        OPT_NOACT,
        OPT_ID,
+       OPT_STRUCTURED_DATA_ID,
+       OPT_STRUCTURED_DATA_PARAM,
        OPT_OCTET_COUNT
 };
 
+/* rfc5424 structured data */
+struct structured_data {
+       char *id;               /* SD-ID */
+       char **params;          /* array with SD-PARAMs */
+
+       struct list_head        sds;
+};
+
 struct logger_ctl {
        int fd;
        int pri;
@@ -108,7 +120,11 @@ struct logger_ctl {
        char *port;
        int socket_type;
        size_t max_message_size;
+       struct list_head user_sds;      /* user defined rfc5424 structured data */
+       struct list_head reserved_sds;  /* standard rfc5424 structured data */
+
        void (*syslogfp)(struct logger_ctl *ctl);
+
        unsigned int
                        unix_socket_errors:1,   /* whether to report or not errors */
                        noact:1,                /* do not write to sockets */
@@ -430,7 +446,177 @@ static void syslog_rfc3164_header(struct logger_ctl *const ctl)
        free(hostname);
 }
 
-/* Some field mappings may be controversal, thus I give the reason
+static inline struct list_head *get_user_structured_data(struct logger_ctl *ctl)
+{
+       return &ctl->user_sds;
+}
+
+static inline struct list_head *get_reserved_structured_data(struct logger_ctl *ctl)
+{
+       return &ctl->reserved_sds;
+}
+
+static int has_structured_data_id(struct list_head *ls, const char *id)
+{
+       struct list_head *p;
+
+       if (!ls || list_empty(ls))
+               return 0;
+
+       list_for_each(p, ls) {
+               struct structured_data *sd = list_entry(p, struct structured_data, sds);
+               if (sd->id && strcmp(sd->id, id) == 0)
+                       return 1;
+       }
+
+       return 0;
+}
+
+static void add_structured_data_id(struct list_head *ls, const char *id)
+{
+       struct structured_data *sd;
+
+       assert(id);
+
+       if (has_structured_data_id(ls, id))
+               errx(EXIT_FAILURE, _("structured data ID '%s' is not unique"), id);
+
+       sd = xcalloc(1, sizeof(*sd));
+       INIT_LIST_HEAD(&sd->sds);
+       sd->id = xstrdup(id);
+
+       list_add_tail(&sd->sds, ls);
+}
+
+static void add_structured_data_param(struct list_head *ls, const char *param)
+{
+       struct structured_data *sd;
+
+       if (list_empty(ls))
+               errx(EXIT_FAILURE, _("--sd-id no specified for --sd-param %s"), param);
+
+       assert(param);
+
+       sd = list_last_entry(ls, struct structured_data, sds);
+
+       if (strv_extend(&sd->params,  param))
+               err_oom();
+}
+
+static void add_structured_data_paramf(struct list_head *ls, const char *fmt, ...)
+{
+       struct structured_data *sd;
+       va_list ap;
+       int x;
+
+       assert(!list_empty(ls));
+       assert(fmt);
+
+       sd = list_last_entry(ls, struct structured_data, sds);
+       va_start(ap, fmt);
+       x = strv_extendv(&sd->params, fmt, ap);
+       va_end(ap);
+
+       if (x)
+               err_oom();
+}
+
+static char *strdup_structured_data(struct structured_data *sd)
+{
+       char *res, *tmp;
+
+       if (strv_isempty(sd->params))
+               return NULL;
+
+       xasprintf(&res, "[%s %s]", sd->id,
+                       (tmp = strv_join(sd->params, " ")));
+       free(tmp);
+       return res;
+}
+
+static char *strdup_structured_data_list(struct list_head *ls)
+{
+       struct list_head *p;
+       char *res = NULL;
+
+       list_for_each(p, ls) {
+               struct structured_data *sd = list_entry(p, struct structured_data, sds);
+               char *one = strdup_structured_data(sd);
+               char *tmp = res;
+
+               if (!one)
+                       continue;
+               res = strappend(tmp, one);
+               free(tmp);
+               free(one);
+       }
+
+       return res;
+}
+
+static char *get_structured_data_string(struct logger_ctl *ctl)
+{
+       char *sys = NULL, *usr = NULL, *res;
+
+       if (!list_empty(&ctl->reserved_sds))
+               sys = strdup_structured_data_list(&ctl->reserved_sds);
+       if (!list_empty(&ctl->user_sds))
+               usr = strdup_structured_data_list(&ctl->user_sds);
+
+       if (sys && usr) {
+               res = strappend(sys, usr);
+               free(sys);
+               free(usr);
+       } else
+               res = sys ? sys : usr;
+
+       return res;
+}
+
+static int valid_structured_data_param(const char *str)
+{
+       char *eq  = strchr(str, '='),
+            *qm1 = strchr(str, '"'),
+            *qm2 = qm1 ? strchr(qm1 + 1, '"') : NULL;
+
+       if (!eq || !qm1 || !qm2)                /* something is missing */
+               return 0;
+
+       /* foo="bar" */
+       return eq > str && eq < qm1 && eq + 1 == qm1 && qm1 < qm2 && *(qm2 + 1) == '\0';
+}
+
+/* SD-ID format:
+ *     name@<private enterprise number>, e.g., "ourSDID@32473"
+ */
+static int valid_structured_data_id(const char *str)
+{
+       char *at = strchr(str, '@');
+       const char *p;
+
+       /* standardized IDs without @<digits> */
+       if (!at && (strcmp(str, "timeQuality") == 0 ||
+                   strcmp(str, "origin") == 0 ||
+                   strcmp(str, "meta") == 0))
+               return 1;
+
+       if (!at || at == str || !*(at + 1))
+               return 0;
+       if (!isdigit_string(at + 1))
+               return 0;
+
+       /* check for forbidden chars in the <name> */
+       for (p = str; p < at; p++) {
+               if (*p == '[' || *p == '=' || *p == '"' || *p == '@')
+                       return 0;
+               if (isblank((unsigned int) *p) || iscntrl((unsigned int) *p))
+                       return 0;
+       }
+       return 1;
+}
+
+
+/* Some field mappings may be controversial, thus I give the reason
  * why this specific mapping was used:
  * APP-NAME <-- tag
  *    Some may argue that "logger" is a better fit, but we think
@@ -457,7 +643,8 @@ static void syslog_rfc5424_header(struct logger_ctl *const ctl)
        char *const app_name = ctl->tag;
        char *procid;
        char *const msgid = xstrdup(ctl->msgid ? ctl->msgid : NILVALUE);
-       char *structured_data;
+       char *structured = NULL;
+       struct list_head *sd;
 
        if (ctl->fd < 0)
                return;
@@ -500,20 +687,29 @@ static void syslog_rfc5424_header(struct logger_ctl *const ctl)
        else
                procid = xstrdup(NILVALUE);
 
-       if (ctl->rfc5424_tq) {
+       sd = get_reserved_structured_data(ctl);
+
+       /* time quality structured data (maybe overwriten by --sd-id timeQuality) */
+       if (ctl->rfc5424_tq && !has_structured_data_id(sd, "timeQuality")) {
+
+               add_structured_data_id(sd, "timeQuality");
+               add_structured_data_param(sd, "tzKnown=\"1\"");
+
 #ifdef HAVE_NTP_GETTIME
                struct ntptimeval ntptv;
 
-               if (ntp_gettime(&ntptv) == TIME_OK)
-                       xasprintf(&structured_data,
-                                "[timeQuality tzKnown=\"1\" isSynced=\"1\" syncAccuracy=\"%ld\"]",
-                                ntptv.maxerror);
-               else
+               if (ntp_gettime(&ntptv) == TIME_OK) {
+                       add_structured_data_param(sd, "isSynced=\"1\"");
+                       add_structured_data_paramf(sd, "syncAccuracy=\"%ld\"", ntptv.maxerror);
+               } else
 #endif
-                       xasprintf(&structured_data,
-                                "[timeQuality tzKnown=\"1\" isSynced=\"0\"]");
-       } else
-               structured_data = xstrdup(NILVALUE);
+                       add_structured_data_paramf(sd, "isSynced=\"0\"");
+       }
+
+       /* convert all structured data to string */
+       structured = get_structured_data_string(ctl);
+       if (!structured)
+               structured = xstrdup(NILVALUE);
 
        xasprintf(&ctl->hdr, "<%d>1 %s %s %s %s %s %s ",
                ctl->pri,
@@ -522,14 +718,14 @@ static void syslog_rfc5424_header(struct logger_ctl *const ctl)
                app_name,
                procid,
                msgid,
-               structured_data);
+               structured);
 
        free(time);
        free(hostname);
        /* app_name points to ctl->tag, do NOT free! */
        free(procid);
        free(msgid);
-       free(structured_data);
+       free(structured);
 }
 
 static void parse_rfc5424_flags(struct logger_ctl *ctl, char *optarg)
@@ -723,6 +919,8 @@ static void __attribute__ ((__noreturn__)) usage(FILE *out)
        fputs(_("     --rfc3164            use the obsolete BSD syslog protocol\n"), out);
        fputs(_("     --rfc5424[=<snip>]   use the syslog protocol (the default for remote);\n"
                "                            <snip> can be notime, or notq, and/or nohost\n"), out);
+       fputs(_("     --sd-id <id>         rfc5424 structured data ID\n"), out);
+       fputs(_("     --sd-param <data>    rfc5424 structured data name=value\n"), out);
        fputs(_("     --msgid <msgid>      set rfc5424 message id field\n"), out);
        fputs(_(" -u, --socket <socket>    write to this Unix socket\n"), out);
        fputs(_("     --socket-errors[=<on|off|auto>]\n"
@@ -794,6 +992,8 @@ int main(int argc, char **argv)
                { "size",          required_argument, 0, 'S'               },
                { "msgid",         required_argument, 0, OPT_MSGID         },
                { "skip-empty",    no_argument,       0, 'e'               },
+               { "sd-id",         required_argument, 0, OPT_STRUCTURED_DATA_ID          },
+               { "sd-param",      required_argument, 0, OPT_STRUCTURED_DATA_PARAM       },
 #ifdef HAVE_LIBSYSTEMD
                { "journald",      optional_argument, 0, OPT_JOURNALD      },
 #endif
@@ -805,6 +1005,9 @@ int main(int argc, char **argv)
        textdomain(PACKAGE);
        atexit(close_stdout);
 
+       INIT_LIST_HEAD(&ctl.user_sds);
+       INIT_LIST_HEAD(&ctl.reserved_sds);
+
        while ((ch = getopt_long(argc, argv, "ef:ip:S:st:u:dTn:P:Vh",
                                            longopts, NULL)) != -1) {
                switch (ch) {
@@ -898,6 +1101,16 @@ int main(int argc, char **argv)
                case OPT_NOACT:
                        ctl.noact = 1;
                        break;
+               case OPT_STRUCTURED_DATA_ID:
+                       if (!valid_structured_data_id(optarg))
+                               errx(EXIT_FAILURE, _("invalid structured data ID: '%s'"), optarg);
+                       add_structured_data_id(get_user_structured_data(&ctl), optarg);
+                       break;
+               case OPT_STRUCTURED_DATA_PARAM:
+                       if (!valid_structured_data_param(optarg))
+                               errx(EXIT_FAILURE, _("invalid structured data parameter: '%s'"), optarg);
+                       add_structured_data_param(get_user_structured_data(&ctl), optarg);
+                       break;
                case '?':
                default:
                        usage(stderr);
@@ -917,6 +1130,11 @@ int main(int argc, char **argv)
                return EXIT_SUCCESS;
        }
 #endif
+
+       /* user overwrites build-in SD-ELEMENT */
+       if (has_structured_data_id(get_user_structured_data(&ctl), "timeQuality"))
+               ctl.rfc5424_tq = 0;
+
        switch (unix_socket_errors_mode) {
        case AF_UNIX_ERRORS_OFF:
                ctl.unix_socket_errors = 0;