summaryrefslogtreecommitdiffstats
path: root/tools/src/qslogger.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-09-17 03:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-09-17 03:51:28 +0000
commit2b07c041cb218eca6e548bac9c4347f8a90c474c (patch)
tree679142f3916fa927903c6f245896f5c0325a3254 /tools/src/qslogger.c
parentInitial commit. (diff)
downloadlibapache2-mod-qos-upstream.tar.xz
libapache2-mod-qos-upstream.zip
Adding upstream version 11.74.upstream/11.74upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/src/qslogger.c')
-rw-r--r--tools/src/qslogger.c423
1 files changed, 423 insertions, 0 deletions
diff --git a/tools/src/qslogger.c b/tools/src/qslogger.c
new file mode 100644
index 0000000..3a8a0d8
--- /dev/null
+++ b/tools/src/qslogger.c
@@ -0,0 +1,423 @@
+/* -*-mode: c; indent-tabs-mode: nil; c-basic-offset: 2; -*-
+ */
+/**
+ * Utilities for the quality of service module mod_qos.
+ *
+ * qslogger.c: Piped logging forwarding log data to syslog
+ *
+ * See http://mod-qos.sourceforge.net/ for further
+ * details.
+ *
+ * Copyright (C) 2023 Pascal Buchbinder
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+static const char revision[] = "$Id: qslogger.c 2654 2022-05-13 09:12:42Z pbuchbinder $";
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <regex.h>
+#include <syslog.h>
+
+#include <apr.h>
+#include <apr_lib.h>
+
+#include "qs_util.h"
+
+// [Wed Mar 28 22:40:41 2012] [warn]
+#define QS_DEFAULTPATTERN "^\\[[0-9a-zA-Z :]+\\] \\[([a-z]+)\\] "
+
+#define QS_MAX_PATTERN_MA 2
+
+static int m_default_severity = LOG_NOTICE;
+
+/**
+ * Similar to standard strstr() but case insensitive and length limitation
+ * (string which is not 0 terminated).
+ *
+ * @param s1 String to search in
+ * @param s2 Pattern to ind
+ * @param len Length of s1
+ * @return pointer to the beginning of the substring s2 within s1, or NULL
+ * if the substring is not found
+ */
+static const char *qs_strncasestr(const char *s1, const char *s2, int len) {
+ const char *e1 = &s1[len-1];
+ char *p1, *p2;
+ if (*s2 == '\0') {
+ /* an empty s2 */
+ return((char *)s1);
+ }
+ while(1) {
+ for ( ; (*s1 != '\0') && (s1 <= e1) && (apr_tolower(*s1) != apr_tolower(*s2)); s1++);
+ if (*s1 == '\0' || s1 > e1) {
+ return(NULL);
+ }
+ /* found first character of s2, see if the rest matches */
+ p1 = (char *)s1;
+ p2 = (char *)s2;
+ for (++p1, ++p2; (apr_tolower(*p1) == apr_tolower(*p2)) && (p1 <= e1); ++p1, ++p2) {
+ if((p1 > e1) && (*p2 != '\0')) {
+ // reached the end without match
+ return NULL;
+ }
+ if (*p2 == '\0') {
+ /* both strings ended together */
+ return((char *)s1);
+ }
+ }
+ if (*p2 == '\0') {
+ /* second string ended, a match */
+ break;
+ }
+ /* didn't find a match here, try starting at next character in s1 */
+ s1++;
+ }
+ return((char *)s1);
+}
+
+/**
+ * Rerurns the priority value
+ *
+ * @param priorityname Part of the log message to search the priority in
+ * @param len Length of the priority string
+ * @return Priority, LOG_NOTICE (see m_default_severity) if provided name is not recognized.
+ */
+static int qsgetprio(const char *priorityname, int len) {
+ int p = m_default_severity;
+ if(!priorityname) {
+ return p;
+ }
+ if(qs_strncasestr(priorityname, "alert", len)) {
+ p = LOG_ALERT;
+ } else if(qs_strncasestr(priorityname, "crit", len)) {
+ p = LOG_CRIT;
+ } else if(qs_strncasestr(priorityname, "debug", len)) {
+ p = LOG_DEBUG;
+ } else if(qs_strncasestr(priorityname, "emerg", len)) {
+ p = LOG_EMERG;
+ } else if(qs_strncasestr(priorityname, "err", len)) {
+ p = LOG_ERR;
+ } else if(qs_strncasestr(priorityname, "info", len)) {
+ p = LOG_INFO;
+ } else if(qs_strncasestr(priorityname, "notice", len)) {
+ p = LOG_NOTICE;
+ } else if(qs_strncasestr(priorityname, "panic", len)) {
+ p = LOG_EMERG;
+ } else if(qs_strncasestr(priorityname, "warn", len)) {
+ p = LOG_WARNING;
+ }
+ return p;
+}
+
+/**
+ * Extracts the severity of the message using the provided
+ * regular expression and determinest the priofity using
+ * qsgetprio().
+ *
+ * @param preg Regular expression to extract the severity
+ * @param line Log fline to extract the severity from
+ * @return Level or LOG_NOTICE (see m_default_severity) if level could not be determined.
+ */
+static int qsgetlevel(regex_t preg, const char *line) {
+ int level = m_default_severity;
+ regmatch_t ma[QS_MAX_PATTERN_MA];
+ if(regexec(&preg, line, QS_MAX_PATTERN_MA, ma, 0) == 0) {
+ int len = ma[1].rm_eo - ma[1].rm_so;
+ level = qsgetprio(&line[ma[1].rm_so], len);
+ }
+ return level;
+}
+
+/* entry within the facility table */
+typedef struct {
+ const char* name;
+ int f;
+} qs_f_t;
+
+/**
+ * Table of known facilities, see sys/syslog.h.
+ */
+static const qs_f_t qs_facilities[] = {
+#ifdef LOG_AUTHPRIV
+ { "authpriv", LOG_AUTHPRIV },
+#endif
+ { "auth", LOG_AUTH },
+ { "cron", LOG_CRON },
+ { "daemon", LOG_DAEMON },
+#ifdef LOG_FTP
+ { "ftp", LOG_FTP },
+#endif
+ { "kern", LOG_KERN },
+ { "lpr", LOG_LPR },
+ { "mail", LOG_MAIL },
+ { "news", LOG_NEWS },
+ { "security", LOG_AUTH },
+ { "syslog", LOG_SYSLOG },
+ { "user", LOG_USER },
+ { "uucp", LOG_UUCP },
+ { "local0", LOG_LOCAL0 },
+ { "local1", LOG_LOCAL1 },
+ { "local2", LOG_LOCAL2 },
+ { "local3", LOG_LOCAL3 },
+ { "local4", LOG_LOCAL4 },
+ { "local5", LOG_LOCAL5 },
+ { "local6", LOG_LOCAL6 },
+ { "local7", LOG_LOCAL7 },
+ { NULL, -1 }
+};
+
+/**
+ * Determines the facility (user input).
+ *
+ * @param facilityname
+ * @return The facility id or LOG_DAEMON if the provided
+ * string is unknown.
+ */
+static int qsgetfacility(const char *facilityname) {
+ int f = LOG_DAEMON;
+ const qs_f_t *facilities = qs_facilities;
+ if(!facilityname) {
+ return f;
+ }
+ while(facilities->name) {
+ if(strcasecmp(facilityname, facilities->name) == 0) {
+ f = facilities->f;
+ break;
+ }
+ facilities++;
+ }
+ return f;
+}
+
+/**
+ * Usage message (or man page)
+ *
+ * @param cmd
+ * @param man
+ */
+static void usage(const char *cmd, int man) {
+ if(man) {
+ //.TH [name of program] [section number] [center footer] [left footer] [center header]
+ printf(".TH %s 1 \"%s\" \"mod_qos utilities %s\" \"%s man page\"\n", qs_CMD(cmd), man_date,
+ man_version, cmd);
+ }
+ printf("\n");
+ if(man) {
+ printf(".SH NAME\n");
+ }
+ qs_man_print(man, "%s - another shell command interface to the system log module (syslog).\n", cmd);
+ printf("\n");
+ if(man) {
+ printf(".SH SYNOPSIS\n");
+ }
+ qs_man_print(man, "%s%s [-t <tag>] [-f <facility>] [-l <level>] [-x <prefix>] [-r <expression>] [-d <level>] [-u <name>] [-p]\n", man ? "" : "Usage: ", cmd);
+ printf("\n");
+ if(man) {
+ printf(".SH DESCRIPTION\n");
+ } else {
+ printf("Summary\n");
+ }
+ qs_man_print(man, "Use this utility to forward log messages to the systems syslog\n");
+ qs_man_print(man, "facility, e.g., to forward the messages to a remote host.\n");
+ qs_man_print(man, "It reads data from stdin.\n");
+ printf("\n");
+ if(man) {
+ printf(".SH OPTIONS\n");
+ } else {
+ printf("Options\n");
+ }
+ if(man) printf("\n.TP\n");
+ qs_man_print(man, " -t <tag>\n");
+ if(man) printf("\n");
+ qs_man_print(man, " Defines the tag name which shall be used to define the origin\n");
+ qs_man_print(man, " of the messages, e.g. 'httpd'.\n");
+ if(man) printf("\n.TP\n");
+ qs_man_print(man, " -f <facility>\n");
+ if(man) printf("\n");
+ qs_man_print(man, " Defines the syslog facility. Default is 'daemon'.\n");
+ if(man) printf("\n.TP\n");
+ qs_man_print(man, " -u <name>\n");
+ if(man) printf("\n");
+ qs_man_print(man, " Becomes another user, e.g. www-data.\n");
+ if(man) printf("\n.TP\n");
+ qs_man_print(man, " -l <level>\n");
+ if(man) printf("\n");
+ qs_man_print(man, " Defines the minimal severity a message must have in order to\n");
+ qs_man_print(man, " be forwarded. Default is 'DEBUG' (forwarding everything).\n");
+ if(man) printf("\n.TP\n");
+ qs_man_print(man, " -x <prefix>\n");
+ if(man) printf("\n");
+ qs_man_print(man, " Allows you to add a prefix (literal string) to every message.\n");
+ if(man) printf("\n.TP\n");
+ qs_man_print(man, " -r <expression>\n");
+ if(man) printf("\n");
+ qs_man_print(man, " Specifies a regular expression which shall be used to\n");
+ qs_man_print(man, " determine the severity (syslog level) for each log line.\n");
+ qs_man_print(man, " The default pattern '"QS_DEFAULTPATTERN"' can\n");
+ qs_man_print(man, " be used for Apache error log messages but you may configure\n");
+ qs_man_print(man, " your own pattern matching other log formats. Use brackets\n");
+ qs_man_print(man, " to define the pattern enclosing the severity string.\n");
+ qs_man_print(man, " Default level (if severity can't be determined) is defined by the\n");
+ qs_man_print(man, " option '-d' (see below).\n");
+ if(man) printf("\n.TP\n");
+ qs_man_print(man, " -d <level>\n");
+ if(man) printf("\n");
+ qs_man_print(man, " The default severity if the specified pattern (-r) does not\n");
+ qs_man_print(man, " match and the message's severity can't be determined. Default\n");
+ qs_man_print(man, " is 'NOTICE'.\n");
+ if(man) printf("\n.TP\n");
+ qs_man_print(man, " -p\n");
+ if(man) printf("\n");
+ qs_man_print(man, " Writes data also to stdout (for piped logging).\n");
+ printf("\n");
+ if(man) {
+ printf(".SH EXAMPLE\n");
+ } else {
+ printf("Example:\n");
+ }
+ qs_man_println(man, " ErrorLog \"|/usr/bin/%s -t apache -f local7\"\n", cmd);
+ printf("\n");
+ if(man) {
+ printf(".SH SEE ALSO\n");
+ printf("qsdt(1), qsexec(1), qsfilter2(1), qsgeo(1), qsgrep(1), qshead(1), qslog(1), qspng(1), qsre(1), qsrespeed(1), qsrotate(1), qssign(1), qstail(1)\n");
+ printf(".SH AUTHOR\n");
+ printf("Pascal Buchbinder, http://mod-qos.sourceforge.net/\n");
+ } else {
+ printf("See http://mod-qos.sourceforge.net/ for further details.\n");
+ }
+ if(man) {
+ exit(0);
+ } else {
+ exit(1);
+ }
+}
+
+int main(int argc, const char * const argv[]) {
+ int line_len;
+ char *line = calloc(1, MAX_LINE_BUFFER+1);
+ const char *cmd = strrchr(argv[0], '/');
+ int pass = 0;
+ const char *tag = NULL;
+ int facility = LOG_DAEMON;
+ int severity = LOG_DEBUG;
+ int level = LOG_INFO;
+ const char *regexpattern = QS_DEFAULTPATTERN;
+ const char *username = NULL;
+ const char *prefix = NULL;
+ regex_t preg;
+ if(cmd == NULL) {
+ cmd = (char *)argv[0];
+ } else {
+ cmd++;
+ }
+
+ argc--;
+ argv++;
+ while(argc >= 1) {
+ if(strcmp(*argv, "-p") == 0) {
+ pass = 1;
+ } else if(strcmp(*argv, "-f") == 0) {
+ if (--argc >= 1) {
+ const char *facilityname = *(++argv);
+ facility = qsgetfacility(facilityname);
+ }
+ } else if(strcmp(*argv, "-l") == 0) {
+ if (--argc >= 1) {
+ const char *severityname = *(++argv);
+ severity = qsgetprio(severityname, strlen(severityname));
+ }
+ } else if(strcmp(*argv, "-x") == 0) {
+ if (--argc >= 1) {
+ prefix = *(++argv);
+ }
+ } else if(strcmp(*argv,"-u") == 0) { /* switch user id */
+ if (--argc >= 1) {
+ username = *(++argv);
+ }
+ } else if(strcmp(*argv, "-d") == 0) {
+ if (--argc >= 1) {
+ const char *severityname = *(++argv);
+ m_default_severity = qsgetprio(severityname, strlen(severityname));
+ }
+ } else if(strcmp(*argv, "-t") == 0) {
+ if (--argc >= 1) {
+ tag = *(++argv);
+ }
+ } else if(strcmp(*argv, "-r") == 0) {
+ if (--argc >= 1) {
+ regexpattern = *(++argv);
+ }
+ } else if(strcmp(*argv,"-h") == 0) {
+ usage(cmd, 0);
+ } else if(strcmp(*argv,"--help") == 0) {
+ usage(cmd, 0);
+ } else if(strcmp(*argv,"-?") == 0) {
+ usage(cmd, 0);
+ } else if(strcmp(*argv,"--man") == 0) {
+ usage(cmd, 1);
+ } else {
+ usage(cmd, 0);
+ }
+ argc--;
+ argv++;
+ }
+
+ if(regcomp(&preg, regexpattern, REG_EXTENDED)) {
+ fprintf(stderr, "[%s] failed to compile pattern %s", cmd, regexpattern);
+ exit(1);
+ }
+
+ qs_setuid(username, cmd);
+
+ openlog(tag ? tag : getlogin(), 0, facility);
+
+ // start reading from stdin
+ while(fgets(line, MAX_LINE_BUFFER, stdin) != NULL) {
+ line_len = strlen(line) - 1;
+ while(line_len > 0) { // cut tailing CR/LF
+ if(line[line_len] >= ' ') {
+ break;
+ }
+ line[line_len] = '\0';
+ line_len--;
+ }
+ // severity is determined using the regular expression provided by the user
+ level = qsgetlevel(preg, line);
+ if(level <= severity) {
+ // send message
+ if(prefix) {
+ syslog(level, "%s%s", prefix, line);
+ } else {
+ syslog(level, "%s", line);
+ }
+ }
+ if(pass) {
+ printf("%s\n", line);
+ fflush(stdout);
+ }
+ }
+ free(line);
+ closelog();
+ return 0;
+}