mod_log_config.c revision 185aa71728867671e105178b4c66fbc22b65ae26
/* 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
*
*
* 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 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:
*
* 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:
*
* 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.
* 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.)
* %...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"
#include <unistd.h>
#endif
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#define DEFAULT_LOG_FORMAT "%h %l %u %t \"%r\" %>s %b"
static apr_hash_t *log_hash;
void *handle,
const char **strs,
int *strl,
int nelts,
void *handle,
const char **strs,
int *strl,
int nelts,
const char* name);
const char* name);
static int buffered_logs = 0; /* default unbuffered */
/* 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;
/*
* 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 {
char outbuf[LOG_BUFSIZE];
} buffered_log;
typedef struct {
const char *fname;
const char *format_string;
void *log_writer;
char *condition_var;
/*
* log_request_state holds request specific log data that is not
* part of the request_rec.
*/
typedef struct {
/*
* Format items...
* Note that many of these could have ap_sprintfs replaced with static buffers.
*/
typedef struct {
char *arg;
int condition_sense;
int want_orig;
static char *pfmt(apr_pool_t *p, int i)
{
if (i <= 0) {
return "-";
}
else {
return apr_itoa(p, i);
}
}
{
return stuff;
}
static const char *log_remote_host(request_rec *r, char *a)
{
r->per_dir_config,
REMOTE_NAME, NULL));
}
static const char *log_remote_address(request_rec *r, char *a)
{
if (a && !strcmp(a, "c")) {
return r->connection->peer_ip;
}
else {
return r->client_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)
{
}
static const char *log_remote_user(request_rec *r, char *a)
{
rvalue = "-";
}
rvalue = "\"\"";
}
else {
}
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_uri_unparse(r->pool,
&r->parsed_uri, 0),
: r->the_request);
}
static const char *log_request_file(request_rec *r, char *a)
{
}
static const char *log_request_uri(request_rec *r, char *a)
{
}
static const char *log_request_method(request_rec *r, char *a)
{
}
static const char *log_log_id(request_rec *r, char *a)
{
if (a && !strcmp(a, "c")) {
}
else {
}
}
static const char *log_request_protocol(request_rec *r, char *a)
{
}
static const char *log_request_query(request_rec *r, char *a)
{
: "";
}
static const char *log_status(request_rec *r, char *a)
{
}
static const char *log_handler(request_rec *r, char *a)
{
}
static const char *clf_log_bytes_sent(request_rec *r, char *a)
{
if (!r->sent_bodyct || !r->bytes_sent) {
return "-";
}
else {
}
}
static const char *log_bytes_sent(request_rec *r, char *a)
{
if (!r->sent_bodyct || !r->bytes_sent) {
return "0";
}
else {
}
}
static const char *log_header_in(request_rec *r, char *a)
{
}
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;
struct sle {
const char *value;
} *result_list, *rp;
return NULL;
}
do {
if (!result_list) {
}
else {
}
}
++t_elt;
if (result_list) {
rp = result_list;
while (rp) {
if (rp != result_list) {
*cp++ = ',';
*cp++ = ' ';
}
}
*cp = '\0';
return result;
}
return NULL;
}
static const char *log_header_out(request_rec *r, char *a)
{
}
else if (!strcasecmp(a, "Set-Cookie")) {
}
else {
}
}
static const char *log_note(request_rec *r, char *a)
{
}
static const char *log_env_var(request_rec *r, char *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
*/
char *value;
char *last;
*last = '\0';
--last;
}
}
}
}
return NULL;
}
static const char *log_request_time_custom(request_rec *r, char *a,
{
char tstr[MAX_STRING_LEN];
}
#define DEFAULT_REQUEST_TIME_SIZE 32
typedef struct {
unsigned t;
char timestr[DEFAULT_REQUEST_TIME_SIZE];
unsigned t_validate;
#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
{
if (state->request_end_time == 0) {
}
return state->request_end_time;
}
static const char *log_request_time(request_rec *r, char *a)
{
int fmt_type = TIME_FMT_CUSTOM;
char *fmt = a;
fmt += 5;
if (!*fmt) {
}
else if (*fmt == ':') {
fmt++;
a = fmt;
}
}
fmt += 3;
if (!*fmt) {
}
else if (*fmt == ':') {
fmt++;
a = fmt;
}
}
fmt += 4;
if (!*fmt) {
}
}
}
fmt += 4;
if (!*fmt) {
}
}
}
}
else if (!*fmt) {
}
}
else {
}
*/
switch (fmt_type) {
case TIME_FMT_ABS_SEC:
break;
case TIME_FMT_ABS_MSEC:
break;
case TIME_FMT_ABS_USEC:
break;
case TIME_FMT_ABS_MSEC_FRAC:
break;
case TIME_FMT_ABS_USEC_FRAC:
break;
default:
return "-";
}
return buf;
}
/* 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.)
*/
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.
*/
sizeof(*cached_time));
unsigned i = t_seconds & TIME_CACHE_MASK;
*cached_time = request_time_cache[i];
if ((t_seconds != cached_time->t) ||
/* Invalid or old snapshot, so compute the proper time string
* and store it in the cache
*/
char sign;
int timz;
if (timz < 0) {
sign = '-';
}
else {
sign = '+';
}
cached_time->t = t_seconds;
"[%02d/%s/%d:%02d:%02d:%02d %c%.2d%.2d]",
request_time_cache[i] = *cached_time;
}
return cached_time->timestr;
}
}
static const char *log_request_duration(request_rec *r, char *a)
{
}
static const char *log_request_duration_microseconds(request_rec *r, char *a)
{
(get_request_end_time(r) - 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)
{
}
static const char *log_server_port(request_rec *r, char *a)
{
}
else if (!strcasecmp(a, "remote")) {
}
else if (!strcasecmp(a, "local")) {
}
else {
/* bogus format */
return a;
}
}
/* 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)
{
}
static const char *log_pid_tid(request_rec *r, char *a)
{
}
#if APR_HAS_THREADS
#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",
#else
/* APR is missing the feature, so always use decimal */
"%pT",
#endif
&tid);
}
/* bogus format */
return a;
}
static const char *log_connection_status(request_rec *r, char *a)
{
if (r->connection->aborted)
return "X";
(!r->server->keep_alive_max ||
return "+";
}
return "-";
}
static const char *log_requests_on_connection(request_rec *r, char *a)
{
}
/*****************************************************************
*
* Parsing the log format string
*/
const char **sa)
{
const char *s;
char *d;
s = *sa;
while (*s && *s != '%') {
s++;
}
/*
* This might allocate a few chars extra if there's a backslash
* escape in the format string.
*/
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;
}
{
const char *s = *sa;
if (*s != '%') {
}
++s;
it->condition_sense = 0;
if (*s == '%') {
*sa = ++s;
return NULL;
}
while (*s) {
int i;
switch (*s) {
case '!':
++s;
break;
case '<':
++s;
break;
case '>':
++s;
break;
case ',':
++s;
break;
case '{':
++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) {
}
break;
default:
if (!handler) {
char dummy[2];
dummy[0] = s[-1];
return apr_pstrcat(p, "Unrecognized LogFormat directive %",
}
}
*sa = s;
return NULL;
}
}
return "Ran off end of LogFormat parsing args to some directive";
}
{
char *res;
while (*s) {
return NULL;
}
}
s = APR_EOL_STR;
return a;
}
/*****************************************************************
*
* Actually logging.
*/
{
const char *cp;
/* First, see if we need to process this thing at all... */
int i;
int in_list = 0;
in_list = 1;
break;
}
}
return "-";
}
}
/* We do. Do it... */
}
{
}
}
{
const char **strs;
int *strl;
int i;
apr_size_t len = 0;
char *envar;
return DECLINED;
}
/*
* See if we've got any conditional envariable-controlled logging decisions
* to make.
*/
if (*envar != '!') {
return DECLINED;
}
}
else {
return DECLINED;
}
}
}
const char *err;
if (rc < 0)
"Error evaluating log condition: %s", err);
if (rc <= 0)
return DECLINED;
}
orig = r;
}
while (r->next) {
r = r->next;
}
}
}
if (!log_writer) {
"log writer isn't correctly setup");
return HTTP_INTERNAL_SERVER_ERROR;
}
if (rv != APR_SUCCESS)
return OK;
}
static int multi_log_transaction(request_rec *r)
{
int i;
/*
* Initialize per request state
*/
/*
* Log this transaction..
*/
}
}
else if (mls->server_config_logs) {
}
}
return OK;
}
/*****************************************************************
*
* Module glue...
*/
{
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.
*/
{
if (!add->default_format) {
}
return add;
}
/*
* Set the default logfile format, or define a nickname for a format string.
*/
const char *name)
{
const char *err_string = NULL;
/*
* 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 (err_string == NULL) {
}
}
else {
}
return err_string;
}
{
const char *err_string = NULL;
return "missing environment variable name";
}
}
const char *err;
return "missing condition";
if (err)
return err;
}
else {
return "error in condition clause";
}
}
}
else {
}
return err_string;
}
const char *fn)
{
}
{
if (buffered_logs) {
}
else {
}
return NULL;
}
static const command_rec config_log_cmds[] =
{
"a file name, a custom log format string or format name, "
"and an optional \"env=\" or \"expr=\" clause (see docs)"),
"the filename of the access log"),
"a log format string (see docs) and an optional format name"),
"Enable Buffered Logging (experimental)"),
{NULL}
};
{
}
return cls; /* Leave it NULL to decline. */
}
return NULL;
return cls;
}
{
int i;
const char *dummy;
const char *format;
if (mls->default_format_string) {
if (format) {
}
}
if (!mls->default_format) {
}
if (cls->format_string) {
if (format) {
}
}
/* Failure already logged by open_config_log */
return DONE;
}
}
}
else if (mls->server_config_logs) {
if (cls->format_string) {
if (format) {
}
}
/* Failure already logged by open_config_log */
return DONE;
}
}
}
return OK;
}
{
server_rec *s = data;
int i;
if (!buffered_logs)
return APR_SUCCESS;
for (; s; s = s->next) {
}
else if (mls->server_config_logs) {
}
if (log_list) {
}
}
}
return APR_SUCCESS;
}
{
int res;
/* First init the buffered logs array, which is needed when opening the logs. */
if (buffered_logs) {
}
/* 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 */
res = open_multi_logs(s, p);
}
return res;
}
{
int mpm_threads;
/* Now register the last buffer flush with the cleanup engine */
if (buffered_logs) {
int i;
for (i = 0; i < all_buffered_logs->nelts; i++) {
#if APR_HAS_THREADS
if (mpm_threads > 1) {
p);
if (rv != APR_SUCCESS) {
"could not initialize buffered log mutex, "
"transfer log may become corrupted");
}
}
else
#endif
{
}
}
}
}
{
}
{
return old;
}
{
log_writer = handle;
return old;
}
void *handle,
const char **strs,
int *strl,
int nelts,
{
char *str;
char *s;
int i;
s += strl[i];
}
return rv;
}
const char* name)
{
if (*name == '|') {
return NULL;;
}
return ap_piped_log_write_fd(pl);
}
else {
apr_file_t *fd;
if (!fname) {
"invalid transfer log path %s.", name);
return NULL;
}
if (rv != APR_SUCCESS) {
"could not open transfer log file %s.", fname);
return NULL;
}
return fd;
}
}
const char* name)
{
buffered_log *b;
b = apr_pcalloc(p, sizeof(buffered_log));
if (b->handle) {
return b;
}
else
return NULL;
}
void *handle,
const char **strs,
int *strl,
int nelts,
{
char *str;
char *s;
int i;
return rv;
}
}
if (len >= LOG_BUFSIZE) {
apr_size_t w;
s += strl[i];
}
w = len;
}
else {
s += strl[i];
}
rv = APR_SUCCESS;
}
return rv;
}
{
if (log_pfn_register) {
}
/* reset to default conditions */
buffered_logs = 0;
return OK;
}
static void register_hooks(apr_pool_t *p)
{
/* 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);
}
{
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 */
};