From f66a511609dcbddfed1782a40d4b42556bc357ad Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jan=20Kalu=C5=BEa?= Date: Mon, 14 Jul 2014 05:52:45 +0000 Subject: [PATCH] mod_journald: New module implementing error_log provider for systemd-journald. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1610339 13f79535-47bb-0310-9956-ffa450edef68 --- docs/manual/mod/mod_journald.xml | 104 ++++++++++++ modules/loggers/config.m4 | 15 ++ modules/loggers/mod_journald.c | 275 +++++++++++++++++++++++++++++++ 3 files changed, 394 insertions(+) create mode 100644 docs/manual/mod/mod_journald.xml create mode 100644 modules/loggers/mod_journald.c diff --git a/docs/manual/mod/mod_journald.xml b/docs/manual/mod/mod_journald.xml new file mode 100644 index 0000000000..5244ef7074 --- /dev/null +++ b/docs/manual/mod/mod_journald.xml @@ -0,0 +1,104 @@ + + + + + + + + + +mod_journald +Provides "journald" ErrorLog provider +Extension +mod_journald.c +journald_module + + +

This module provides "journald" ErrorLog provider. It allows logging + error messages and CustomLog/TransferLog via systemd-journald(8).

+
+ + +JournaldCustomLog +Enable logging of CustomLog/TransferLog to systemd-journald +JournaldCustomLog on|off +JournaldCustomLog off +server config + + + +

The JournaldCustomLog directive enables logging + of CustomLog and TransferLog messages to systemd-journald. +

+ + Performance warning

+ Currently, systemd-journald is not designed for high-throughput logging + and logging access_log to systemd-journald could decrease the performance + a lot. +

+
+
+ +
+ Structured logging +

Systemd-journald allows structured logging and therefore it is + possible to filter logged messages according to various variables. + Currently supported variables are: +

+
+
LOG
+
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.
+
REQUEST_HOSTNAME
+
Host, as set by full URI or Host: header in the request.
+
REQUEST_USER
+
If an authentication check was made, this gets set to the user + name.
+
REQUEST_USERAGENT_IP
+
The address that originated the request.
+
REQUEST_URI
+
The path portion of the URI, or "/" if no path provided.
+
SERVER_HOSTNAME
+
The hostname of server for which the log message has been + generated.
+
+ +

These variables can be for example used to show only log messages + for particular URI using journalctl: +

+ + journalctl REQUEST_URI=/index.html -a + +

For more examples, see systemd-journalctl documentation.

+
+ +
+ Examples + +

Using journald in ErrorLog directive (see core) + instead of a filename enables logging via systemd-journald(8) + if the system supports it. +

+ + ErrorLog journald + +
+ + +
diff --git a/modules/loggers/config.m4 b/modules/loggers/config.m4 index 58ad35f72e..c66e4d3268 100644 --- a/modules/loggers/config.m4 +++ b/modules/loggers/config.m4 @@ -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 index 0000000000..c0cc9c3a31 --- /dev/null +++ b/modules/loggers/mod_journald.c @@ -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 +#include +#include "ap_mpm.h" +#include "ap_provider.h" +#include +#include +#include +#include +#include +#include +#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 +#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, +}; -- 2.40.0