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.
.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
#include "pathnames.h"
#include "strutils.h"
#include "xalloc.h"
+#include "strv.h"
+#include "list.h"
#define SYSLOG_NAMES
#include <syslog.h>
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;
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 */
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
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;
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,
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)
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"
{ "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
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) {
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);
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;