]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
Add ErrorLogFormat directive for configuring the error log format, including
authorStefan Fritsch <sf@apache.org>
Sun, 5 Sep 2010 15:44:19 +0000 (15:44 +0000)
committerStefan Fritsch <sf@apache.org>
Sun, 5 Sep 2010 15:44:19 +0000 (15:44 +0000)
additional information that is logged once per connection or request.

Add error log IDs for connections and request to allow correlating error log
lines and the corresponding access log entry.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@992806 13f79535-47bb-0310-9956-ffa450edef68

15 files changed:
CHANGES
STATUS
docs/manual/logs.xml
docs/manual/mod/core.xml
docs/manual/mod/mod_log_config.xml
include/ap_mmn.h
include/http_config.h
include/http_core.h
include/httpd.h
include/util_time.h
modules/loggers/mod_log_config.c
server/config.c
server/core.c
server/log.c
server/util_time.c

diff --git a/CHANGES b/CHANGES
index 10f8e47a5fcf7f3924d025e8ebf6f4a806a83185..d90f587be84fa7162c4ea2c7fde9d66a0fdfa1b7 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -2,6 +2,11 @@
 
 Changes with Apache 2.3.9
 
+  *) core: Add ErrorLogFormat to allow configuring error log format, including
+     additional information that is logged once per connection or request. Add
+     error log IDs for connections and request to allow correlating error log
+     lines and the corresponding access log entry.
+
   *) core: Disable sendfile by default. [Stefan Fritsch]
 
   *) mod_cache: Check the request to determine whether we are allowed
diff --git a/STATUS b/STATUS
index e98911859059a6da7ff8de9e74417c9f9c893575..43fddc7b6172c7de6043f354bfc49e4005c362b4 100644 (file)
--- a/STATUS
+++ b/STATUS
@@ -64,11 +64,6 @@ RELEASE SHOWSTOPPERS:
     - MPM simple
     - mod_serf
 
-  * Error log format should be configurable. The current format is
-    too verbose: The prefix length ranges from 70 chars (server-scope,
-    level <= info) to 120 chars (conn-scope, level >= debug).
-    (sf is working on this).
-
   FOR BETA:
 
   * Modules without documentation need to be moved to experimental or be
@@ -141,9 +136,6 @@ CURRENT VOTES:
 
 RELEASE NON-SHOWSTOPPERS BUT WOULD BE REAL NICE TO WRAP THESE UP:
 
-  * Allow to log module name in error log format.
-    (sf is working on this)
   * Add mod_allow_method or some other (usable) functionality to replace
     Limit/LimitExcept.
 
index d6d8a2768bacc32f5d881e2c233ea56b584011a2..1c00a1df95cf9c9d1aa053dc2e8f71e8b141cd5a 100644 (file)
@@ -92,6 +92,7 @@
       </modulelist>
       <directivelist>
         <directive module="core">ErrorLog</directive>
+        <directive module="core">ErrorLogFormat</directive>
         <directive module="core">LogLevel</directive>
       </directivelist>
     </related>
index 4811f6a0e821fa2f9bfa26ba53d315238f8daa2f..6aa6333c2e44df3b0df529cc1ae022c80caf41cb 100644 (file)
@@ -1043,6 +1043,153 @@ in case of an error</description>
 <seealso><a href="../logs.html">Apache HTTP Server Log Files</a></seealso>
 </directivesynopsis>
 
+<directivesynopsis>
+<name>ErrorLogFormat</name>
+<description>Format specification for error log entries</description>
+<syntax> ErrorLog [connection|request] <var>format</var></syntax>
+<contextlist><context>server config</context><context>virtual host</context>
+</contextlist>
+<compatibility>Available in Apache httpd 2.3.9 and later</compatibility>
+
+<usage>
+    <p><directive>ErrorLogFormat</directive> allows to specify what
+    supplementary information is logged in the error log in addition to the
+    actual log message.</p>
+
+    <example><title>Simple example</title>
+        ErrorLogFormat "[%t] [%l] [pid %P] %F: %E: [client %a] %M"
+    </example>
+
+    <p>Specifying <code>connection</code> or <code>request</code> as first
+    paramter allows to specify additional formats, causing additional
+    information to be logged when the first message is logged for a specific
+    connection or request, respectivly. This additional information is only
+    logged once per connection/request. If a connection or request is processed
+    without causing any log message, the additional information is not logged
+    either.</p>
+
+    <p>It can happen that some format string items do not produce output.  For
+    example, the Referer header is only present if the log message is
+    associated to a request and the log message happens at a time when the
+    Referer header has already been read from the client.  If no output is
+    produced, the default behaviour is to delete everything from the preceeding
+    space character to the next space character.  This means the log line is
+    implicitly divided into fields on non-whitespace to whitespace transitions.
+    If a format string item does not produce output, the whole field is
+    ommitted.  For example, if the remote address <code>%a</code> in the log
+    format <code>[%t] [%l] [%a] %M&nbsp;</code> is not available, the surrounding
+    brackets are not logged either.  Space characters can be escaped with a
+    backslash to prevent them from delimiting a field.  The combination '%&nbsp;'
+    (percent space) is a zero-witdh field delimiter that does not produce any
+    output.</p>
+
+    <p>The above behaviour can be changed by adding flags to the format string
+    item. A <code>-</code> (minus) flag causes a minus to be logged if the
+    respective item does not produce any output. In once-per-connection/request
+    formats, it is also possible to use the <code>+</code> (plus) flag. If an
+    item with the plus flag does not produce any output, the whole line is
+    ommitted.</p>
+
+    <p>Some format string items accept additional parameters in braces.</p>
+
+    <table border="1" style="zebra">
+    <columnspec><column width=".2"/><column width=".8"/></columnspec>
+
+    <tr><th>Format&nbsp;String</th> <th>Description</th></tr>
+
+    <tr><td><code>%%</code></td>
+        <td>The percent sign</td></tr>
+
+    <tr><td><code>%...a</code></td>
+        <td>Remote IP-address and port</td></tr>
+
+    <tr><td><code>%...A</code></td>
+        <td>Local IP-address and port</td></tr>
+
+    <tr><td><code>%...E</code></td>
+        <td>APR/OS error status code and string</td></tr>
+
+    <tr><td><code>%...F</code></td>
+        <td>Source file name and line number of the log call</td></tr>
+
+    <tr><td><code>%...{name}i</code></td>
+        <td>Request header <code>name</code></td></tr>
+
+    <tr><td><code>%...k</code></td>
+        <td>Number of keep-alive requests on this connection</td></tr>
+
+    <tr><td><code>%...l</code></td>
+        <td>Loglevel of the message</td></tr>
+
+    <tr><td><code>%...L</code></td>
+        <td>Log ID of the request</td></tr>
+
+    <tr><td><code>%...{c}L</code></td>
+        <td>Log ID of the connection</td></tr>
+
+    <tr><td><code>%...{C}L</code></td>
+        <td>Log ID of the connection if used in connection scope, empty otherwise</td></tr>
+
+    <tr><td><code>%...m</code></td>
+        <td>Name of the module logging the message</td></tr>
+
+    <tr><td><code>%M</code></td>
+        <td>The actual log message</td></tr>
+
+    <tr><td><code>%...P</code></td>
+        <td>Process ID of current process</td></tr>
+
+    <tr><td><code>%...T</code></td>
+        <td>Thread ID of current thread</td></tr>
+
+    <tr><td><code>%...t</code></td>
+        <td>The current time</td></tr>
+
+    <tr><td><code>%...{u}t</code></td>
+        <td>The current time including micro-seconds</td></tr>
+
+    <tr><td><code>%...{cu}t</code></td>
+        <td>The current time in compact ISO 8601 format, including
+            micro-seconds</td></tr>
+
+    <tr><td><code>\&nbsp;</code> (backslash space)</td>
+        <td>Non-field delimiting space</td></tr>
+
+    <tr><td><code>%&nbsp;</code> (percent space)</td>
+        <td>Field delimiter (no output)</td></tr>
+    </table>
+
+    <p>The log ID format <code>%L</code> produces a unique id for a connection
+    or request. This can be used to correlate which log lines belong to the
+    same connection or request, which request happens on which connection.
+    A <code>%L</code> format string is also available in
+    <module>mod_log_config</module>, to allow to correlate access log entries
+    with error log lines.</p>
+
+    <example><title>Example (somewhat similar to default format)</title>
+        ErrorLogFormat "[%{u}t] [%-m:%l] [pid %P] %F: %E: [client\ %a]
+        %M%&nbsp;,\&nbsp;referer\&nbsp;%{Referer}i"
+    </example>
+
+    <example><title>Example (similar to the 2.2.x format)</title>
+        ErrorLogFormat "[%t] [%l] %F: %E: [client\ %a]
+        %M%&nbsp;,\&nbsp;referer\&nbsp;%{Referer}i"
+    </example>
+
+    <example><title>Advanced example with request/connection log IDs</title>
+        ErrorLogFormat "[%{uc}t] [%-m:%-l] [R:%L] [C:%{C}L] %M"<br/>
+        ErrorLogFormat request "[%{uc}t] [R:%L] Request %k on C:%{c}L pid:%P tid:%T"<br/>
+        ErrorLogFormat request "[%{uc}t] [R:%L] UA:'%+{User-Agent}i'"<br/>
+        ErrorLogFormat request "[%{uc}t] [R:%L] Referer:'%+{Referer}i'"<br/>
+        ErrorLogFormat connection "[%{uc}t] [C:%{c}L] local\ %a remote\ %A"<br/>
+    </example>
+
+</usage>
+<seealso><directive module="core">ErrorLog</directive></seealso>
+<seealso><directive module="core">LogLevel</directive></seealso>
+<seealso><a href="../logs.html">Apache HTTP Server Log Files</a></seealso>
+</directivesynopsis>
+
 <directivesynopsis>
 <name>ExtendedStatus</name>
 <description>Keep track of extended status information for each 
index 7761b69edc5ffbbb805ac58ad2cd1ca1d4e51a36..7ac233ab835826db877a2c321f8135d3ada87901 100644 (file)
         module="mod_ident">IdentityCheck</directive> is set
         <code>On</code>.</td></tr>
 
+    <tr><td><code>%L</code></td>
+        <td>The request log ID from the error log (or '-' if nothing has been
+            logged to the error log for this request)</td></tr>
+
     <tr><td><code>%m</code></td>
         <td>The request method</td></tr>
 
index 010484725d8d8815d21bf2a16828df966c1695dd..a066a83a2dd32726ad946770ac0d7179ea0fd6a1 100644 (file)
  *                         proxy worker structs
  * 20100723.2 (2.3.7-dev)  Add ap_request_has_body()
  * 20100723.3 (2.3.8-dev)  Add ap_check_mpm()
+ * 20100905.0 (2.3.9-dev)  Add log_id to conn and req recs. Add error log
+ *                         format handlers. Support AP_CTIME_OPTION_COMPACT in
+ *                         ap_recent_ctime_ex().
  */
 
 #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */
 
 #ifndef MODULE_MAGIC_NUMBER_MAJOR
-#define MODULE_MAGIC_NUMBER_MAJOR 20100723
+#define MODULE_MAGIC_NUMBER_MAJOR 20100905
 #endif
-#define MODULE_MAGIC_NUMBER_MINOR 3                     /* 0...n */
+#define MODULE_MAGIC_NUMBER_MINOR 0                     /* 0...n */
 
 /**
  * Determine if the server's current MODULE_MAGIC_NUMBER is at least a
index c0b2dd7b44ccfc64cf14f5ab11a5aa9f9bc8f8ef..095880d918d062c7193c524d61c91789f04d7b04 100644 (file)
@@ -724,7 +724,7 @@ AP_DECLARE(const char *) ap_find_module_name(module *m);
 /**
  * Find the short name of the module identified by the specified module index
  * @param module_index The module index to get the name for
- * @return the name of the module
+ * @return the name of the module, NULL if not found
  */
 AP_DECLARE(const char *) ap_find_module_short_name(int module_index);
 /**
index 7951978759a57f5b6de63fd781fce2b196b10eae..affd42a6ada3b99871e1786ee3f926107903c516 100644 (file)
@@ -569,6 +569,15 @@ typedef struct {
     const char *protocol;
     apr_table_t *accf_map;
 
+    /* array of ap_errorlog_format_item for error log format string */
+    apr_array_header_t *error_log_format;
+    /*
+     * two arrays of arrays of ap_errorlog_format_item for additional information
+     * logged to the error log once per connection/request
+     */
+    apr_array_header_t *error_log_conn;
+    apr_array_header_t *error_log_req;
+
     /* TRACE control */
 #define AP_TRACE_UNSET    -1
 #define AP_TRACE_DISABLE   0
@@ -661,6 +670,62 @@ APR_DECLARE_OPTIONAL_FN(void, ap_logio_add_bytes_in,
 
 APR_DECLARE_OPTIONAL_FN(apr_off_t, ap_logio_get_last_bytes, (conn_rec *c));
 
+/* ----------------------------------------------------------------------
+ *
+ * Error log formats
+ */
+
+/**
+ * info structure passed to callback functions of errorlog handlers
+ */
+typedef struct ap_errorlog_info {
+    const server_rec *s;
+    const conn_rec *c;
+    const request_rec *r;
+    const request_rec *rmain;
+    const char *file;
+    int line;
+    int module_index;
+    int level;
+    apr_status_t status;
+    int using_syslog;
+    int startup;
+} ap_errorlog_info;
+
+/**
+ * callback function prototype for a external errorlog handler
+ */
+typedef int ap_errorlog_handler_fn_t(const ap_errorlog_info *info,
+                                     const char *arg, char *buf, int buflen);
+
+/**
+ * Register external errorlog handler
+ * @param p config pool to use
+ * @param tag the new format specifier (i.e. the letter after the %)
+ * @param handler the handler function
+ * @param flags flags (reserved, set to 0)
+ */
+AP_DECLARE(void) ap_register_errorlog_handler(apr_pool_t *p, char *tag,
+                                              ap_errorlog_handler_fn_t *handler,
+                                              int flags);
+
+typedef struct ap_errorlog_handler {
+    ap_errorlog_handler_fn_t *func;
+    int flags;
+} ap_errorlog_handler;
+
+typedef struct {
+    ap_errorlog_handler_fn_t *func;
+    const char *arg;
+#define AP_ERRORLOG_FLAG_FIELD_SEP       1
+#define AP_ERRORLOG_FLAG_MESSAGE         2
+#define AP_ERRORLOG_FLAG_REQUIRED        4
+#define AP_ERRORLOG_FLAG_NULL_AS_HYPHEN  8
+    unsigned int flags;
+} ap_errorlog_format_item;
+
+AP_DECLARE(void) ap_register_builtin_errorlog_handlers(apr_pool_t *p);
+
 /* ----------------------------------------------------------------------
  *
  * ident lookups with mod_ident
index 0447866cf378b4776c5ed159c1644b40eeeeef10..c275132d267de767e1656cbe2b0710a53c094a59 100644 (file)
@@ -962,6 +962,11 @@ struct request_rec {
      *  modifying */
     const struct ap_logconf *log;
 
+    /** Id to identify request in access and error log. Set when the first
+     *  error log entry for this request is generated.
+     */
+    const char *log_id;
+
     /**
      * A linked list of the .htaccess configuration directives
      * accessed by this request.
@@ -1103,6 +1108,12 @@ struct conn_rec {
      *  per_dir config, i.e. must be copied before modifying */
     const struct ap_logconf *log;
 
+    /** Id to identify this connection in error log. Set when the first
+     *  error log entry for this connection is generated.
+     */
+    const char *log_id;
+
+
     /** This points to the current thread being used to process this request,
      * over the lifetime of a request, the value may change. Users of the connection
      * record should not rely upon it staying the same between calls that invole
index 16706cff865ab5bb2da519e6e26a557afb911c7a..e4e02dbbfd6c2744527b61b39917aa7db475a6e7 100644 (file)
@@ -45,6 +45,8 @@ extern "C" {
 #define AP_CTIME_OPTION_NONE    0x0
 /* Add sub second timestamps with micro second resolution */
 #define AP_CTIME_OPTION_USEC    0x1
+/* Use more compact ISO 8601 format */
+#define AP_CTIME_OPTION_COMPACT 0x2
 
 
 /**
index 7f0085f030939ab039048133217f3ce237198e79..54acb20021e05b5b417e3881c950ea38cfc2d4b6 100644 (file)
  *         'X' = connection aborted before the response completed.
  *         '+' = connection may be kept alive after the response is sent.
  *         '-' = connection will be closed after the response is sent.
-           (This directive was %...c in late versions of Apache 1.3, but
-            this conflicted with the historical ssl %...{var}c syntax.)
-*
+ *         (This directive was %...c in late versions of Apache 1.3, but
+ *          this conflicted with the historical ssl %...{var}c syntax.)
+ * %...L:  Log-Id of the Request (or '-' if none)
+ * %...{c}L:  Log-Id of the Connection (or '-' if none)
+ *
  * The '...' can be nothing at all (e.g. "%h %u %r %s %b"), or it can
  * indicate conditions for inclusion of the item (which will cause it
  * to be replaced with '-' if the condition is not met).  Note that
@@ -369,6 +371,15 @@ static const char *log_request_method(request_rec *r, char *a)
 {
     return ap_escape_logitem(r->pool, r->method);
 }
+static const char *log_log_id(request_rec *r, char *a)
+{
+    if (a && !strcmp(a, "c")) {
+        return r->connection->log_id ? r->connection->log_id : "-";
+    }
+    else {
+        return r->log_id ? r->log_id : "-";
+    }
+}
 static const char *log_request_protocol(request_rec *r, char *a)
 {
     return ap_escape_logitem(r->pool, r->protocol);
@@ -1613,6 +1624,7 @@ static int log_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp)
         log_pfn_register(p, "i", log_header_in, 0);
         log_pfn_register(p, "o", log_header_out, 0);
         log_pfn_register(p, "n", log_note, 0);
+        log_pfn_register(p, "L", log_log_id, 1);
         log_pfn_register(p, "e", log_env_var, 0);
         log_pfn_register(p, "V", log_server_name, 0);
         log_pfn_register(p, "v", log_virtual_host, 0);
index ea54280ea46888e84c5a786ee9cd229034dd7592..dc9b3b64a45cdd79dd364781487c02cdd9f2ec7b 100644 (file)
@@ -747,7 +747,7 @@ AP_DECLARE(const char *) ap_setup_prelinked_modules(process_rec *process)
     ap_loaded_modules = (module **)apr_palloc(process->pool,
         sizeof(module *) * conf_vector_length);
     if (!ap_module_short_names)
-        ap_module_short_names = malloc(sizeof(char *) * conf_vector_length);
+        ap_module_short_names = calloc(sizeof(char *), conf_vector_length);
 
     if (ap_loaded_modules == NULL || ap_module_short_names == NULL) {
         return "Ouch! Out of memory in ap_setup_prelinked_modules()!";
@@ -780,8 +780,8 @@ AP_DECLARE(const char *) ap_find_module_name(module *m)
 
 AP_DECLARE(const char *) ap_find_module_short_name(int module_index)
 {
-        if (module_index < 0)
-                return "-";
+        if (module_index < 0 || module_index >= conf_vector_length)
+                return NULL;
         return ap_module_short_names[module_index];
 }
 
index 47129cea4337c642515ad4a3c21f7338819a3151..25f307f5b05a7825bcdf32a1773b293894aa1b80 100644 (file)
@@ -42,6 +42,7 @@
 #include "util_filter.h"
 #include "util_ebcdic.h"
 #include "util_mutex.h"
+#include "util_time.h"
 #include "mpm_common.h"
 #include "scoreboard.h"
 #include "mod_core.h"
@@ -53,6 +54,9 @@
 #if defined(RLIMIT_CPU) || defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined(RLIMIT_AS) || defined (RLIMIT_NPROC)
 #include "unixd.h"
 #endif
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
 
 /* LimitRequestBody handling */
 #define AP_LIMIT_REQ_BODY_UNSET         ((apr_off_t) -1)
@@ -460,6 +464,15 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv)
     if (virt->gprof_dir)
         conf->gprof_dir = virt->gprof_dir;
 
+    if (virt->error_log_format)
+        conf->error_log_format = virt->error_log_format;
+
+    if (virt->error_log_conn)
+        conf->error_log_conn = virt->error_log_conn;
+
+    if (virt->error_log_req)
+        conf->error_log_req = virt->error_log_req;
+
     return conf;
 }
 
@@ -3100,6 +3113,229 @@ static const char *set_trace_enable(cmd_parms *cmd, void *dummy,
     return NULL;
 }
 
+static apr_hash_t *errorlog_hash;
+
+static int log_constant_item(const ap_errorlog_info *info, const char *arg,
+                             char *buf, int buflen)
+{
+    char *end = apr_cpystrn(buf, arg, buflen);
+    return end - buf;
+}
+
+static char *parse_errorlog_misc_string(apr_pool_t *p,
+                                        ap_errorlog_format_item *it,
+                                        const char **sa)
+{
+    const char *s;
+    char scratch[MAX_STRING_LEN];
+    char *d = scratch;
+    /*
+     * non-leading white space terminates this string to allow the next field
+     * separator to be inserted
+     */
+    int at_start = 1;
+
+    it->func = log_constant_item;
+    s = *sa;
+
+    while (*s && *s != '%' && (*s != ' ' || at_start) && d < scratch + MAX_STRING_LEN) {
+        if (*s != '\\') {
+            if (*s != ' ') {
+                at_start = 0;
+            }
+            *d++ = *s++;
+        }
+        else {
+            s++;
+            switch (*s) {
+            case 'r':
+                *d++ = '\r';
+                s++;
+                break;
+            case 'n':
+                *d++ = '\n';
+                s++;
+                break;
+            case 't':
+                *d++ = '\t';
+                s++;
+                break;
+            case '\0':
+                /* handle end of string */
+                *d++ = '\\';
+                break;
+            default:
+                /* copy next char verbatim */
+                *d++ = *s++;
+                break;
+            }
+        }
+    }
+    *d = '\0';
+    it->arg = apr_pstrdup(p, scratch);
+
+    *sa = s;
+    return NULL;
+}
+
+static char *parse_errorlog_item(apr_pool_t *p, ap_errorlog_format_item *it,
+                                 const char **sa)
+{
+    const char *s = *sa;
+    ap_errorlog_handler *handler;
+
+    if (*s != '%') {
+        if (*s == ' ') {
+            it->flags |= AP_ERRORLOG_FLAG_FIELD_SEP;
+        }
+        return parse_errorlog_misc_string(p, it, sa);
+    }
+
+    ++s;
+
+    if (*s == ' ') {
+        /* percent-space (% ) is a field separator */
+        it->flags |= AP_ERRORLOG_FLAG_FIELD_SEP;
+        *sa = ++s;
+        /* recurse */
+        return parse_errorlog_item(p, it, sa);
+    }
+    else if (*s == '%') {
+        it->arg = "%";
+        it->func = log_constant_item;
+        *sa = ++s;
+        return NULL;
+    }
+
+    while (*s) {
+        switch (*s) {
+        case '{':
+            ++s;
+            it->arg = ap_getword(p, &s, '}');
+            break;
+        case '+':
+            ++s;
+            it->flags |= AP_ERRORLOG_FLAG_REQUIRED;
+            break;
+        case '-':
+            ++s;
+            it->flags |= AP_ERRORLOG_FLAG_NULL_AS_HYPHEN;
+            break;
+        case 'M':
+            it->func = NULL;
+            it->flags |= AP_ERRORLOG_FLAG_MESSAGE;
+            *sa = ++s;
+            return NULL;
+        default:
+            handler = (ap_errorlog_handler *)apr_hash_get(errorlog_hash, s, 1);
+            if (!handler) {
+                char dummy[2];
+
+                dummy[0] = *s;
+                dummy[1] = '\0';
+                return apr_pstrcat(p, "Unrecognized error log format directive %",
+                               dummy, NULL);
+            }
+            it->func = handler->func;
+            *sa = ++s;
+            return NULL;
+        }
+    }
+
+    return "Ran off end of error log format parsing args to some directive";
+}
+
+static apr_array_header_t *parse_errorlog_string(apr_pool_t *p,
+                                                 const char *s,
+                                                 const char **err,
+                                                 int want_msg_fmt)
+{
+    apr_array_header_t *a = apr_array_make(p, 30,
+                                           sizeof(ap_errorlog_format_item));
+    char *res;
+    int seen_msg_fmt = 0;
+
+    while (s && *s) {
+        ap_errorlog_format_item *item =
+            (ap_errorlog_format_item *)apr_array_push(a);
+        memset(item, 0, sizeof(*item));
+        res = parse_errorlog_item(p, item, &s);
+        if (res) {
+            *err = res;
+            return NULL;
+        }
+        if (item->flags & AP_ERRORLOG_FLAG_MESSAGE) {
+            if (!want_msg_fmt) {
+                *err = "%M cannot be used in once-per-request or "
+                       "once-per-connection formats";
+                return NULL;
+            }
+            seen_msg_fmt = 1;
+        }
+    }
+
+    if (want_msg_fmt && !seen_msg_fmt) {
+        *err = "main ErrorLogFormat must contain message format string '%M'";
+        return NULL;
+    }
+
+    return a;
+}
+
+static const char *set_errorlog_format(cmd_parms *cmd, void *dummy,
+                                       const char *arg1, const char *arg2)
+{
+    const char *err_string = NULL;
+    core_server_config *conf = ap_get_module_config(cmd->server->module_config,
+                                                    &core_module);
+
+    if (!arg2) {
+        conf->error_log_format = parse_errorlog_string(cmd->pool, arg1,
+                                                       &err_string, 1);
+    }
+    else if (!strcasecmp(arg1, "connection")) {
+        if (!conf->error_log_conn) {
+            conf->error_log_conn = apr_array_make(cmd->pool, 5,
+                                                  sizeof(apr_array_header_t *));
+        }
+
+        if (arg2 && *arg2) {
+            apr_array_header_t **e;
+            e = (apr_array_header_t **) apr_array_push(conf->error_log_conn);
+            *e = parse_errorlog_string(cmd->pool, arg2, &err_string, 0);
+        }
+    }
+    else if (!strcasecmp(arg1, "request")) {
+        if (!conf->error_log_req) {
+            conf->error_log_req = apr_array_make(cmd->pool, 5,
+                                                 sizeof(apr_array_header_t *));
+        }
+
+        if (arg2 && *arg2) {
+            apr_array_header_t **e;
+            e = (apr_array_header_t **) apr_array_push(conf->error_log_req);
+            *e = parse_errorlog_string(cmd->pool, arg2, &err_string, 0);
+        }
+    }
+    else {
+        err_string = "ErrorLogFormat type must be one of request, connection";
+    }
+
+    return err_string;
+}
+
+AP_DECLARE(void) ap_register_errorlog_handler(apr_pool_t *p, char *tag,
+                                              ap_errorlog_handler_fn_t *handler,
+                                              int flags)
+{
+    ap_errorlog_handler *log_struct = apr_palloc(p, sizeof(*log_struct));
+    log_struct->func = handler;
+    log_struct->flags = flags;
+
+    apr_hash_set(errorlog_hash, tag, 1, (const void *)log_struct);
+}
+
+
 /* Note --- ErrorDocument will now work from .htaccess files.
  * The AllowOverride of Fileinfo allows webmasters to turn it off
  */
@@ -3199,6 +3435,8 @@ AP_INIT_TAKE1("ServerRoot", set_server_root, NULL, RSRC_CONF | EXEC_ON_READ,
 AP_INIT_TAKE1("ErrorLog", set_server_string_slot,
   (void *)APR_OFFSETOF(server_rec, error_fname), RSRC_CONF,
   "The filename of the error log"),
+AP_INIT_TAKE12("ErrorLogFormat", set_errorlog_format, NULL, RSRC_CONF,
+  "Format string for the ErrorLog"),
 AP_INIT_RAW_ARGS("ServerAlias", set_server_alias, NULL, RSRC_CONF,
   "A name or names alternately used to access the server"),
 AP_INIT_TAKE1("ServerPath", set_serverpath, NULL, RSRC_CONF,
@@ -3632,6 +3870,9 @@ AP_DECLARE(int) ap_sys_privileges_handlers(int inc)
 static int core_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
 {
     ap_mutex_init(pconf);
+
+    ap_register_builtin_errorlog_handlers(pconf);
+
     return APR_SUCCESS;
 }
 
@@ -3843,6 +4084,8 @@ static int core_pre_connection(conn_rec *c, void *csd)
 
 static void register_hooks(apr_pool_t *p)
 {
+    errorlog_hash = apr_hash_make(p);
+
     /* create_connection and pre_connection should always be hooked
      * APR_HOOK_REALLY_LAST by core to give other modules the opportunity
      * to install alternate network transports and stop other functions 
index a5ba651b5966821cbb863964c6990fc373270154..42d711744acc36317b5eda0d6a64889c5d086207 100644 (file)
@@ -29,6 +29,7 @@
 #include "apr_lib.h"
 #include "apr_signal.h"
 #include "apr_portable.h"
+#include "apr_base64.h"
 
 #define APR_WANT_STDIO
 #define APR_WANT_STRFUNC
@@ -378,7 +379,7 @@ static int open_error_log(server_rec *s, int is_main, apr_pool_t *p)
             cmdtype = APR_SHELLCMD_ENV;
             ++fname;
         }
-       
+
         /* Spawn a new child logger.  If this is the main server_rec,
          * the new child must use a dummy stderr since the current
          * stderr might be a pipe to the old logger.  Otherwise, the
@@ -546,106 +547,132 @@ AP_DECLARE(void) ap_error_log2stderr(server_rec *s) {
     }
 }
 
-static void log_error_core(const char *file, int line, int module_index,
-                           int level,
-                           apr_status_t status, const server_rec *s,
-                           const conn_rec *c,
-                           const request_rec *r, apr_pool_t *pool,
-                           const char *fmt, va_list args)
+static int cpystrn(char *buf, const char *arg, int buflen)
 {
-    char errstr[MAX_STRING_LEN];
-#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED
-    char scratch[MAX_STRING_LEN];
-#endif
-    apr_size_t len, errstrlen;
-    apr_file_t *logf = NULL;
-    const char *referer;
-    int level_and_mask = level & APLOG_LEVELMASK;
+    char *end;
+    if (!arg)
+        return 0;
+    end = apr_cpystrn(buf, arg, buflen);
+    return end - buf;
+}
 
-    if (r && r->connection) {
-        c = r->connection;
-    }
 
-    if (s == NULL) {
-        /*
-         * If we are doing stderr logging (startup), don't log messages that are
-         * above the default server log level unless it is a startup/shutdown
-         * notice
-         */
-#ifndef DEBUG
-        if ((level_and_mask != APLOG_NOTICE)
-            && (level_and_mask > ap_default_loglevel)) {
-            return;
-        }
-#endif
+static int log_remote_address(const ap_errorlog_info *info, const char *arg,
+                              char *buf, int buflen)
+{
+    if (info->c)
+        return apr_snprintf(buf, buflen, "%s:%d", info->c->remote_ip,
+                            info->c->remote_addr->port);
+    else
+        return 0;
+}
 
-        logf = stderr_log;
+static int log_local_address(const ap_errorlog_info *info, const char *arg,
+                             char *buf, int buflen)
+{
+    if (info->c)
+        return apr_snprintf(buf, buflen, "%s:%d", info->c->local_ip,
+                            info->c->local_addr->port);
+    else
+        return 0;
+}
+
+static int log_pid(const ap_errorlog_info *info, const char *arg,
+                   char *buf, int buflen)
+{
+    pid_t pid = getpid();
+    return apr_snprintf(buf, buflen, "%" APR_PID_T_FMT, pid);
+}
+
+static int log_tid(const ap_errorlog_info *info, const char *arg,
+                   char *buf, int buflen)
+{
+#if APR_HAS_THREADS
+    int result;
+
+    if (ap_mpm_query(AP_MPMQ_IS_THREADED, &result) == APR_SUCCESS
+        && result != AP_MPMQ_NOT_SUPPORTED)
+    {
+        apr_os_thread_t tid = apr_os_thread_current();
+        return apr_snprintf(buf, buflen, "%pT", &tid);
     }
-    else {
-        int configured_level = r ? ap_get_request_module_loglevel(r, module_index)        :
-                               c ? ap_get_conn_server_module_loglevel(c, s, module_index) :
-                                   ap_get_server_module_loglevel(s, module_index);
-        if (s->error_log) {
-            /*
-             * If we are doing normal logging, don't log messages that are
-             * above the module's log level unless it is a startup/shutdown notice
-             */
-            if ((level_and_mask != APLOG_NOTICE)
-                && (level_and_mask > configured_level)) {
-                return;
-            }
+#endif
+    return 0;
+}
 
-            logf = s->error_log;
-        }
-        else {
-            /*
-             * If we are doing syslog logging, don't log messages that are
-             * above the module's log level (including a startup/shutdown notice)
-             */
-            if (level_and_mask > configured_level) {
-                return;
-            }
+static int log_ctime(const ap_errorlog_info *info, const char *arg,
+                     char *buf, int buflen)
+{
+    int time_len = buflen;
+    int option = AP_CTIME_OPTION_NONE;
+
+    while(arg && *arg) {
+        switch (*arg) {
+            case 'u':   option |= AP_CTIME_OPTION_USEC;
+                        break;
+            case 'c':   option |= AP_CTIME_OPTION_COMPACT;
+                        break;
         }
+        arg++;
     }
 
-    if (logf && ((level & APLOG_STARTUP) != APLOG_STARTUP)) {
-        int time_len;
+    ap_recent_ctime_ex(buf, apr_time_now(), option, &time_len);
 
-        errstr[0] = '[';
-        len = 1;
-        time_len = MAX_STRING_LEN - len;
-        ap_recent_ctime_ex(errstr + len, apr_time_now(),
-                           AP_CTIME_OPTION_USEC, &time_len);
-        len += time_len -1;
-        errstr[len++] = ']';
-        errstr[len++] = ' ';
-    } else {
-        len = 0;
-    }
-
-    if ((level & APLOG_STARTUP) != APLOG_STARTUP) {
-        len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
-                            "[%s] ", priorities[level_and_mask].t_name);
+    /* ap_recent_ctime_ex includes the trailing \0 in time_len */
+    return time_len - 1;
+}
 
-        len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
-                            "[pid %" APR_PID_T_FMT, getpid());
-#if APR_HAS_THREADS
-        {
-            int result;
+static int log_loglevel(const ap_errorlog_info *info, const char *arg,
+                        char *buf, int buflen)
+{
+    if (info->level < 0)
+        return 0;
+    else
+        return cpystrn(buf, priorities[info->level].t_name, buflen);
+}
 
-            if (ap_mpm_query(AP_MPMQ_IS_THREADED, &result) == APR_SUCCESS
-                && result != AP_MPMQ_NOT_SUPPORTED) {
-                apr_os_thread_t tid = apr_os_thread_current();
-                len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
-                                    ":tid %pT", &tid);
-            }
+static int log_log_id(const ap_errorlog_info *info, const char *arg,
+                      char *buf, int buflen)
+{
+    /*
+     * C: log conn log_id if available,
+     * c: log conn log id if available and not a once-per-request log line
+     * else: log request log id if available
+     */
+    if (arg && !strcasecmp(arg, "c")) {
+        if (info->c && (*arg != 'C' || !info->r)) {
+            return cpystrn(buf, info->c->log_id, buflen);
         }
-#endif
-        errstr[len++] = ']';
-        errstr[len++] = ' ';
     }
+    else if (info->r) {
+        return cpystrn(buf, info->r->log_id, buflen);
+    }
+    return 0;
+}
+
+static int log_keepalives(const ap_errorlog_info *info, const char *arg,
+                          char *buf, int buflen)
+{
+    if (!info->c)
+        return 0;
+
+    return apr_snprintf(buf, buflen, "%d", info->c->keepalives);
+}
 
-    if (file && level_and_mask >= APLOG_DEBUG) {
+static int log_module_name(const ap_errorlog_info *info, const char *arg,
+                           char *buf, int buflen)
+{
+    return cpystrn(buf, ap_find_module_short_name(info->module_index), buflen);
+}
+
+static int log_file_line(const ap_errorlog_info *info, const char *arg,
+                         char *buf, int buflen)
+{
+    if (info->file == NULL) {
+        return 0;
+    }
+    else {
+        const char *file = info->file;
 #if defined(_OSD_POSIX) || defined(WIN32) || defined(__MVS__)
         char tmp[256];
         char *e = strrchr(file, '/');
@@ -677,75 +704,303 @@ static void log_error_core(const char *file, int line, int module_index,
             file = p + 1;
         }
 #endif /*_OSD_POSIX || WIN32 */
-        len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
-                            "%s(%d): ", file, line);
+        return apr_snprintf(buf, buflen, "%s(%d)", file, info->line);
+    }
+}
+
+static int log_apr_status(const ap_errorlog_info *info, const char *arg,
+                          char *buf, int buflen)
+{
+    apr_status_t status = info->status;
+    int len = 0;
+    if (!status)
+        return 0;
+
+    if (status < APR_OS_START_EAIERR) {
+        len += apr_snprintf(buf + len, buflen - len,
+                            "(%d)", status);
+    }
+    else if (status < APR_OS_START_SYSERR) {
+        len += apr_snprintf(buf + len, buflen - len,
+                            "(EAI %d)", status - APR_OS_START_EAIERR);
+    }
+    else if (status < 100000 + APR_OS_START_SYSERR) {
+        len += apr_snprintf(buf + len, buflen - len,
+                            "(OS %d)", status - APR_OS_START_SYSERR);
+    }
+    else {
+        len += apr_snprintf(buf + len, buflen - len,
+                            "(os 0x%08x)", status - APR_OS_START_SYSERR);
     }
+    apr_strerror(status, buf + len, buflen - len);
+    len += strlen(buf + len);
+    return len;
+}
 
+static int log_header(const ap_errorlog_info *info, const char *arg,
+                      char *buf, int buflen)
+{
+    const char *header;
+    int len = 0;
+#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED
+    char scratch[MAX_STRING_LEN];
+#endif
+
+    if ( info->r && (header = apr_table_get(info->r->headers_in, arg))
+#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED
+         && ap_escape_errorlog_item(scratch, header, MAX_STRING_LEN)
+#endif
+       ) {
+        len = cpystrn(buf,
+#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED
+                           scratch,
+#else
+                           header,
+#endif
+                           buflen);
+    }
+    return len;
+}
+
+
+
+
+AP_DECLARE(void) ap_register_builtin_errorlog_handlers(apr_pool_t *p)
+{
+    ap_register_errorlog_handler(p, "a", log_remote_address, 0);
+    ap_register_errorlog_handler(p, "A", log_local_address, 0);
+    ap_register_errorlog_handler(p, "E", log_apr_status, 0);
+    ap_register_errorlog_handler(p, "F", log_file_line, 0);
+    ap_register_errorlog_handler(p, "i", log_header, 0);
+    ap_register_errorlog_handler(p, "k", log_keepalives, 0);
+    ap_register_errorlog_handler(p, "l", log_loglevel, 0);
+    ap_register_errorlog_handler(p, "L", log_log_id, 0);
+    ap_register_errorlog_handler(p, "m", log_module_name, 0);
+    ap_register_errorlog_handler(p, "P", log_pid, 0);
+    ap_register_errorlog_handler(p, "t", log_ctime, 0);
+    ap_register_errorlog_handler(p, "T", log_tid, 0);
+
+    /* XXX: TODO: envvars, notes */
+}
+
+static void add_log_id(const conn_rec *c, const request_rec *r)
+{
+    apr_uint64_t id, tmp;
+    pid_t pid;
+    int len;
+    char *encoded;
+
+    if (r && r->request_time) {
+        id = (apr_uint64_t)r->request_time;
+    }
+    else {
+        id = (apr_uint64_t)apr_time_now();
+    }
+
+    pid = getpid();
+    if (sizeof(pid_t) > 2) {
+        tmp = pid;
+        tmp = tmp << 40;
+        id ^= tmp;
+        pid = pid >> 24;
+        tmp = pid;
+        tmp = tmp << 56;
+        id ^= tmp;
+    }
+    else {
+        tmp = pid;
+        tmp = tmp << 40;
+        id ^= tmp;
+    }
+#if APR_HAS_THREADS
     if (c) {
-        /* XXX: TODO: add a method of selecting whether logged remote
-         * addresses are in dotted quad or resolved form... dotted
-         * quad is the most secure, which is why I'm implementing it
-         * first. -djg
-         */
+        apr_uintptr_t tmp2 = (apr_uintptr_t)c->current_thread;
+        tmp = tmp2;
+        tmp = tmp << 32;
+        id ^= tmp;
+    }
+#endif
+
+    /*
+     * The apr-util docs wrongly states encoded strings are not 0-terminated.
+     * Let's be save and allocate an additional byte.
+     */
+    len = 1 + apr_base64_encode_len(sizeof(id));
+    encoded = apr_palloc(r ? r->pool : c->pool, len);
+    apr_base64_encode(encoded, (char *)&id, sizeof(id));
+    encoded[11] = '\0'; /* omit last char which is always '=' */
+
+    /* need to cast const away */
+    if (r) {
+        ((request_rec *)r)->log_id = encoded;
+    }
+    else {
+        ((conn_rec *)c)->log_id = encoded;
+    }
+}
+
+/*
+ * This is used if no error log format is defined and during startup.
+ * It automatically omits the timestamp if logging to syslog.
+ */
+static int do_errorlog_default(const ap_errorlog_info *info, char *buf,
+                               int buflen, int *errstr_start, int *errstr_end,
+                               const char *errstr_fmt, va_list args)
+{
+    int len = 0;
+    int field_start = 0;
+    int item_len;
+#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED
+    char scratch[MAX_STRING_LEN];
+#endif
+
+    if (!info->using_syslog && !info->startup) {
+        buf[len++] = '[';
+        len += log_ctime(info, "u", buf + len, buflen - len);
+        buf[len++] = ']';
+        buf[len++] = ' ';
+    }
+
+    if (!info->startup) {
+        buf[len++] = '[';
+        len += log_module_name(info, NULL, buf + len, buflen - len);
+        buf[len++] = ':';
+        len += log_loglevel(info, NULL, buf + len, buflen - len);
+        len += cpystrn(buf + len, "] [pid ", buflen - len);
+
+        len += log_pid(info, NULL, buf + len, buflen - len);
+#if APR_HAS_THREADS
+        field_start = len;
+        len += cpystrn(buf + len, ":tid ", buflen - len);
+        item_len = log_tid(info, NULL, buf + len, buflen - len);
+        if (!item_len)
+            len = field_start;
+        else
+            len += item_len;
+#endif
+        buf[len++] = ']';
+        buf[len++] = ' ';
+    }
+
+    if (info->level >= APLOG_DEBUG) {
+        item_len = log_file_line(info, NULL, buf + len, buflen - len);
+        if (item_len) {
+            len += item_len;
+            len += cpystrn(buf + len, ": ", buflen - len);
+        }
+    }
+
+    if (info->status) {
+        item_len = log_apr_status(info, NULL, buf + len, buflen - len);
+        if (item_len) {
+            len += item_len;
+            len += cpystrn(buf + len, ": ", buflen - len);
+        }
+    }
+
+    if (info->c) {
         /*
          * remote_ip can be client or backend server. If we have a scoreboard
          * handle, it is likely a client.
          */
-        len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
-                            c->sbh ? "[client %s:%d] " : "[remote %s:%d] ",
-                            c->remote_ip, c->remote_addr->port);
-    }
-    if (status != 0) {
-        if (status < APR_OS_START_EAIERR) {
-            len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
-                                "(%d)", status);
-        }
-        else if (status < APR_OS_START_SYSERR) {
-            len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
-                                "(EAI %d)", status - APR_OS_START_EAIERR);
-        }
-        else if (status < 100000 + APR_OS_START_SYSERR) {
-            len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
-                                "(OS %d)", status - APR_OS_START_SYSERR);
-        }
-        else {
-            len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
-                                "(os 0x%08x)", status - APR_OS_START_SYSERR);
-        }
-        apr_strerror(status, errstr + len, MAX_STRING_LEN - len);
-        len += strlen(errstr + len);
-        if (MAX_STRING_LEN - len > 2) {
-            errstr[len++] = ':';
-            errstr[len++] = ' ';
-            errstr[len] = '\0';
-        }
+        len += apr_snprintf(buf + len, buflen - len,
+                            info->c->sbh ? "[client %s:%d] " : "[remote %s:%d] ",
+                            info->c->remote_ip, info->c->remote_addr->port);
     }
 
-    errstrlen = len;
+    /* the actual error message */
+    *errstr_start = len;
 #ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED
-    if (apr_vsnprintf(scratch, MAX_STRING_LEN - len, fmt, args)) {
-        len += ap_escape_errorlog_item(errstr + len, scratch,
-                                       MAX_STRING_LEN - len);
+    if (apr_vsnprintf(scratch, MAX_STRING_LEN, errstr_fmt, args)) {
+        len += ap_escape_errorlog_item(buf + len, scratch,
+                                       buflen - len);
+
     }
 #else
-    len += apr_vsnprintf(errstr + len, MAX_STRING_LEN - len, fmt, args);
+    len += apr_vsnprintf(buf + len, buflen - len, errstr_fmt, args);
 #endif
+    *errstr_end = len;
+
+    field_start = len;
+    len += cpystrn(buf + len, ", referer: ", buflen - len);
+    item_len = log_header(info, "Referer", buf + len, buflen - len);
+    if (item_len)
+        len += item_len;
+    else
+        len = field_start;
+
+    return len;
+}
 
-    if (   r && (referer = apr_table_get(r->headers_in, "Referer"))
+static int do_errorlog_format(apr_array_header_t *fmt, ap_errorlog_info *info,
+                              char *buf, int buflen, int *errstr_start,
+                              int *errstr_end, const char *err_fmt, va_list args)
+{
 #ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED
-        && ap_escape_errorlog_item(scratch, referer, MAX_STRING_LEN - len)
+    char scratch[MAX_STRING_LEN];
 #endif
-        ) {
-        len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
-                            ", referer: %s",
+    int i;
+    int len = 0;
+    int field_start = 0;
+    int skipping = 0;
+    ap_errorlog_format_item *items = (ap_errorlog_format_item *)fmt->elts;
+
+    for (i = 0; i < fmt->nelts; ++i) {
+        ap_errorlog_format_item *item = &items[i];
+        if (item->flags & AP_ERRORLOG_FLAG_FIELD_SEP) {
+            if (skipping) {
+                skipping = 0;
+            }
+            else {
+                field_start = len;
+            }
+        }
+
+        if (item->flags & AP_ERRORLOG_FLAG_MESSAGE) {
+            /* the actual error message */
+            *errstr_start = len;
 #ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED
-                            scratch
+            if (apr_vsnprintf(scratch, MAX_STRING_LEN, err_fmt, args)) {
+                len += ap_escape_errorlog_item(buf + len, scratch,
+                                               buflen - len);
+
+            }
 #else
-                            referer
+            len += apr_vsnprintf(buf + len, buflen - len, err_fmt, args);
 #endif
-                            );
+            *errstr_end = len;
+        }
+        else if (skipping) {
+            continue;
+        }
+        else {
+            int item_len = (*item->func)(info, item->arg, buf + len,
+                                         buflen - len);
+            if (!item_len) {
+                if (item->flags & AP_ERRORLOG_FLAG_REQUIRED) {
+                    /* required item is empty. skip whole line */
+                    buf[0] = '\0';
+                    return 0;
+                }
+                else if (item->flags & AP_ERRORLOG_FLAG_NULL_AS_HYPHEN) {
+                    buf[len++] = '-';
+                }
+                else {
+                    len = field_start;
+                    skipping = 1;
+                }
+            }
+            else {
+                len += item_len;
+            }
+        }
     }
+    return len;
+}
 
+static void write_logline(char *errstr, apr_size_t len, apr_file_t *logf,
+                          int level)
+{
     /* NULL if we are logging to syslog */
     if (logf) {
         /* Truncate for the terminator (as apr_snprintf does) */
@@ -758,13 +1013,193 @@ static void log_error_core(const char *file, int line, int module_index,
     }
 #ifdef HAVE_SYSLOG
     else {
-        syslog(level_and_mask < LOG_PRIMASK ? level_and_mask : APLOG_DEBUG,
-               "%s", errstr);
+        syslog(level < LOG_PRIMASK ? level : APLOG_DEBUG, "%s", errstr);
     }
 #endif
+}
 
-    ap_run_error_log(file, line, module_index, level, status, s, r, pool,
-                     errstr + errstrlen);
+static void log_error_core(const char *file, int line, int module_index,
+                           int level,
+                           apr_status_t status, const server_rec *s,
+                           const conn_rec *c,
+                           const request_rec *r, apr_pool_t *pool,
+                           const char *fmt, va_list args)
+{
+    char errstr[MAX_STRING_LEN];
+    apr_file_t *logf = NULL;
+    int level_and_mask = level & APLOG_LEVELMASK;
+    const request_rec *rmain = NULL;
+    core_server_config *sconf = NULL;
+    ap_errorlog_info info;
+
+    /* do we need to log once-per-req or once-per-conn info? */
+    int log_conn_info = 0, log_req_info = 0;
+    apr_array_header_t **lines = NULL;
+    int done = 0;
+    int line_number = 0;
+
+    if (r && r->connection) {
+        c = r->connection;
+    }
+
+    if (s == NULL) {
+        /*
+         * If we are doing stderr logging (startup), don't log messages that are
+         * above the default server log level unless it is a startup/shutdown
+         * notice
+         */
+#ifndef DEBUG
+        if ((level_and_mask != APLOG_NOTICE)
+            && (level_and_mask > ap_default_loglevel)) {
+            return;
+        }
+#endif
+
+        logf = stderr_log;
+    }
+    else {
+        int configured_level = r ? ap_get_request_module_loglevel(r, module_index)        :
+                               c ? ap_get_conn_server_module_loglevel(c, s, module_index) :
+                                   ap_get_server_module_loglevel(s, module_index);
+        if (s->error_log) {
+            /*
+             * If we are doing normal logging, don't log messages that are
+             * above the module's log level unless it is a startup/shutdown notice
+             */
+            if ((level_and_mask != APLOG_NOTICE)
+                && (level_and_mask > configured_level)) {
+                return;
+            }
+
+            logf = s->error_log;
+        }
+        else {
+            /*
+             * If we are doing syslog logging, don't log messages that are
+             * above the module's log level (including a startup/shutdown notice)
+             */
+            if (level_and_mask > configured_level) {
+                return;
+            }
+        }
+
+        sconf = ap_get_module_config(s->module_config, &core_module);
+        if (c && !c->log_id) {
+            add_log_id(c, NULL);
+            if (sconf->error_log_conn && sconf->error_log_conn->nelts > 0)
+                log_conn_info = 1;
+        }
+        if (r) {
+            if (r->main)
+                rmain = r->main;
+            else
+                rmain = r;
+
+            if (!rmain->log_id) {
+                /* XXX: do we need separate log ids for subrequests? */
+                if (sconf->error_log_req && sconf->error_log_req->nelts > 0)
+                    log_req_info = 1;
+                /*
+                 * XXX: potential optimization: only create log id if %L is
+                 * XXX: actually used
+                 */
+                add_log_id(c, rmain);
+            }
+        }
+    }
+
+    info.s             = s;
+    info.c             = c;
+    info.file          = file;
+    info.line          = line;
+    info.status        = status;
+    info.using_syslog  = (logf == NULL);
+    info.startup       = ((level & APLOG_STARTUP) == APLOG_STARTUP);
+
+
+    while (!done) {
+        apr_array_header_t *log_format;
+        int len = 0, errstr_start = 0, errstr_end = 0;
+        /* XXX: potential optimization: format common prefixes only once */
+        if (log_conn_info) {
+            /* once-per-connection info */
+            if (line_number == 0) {
+                lines = (apr_array_header_t **)sconf->error_log_conn->elts;
+                info.r = NULL;
+                info.rmain = NULL;
+                info.level = -1;
+                info.module_index = APLOG_NO_MODULE;
+            }
+
+            log_format = lines[line_number++];
+
+            if (line_number == sconf->error_log_conn->nelts) {
+                /* this is the last line of once-per-connection info */
+                line_number = 0;
+                log_conn_info = 0;
+            }
+        }
+        else if (log_req_info) {
+            /* once-per-request info */
+            if (line_number == 0) {
+                lines = (apr_array_header_t **)sconf->error_log_req->elts;
+                info.r = rmain;
+                info.rmain = rmain;
+                info.level = -1;
+                info.module_index = APLOG_NO_MODULE;
+            }
+
+            log_format = lines[line_number++];
+
+            if (line_number == sconf->error_log_req->nelts) {
+                /* this is the last line of once-per-request info */
+                line_number = 0;
+                log_req_info = 0;
+            }
+        }
+        else {
+            /* the actual error message */
+            info.r = r;
+            info.rmain = rmain;
+            info.level = level_and_mask;
+            info.module_index = module_index;
+            log_format = sconf ? sconf->error_log_format : NULL;
+            done = 1;
+        }
+
+        /*
+         * prepare and log one line
+         */
+
+        if (log_format) {
+            len += do_errorlog_format(log_format, &info, errstr + len,
+                                      MAX_STRING_LEN - len,
+                                      &errstr_start, &errstr_end, fmt, args);
+        }
+        else {
+            len += do_errorlog_default(&info, errstr + len, MAX_STRING_LEN - len,
+                                       &errstr_start, &errstr_end, fmt, args);
+        }
+
+        if (!*errstr)
+        {
+            /*
+             * Don't log empty lines. This can happen with once-per-conn/req
+             * info if an item with AP_ERRORLOG_FLAG_REQUIRED is NULL.
+             */
+            continue;
+        }
+        write_logline(errstr, len, logf, level_and_mask);
+
+        if (!log_format) {
+            /* only pass the real error string to the hook */
+            errstr[errstr_end] = '\0';
+            ap_run_error_log(file, line, module_index, level, status, s, r, pool,
+                             errstr + errstr_start);
+        }
+
+        *errstr = '\0';
+    }
 }
 
 AP_DECLARE(void) ap_log_error_(const char *file, int line, int module_index,
index 4e0df9912e20fda045562fa2e30e4eb2e935046a..3632d0d583196ed33df67f1266fd997dccbe0a9d 100644 (file)
@@ -22,6 +22,9 @@
  *   */
 #define AP_CTIME_USEC_LENGTH      7
 
+/* Length of ISO 8601 date/time */
+#define AP_CTIME_COMPACT_LEN      20
+
 
 /* Cache for exploded values of recent timestamps
  */
@@ -169,7 +172,11 @@ AP_DECLARE(apr_status_t) ap_recent_ctime_ex(char *date_str, apr_time_t t,
 
 
     /* Calculate the needed buffer length */
-    needed = APR_CTIME_LEN;
+    if (option & AP_CTIME_OPTION_COMPACT)
+        needed = AP_CTIME_COMPACT_LEN;
+    else
+        needed = APR_CTIME_LEN;
+
     if (option & AP_CTIME_OPTION_USEC) {
         needed += AP_CTIME_USEC_LENGTH;
     }
@@ -187,18 +194,34 @@ AP_DECLARE(apr_status_t) ap_recent_ctime_ex(char *date_str, apr_time_t t,
 
     /* example without options: "Wed Jun 30 21:49:08 1993" */
     /*                           123456789012345678901234  */
+    /* example for compact format: "1993-06-30 21:49:08" */
+    /*                              1234567890123456789  */
 
     ap_explode_recent_localtime(&xt, t);
-    s = &apr_day_snames[xt.tm_wday][0];
-    *date_str++ = *s++;
-    *date_str++ = *s++;
-    *date_str++ = *s++;
-    *date_str++ = ' ';
-    s = &apr_month_snames[xt.tm_mon][0];
-    *date_str++ = *s++;
-    *date_str++ = *s++;
-    *date_str++ = *s++;
-    *date_str++ = ' ';
+    real_year = 1900 + xt.tm_year;
+    if (option & AP_CTIME_OPTION_COMPACT) {
+        int real_month = xt.tm_mon + 1;
+        *date_str++ = real_year / 1000 + '0';
+        *date_str++ = real_year % 1000 / 100 + '0';
+        *date_str++ = real_year % 100 / 10 + '0';
+        *date_str++ = real_year % 10 + '0';
+        *date_str++ = '-';
+        *date_str++ = real_month / 10 + '0';
+        *date_str++ = real_month % 10 + '0';
+        *date_str++ = '-';
+    }
+    else {
+        s = &apr_day_snames[xt.tm_wday][0];
+        *date_str++ = *s++;
+        *date_str++ = *s++;
+        *date_str++ = *s++;
+        *date_str++ = ' ';
+        s = &apr_month_snames[xt.tm_mon][0];
+        *date_str++ = *s++;
+        *date_str++ = *s++;
+        *date_str++ = *s++;
+        *date_str++ = ' ';
+    }
     *date_str++ = xt.tm_mday / 10 + '0';
     *date_str++ = xt.tm_mday % 10 + '0';
     *date_str++ = ' ';
@@ -219,12 +242,13 @@ AP_DECLARE(apr_status_t) ap_recent_ctime_ex(char *date_str, apr_time_t t,
             usec = usec % div;
         }
     }
-    *date_str++ = ' ';
-    real_year = 1900 + xt.tm_year;
-    *date_str++ = real_year / 1000 + '0';
-    *date_str++ = real_year % 1000 / 100 + '0';
-    *date_str++ = real_year % 100 / 10 + '0';
-    *date_str++ = real_year % 10 + '0';
+    if (!(option & AP_CTIME_OPTION_COMPACT)) {
+        *date_str++ = ' ';
+        *date_str++ = real_year / 1000 + '0';
+        *date_str++ = real_year % 1000 / 100 + '0';
+        *date_str++ = real_year % 100 / 10 + '0';
+        *date_str++ = real_year % 10 + '0';
+    }
     *date_str++ = 0;
 
     return APR_SUCCESS;