upload http
[bottlenecks.git] / rubbos / app / httpd-2.0.64 / modules / loggers / mod_log_config.c
diff --git a/rubbos/app/httpd-2.0.64/modules/loggers/mod_log_config.c b/rubbos/app/httpd-2.0.64/modules/loggers/mod_log_config.c
new file mode 100644 (file)
index 0000000..2bfdf34
--- /dev/null
@@ -0,0 +1,1519 @@
+/* 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.
+ */
+
+/*
+ * Modified by djm@va.pubnix.com:
+ * If no TransferLog is given explicitly, decline to log.
+ *
+ * This is module implements the TransferLog directive (same as the
+ * common log module), and additional directives, LogFormat and CustomLog.
+ *
+ *
+ * Syntax:
+ *
+ *    TransferLog fn      Logs transfers to fn in standard log format, unless
+ *                        a custom format is set with LogFormat
+ *    LogFormat format    Set a log format from TransferLog files
+ *    CustomLog fn format
+ *                        Log to file fn with format given by the format
+ *                        argument
+ *
+ *    CookieLog fn        For backwards compatability with old Cookie
+ *                        logging module - now deprecated.
+ *
+ * There can be any number of TransferLog and CustomLog
+ * commands. Each request will be logged to _ALL_ the
+ * named files, in the appropriate format.
+ *
+ * If no TransferLog or CustomLog directive appears in a VirtualHost,
+ * the request will be logged to the log file(s) defined outside
+ * the virtual host section. If a TransferLog or CustomLog directive
+ * appears in the VirtualHost section, the log files defined outside
+ * the VirtualHost will _not_ be used. This makes this module compatable
+ * with the CLF and config log modules, where the use of TransferLog
+ * inside the VirtualHost section overrides its use outside.
+ * 
+ * Examples:
+ *
+ *    TransferLog    logs/access_log
+ *    <VirtualHost>
+ *    LogFormat      "... custom format ..."
+ *    TransferLog    log/virtual_only
+ *    CustomLog      log/virtual_useragents "%t %{user-agent}i"
+ *    </VirtualHost>
+ *
+ * This will log using CLF to access_log any requests handled by the
+ * main server, while any requests to the virtual host will be logged
+ * with the "... custom format..." to virtual_only _AND_ using
+ * the custom user-agent log to virtual_useragents.
+ *
+ * Note that the NCSA referer and user-agent logs are easily added with
+ * CustomLog:
+ *   CustomLog   logs/referer  "%{referer}i -> %U"
+ *   CustomLog   logs/agent    "%{user-agent}i"
+ *
+ * RefererIgnore functionality can be obtained with conditional
+ * logging (SetEnvIf and CustomLog ... env=!VAR).
+ *
+ * But using this method allows much easier modification of the
+ * log format, e.g. to log hosts along with UA:
+ *   CustomLog   logs/referer "%{referer}i %U %h"
+ *
+ * The argument to LogFormat and CustomLog is a string, which can include
+ * literal characters copied into the log files, and '%' directives as
+ * follows:
+ *
+ * %...B:  bytes sent, excluding HTTP headers.
+ * %...b:  bytes sent, excluding HTTP headers in CLF format, i.e. a '-'
+ *         when no bytes where sent (rather than a '0'.
+ * %...{FOOBAR}C:  The contents of the HTTP cookie FOOBAR
+ * %...{FOOBAR}e:  The contents of the environment variable FOOBAR
+ * %...f:  filename
+ * %...h:  remote host
+ * %...a:  remote IP-address
+ * %...A:  local IP-address
+ * %...{Foobar}i:  The contents of Foobar: header line(s) in the request
+ *                 sent to the client.
+ * %...l:  remote logname (from identd, if supplied)
+ * %...{Foobar}n:  The contents of note "Foobar" from another module.
+ * %...{Foobar}o:  The contents of Foobar: header line(s) in the reply.
+ * %...p:  the port the request was served to
+ * %...P:  the process ID of the child that serviced the request.
+ * %...{format}P: the process ID or thread ID of the child/thread that
+ *                serviced the request
+ * %...r:  first line of request
+ * %...s:  status.  For requests that got internally redirected, this
+ *         is status of the *original* request --- %...>s for the last.
+ * %...t:  time, in common log format time format
+ * %...{format}t:  The time, in the form given by format, which should
+ *                 be in strftime(3) format.
+ * %...T:  the time taken to serve the request, in seconds.
+ * %...D:  the time taken to serve the request, in micro seconds.
+ * %...u:  remote user (from auth; may be bogus if return status (%s) is 401)
+ * %...U:  the URL path requested.
+ * %...v:  the configured name of the server (i.e. which virtual host?)
+ * %...V:  the server name according to the UseCanonicalName setting
+ * %...m:  the request method
+ * %...H:  the request protocol
+ * %...q:  the query string prepended by "?", or empty if no query string
+ * %...X:  Status of the connection.
+ *         '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.)
+*
+ * 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
+ * there is no escaping performed on the strings from %r, %...i and
+ * %...o; some with long memories may remember that I thought this was
+ * a bad idea, once upon a time, and I'm still not comfortable with
+ * it, but it is difficult to see how to "do the right thing" with all
+ * of '%..i', unless we URL-escape everything and break with CLF.
+ *
+ * The forms of condition are a list of HTTP status codes, which may
+ * or may not be preceded by '!'.  Thus, '%400,501{User-agent}i' logs
+ * User-agent: on 400 errors and 501 errors (Bad Request, Not
+ * Implemented) only; '%!200,304,302{Referer}i' logs Referer: on all
+ * requests which did *not* return some sort of normal status.
+ *
+ * The default LogFormat reproduces CLF; see below.
+ *
+ * The way this is supposed to work with virtual hosts is as follows:
+ * a virtual host can have its own LogFormat, or its own TransferLog.
+ * If it doesn't have its own LogFormat, it inherits from the main
+ * server.  If it doesn't have its own TransferLog, it writes to the
+ * same descriptor (meaning the same process for "| ...").
+ *
+ * --- rst */
+
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_hash.h"
+#include "apr_optional.h"
+#include "apr_anylock.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "mod_log_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"          /* For REMOTE_NAME */
+#include "http_log.h"
+#include "http_protocol.h"
+#include "util_time.h"
+#include "ap_mpm.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#define DEFAULT_LOG_FORMAT "%h %l %u %t \"%r\" %>s %b"
+
+module AP_MODULE_DECLARE_DATA log_config_module;
+
+#ifndef APR_LARGEFILE
+#define APR_LARGEFILE 0
+#endif
+
+static int xfer_flags = (APR_WRITE | APR_APPEND | APR_CREATE | APR_LARGEFILE);
+static apr_fileperms_t xfer_perms = APR_OS_DEFAULT;
+static apr_hash_t *log_hash;
+static apr_status_t ap_default_log_writer(request_rec *r,
+                           void *handle, 
+                           const char **strs,
+                           int *strl,
+                           int nelts,
+                           apr_size_t len);
+static apr_status_t ap_buffered_log_writer(request_rec *r,
+                           void *handle, 
+                           const char **strs,
+                           int *strl,
+                           int nelts,
+                           apr_size_t len);
+static void *ap_default_log_writer_init(apr_pool_t *p, server_rec *s, 
+                                        const char* name);
+static void *ap_buffered_log_writer_init(apr_pool_t *p, server_rec *s, 
+                                        const char* name);
+
+static void ap_log_set_writer_init(ap_log_writer_init *handle);
+static void ap_log_set_writer(ap_log_writer *handle);
+static ap_log_writer *log_writer = ap_default_log_writer;
+static ap_log_writer_init *log_writer_init = ap_default_log_writer_init;
+static int buffered_logs = 0; /* default unbuffered */
+static apr_array_header_t *all_buffered_logs = NULL;
+
+/* POSIX.1 defines PIPE_BUF as the maximum number of bytes that is
+ * guaranteed to be atomic when writing a pipe.  And PIPE_BUF >= 512
+ * is guaranteed.  So we'll just guess 512 in the event the system
+ * doesn't have this.  Now, for file writes there is actually no limit,
+ * the entire write is atomic.  Whether all systems implement this
+ * correctly is another question entirely ... so we'll just use PIPE_BUF
+ * because it's probably a good guess as to what is implemented correctly
+ * everywhere.
+ */
+#ifdef PIPE_BUF
+#define LOG_BUFSIZE     PIPE_BUF
+#else
+#define LOG_BUFSIZE     (512)
+#endif
+
+/*
+ * multi_log_state is our per-(virtual)-server configuration. We store
+ * an array of the logs we are going to use, each of type config_log_state.
+ * If a default log format is given by LogFormat, store in default_format
+ * (backward compat. with mod_log_config).  We also store for each virtual
+ * server a pointer to the logs specified for the main server, so that if this
+ * vhost has no logs defined, we can use the main server's logs instead.
+ *
+ * So, for the main server, config_logs contains a list of the log files
+ * and server_config_logs is empty. For a vhost, server_config_logs
+ * points to the same array as config_logs in the main server, and
+ * config_logs points to the array of logs defined inside this vhost,
+ * which might be empty.
+ */
+
+typedef struct {
+    const char *default_format_string;
+    apr_array_header_t *default_format;
+    apr_array_header_t *config_logs;
+    apr_array_header_t *server_config_logs;
+    apr_table_t *formats;
+} multi_log_state;
+
+/*
+ * config_log_state holds the status of a single log file. fname might
+ * be NULL, which means this module does no logging for this
+ * request. format might be NULL, in which case the default_format
+ * from the multi_log_state should be used, or if that is NULL as
+ * well, use the CLF. 
+ * log_writer is NULL before the log file is opened and is
+ * set to a opaque structure (usually a fd) after it is opened.
+ */
+typedef struct {
+    apr_file_t *handle;
+    apr_size_t outcnt;
+    char outbuf[LOG_BUFSIZE];
+    apr_anylock_t mutex;
+} buffered_log;
+
+typedef struct {
+    const char *fname;
+    const char *format_string;
+    apr_array_header_t *format;
+    void *log_writer;
+    char *condition_var;
+} config_log_state;
+
+/*
+ * Format items...
+ * Note that many of these could have ap_sprintfs replaced with static buffers.
+ */
+
+typedef struct {
+    ap_log_handler_fn_t *func;
+    char *arg;
+    int condition_sense;
+    int want_orig;
+    apr_array_header_t *conditions;
+} log_format_item;
+
+static char *format_integer(apr_pool_t *p, int i)
+{
+    return apr_itoa(p, i);
+}
+
+static char *pfmt(apr_pool_t *p, int i)
+{
+    if (i <= 0) {
+        return "-";
+    }
+    else {
+        return format_integer(p, i);
+    }
+}
+
+static const char *constant_item(request_rec *dummy, char *stuff)
+{
+    return stuff;
+}
+
+static const char *log_remote_host(request_rec *r, char *a)
+{
+    return ap_escape_logitem(r->pool, ap_get_remote_host(r->connection,
+                                                         r->per_dir_config,
+                                                         REMOTE_NAME, NULL));
+}
+
+static const char *log_remote_address(request_rec *r, char *a)
+{
+    return r->connection->remote_ip;
+}
+
+static const char *log_local_address(request_rec *r, char *a)
+{
+    return r->connection->local_ip;
+}
+
+static const char *log_remote_logname(request_rec *r, char *a)
+{
+    return ap_escape_logitem(r->pool, ap_get_remote_logname(r));
+}
+
+static const char *log_remote_user(request_rec *r, char *a)
+{
+    char *rvalue = r->user;
+
+    if (rvalue == NULL) {
+        rvalue = "-";
+    }
+    else if (strlen(rvalue) == 0) {
+        rvalue = "\"\"";
+    }
+    else {
+        rvalue = ap_escape_logitem(r->pool, rvalue);
+    }
+
+    return rvalue;
+}
+
+static const char *log_request_line(request_rec *r, char *a)
+{
+    /* NOTE: If the original request contained a password, we
+     * re-write the request line here to contain XXXXXX instead:
+     * (note the truncation before the protocol string for HTTP/0.9 requests)
+     * (note also that r->the_request contains the unmodified request)
+     */
+    return ap_escape_logitem(r->pool,
+                             (r->parsed_uri.password)
+                               ? apr_pstrcat(r->pool, r->method, " ",
+                                             apr_uri_unparse(r->pool,
+                                                             &r->parsed_uri, 0),
+                                             r->assbackwards ? NULL : " ",
+                                             r->protocol, NULL)
+                               : r->the_request);
+}
+
+static const char *log_request_file(request_rec *r, char *a)
+{
+    return ap_escape_logitem(r->pool, r->filename);
+}
+static const char *log_request_uri(request_rec *r, char *a)
+{
+    return ap_escape_logitem(r->pool, r->uri);
+}
+static const char *log_request_method(request_rec *r, char *a)
+{
+    return ap_escape_logitem(r->pool, r->method);
+}
+static const char *log_request_protocol(request_rec *r, char *a)
+{
+    return ap_escape_logitem(r->pool, r->protocol);
+}
+static const char *log_request_query(request_rec *r, char *a)
+{
+    return (r->args) ? apr_pstrcat(r->pool, "?",
+                                   ap_escape_logitem(r->pool, r->args), NULL)
+                     : "";
+}
+static const char *log_status(request_rec *r, char *a)
+{
+    return pfmt(r->pool, r->status);
+}
+
+static const char *clf_log_bytes_sent(request_rec *r, char *a)
+{
+    if (!r->sent_bodyct || !r->bytes_sent) {
+        return "-";
+    }
+    else {
+        return apr_off_t_toa(r->pool, r->bytes_sent);
+    }
+}
+
+static const char *log_bytes_sent(request_rec *r, char *a)
+{
+    if (!r->sent_bodyct || !r->bytes_sent) {
+        return "0";
+    }
+    else {
+        return apr_psprintf(r->pool, "%" APR_OFF_T_FMT, r->bytes_sent);
+    }
+}
+
+
+static const char *log_header_in(request_rec *r, char *a)
+{
+    return ap_escape_logitem(r->pool, apr_table_get(r->headers_in, a));
+}
+
+static APR_INLINE char *find_multiple_headers(apr_pool_t *pool,
+                                              const apr_table_t *table,
+                                              const char *key)
+{
+    const apr_array_header_t *elts;
+    const apr_table_entry_t *t_elt;
+    const apr_table_entry_t *t_end;
+    apr_size_t len;
+    struct sle {
+        struct sle *next;
+        const char *value;
+        apr_size_t len;
+    } *result_list, *rp;
+
+    elts = apr_table_elts(table);
+
+    if (!elts->nelts) {
+        return NULL;
+    }
+
+    t_elt = (const apr_table_entry_t *)elts->elts;
+    t_end = t_elt + elts->nelts;
+    len = 1; /* \0 */
+    result_list = rp = NULL;
+
+    do {
+        if (!strcasecmp(t_elt->key, key)) {
+            if (!result_list) {
+                result_list = rp = apr_palloc(pool, sizeof(*rp));
+            }
+            else {
+                rp = rp->next = apr_palloc(pool, sizeof(*rp));
+                len += 2; /* ", " */
+            }
+
+            rp->next = NULL;
+            rp->value = t_elt->val;
+            rp->len = strlen(rp->value);
+
+            len += rp->len;
+        }
+        ++t_elt;
+    } while (t_elt < t_end);
+
+    if (result_list) {
+        char *result = apr_palloc(pool, len);
+        char *cp = result;
+
+        rp = result_list;
+        while (rp) {
+            if (rp != result_list) {
+                *cp++ = ',';
+                *cp++ = ' ';
+            }
+            memcpy(cp, rp->value, rp->len);
+            cp += rp->len;
+            rp = rp->next;
+        }
+        *cp = '\0';
+
+        return result;
+    }
+
+    return NULL;
+}
+
+static const char *log_header_out(request_rec *r, char *a)
+{
+    const char *cp = NULL;
+
+    if (!strcasecmp(a, "Content-type") && r->content_type) {
+        cp = ap_field_noparam(r->pool, r->content_type);
+    }
+    else if (!strcasecmp(a, "Set-Cookie")) {
+        cp = find_multiple_headers(r->pool, r->headers_out, a);
+    }
+    else {
+        cp = apr_table_get(r->headers_out, a);
+    }
+
+    return ap_escape_logitem(r->pool, cp);
+}
+
+static const char *log_note(request_rec *r, char *a)
+{
+    return ap_escape_logitem(r->pool, apr_table_get(r->notes, a));
+}
+static const char *log_env_var(request_rec *r, char *a)
+{
+    return ap_escape_logitem(r->pool, apr_table_get(r->subprocess_env, a));
+}
+
+static const char *log_cookie(request_rec *r, char *a)
+{
+    const char *cookies;
+    const char *start_cookie;
+
+    if ((cookies = apr_table_get(r->headers_in, "Cookie"))) {
+        if ((start_cookie = ap_strstr_c(cookies,a))) {
+            char *cookie, *end_cookie;
+            start_cookie += strlen(a) + 1; /* cookie_name + '=' */
+            cookie = apr_pstrdup(r->pool, start_cookie);
+            /* kill everything in cookie after ';' */
+            end_cookie = strchr(cookie, ';'); 
+            if (end_cookie) {
+                *end_cookie = '\0';
+            }
+            return ap_escape_logitem(r->pool, cookie);
+        }
+    }
+    return NULL;
+}
+
+static const char *log_request_time_custom(request_rec *r, char *a,
+                                           apr_time_exp_t *xt)
+{
+    apr_size_t retcode;
+    char tstr[MAX_STRING_LEN];
+    apr_strftime(tstr, &retcode, sizeof(tstr), a, xt);
+    return apr_pstrdup(r->pool, tstr);
+}
+
+#define DEFAULT_REQUEST_TIME_SIZE 32
+typedef struct {
+    unsigned t;
+    char timestr[DEFAULT_REQUEST_TIME_SIZE];
+    unsigned t_validate;
+} cached_request_time;
+
+#define TIME_CACHE_SIZE 4
+#define TIME_CACHE_MASK 3
+static cached_request_time request_time_cache[TIME_CACHE_SIZE];
+
+static const char *log_request_time(request_rec *r, char *a)
+{
+    apr_time_exp_t xt;
+
+    /* ###  I think getting the time again at the end of the request
+     * just for logging is dumb.  i know it's "required" for CLF.
+     * folks writing log parsing tools don't realise that out of order
+     * times have always been possible (consider what happens if one
+     * process calculates the time to log, but then there's a context
+     * switch before it writes and before that process is run again the
+     * log rotation occurs) and they should just fix their tools rather
+     * than force the server to pay extra cpu cycles.  if you've got
+     * a problem with this, you can set the define.  -djg
+     */
+    if (a && *a) {              /* Custom format */
+        /* The custom time formatting uses a very large temp buffer
+         * on the stack.  To avoid using so much stack space in the
+         * common case where we're not using a custom format, the code
+         * for the custom format in a separate function.  (That's why
+         * log_request_time_custom is not inlined right here.)
+         */
+#ifdef I_INSIST_ON_EXTRA_CYCLES_FOR_CLF_COMPLIANCE
+        ap_explode_recent_localtime(&xt, apr_time_now());
+#else
+        ap_explode_recent_localtime(&xt, r->request_time);
+#endif
+        return log_request_time_custom(r, a, &xt);
+    }
+    else {                      /* CLF format */
+        /* This code uses the same technique as ap_explode_recent_localtime():
+         * optimistic caching with logic to detect and correct race conditions.
+         * See the comments in server/util_time.c for more information.
+         */
+        cached_request_time* cached_time = apr_palloc(r->pool,
+                                                      sizeof(*cached_time));
+#ifdef I_INSIST_ON_EXTRA_CYCLES_FOR_CLF_COMPLIANCE
+        apr_time_t request_time = apr_time_now();
+#else
+        apr_time_t request_time = r->request_time;
+#endif
+        unsigned t_seconds = (unsigned)apr_time_sec(request_time);
+        unsigned i = t_seconds & TIME_CACHE_MASK;
+        memcpy(cached_time, &(request_time_cache[i]), sizeof(*cached_time));
+        if ((t_seconds != cached_time->t) ||
+            (t_seconds != cached_time->t_validate)) {
+
+            /* Invalid or old snapshot, so compute the proper time string
+             * and store it in the cache
+             */
+            char sign;
+            int timz;
+
+            ap_explode_recent_localtime(&xt, request_time);
+            timz = xt.tm_gmtoff;
+            if (timz < 0) {
+                timz = -timz;
+                sign = '-';
+            }
+            else {
+                sign = '+';
+            }
+            cached_time->t = t_seconds;
+            apr_snprintf(cached_time->timestr, DEFAULT_REQUEST_TIME_SIZE,
+                         "[%02d/%s/%d:%02d:%02d:%02d %c%.2d%.2d]",
+                         xt.tm_mday, apr_month_snames[xt.tm_mon],
+                         xt.tm_year+1900, xt.tm_hour, xt.tm_min, xt.tm_sec,
+                         sign, timz / (60*60), (timz % (60*60)) / 60);
+            cached_time->t_validate = t_seconds;
+            memcpy(&(request_time_cache[i]), cached_time,
+                   sizeof(*cached_time));
+        }
+        return cached_time->timestr;
+    }
+}
+
+static const char *log_request_duration(request_rec *r, char *a)
+{
+    apr_time_t duration = apr_time_now() - r->request_time;
+    return apr_psprintf(r->pool, "%" APR_TIME_T_FMT, apr_time_sec(duration));
+}
+
+static const char *log_request_duration_microseconds(request_rec *r, char *a)
+{
+    return apr_psprintf(r->pool, "%" APR_TIME_T_FMT, 
+                        (apr_time_now() - r->request_time));
+}
+
+/* These next two routines use the canonical name:port so that log
+ * parsers don't need to duplicate all the vhost parsing crud.
+ */
+static const char *log_virtual_host(request_rec *r, char *a)
+{
+    return ap_escape_logitem(r->pool, r->server->server_hostname);
+}
+
+static const char *log_server_port(request_rec *r, char *a)
+{
+    return apr_psprintf(r->pool, "%u",
+                        r->server->port ? r->server->port : ap_default_port(r));
+}
+
+/* This respects the setting of UseCanonicalName so that
+ * the dynamic mass virtual hosting trick works better.
+ */
+static const char *log_server_name(request_rec *r, char *a)
+{
+    return ap_escape_logitem(r->pool, ap_get_server_name(r));
+}
+
+static const char *log_pid_tid(request_rec *r, char *a)
+{
+    if (*a == '\0' || !strcmp(a, "pid")) {
+        return apr_psprintf(r->pool, "%" APR_PID_T_FMT, getpid());
+    }
+    else if (!strcmp(a, "tid")) {
+#if APR_HAS_THREADS
+        apr_os_thread_t tid = apr_os_thread_current();
+#else
+        int tid = 0; /* APR will format "0" anyway but an arg is needed */
+#endif
+        return apr_psprintf(r->pool, "%pT", &tid);
+    }
+    /* bogus format */
+    return a;
+}
+
+static const char *log_connection_status(request_rec *r, char *a)
+{
+    if (r->connection->aborted)
+        return "X";
+
+    if (r->connection->keepalive == AP_CONN_KEEPALIVE && 
+        (!r->server->keep_alive_max ||
+         (r->server->keep_alive_max - r->connection->keepalives) > 0)) {
+        return "+";
+    }
+    return "-";
+}
+
+/*****************************************************************
+ *
+ * Parsing the log format string
+ */
+
+static char *parse_log_misc_string(apr_pool_t *p, log_format_item *it,
+                                   const char **sa)
+{
+    const char *s;
+    char *d;
+
+    it->func = constant_item;
+    it->conditions = NULL;
+
+    s = *sa;
+    while (*s && *s != '%') {
+        s++;
+    }
+    /*
+     * This might allocate a few chars extra if there's a backslash
+     * escape in the format string.
+     */
+    it->arg = apr_palloc(p, s - *sa + 1);
+
+    d = it->arg;
+    s = *sa;
+    while (*s && *s != '%') {
+        if (*s != '\\') {
+            *d++ = *s++;
+        }
+        else {
+            s++;
+            switch (*s) {
+            case '\\':
+                *d++ = '\\';
+                s++;
+                break;
+            case 'r':
+                *d++ = '\r';
+                s++;
+                break;
+            case 'n':
+                *d++ = '\n';
+                s++;
+                break;
+            case 't':
+                *d++ = '\t';
+                s++;
+                break;
+            default:
+                /* copy verbatim */
+                *d++ = '\\';
+                /*
+                 * Allow the loop to deal with this *s in the normal
+                 * fashion so that it handles end of string etc.
+                 * properly.
+                 */
+                break;
+            }
+        }
+    }
+    *d = '\0';
+
+    *sa = s;
+    return NULL;
+}
+
+static char *parse_log_item(apr_pool_t *p, log_format_item *it, const char **sa)
+{
+    const char *s = *sa;
+    ap_log_handler *handler;
+
+    if (*s != '%') {
+        return parse_log_misc_string(p, it, sa);
+    }
+
+    ++s;
+    it->condition_sense = 0;
+    it->conditions = NULL;
+
+    if (*s == '%') {
+        it->arg = "%";
+        it->func = constant_item;
+        *sa = ++s;
+    
+        return NULL;
+    }
+
+    it->want_orig = -1;
+    it->arg = "";               /* For safety's sake... */
+
+    while (*s) {
+        int i;
+
+        switch (*s) {
+        case '!':
+            ++s;
+            it->condition_sense = !it->condition_sense;
+            break;
+
+        case '<':
+            ++s;
+            it->want_orig = 1;
+            break;
+
+        case '>':
+            ++s;
+            it->want_orig = 0;
+            break;
+
+        case ',':
+            ++s;
+            break;
+
+        case '{':
+            ++s;
+            it->arg = ap_getword(p, &s, '}');
+            break;
+
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+            i = *s - '0';
+            while (apr_isdigit(*++s)) {
+                i = i * 10 + (*s) - '0';
+            }
+            if (!it->conditions) {
+                it->conditions = apr_array_make(p, 4, sizeof(int));
+            }
+            *(int *) apr_array_push(it->conditions) = i;
+            break;
+
+        default:
+            handler = (ap_log_handler *)apr_hash_get(log_hash, s++, 1);
+            if (!handler) {
+                char dummy[2];
+
+                dummy[0] = s[-1];
+                dummy[1] = '\0';
+                return apr_pstrcat(p, "Unrecognized LogFormat directive %",
+                               dummy, NULL);
+            }
+            it->func = handler->func;
+            if (it->want_orig == -1) {
+                it->want_orig = handler->want_orig_default;
+            }
+            *sa = s;
+            return NULL;
+        }
+    }
+
+    return "Ran off end of LogFormat parsing args to some directive";
+}
+
+static apr_array_header_t *parse_log_string(apr_pool_t *p, const char *s, const char **err)
+{
+    apr_array_header_t *a = apr_array_make(p, 30, sizeof(log_format_item));
+    char *res;
+
+    while (*s) {
+        if ((res = parse_log_item(p, (log_format_item *) apr_array_push(a), &s))) {
+            *err = res;
+            return NULL;
+        }
+    }
+
+    s = APR_EOL_STR;
+    parse_log_item(p, (log_format_item *) apr_array_push(a), &s);
+    return a;
+}
+
+/*****************************************************************
+ *
+ * Actually logging.
+ */
+
+static const char *process_item(request_rec *r, request_rec *orig,
+                          log_format_item *item)
+{
+    const char *cp;
+
+    /* First, see if we need to process this thing at all... */
+
+    if (item->conditions && item->conditions->nelts != 0) {
+        int i;
+        int *conds = (int *) item->conditions->elts;
+        int in_list = 0;
+
+        for (i = 0; i < item->conditions->nelts; ++i) {
+            if (r->status == conds[i]) {
+                in_list = 1;
+                break;
+            }
+        }
+
+        if ((item->condition_sense && in_list)
+            || (!item->condition_sense && !in_list)) {
+            return "-";
+        }
+    }
+
+    /* We do.  Do it... */
+
+    cp = (*item->func) (item->want_orig ? orig : r, item->arg);
+    return cp ? cp : "-";
+}
+
+static void flush_log(buffered_log *buf)
+{
+    if (buf->outcnt && buf->handle != NULL) {
+        apr_file_write(buf->handle, buf->outbuf, &buf->outcnt);
+        buf->outcnt = 0;
+    }
+}
+
+
+static int config_log_transaction(request_rec *r, config_log_state *cls,
+                                  apr_array_header_t *default_format)
+{
+    log_format_item *items;
+    const char **strs;
+    int *strl;
+    request_rec *orig;
+    int i;
+    apr_size_t len = 0;
+    apr_array_header_t *format;
+    char *envar;
+    apr_status_t rv;
+
+    if (cls->fname == NULL) {
+        return DECLINED;
+    }
+
+    /*
+     * See if we've got any conditional envariable-controlled logging decisions
+     * to make.
+     */
+    if (cls->condition_var != NULL) {
+        envar = cls->condition_var;
+        if (*envar != '!') {
+            if (apr_table_get(r->subprocess_env, envar) == NULL) {
+                return DECLINED;
+            }
+        }
+        else {
+            if (apr_table_get(r->subprocess_env, &envar[1]) != NULL) {
+                return DECLINED;
+            }
+        }
+    }
+
+    format = cls->format ? cls->format : default_format;
+
+    strs = apr_palloc(r->pool, sizeof(char *) * (format->nelts));
+    strl = apr_palloc(r->pool, sizeof(int) * (format->nelts));
+    items = (log_format_item *) format->elts;
+
+    orig = r;
+    while (orig->prev) {
+        orig = orig->prev;
+    }
+    while (r->next) {
+        r = r->next;
+    }
+
+    for (i = 0; i < format->nelts; ++i) {
+        strs[i] = process_item(r, orig, &items[i]);
+    }
+
+    for (i = 0; i < format->nelts; ++i) {
+        len += strl[i] = strlen(strs[i]);
+    }
+    if (!log_writer) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r,
+                "log writer isn't correctly setup");
+         return HTTP_INTERNAL_SERVER_ERROR;
+    }
+    rv = log_writer(r, cls->log_writer, strs, strl, format->nelts, len);
+    /* xxx: do we return an error on log_writer? */
+    return OK;
+}
+
+static int multi_log_transaction(request_rec *r)
+{
+    multi_log_state *mls = ap_get_module_config(r->server->module_config,
+                                                &log_config_module);
+    config_log_state *clsarray;
+    int i;
+
+    /*
+     * Log this transaction..
+     */
+    if (mls->config_logs->nelts) {
+        clsarray = (config_log_state *) mls->config_logs->elts;
+        for (i = 0; i < mls->config_logs->nelts; ++i) {
+            config_log_state *cls = &clsarray[i];
+
+            config_log_transaction(r, cls, mls->default_format);
+        }
+    }
+    else if (mls->server_config_logs) {
+        clsarray = (config_log_state *) mls->server_config_logs->elts;
+        for (i = 0; i < mls->server_config_logs->nelts; ++i) {
+            config_log_state *cls = &clsarray[i];
+
+            config_log_transaction(r, cls, mls->default_format);
+        }
+    }
+
+    return OK;
+}
+
+/*****************************************************************
+ *
+ * Module glue...
+ */
+
+static void *make_config_log_state(apr_pool_t *p, server_rec *s)
+{
+    multi_log_state *mls;
+
+    mls = (multi_log_state *) apr_palloc(p, sizeof(multi_log_state));
+    mls->config_logs = apr_array_make(p, 1, sizeof(config_log_state));
+    mls->default_format_string = NULL;
+    mls->default_format = NULL;
+    mls->server_config_logs = NULL;
+    mls->formats = apr_table_make(p, 4);
+    apr_table_setn(mls->formats, "CLF", DEFAULT_LOG_FORMAT);
+
+    return mls;
+}
+
+/*
+ * Use the merger to simply add a pointer from the vhost log state
+ * to the log of logs specified for the non-vhost configuration.  Make sure
+ * vhosts inherit any globally-defined format names.
+ */
+
+static void *merge_config_log_state(apr_pool_t *p, void *basev, void *addv)
+{
+    multi_log_state *base = (multi_log_state *) basev;
+    multi_log_state *add = (multi_log_state *) addv;
+
+    add->server_config_logs = base->config_logs;
+    if (!add->default_format) {
+        add->default_format_string = base->default_format_string;
+        add->default_format = base->default_format;
+    }
+    add->formats = apr_table_overlay(p, base->formats, add->formats);
+
+    return add;
+}
+
+/*
+ * Set the default logfile format, or define a nickname for a format string.
+ */
+static const char *log_format(cmd_parms *cmd, void *dummy, const char *fmt,
+                              const char *name)
+{
+    const char *err_string = NULL;
+    multi_log_state *mls = ap_get_module_config(cmd->server->module_config,
+                                                &log_config_module);
+
+    /*
+     * If we were given two arguments, the second is a name to be given to the
+     * format.  This syntax just defines the nickname - it doesn't actually
+     * make the format the default.
+     */
+    if (name != NULL) {
+        parse_log_string(cmd->pool, fmt, &err_string);
+        if (err_string == NULL) {
+            apr_table_setn(mls->formats, name, fmt);
+        }
+    }
+    else {
+        mls->default_format_string = fmt;
+        mls->default_format = parse_log_string(cmd->pool, fmt, &err_string);
+    }
+    return err_string;
+}
+
+
+static const char *add_custom_log(cmd_parms *cmd, void *dummy, const char *fn,
+                                  const char *fmt, const char *envclause)
+{
+    const char *err_string = NULL;
+    multi_log_state *mls = ap_get_module_config(cmd->server->module_config,
+                                                &log_config_module);
+    config_log_state *cls;
+
+    cls = (config_log_state *) apr_array_push(mls->config_logs);
+    cls->condition_var = NULL;
+    if (envclause != NULL) {
+        if (strncasecmp(envclause, "env=", 4) != 0) {
+            return "error in condition clause";
+        }
+        if ((envclause[4] == '\0')
+            || ((envclause[4] == '!') && (envclause[5] == '\0'))) {
+            return "missing environment variable name";
+        }
+        cls->condition_var = apr_pstrdup(cmd->pool, &envclause[4]);
+    }
+
+    cls->fname = fn;
+    cls->format_string = fmt;
+    if (fmt == NULL) {
+        cls->format = NULL;
+    }
+    else {
+        cls->format = parse_log_string(cmd->pool, fmt, &err_string);
+    }
+    cls->log_writer = NULL;
+
+    return err_string;
+}
+
+static const char *set_transfer_log(cmd_parms *cmd, void *dummy,
+                                    const char *fn)
+{
+    return add_custom_log(cmd, dummy, fn, NULL, NULL);
+}
+
+static const char *set_cookie_log(cmd_parms *cmd, void *dummy, const char *fn)
+{
+    return add_custom_log(cmd, dummy, fn, "%{Cookie}n \"%r\" %t", NULL);
+}
+
+static const char *set_buffered_logs_on(cmd_parms *parms, void *dummy, int flag)
+{
+    buffered_logs = flag;
+    if (buffered_logs) {
+        ap_log_set_writer_init(ap_buffered_log_writer_init);
+        ap_log_set_writer(ap_buffered_log_writer);
+    }
+    return NULL;
+}
+static const command_rec config_log_cmds[] =
+{
+AP_INIT_TAKE23("CustomLog", add_custom_log, NULL, RSRC_CONF,
+     "a file name, a custom log format string or format name, "
+     "and an optional \"env=\" clause (see docs)"),
+AP_INIT_TAKE1("TransferLog", set_transfer_log, NULL, RSRC_CONF,
+     "the filename of the access log"),
+AP_INIT_TAKE12("LogFormat", log_format, NULL, RSRC_CONF,
+     "a log format string (see docs) and an optional format name"),
+AP_INIT_TAKE1("CookieLog", set_cookie_log, NULL, RSRC_CONF,
+     "the filename of the cookie log"),
+AP_INIT_FLAG("BufferedLogs", set_buffered_logs_on, NULL, RSRC_CONF,
+                 "Enable Buffered Logging (experimental)"),
+    {NULL}
+};
+
+static config_log_state *open_config_log(server_rec *s, apr_pool_t *p,
+                                         config_log_state *cls,
+                                         apr_array_header_t *default_format)
+{
+    if (cls->log_writer != NULL) {
+        return cls;             /* virtual config shared w/main server */
+    }
+
+    if (cls->fname == NULL) {
+        return cls;             /* Leave it NULL to decline.  */
+    }
+    
+    cls->log_writer = log_writer_init(p, s, cls->fname);
+    if (cls->log_writer == NULL)
+        return NULL; 
+
+    return cls;
+}
+
+static int open_multi_logs(server_rec *s, apr_pool_t *p)
+{
+    int i;
+    multi_log_state *mls = ap_get_module_config(s->module_config,
+                                             &log_config_module);
+    config_log_state *clsarray;
+    const char *dummy;
+    const char *format;
+
+    if (mls->default_format_string) {
+        format = apr_table_get(mls->formats, mls->default_format_string);
+        if (format) {
+            mls->default_format = parse_log_string(p, format, &dummy);
+        }
+    }    
+
+    if (!mls->default_format) {
+        mls->default_format = parse_log_string(p, DEFAULT_LOG_FORMAT, &dummy);
+    }
+
+    if (mls->config_logs->nelts) {
+        clsarray = (config_log_state *) mls->config_logs->elts;
+        for (i = 0; i < mls->config_logs->nelts; ++i) {
+            config_log_state *cls = &clsarray[i];
+
+            if (cls->format_string) {
+                format = apr_table_get(mls->formats, cls->format_string);
+                if (format) {
+                    cls->format = parse_log_string(p, format, &dummy);
+                }
+            }
+
+            if (!open_config_log(s, p, cls, mls->default_format)) {
+                /* Failure already logged by open_config_log */
+                return DONE;
+            }
+        }
+    }
+    else if (mls->server_config_logs) {
+        clsarray = (config_log_state *) mls->server_config_logs->elts;
+        for (i = 0; i < mls->server_config_logs->nelts; ++i) {
+            config_log_state *cls = &clsarray[i];
+
+            if (cls->format_string) {
+                format = apr_table_get(mls->formats, cls->format_string);
+                if (format) {
+                    cls->format = parse_log_string(p, format, &dummy);
+                }
+            }
+
+            if (!open_config_log(s, p, cls, mls->default_format)) {
+                /* Failure already logged by open_config_log */
+                return DONE;
+            }
+        }
+    }
+
+    return OK;
+}
+
+
+static apr_status_t flush_all_logs(void *data)
+{
+    server_rec *s = data;
+    multi_log_state *mls;
+    apr_array_header_t *log_list;
+    config_log_state *clsarray;
+    buffered_log *buf;
+    int i;
+
+    if (!buffered_logs)
+        return APR_SUCCESS;
+    
+    for (; s; s = s->next) {
+        mls = ap_get_module_config(s->module_config, &log_config_module);
+        log_list = NULL;
+        if (mls->config_logs->nelts) {
+            log_list = mls->config_logs;
+        }
+        else if (mls->server_config_logs) {
+            log_list = mls->server_config_logs;
+        }
+        if (log_list) {
+            clsarray = (config_log_state *) log_list->elts;
+            for (i = 0; i < log_list->nelts; ++i) {
+                buf = clsarray[i].log_writer;
+                flush_log(buf);
+            }
+        }
+    }
+    return APR_SUCCESS;
+}
+
+
+static int init_config_log(apr_pool_t *pc, apr_pool_t *p, apr_pool_t *pt, server_rec *s)
+{
+    int res;
+
+    /* First init the buffered logs array, which is needed when opening the logs. */
+    if (buffered_logs) {
+        all_buffered_logs = apr_array_make(p, 5, sizeof(buffered_log *));
+    }
+
+    /* Next, do "physical" server, which gets default log fd and format
+     * for the virtual servers, if they don't override...
+     */
+    res = open_multi_logs(s, p);
+
+    /* Then, virtual servers */
+
+    for (s = s->next; (res == OK) && s; s = s->next) {
+        res = open_multi_logs(s, p);
+    }
+
+    return res;
+}
+
+static void init_child(apr_pool_t *p, server_rec *s)
+{
+    int mpm_threads;
+
+    ap_mpm_query(AP_MPMQ_MAX_THREADS, &mpm_threads);
+
+    /* Now register the last buffer flush with the cleanup engine */
+    if (buffered_logs) {
+        int i;
+        buffered_log **array = (buffered_log **)all_buffered_logs->elts;
+        
+        apr_pool_cleanup_register(p, s, flush_all_logs, flush_all_logs);
+
+        for (i = 0; i < all_buffered_logs->nelts; i++) {
+            buffered_log *this = array[i];
+            
+#if APR_HAS_THREADS
+            if (mpm_threads > 1) {
+                apr_status_t rv;
+
+                this->mutex.type = apr_anylock_threadmutex;
+                rv = apr_thread_mutex_create(&this->mutex.lock.tm,
+                                             APR_THREAD_MUTEX_DEFAULT,
+                                             p);
+                if (rv != APR_SUCCESS) {
+                    ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+                                 "could not initialize buffered log mutex, "
+                                 "transfer log may become corrupted");
+                    this->mutex.type = apr_anylock_none;
+                }
+            }
+            else
+#endif
+            {
+                this->mutex.type = apr_anylock_none;
+            }
+        }
+    }
+}
+
+static void ap_register_log_handler(apr_pool_t *p, char *tag, 
+                                    ap_log_handler_fn_t *handler, int def)
+{
+    ap_log_handler *log_struct = apr_palloc(p, sizeof(*log_struct));
+    log_struct->func = handler;
+    log_struct->want_orig_default = def;
+
+    apr_hash_set(log_hash, tag, 1, (const void *)log_struct);
+}
+static void ap_log_set_writer_init(ap_log_writer_init *handle)
+{
+    log_writer_init = handle;
+
+}
+static void ap_log_set_writer(ap_log_writer *handle)
+{
+    log_writer = handle;
+}
+
+static apr_status_t ap_default_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;
+
+    str = apr_palloc(r->pool, len + 1);
+
+    for (i = 0, s = str; i < nelts; ++i) {
+        memcpy(s, strs[i], strl[i]);
+        s += strl[i];
+    }
+
+    rv = apr_file_write((apr_file_t*)handle, str, &len);
+
+    return rv;
+}
+static void *ap_default_log_writer_init(apr_pool_t *p, server_rec *s, 
+                                        const char* name)
+{
+    if (*name == '|') {
+        piped_log *pl;
+
+        pl = ap_open_piped_log(p, name + 1);
+        if (pl == NULL) {
+           return NULL;;
+        }
+        return ap_piped_log_write_fd(pl);
+    }
+    else {
+        const char *fname = ap_server_root_relative(p, name);
+        apr_file_t *fd;
+        apr_status_t rv;
+
+        if (!fname) {
+            ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s,
+                            "invalid transfer log path %s.", name);
+            return NULL;
+        }
+        rv = apr_file_open(&fd, fname, xfer_flags, xfer_perms, p);
+        if (rv != APR_SUCCESS) {
+            ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                            "could not open transfer log file %s.", fname);
+            return NULL;
+        }
+        return fd;
+    }
+}
+static void *ap_buffered_log_writer_init(apr_pool_t *p, server_rec *s, 
+                                        const char* name)
+{
+    buffered_log *b;
+    b = apr_pcalloc(p, sizeof(buffered_log));
+    b->handle = ap_default_log_writer_init(p, s, name);
+    
+    if (b->handle) {
+        *(buffered_log **)apr_array_push(all_buffered_logs) = b;
+        return b;
+    }
+    else
+        return NULL;
+}
+static apr_status_t ap_buffered_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;
+    buffered_log *buf = (buffered_log*)handle;
+
+    if ((rv = APR_ANYLOCK_LOCK(&buf->mutex)) != APR_SUCCESS) {
+        return rv;
+    }
+
+    if (len + buf->outcnt > LOG_BUFSIZE) {
+        flush_log(buf);
+    }
+    if (len >= LOG_BUFSIZE) {
+        apr_size_t w;
+
+        str = apr_palloc(r->pool, len + 1);
+        for (i = 0, s = str; i < nelts; ++i) {
+            memcpy(s, strs[i], strl[i]);
+            s += strl[i];
+        }
+        w = len;
+        rv = apr_file_write(buf->handle, str, &w);
+        
+    }
+    else {
+        for (i = 0, s = &buf->outbuf[buf->outcnt]; i < nelts; ++i) {
+            memcpy(s, strs[i], strl[i]);
+            s += strl[i];
+        }
+        buf->outcnt += len;
+        rv = APR_SUCCESS;
+    }
+
+    APR_ANYLOCK_UNLOCK(&buf->mutex);
+    return rv;
+}
+
+static int log_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+    static APR_OPTIONAL_FN_TYPE(ap_register_log_handler) *log_pfn_register;
+    
+    log_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_log_handler);
+
+    if (log_pfn_register) {
+        log_pfn_register(p, "h", log_remote_host, 0);
+        log_pfn_register(p, "a", log_remote_address, 0 );
+        log_pfn_register(p, "A", log_local_address, 0 );
+        log_pfn_register(p, "l", log_remote_logname, 0);
+        log_pfn_register(p, "u", log_remote_user, 0);
+        log_pfn_register(p, "t", log_request_time, 0);
+        log_pfn_register(p, "f", log_request_file, 0);
+        log_pfn_register(p, "b", clf_log_bytes_sent, 0);
+        log_pfn_register(p, "B", log_bytes_sent, 0);
+        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, "e", log_env_var, 0);
+        log_pfn_register(p, "V", log_server_name, 0);
+        log_pfn_register(p, "v", log_virtual_host, 0);
+        log_pfn_register(p, "p", log_server_port, 0);
+        log_pfn_register(p, "P", log_pid_tid, 0);
+        log_pfn_register(p, "H", log_request_protocol, 0);
+        log_pfn_register(p, "m", log_request_method, 0);
+        log_pfn_register(p, "q", log_request_query, 0);
+        log_pfn_register(p, "X", log_connection_status, 0);
+        log_pfn_register(p, "C", log_cookie, 0);
+        log_pfn_register(p, "r", log_request_line, 1);
+        log_pfn_register(p, "D", log_request_duration_microseconds, 1);
+        log_pfn_register(p, "T", log_request_duration, 1);
+        log_pfn_register(p, "U", log_request_uri, 1);
+        log_pfn_register(p, "s", log_status, 1);
+    }
+
+    return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+    ap_hook_pre_config(log_pre_config,NULL,NULL,APR_HOOK_REALLY_FIRST);
+    ap_hook_child_init(init_child,NULL,NULL,APR_HOOK_MIDDLE);
+    ap_hook_open_logs(init_config_log,NULL,NULL,APR_HOOK_MIDDLE);
+    ap_hook_log_transaction(multi_log_transaction,NULL,NULL,APR_HOOK_MIDDLE);
+
+    /* Init log_hash before we register the optional function. It is 
+     * possible for the optional function, ap_register_log_handler,
+     * to be called before any other mod_log_config hooks are called.
+     * As a policy, we should init everything required by an optional function
+     * before calling APR_REGISTER_OPTIONAL_FN.
+     */ 
+    log_hash = apr_hash_make(p);
+    APR_REGISTER_OPTIONAL_FN(ap_register_log_handler);
+    APR_REGISTER_OPTIONAL_FN(ap_log_set_writer_init);
+    APR_REGISTER_OPTIONAL_FN(ap_log_set_writer);
+}
+
+module AP_MODULE_DECLARE_DATA log_config_module =
+{
+    STANDARD20_MODULE_STUFF,
+    NULL,                       /* create per-dir config */
+    NULL,                       /* merge per-dir config */
+    make_config_log_state,      /* server config */
+    merge_config_log_state,     /* merge server config */
+    config_log_cmds,            /* command apr_table_t */
+    register_hooks              /* register hooks */
+};
+