]> granicus.if.org Git - apache/commitdiff
mod_journald: New module implementing error_log provider for systemd-journald.
authorJan Kaluža <jkaluza@apache.org>
Mon, 14 Jul 2014 05:52:45 +0000 (05:52 +0000)
committerJan Kaluža <jkaluza@apache.org>
Mon, 14 Jul 2014 05:52:45 +0000 (05:52 +0000)
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1610339 13f79535-47bb-0310-9956-ffa450edef68

docs/manual/mod/mod_journald.xml [new file with mode: 0644]
modules/loggers/config.m4
modules/loggers/mod_journald.c [new file with mode: 0644]

diff --git a/docs/manual/mod/mod_journald.xml b/docs/manual/mod/mod_journald.xml
new file mode 100644 (file)
index 0000000..5244ef7
--- /dev/null
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<!DOCTYPE modulesynopsis SYSTEM "../style/modulesynopsis.dtd">
+<?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?>
+<!-- $LastChangedRevision: 1533940 $ -->
+
+<!--
+ 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.
+-->
+
+<modulesynopsis metafile="mod_journald.xml.meta">
+
+<name>mod_journald</name>
+<description>Provides "journald" ErrorLog provider</description>
+<status>Extension</status>
+<sourcefile>mod_journald.c</sourcefile>
+<identifier>journald_module</identifier>
+
+<summary>
+    <p>This module provides "journald" ErrorLog provider. It allows logging
+    error messages and CustomLog/TransferLog via systemd-journald(8).</p>
+</summary>
+
+<directivesynopsis>
+<name>JournaldCustomLog</name>
+<description>Enable logging of CustomLog/TransferLog to systemd-journald</description>
+<syntax>JournaldCustomLog on|off</syntax>
+<default>JournaldCustomLog off</default>
+<contextlist><context>server config</context></contextlist>
+
+<usage>
+
+    <p>The <directive>JournaldCustomLog</directive> directive enables logging
+    of CustomLog and TransferLog messages to systemd-journald.
+    </p>
+
+    <note type="warning"><title>Performance warning</title><p>
+    Currently, systemd-journald is not designed for high-throughput logging
+    and logging access_log to systemd-journald could decrease the performance
+    a lot.
+    </p></note>
+</usage>
+</directivesynopsis>
+
+<section id="structured">
+    <title>Structured logging</title>
+    <p>Systemd-journald allows structured logging and therefore it is
+    possible to filter logged messages according to various variables.
+    Currently supported variables are:
+    </p>
+    <dl>
+      <dt><code>LOG</code></dt>
+      <dd>The name of the log. For ErrorLog, the value is "error_log".
+          For CustomLog or TransferLog, the value is the first argument of
+          these directives.</dd>
+      <dt><code>REQUEST_HOSTNAME</code></dt>
+      <dd>Host, as set by full URI or Host: header in the request.</dd>
+      <dt><code>REQUEST_USER</code></dt>
+      <dd>If an authentication check was made, this gets set to the user
+          name.</dd>
+      <dt><code>REQUEST_USERAGENT_IP</code></dt>
+      <dd>The address that originated the request.</dd>
+      <dt><code>REQUEST_URI</code></dt>
+      <dd>The path portion of the URI, or "/" if no path provided.</dd>
+      <dt><code>SERVER_HOSTNAME</code></dt>
+      <dd>The hostname of server for which the log message has been
+          generated.</dd>
+    </dl>
+
+    <p>These variables can be for example used to show only log messages
+    for particular URI using <code>journalctl</code>:
+    </p>
+    
+    <highlight>journalctl REQUEST_URI=/index.html -a</highlight>
+    
+    <p>For more examples, see systemd-journalctl documentation.</p>
+</section>
+
+<section id="examples">
+    <title>Examples</title>
+
+    <p>Using <code>journald</code> in ErrorLog directive (see <module>core</module>)
+    instead of a filename enables logging via systemd-journald(8)
+    if the system supports it.
+    </p>
+    
+    <highlight language="config">ErrorLog journald</highlight>
+
+</section>
+
+
+</modulesynopsis>
index 58ad35f72e40d7ff8a9a1bdccfc261685a6696b6..c66e4d32680e5c085e1767663ab9ec937955584e 100644 (file)
@@ -4,6 +4,21 @@ dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
 
 APACHE_MODPATH_INIT(loggers)
 
+APACHE_MODULE(journald, Journald support, , , all, [
+  AC_CHECK_LIB(systemd-journal, sd_journal_sendv, JOURNALD_LIBS="-lsystemd-journal")
+  AC_CHECK_LIB(systemd-id128, sd_id128_to_string, ID128_LIBS="-lsystemd-id128")
+  AC_CHECK_HEADERS(systemd/sd-journal.h, [ap_HAVE_SD_JOURNAL_H="yes"], [ap_HAVE_SD_JOURNAL_H="no"])
+  AC_CHECK_HEADERS(systemd/sd-id128.h, [ap_HAVE_SD_ID128_H="yes"], [ap_HAVE_SD_ID128_H="no"])
+  if test $ap_HAVE_SD_JOURNAL_H = "no" || test $ap_HAVE_SD_ID128_H = "no" || test -z "${JOURNALD_LIBS}" || test -z "${ID128_LIBS}"; then
+    AC_MSG_WARN([Your system does not support Journald.])
+    enable_journald="no"
+  else
+    APR_ADDTO(MOD_JOURNALD_LDADD, [$JOURNALD_LIBS])
+    APR_ADDTO(MOD_JOURNALD_LDADD, [$ID128_LIBS])
+    enable_journald="yes"
+  fi
+])
+
 APACHE_MODULE(syslog, logging to syslog, , , all, [
   AC_CHECK_HEADERS(syslog.h, [ap_HAVE_SYSLOG_H="yes"], [ap_HAVE_SYSLOG_H="no"])
   if test $ap_HAVE_SYSLOG_H = "no"; then
diff --git a/modules/loggers/mod_journald.c b/modules/loggers/mod_journald.c
new file mode 100644 (file)
index 0000000..c0cc9c3
--- /dev/null
@@ -0,0 +1,275 @@
+/* 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.
+ * 
+ */
+
+#include <stdint.h>
+#include <ap_config.h>
+#include "ap_mpm.h"
+#include "ap_provider.h"
+#include <http_core.h>
+#include <httpd.h>
+#include <http_log.h>
+#include <apr_version.h>
+#include <apr_pools.h>
+#include <apr_strings.h>
+#include "unixd.h"
+#include "scoreboard.h"
+#include "mpm_common.h"
+#include "mod_log_config.h"
+
+#define SD_JOURNAL_SUPPRESS_LOCATION 1
+
+#include "systemd/sd-journal.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#define MAX_ENTRIES 15
+
+static int handle_custom_log = 0;
+
+static int journald_info_get_priority(int level)
+{
+    switch(level) {
+        /* We don't use EMERG here, because journald broadcasts EMERG messages
+         * to all terminals. APLOG_EMERG is usually not used in this context.
+         * in httpd code. */
+        case APLOG_EMERG:   return LOG_ALERT;
+        case APLOG_ALERT:   return LOG_ALERT;
+        case APLOG_CRIT:    return LOG_CRIT;
+        case APLOG_ERR:     return LOG_ERR;
+        case APLOG_WARNING: return LOG_WARNING;
+        case APLOG_NOTICE:  return LOG_NOTICE;
+        case APLOG_INFO:    return LOG_INFO;
+        case APLOG_DEBUG:   return LOG_DEBUG;
+        case -1:            return LOG_INFO;
+        default:            return LOG_DEBUG;
+    }
+    return LOG_INFO;
+}
+
+static apr_pool_t *journald_info_get_pool(const ap_errorlog_info *info)
+{
+    if (info->r && info->r->pool)
+        return info->r->pool;
+    if (info->c && info->c->pool)
+        return info->c->pool;
+    if (info->pool)
+        return info->pool;
+    if (info->s && info->s->process && info->s->process->pool)
+        return info->s->process->pool;
+    return 0;
+}
+
+static apr_status_t iovec_add_entry(apr_pool_t *pool, struct iovec *iov,
+                                    const char *format, int len, ...)
+{
+    va_list ap;
+    va_start(ap, len);
+    iov->iov_base = apr_pvsprintf(pool, format, ap);
+    va_end(ap);
+    if (!iov->iov_base) {
+        return APR_ENOMEM;
+    }
+    if (len < 0) {
+        iov->iov_len = strlen(iov->iov_base);
+    }
+    else {
+        iov->iov_len = len;
+    }
+    return APR_SUCCESS;
+}
+
+static void journald_log(apr_pool_t *pool, const char *log,
+                         const char *errstr, int len, int priority,
+                         const server_rec *s, const request_rec *r)
+{
+    apr_pool_t *subpool;
+    apr_status_t rv = APR_SUCCESS;
+    struct iovec iov[MAX_ENTRIES];
+    int iov_size = 0;
+
+    if (apr_pool_create(&subpool, pool) != APR_SUCCESS) {
+        /* We were not able to create subpool, log at least what we have. */
+        sd_journal_send("MESSAGE=%s", errstr, "LOG=%s", log,
+                    "PRIORITY=%i", priority,
+                    NULL);
+        return;
+    }
+
+    /* Adds new entry to iovec if previous additions were successful. */
+#define IOVEC_ADD_LEN(FORMAT, VAR, LEN) \
+    if (rv == APR_SUCCESS && iov_size < MAX_ENTRIES) { \
+        if ((rv = iovec_add_entry(subpool, &iov[iov_size], FORMAT, LEN, VAR)) \
+            == APR_SUCCESS) \
+                iov_size++; \
+    }
+#define IOVEC_ADD(FORMAT, VAR) IOVEC_ADD_LEN(FORMAT, VAR, -1)
+
+    IOVEC_ADD_LEN("MESSAGE=%s", errstr, len + 8);
+    IOVEC_ADD("LOG=%s", log);
+    IOVEC_ADD("PRIORITY=%i", priority);
+
+    if (s) {
+        IOVEC_ADD("SERVER_HOSTNAME=%s", s->server_hostname);
+    }
+
+    if (r) {
+        IOVEC_ADD("REQUEST_HOSTNAME=%s", r->hostname);
+        IOVEC_ADD("REQUEST_USER=%s", r->user ? r->user : "");
+        IOVEC_ADD("REQUEST_URI=%s", r->uri ? r->uri : "");
+        IOVEC_ADD("REQUEST_USERAGENT_IP=%s", r->useragent_ip);
+    }
+
+    sd_journal_sendv(iov, iov_size);
+    apr_pool_destroy(subpool);
+}
+
+static void *journald_error_log_init(apr_pool_t *p, server_rec *s)
+{
+    void *success = (void *)p; /* anything non-NULL is success */
+    return success;
+}
+
+static apr_status_t journald_error_log(const ap_errorlog_info *info,
+                                       void *handle, const char *errstr,
+                                       apr_size_t len)
+{
+    const server_rec *s = info->s;
+    const request_rec *r = info->r;
+    apr_pool_t *pool;
+    const char *log_name = (s && s->error_fname && *s->error_fname) ?
+                            s->error_fname : "error_log";
+
+    pool = journald_info_get_pool(info);
+    if (!pool) {
+        /* We don't have any pool, so at least log the message without
+         * any additional data. */
+        sd_journal_send("MESSAGE=%s", errstr, "LOG=%s", "log_name",
+                    "PRIORITY=%i", journald_info_get_priority(info->level),
+                    NULL);
+        return APR_SUCCESS;
+    }
+
+    journald_log(pool, log_name, errstr, len,
+                 journald_info_get_priority(info->level), s, r);
+
+    return APR_SUCCESS;
+}
+
+static const char *journald_error_log_parse(cmd_parms *cmd, const char *arg)
+{
+    return NULL;
+}
+
+static apr_status_t journald_log_writer(request_rec *r,
+                           void *handle,
+                           const char **strs,
+                           int *strl,
+                           int nelts,
+                           apr_size_t len)
+
+{
+    char *str;
+    char *s;
+    int i;
+    apr_status_t rv = APR_SUCCESS;
+
+    str = apr_palloc(r->pool, len + 1);
+
+    /* Last string is always \n, so skipt it */
+    for (i = 0, s = str; i < nelts - 1; ++i) {
+        memcpy(s, strs[i], strl[i]);
+        s += strl[i];
+    }
+
+    journald_log(r->pool, (char *) handle, str, len,
+                 LOG_INFO, r->server, r);
+
+    return rv;
+}
+
+static void *journald_log_writer_init(apr_pool_t *p, server_rec *s,
+                                        const char* name)
+{
+    char *log_name = apr_pstrdup(p, name);
+    return log_name;
+}
+
+static int journald_open_logs(apr_pool_t *p, apr_pool_t *plog,
+                               apr_pool_t *ptemp, server_rec *s)
+{
+    APR_OPTIONAL_FN_TYPE(ap_log_set_writer_init) *log_set_writer_init;
+    APR_OPTIONAL_FN_TYPE(ap_log_set_writer) *log_set_writer;
+
+    if (!handle_custom_log) {
+        return OK;
+    }
+
+    log_set_writer_init = APR_RETRIEVE_OPTIONAL_FN(ap_log_set_writer_init);
+    log_set_writer = APR_RETRIEVE_OPTIONAL_FN(ap_log_set_writer);
+
+    if (log_set_writer_init) {
+        log_set_writer_init(&journald_log_writer_init);
+    }
+
+    if (log_set_writer) {
+        log_set_writer(&journald_log_writer);
+    }
+
+    return OK;
+}
+
+static void journald_register_hooks(apr_pool_t *p)
+{
+    static const ap_errorlog_provider journald_provider = {
+        &journald_error_log_init,
+        &journald_error_log,
+        &journald_error_log_parse,
+        0
+    };
+
+    ap_register_provider(p, AP_ERRORLOG_PROVIDER_GROUP, "journald",
+                         AP_ERRORLOG_PROVIDER_VERSION,
+                         &journald_provider);
+
+    ap_hook_open_logs(journald_open_logs, NULL, NULL, APR_HOOK_FIRST);
+}
+
+static const char *set_custom_log_on(cmd_parms *parms, void *dummy, int flag)
+{
+    handle_custom_log = flag;
+    return NULL;
+}
+
+static const command_rec journald_cmds[] =
+{
+AP_INIT_FLAG("JournaldCustomLog", set_custom_log_on, NULL, RSRC_CONF,
+     "Enable logging of CustomLog/TransferLog to journald"),
+    {NULL}
+};
+
+AP_DECLARE_MODULE(journald) =
+{
+    STANDARD20_MODULE_STUFF,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    journald_cmds,
+    journald_register_hooks,
+};