--- /dev/null
+<?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>
--- /dev/null
+/* 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,
+};