1858 lines
58 KiB
C
1858 lines
58 KiB
C
/* 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
|
|
*
|
|
* 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 compatible
|
|
* 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.
|
|
* %...k: number of keepalive requests served over this connection
|
|
* %...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 canonical port for the server
|
|
* %...{format}p: the canonical port for the server, or the actual local
|
|
* or remote port
|
|
* %...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.
|
|
* %...{s}T: the time taken to serve the request, in seconds, same as %T.
|
|
* %...{us}T: the time taken to serve the request, in micro seconds, same as %D.
|
|
* %...{ms}T: the time taken to serve the request, in milliseconds.
|
|
* %...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.)
|
|
* %...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
|
|
* 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;
|
|
|
|
|
|
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 ap_log_writer_init *ap_log_set_writer_init(ap_log_writer_init *handle);
|
|
static ap_log_writer *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;
|
|
int inherit;
|
|
ap_expr_info_t *condition_expr;
|
|
/** place of definition or NULL if already checked */
|
|
const ap_directive_t *directive;
|
|
} config_log_state;
|
|
|
|
/*
|
|
* log_request_state holds request specific log data that is not
|
|
* part of the request_rec.
|
|
*/
|
|
typedef struct {
|
|
apr_time_t request_end_time;
|
|
} log_request_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 *pfmt(apr_pool_t *p, int i)
|
|
{
|
|
if (i <= 0) {
|
|
return "-";
|
|
}
|
|
else {
|
|
return apr_itoa(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)
|
|
{
|
|
const char *remote_host;
|
|
if (a && !strcmp(a, "c")) {
|
|
remote_host = ap_get_remote_host(r->connection, r->per_dir_config,
|
|
REMOTE_NAME, NULL);
|
|
}
|
|
else {
|
|
remote_host = ap_get_useragent_host(r, REMOTE_NAME, NULL);
|
|
}
|
|
return ap_escape_logitem(r->pool, remote_host);
|
|
}
|
|
|
|
static const char *log_remote_address(request_rec *r, char *a)
|
|
{
|
|
if (a && !strcmp(a, "c")) {
|
|
return r->connection->client_ip;
|
|
}
|
|
else {
|
|
return r->useragent_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_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);
|
|
}
|
|
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 *log_handler(request_rec *r, char *a)
|
|
{
|
|
return ap_escape_logitem(r->pool, r->handler);
|
|
}
|
|
|
|
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_off_t_toa(r->pool, 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 const char *log_trailer_in(request_rec *r, char *a)
|
|
{
|
|
return ap_escape_logitem(r->pool, apr_table_get(r->trailers_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 (!ap_cstr_casecmp(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 (!ap_cstr_casecmp(a, "Content-type") && r->content_type) {
|
|
cp = ap_field_noparam(r->pool, r->content_type);
|
|
}
|
|
else if (!ap_cstr_casecmp(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_trailer_out(request_rec *r, char *a)
|
|
{
|
|
return ap_escape_logitem(r->pool, apr_table_get(r->trailers_out, a));
|
|
}
|
|
|
|
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_entry;
|
|
|
|
/*
|
|
* This supports Netscape version 0 cookies while being tolerant to
|
|
* some properties of RFC2109/2965 version 1 cookies:
|
|
* - case-insensitive match of cookie names
|
|
* - white space between the tokens
|
|
* It does not support the following version 1 features:
|
|
* - quoted strings as cookie values
|
|
* - commas to separate cookies
|
|
*/
|
|
|
|
if ((cookies_entry = apr_table_get(r->headers_in, "Cookie"))) {
|
|
char *cookie, *last1, *last2;
|
|
char *cookies = apr_pstrdup(r->pool, cookies_entry);
|
|
|
|
while ((cookie = apr_strtok(cookies, ";", &last1))) {
|
|
char *name = apr_strtok(cookie, "=", &last2);
|
|
/* last2 points to the next char following an '=' delim,
|
|
or the trailing NUL char of the string */
|
|
char *value = last2;
|
|
if (name && *name && value && *value) {
|
|
char *last = value - 2;
|
|
/* Move past leading WS */
|
|
name += strspn(name, " \t");
|
|
while (last >= name && apr_isspace(*last)) {
|
|
*last = '\0';
|
|
--last;
|
|
}
|
|
|
|
if (!ap_cstr_casecmp(name, a)) {
|
|
/* last1 points to the next char following the ';' delim,
|
|
or the trailing NUL char of the string */
|
|
last = last1 - (*last1 ? 2 : 1);
|
|
/* Move past leading WS */
|
|
value += strspn(value, " \t");
|
|
while (last >= value && apr_isspace(*last)) {
|
|
*last = '\0';
|
|
--last;
|
|
}
|
|
|
|
return ap_escape_logitem(r->pool, value);
|
|
}
|
|
}
|
|
/* Iterate the remaining tokens using apr_strtok(NULL, ...) */
|
|
cookies = NULL;
|
|
}
|
|
}
|
|
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_FMT_CUSTOM 0
|
|
#define TIME_FMT_CLF 1
|
|
#define TIME_FMT_ABS_SEC 2
|
|
#define TIME_FMT_ABS_MSEC 3
|
|
#define TIME_FMT_ABS_USEC 4
|
|
#define TIME_FMT_ABS_MSEC_FRAC 5
|
|
#define TIME_FMT_ABS_USEC_FRAC 6
|
|
|
|
#define TIME_CACHE_SIZE 4
|
|
#define TIME_CACHE_MASK 3
|
|
static cached_request_time request_time_cache[TIME_CACHE_SIZE];
|
|
|
|
static apr_time_t get_request_end_time(request_rec *r)
|
|
{
|
|
log_request_state *state = (log_request_state *)ap_get_module_config(r->request_config,
|
|
&log_config_module);
|
|
if (!state) {
|
|
state = apr_pcalloc(r->pool, sizeof(log_request_state));
|
|
ap_set_module_config(r->request_config, &log_config_module, state);
|
|
}
|
|
if (state->request_end_time == 0) {
|
|
state->request_end_time = apr_time_now();
|
|
}
|
|
return state->request_end_time;
|
|
}
|
|
|
|
|
|
static const char *log_request_time(request_rec *r, char *a)
|
|
{
|
|
apr_time_exp_t xt;
|
|
apr_time_t request_time = r->request_time;
|
|
int fmt_type = TIME_FMT_CUSTOM;
|
|
char *fmt = a;
|
|
|
|
if (fmt && *fmt) {
|
|
if (!strncmp(fmt, "begin", 5)) {
|
|
fmt += 5;
|
|
if (!*fmt) {
|
|
fmt_type = TIME_FMT_CLF;
|
|
}
|
|
else if (*fmt == ':') {
|
|
fmt++;
|
|
a = fmt;
|
|
}
|
|
}
|
|
else if (!strncmp(fmt, "end", 3)) {
|
|
fmt += 3;
|
|
if (!*fmt) {
|
|
request_time = get_request_end_time(r);
|
|
fmt_type = TIME_FMT_CLF;
|
|
}
|
|
else if (*fmt == ':') {
|
|
fmt++;
|
|
a = fmt;
|
|
request_time = get_request_end_time(r);
|
|
}
|
|
}
|
|
if (!strncmp(fmt, "msec", 4)) {
|
|
fmt += 4;
|
|
if (!*fmt) {
|
|
fmt_type = TIME_FMT_ABS_MSEC;
|
|
}
|
|
else if (!strcmp(fmt, "_frac")) {
|
|
fmt_type = TIME_FMT_ABS_MSEC_FRAC;
|
|
}
|
|
}
|
|
else if (!strncmp(fmt, "usec", 4)) {
|
|
fmt += 4;
|
|
if (!*fmt) {
|
|
fmt_type = TIME_FMT_ABS_USEC;
|
|
}
|
|
else if (!strcmp(fmt, "_frac")) {
|
|
fmt_type = TIME_FMT_ABS_USEC_FRAC;
|
|
}
|
|
}
|
|
else if (!strcmp(fmt, "sec")) {
|
|
fmt_type = TIME_FMT_ABS_SEC;
|
|
}
|
|
else if (!*fmt) {
|
|
fmt_type = TIME_FMT_CLF;
|
|
}
|
|
}
|
|
else {
|
|
fmt_type = TIME_FMT_CLF;
|
|
}
|
|
|
|
if (fmt_type >= TIME_FMT_ABS_SEC) { /* Absolute (micro-/milli-)second time
|
|
* or msec/usec fraction
|
|
*/
|
|
char* buf = apr_palloc(r->pool, 20);
|
|
switch (fmt_type) {
|
|
case TIME_FMT_ABS_SEC:
|
|
apr_snprintf(buf, 20, "%" APR_TIME_T_FMT, apr_time_sec(request_time));
|
|
break;
|
|
case TIME_FMT_ABS_MSEC:
|
|
apr_snprintf(buf, 20, "%" APR_TIME_T_FMT, apr_time_as_msec(request_time));
|
|
break;
|
|
case TIME_FMT_ABS_USEC:
|
|
apr_snprintf(buf, 20, "%" APR_TIME_T_FMT, request_time);
|
|
break;
|
|
case TIME_FMT_ABS_MSEC_FRAC:
|
|
apr_snprintf(buf, 20, "%03" APR_TIME_T_FMT, apr_time_msec(request_time));
|
|
break;
|
|
case TIME_FMT_ABS_USEC_FRAC:
|
|
apr_snprintf(buf, 20, "%06" APR_TIME_T_FMT, apr_time_usec(request_time));
|
|
break;
|
|
default:
|
|
return "-";
|
|
}
|
|
return buf;
|
|
}
|
|
else if (fmt_type == TIME_FMT_CUSTOM) { /* 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.)
|
|
*/
|
|
ap_explode_recent_localtime(&xt, request_time);
|
|
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));
|
|
unsigned t_seconds = (unsigned)apr_time_sec(request_time);
|
|
unsigned i = t_seconds & TIME_CACHE_MASK;
|
|
*cached_time = request_time_cache[i];
|
|
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;
|
|
request_time_cache[i] = *cached_time;
|
|
}
|
|
return cached_time->timestr;
|
|
}
|
|
}
|
|
|
|
static const char *log_request_duration_microseconds(request_rec *r, char *a)
|
|
{
|
|
return apr_psprintf(r->pool, "%" APR_TIME_T_FMT,
|
|
(get_request_end_time(r) - r->request_time));
|
|
}
|
|
|
|
static const char *log_request_duration_scaled(request_rec *r, char *a)
|
|
{
|
|
apr_time_t duration = get_request_end_time(r) - r->request_time;
|
|
if (*a == '\0' || !strcasecmp(a, "s")) {
|
|
duration = apr_time_sec(duration);
|
|
}
|
|
else if (!strcasecmp(a, "ms")) {
|
|
duration = apr_time_as_msec(duration);
|
|
}
|
|
else if (!strcasecmp(a, "us")) {
|
|
}
|
|
else {
|
|
/* bogus format */
|
|
return a;
|
|
}
|
|
return apr_psprintf(r->pool, "%" APR_TIME_T_FMT, duration);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
apr_port_t port;
|
|
|
|
if (*a == '\0' || !strcasecmp(a, "canonical")) {
|
|
port = r->server->port ? r->server->port : ap_default_port(r);
|
|
}
|
|
else if (!strcasecmp(a, "remote")) {
|
|
port = r->useragent_addr->port;
|
|
}
|
|
else if (!strcasecmp(a, "local")) {
|
|
port = r->connection->local_addr->port;
|
|
}
|
|
else {
|
|
/* bogus format */
|
|
return a;
|
|
}
|
|
return apr_itoa(r->pool, (int)port);
|
|
}
|
|
|
|
/* 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' || !strcasecmp(a, "pid")) {
|
|
return ap_append_pid(r->pool, "", "");
|
|
}
|
|
else if (!strcasecmp(a, "tid") || !strcasecmp(a, "hextid")) {
|
|
#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,
|
|
/* APR can format a thread id in hex */
|
|
*a == 'h' ? "%pt" : "%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 "-";
|
|
}
|
|
|
|
static const char *log_requests_on_connection(request_rec *r, char *a)
|
|
{
|
|
int num = r->connection->keepalives ? r->connection->keepalives - 1 : 0;
|
|
return apr_itoa(r->pool, num);
|
|
}
|
|
|
|
/*****************************************************************
|
|
*
|
|
* 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 = NULL;
|
|
|
|
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:
|
|
/* check for '^' + two character format first */
|
|
if (*s == '^' && *(s+1) && *(s+2)) {
|
|
handler = (ap_log_handler *)apr_hash_get(log_hash, s, 3);
|
|
if (handler) {
|
|
s += 3;
|
|
}
|
|
}
|
|
if (!handler) {
|
|
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) {
|
|
/* XXX: error handling */
|
|
apr_file_write_full(buf->handle, buf->outbuf, buf->outcnt, NULL);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
else if (cls->condition_expr != NULL) {
|
|
const char *err;
|
|
int rc = ap_expr_exec(r, cls->condition_expr, &err);
|
|
if (rc < 0)
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(00644)
|
|
"Error evaluating log condition: %s", err);
|
|
if (rc <= 0)
|
|
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]);
|
|
len += strl[i] = strlen(strs[i]);
|
|
}
|
|
|
|
if (!log_writer) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00645)
|
|
"log writer isn't correctly setup");
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
rv = log_writer(r, cls->log_writer, strs, strl, format->nelts, len);
|
|
if (rv != APR_SUCCESS) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00646)
|
|
"Error writing to %s", cls->fname);
|
|
}
|
|
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;
|
|
|
|
/*
|
|
* Initialize per request state
|
|
*/
|
|
log_request_state *state = apr_pcalloc(r->pool, sizeof(log_request_state));
|
|
ap_set_module_config(r->request_config, &log_config_module, state);
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
}
|
|
|
|
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->inherit || !mls->config_logs->nelts) {
|
|
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;
|
|
cls->condition_expr = NULL;
|
|
if (envclause != NULL) {
|
|
if (strncasecmp(envclause, "env=", 4) == 0) {
|
|
if ((envclause[4] == '\0')
|
|
|| ((envclause[4] == '!') && (envclause[5] == '\0'))) {
|
|
return "missing environment variable name";
|
|
}
|
|
cls->condition_var = apr_pstrdup(cmd->pool, &envclause[4]);
|
|
}
|
|
else if (strncasecmp(envclause, "expr=", 5) == 0) {
|
|
const char *err;
|
|
if ((envclause[5] == '\0'))
|
|
return "missing condition";
|
|
cls->condition_expr = ap_expr_parse_cmd(cmd, &envclause[5],
|
|
AP_EXPR_FLAG_DONT_VARY,
|
|
&err, NULL);
|
|
if (err)
|
|
return err;
|
|
}
|
|
else {
|
|
return "error in condition clause";
|
|
}
|
|
}
|
|
|
|
cls->fname = fn;
|
|
cls->format_string = fmt;
|
|
cls->directive = cmd->directive;
|
|
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 *add_global_log(cmd_parms *cmd, void *dummy, const char *fn,
|
|
const char *fmt, const char *envclause) {
|
|
multi_log_state *mls = ap_get_module_config(cmd->server->module_config,
|
|
&log_config_module);
|
|
config_log_state *clsarray;
|
|
config_log_state *cls;
|
|
const char *ret;
|
|
|
|
const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
|
|
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
/* Add a custom log through the normal channel */
|
|
ret = add_custom_log(cmd, dummy, fn, fmt, envclause);
|
|
|
|
/* Set the inherit flag unless there was some error */
|
|
if (ret == NULL) {
|
|
clsarray = (config_log_state*)mls->config_logs->elts;
|
|
cls = &clsarray[mls->config_logs->nelts-1];
|
|
cls->inherit = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
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_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);
|
|
}
|
|
else {
|
|
ap_log_set_writer_init(ap_default_log_writer_init);
|
|
ap_log_set_writer(ap_default_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=\" or \"expr=\" clause (see docs)"),
|
|
AP_INIT_TAKE23("GlobalLog", add_global_log, NULL, RSRC_CONF,
|
|
"Same as CustomLog, but forces virtualhosts to inherit the log"),
|
|
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_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, APLOGNO(00647)
|
|
"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, strlen(tag), (const void *)log_struct);
|
|
}
|
|
static ap_log_writer_init *ap_log_set_writer_init(ap_log_writer_init *handle)
|
|
{
|
|
ap_log_writer_init *old = log_writer_init;
|
|
log_writer_init = handle;
|
|
|
|
return old;
|
|
|
|
}
|
|
static ap_log_writer *ap_log_set_writer(ap_log_writer *handle)
|
|
{
|
|
ap_log_writer *old = log_writer;
|
|
log_writer = handle;
|
|
|
|
return old;
|
|
}
|
|
|
|
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;
|
|
|
|
/*
|
|
* We do this memcpy dance because write() is atomic for len < PIPE_BUF,
|
|
* while writev() need not be.
|
|
*/
|
|
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, APLOGNO(00648)
|
|
"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, APLOGNO(00649)
|
|
"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;
|
|
|
|
/*
|
|
* We do this memcpy dance because write() is atomic for
|
|
* len < PIPE_BUF, while writev() need not be.
|
|
*/
|
|
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_full(buf->handle, str, w, NULL);
|
|
|
|
}
|
|
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, "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);
|
|
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, "k", log_requests_on_connection, 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_scaled, 1);
|
|
log_pfn_register(p, "U", log_request_uri, 1);
|
|
log_pfn_register(p, "s", log_status, 1);
|
|
log_pfn_register(p, "R", log_handler, 1);
|
|
|
|
log_pfn_register(p, "^ti", log_trailer_in, 0);
|
|
log_pfn_register(p, "^to", log_trailer_out, 0);
|
|
}
|
|
|
|
/* reset to default conditions */
|
|
ap_log_set_writer_init(ap_default_log_writer_init);
|
|
ap_log_set_writer(ap_default_log_writer);
|
|
buffered_logs = 0;
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int check_log_dir(apr_pool_t *p, server_rec *s, config_log_state *cls)
|
|
{
|
|
if (!cls->fname || cls->fname[0] == '|' || !cls->directive) {
|
|
return OK;
|
|
}
|
|
else {
|
|
char *abs = ap_server_root_relative(p, cls->fname);
|
|
char *dir = ap_make_dirstr_parent(p, abs);
|
|
apr_finfo_t finfo;
|
|
const ap_directive_t *directive = cls->directive;
|
|
apr_status_t rv = apr_stat(&finfo, dir, APR_FINFO_TYPE, p);
|
|
cls->directive = NULL; /* Don't check this config_log_state again */
|
|
if (rv == APR_SUCCESS && finfo.filetype != APR_DIR)
|
|
rv = APR_ENOTDIR;
|
|
if (rv != APR_SUCCESS) {
|
|
ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_EMERG, rv, s,
|
|
APLOGNO(02297)
|
|
"Cannot access directory '%s' for log file '%s' "
|
|
"defined at %s:%d", dir, cls->fname,
|
|
directive->filename, directive->line_num);
|
|
return !OK;
|
|
}
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
static int log_check_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
|
|
{
|
|
int rv = OK;
|
|
while (s) {
|
|
multi_log_state *mls = ap_get_module_config(s->module_config,
|
|
&log_config_module);
|
|
/*
|
|
* We don't need to check mls->server_config_logs because it just
|
|
* points to the parent server's mls->config_logs.
|
|
*/
|
|
apr_array_header_t *log_list = mls->config_logs;
|
|
config_log_state *clsarray = (config_log_state *) log_list->elts;
|
|
int i;
|
|
for (i = 0; i < log_list->nelts; ++i) {
|
|
if (check_log_dir(ptemp, s, &clsarray[i]) != OK)
|
|
rv = !OK;
|
|
}
|
|
|
|
s = s->next;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
static void register_hooks(apr_pool_t *p)
|
|
{
|
|
ap_hook_pre_config(log_pre_config,NULL,NULL,APR_HOOK_REALLY_FIRST);
|
|
ap_hook_check_config(log_check_config,NULL,NULL,APR_HOOK_MIDDLE);
|
|
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);
|
|
}
|
|
|
|
AP_DECLARE_MODULE(log_config) =
|
|
{
|
|
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 */
|
|
};
|
|
|