#define DEFAULT_LOG_FORMAT "%h %l %u %t \"%r\" %>s %b"
#include "httpd.h"
#include "http_config.h"
#include "http_core.h" /* For REMOTE_NAME */
#include "http_log.h"
#include <limits.h>
module MODULE_VAR_EXPORT config_log_module;
static
int
xfer_flags = (O_WRONLY | O_APPEND | O_CREAT);
#if defined(OS2) || defined(WIN32) || defined(NETWARE)
static
mode_t xfer_mode = (S_IREAD | S_IWRITE);
#else
static
mode_t xfer_mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
#endif
#ifdef PIPE_BUF
#define LOG_BUFSIZE PIPE_BUF
#else
#define LOG_BUFSIZE (512)
#endif
typedef
struct
{
char
*default_format_string;
array_header *default_format;
array_header *config_logs;
array_header *server_config_logs;
table *formats;
} multi_log_state;
typedef
struct
{
char
*fname;
char
*format_string;
array_header *format;
int
log_fd;
char
*condition_var;
#ifdef BUFFERED_LOGS
int
outcnt;
char
outbuf[LOG_BUFSIZE];
#endif
} config_log_state;
typedef
const
char
*(*item_key_func) (request_rec *,
char
*);
typedef
struct
{
item_key_func func;
char
*arg;
int
condition_sense;
int
want_orig;
array_header *conditions;
} log_format_item;
static
char
*format_integer(pool *p,
int
i)
{
return
ap_psprintf(p,
"%d"
, i);
}
static
char
*pfmt(pool *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_get_remote_host(r->connection, r->per_dir_config,
REMOTE_NAME);
}
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_get_remote_logname(r);
}
static
const
char
*log_remote_user(request_rec *r,
char
*a)
{
char
*rvalue = r->connection->user;
if
(rvalue == NULL) {
rvalue =
"-"
;
}
else
if
(
strlen
(rvalue) == 0) {
rvalue =
"\"\""
;
}
return
rvalue;
}
static
const
char
*log_request_line(request_rec *r,
char
*a)
{
return
(r->parsed_uri.password) ? ap_pstrcat(r->pool, r->method,
" "
,
ap_unparse_uri_components(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
r->filename;
}
static
const
char
*log_request_uri(request_rec *r,
char
*a)
{
return
r->uri;
}
static
const
char
*log_request_method(request_rec *r,
char
*a)
{
return
r->method;
}
static
const
char
*log_request_protocol(request_rec *r,
char
*a)
{
return
r->protocol;
}
static
const
char
*log_request_query(request_rec *r,
char
*a)
{
return
(r->args != NULL) ? ap_pstrcat(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) {
return
"-"
;
}
else
{
long
int
bs;
ap_bgetopt(r->connection->client, BO_BYTECT, &bs);
return
ap_psprintf(r->pool,
"%ld"
, bs);
}
}
static
const
char
*log_bytes_sent(request_rec *r,
char
*a)
{
if
(!r->sent_bodyct) {
return
"0"
;
}
else
{
long
int
bs;
ap_bgetopt(r->connection->client, BO_BYTECT, &bs);
return
ap_psprintf(r->pool,
"%ld"
, bs);
}
}
static
const
char
*log_header_in(request_rec *r,
char
*a)
{
return
ap_table_get(r->headers_in, a);
}
static
const
char
*log_header_out(request_rec *r,
char
*a)
{
const
char
*cp = ap_table_get(r->headers_out, a);
if
(!strcasecmp(a,
"Content-type"
) && r->content_type) {
cp = ap_field_noparam(r->pool, r->content_type);
}
if
(cp) {
return
cp;
}
return
ap_table_get(r->err_headers_out, a);
}
static
const
char
*log_note(request_rec *r,
char
*a)
{
return
ap_table_get(r->notes, a);
}
static
const
char
*log_env_var(request_rec *r,
char
*a)
{
return
ap_table_get(r->subprocess_env, a);
}
static
const
char
*log_request_time(request_rec *r,
char
*a)
{
int
timz;
struct
tm
*t;
char
tstr[MAX_STRING_LEN];
t = ap_get_gmtoff(&timz);
if
(a && *a) {
strftime
(tstr, MAX_STRING_LEN, a, t);
}
else
{
char
sign = (timz < 0 ?
'-'
:
'+'
);
if
(timz < 0) {
timz = -timz;
}
ap_snprintf(tstr,
sizeof
(tstr),
"[%02d/%s/%d:%02d:%02d:%02d %c%.2d%.2d]"
,
t->tm_mday, ap_month_snames[t->tm_mon], t->tm_year+1900,
t->tm_hour, t->tm_min, t->tm_sec,
sign, timz / 60, timz % 60);
}
return
ap_pstrdup(r->pool, tstr);
}
static
const
char
*log_request_duration(request_rec *r,
char
*a)
{
return
ap_psprintf(r->pool,
"%ld"
,
time
(NULL) - r->request_time);
}
static
const
char
*log_virtual_host(request_rec *r,
char
*a)
{
return
r->server->server_hostname;
}
static
const
char
*log_server_port(request_rec *r,
char
*a)
{
return
ap_psprintf(r->pool,
"%u"
,
r->server->port ? r->server->port : ap_default_port(r));
}
static
const
char
*log_server_name(request_rec *r,
char
*a)
{
return
ap_get_server_name(r);
}
static
const
char
*log_child_pid(request_rec *r,
char
*a)
{
return
ap_psprintf(r->pool,
"%ld"
, (
long
) getpid());
}
static
const
char
*log_connection_status(request_rec *r,
char
*a)
{
if
(r->connection->aborted)
return
"X"
;
if
((r->connection->keepalive) &&
((r->server->keep_alive_max - r->connection->keepalives) > 0)) {
return
"+"
;
}
return
"-"
;
}
static
struct
log_item_list {
char
ch;
item_key_func func;
int
want_orig_default;
} log_item_keys[] = {
{
'h'
, log_remote_host, 0
},
{
'a'
, log_remote_address, 0
},
{
'A'
, log_local_address, 0
},
{
'l'
, log_remote_logname, 0
},
{
'u'
, log_remote_user, 0
},
{
't'
, log_request_time, 0
},
{
'T'
, log_request_duration, 1
},
{
'r'
, log_request_line, 1
},
{
'f'
, log_request_file, 0
},
{
'U'
, log_request_uri, 1
},
{
's'
, log_status, 1
},
{
'b'
, clf_log_bytes_sent, 0
},
{
'B'
, log_bytes_sent, 0
},
{
'i'
, log_header_in, 0
},
{
'o'
, log_header_out, 0
},
{
'n'
, log_note, 0
},
{
'e'
, log_env_var, 0
},
{
'V'
, log_server_name, 0
},
{
'v'
, log_virtual_host, 0
},
{
'p'
, log_server_port, 0
},
{
'P'
, log_child_pid, 0
},
{
'H'
, log_request_protocol, 0
},
{
'm'
, log_request_method, 0
},
{
'q'
, log_request_query, 0
},
{
'c'
, log_connection_status, 0
},
{
'\0'
}
};
static
struct
log_item_list *find_log_func(
char
k)
{
int
i;
for
(i = 0; log_item_keys[i].ch; ++i)
if
(k == log_item_keys[i].ch) {
return
&log_item_keys[i];
}
return
NULL;
}
static
char
*parse_log_misc_string(pool *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++;
}
it->arg = ap_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
'n'
:
*d++ =
'\n'
;
s++;
break
;
case
't'
:
*d++ =
'\t'
;
s++;
break
;
default
:
*d++ =
'\\'
;
break
;
}
}
}
*d =
'\0'
;
*sa = s;
return
NULL;
}
static
char
*parse_log_item(pool *p, log_format_item *it,
const
char
**sa)
{
const
char
*s = *sa;
if
(*s !=
'%'
) {
return
parse_log_misc_string(p, it, sa);
}
++s;
it->condition_sense = 0;
it->conditions = NULL;
it->want_orig = -1;
it->arg =
""
;
while
(*s) {
int
i;
struct
log_item_list *l;
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
(ap_isdigit(*++s)) {
i = i * 10 + (*s) -
'0'
;
}
if
(!it->conditions) {
it->conditions = ap_make_array(p, 4,
sizeof
(
int
));
}
*(
int
*) ap_push_array(it->conditions) = i;
break
;
default
:
l = find_log_func(*s++);
if
(!l) {
char
dummy[2];
dummy[0] = s[-1];
dummy[1] =
'\0'
;
return
ap_pstrcat(p,
"Unrecognized LogFormat directive %"
,
dummy, NULL);
}
it->func = l->func;
if
(it->want_orig == -1) {
it->want_orig = l->want_orig_default;
}
*sa = s;
return
NULL;
}
}
return
"Ran off end of LogFormat parsing args to some directive"
;
}
static
array_header *parse_log_string(pool *p,
const
char
*s,
const
char
**err)
{
array_header *a = ap_make_array(p, 30,
sizeof
(log_format_item));
char
*res;
while
(*s) {
if
((res = parse_log_item(p, (log_format_item *) ap_push_array(a), &s))) {
*err = res;
return
NULL;
}
}
s =
"\n"
;
parse_log_item(p, (log_format_item *) ap_push_array(a), &s);
return
a;
}
static
const
char
*process_item(request_rec *r, request_rec *orig,
log_format_item *item)
{
const
char
*cp;
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
"-"
;
}
}
cp = (*item->func) (item->want_orig ? orig : r, item->arg);
return
cp ? cp :
"-"
;
}
#ifdef BUFFERED_LOGS
static
void
flush_log(config_log_state *cls)
{
if
(cls->outcnt && cls->log_fd != -1) {
write(cls->log_fd, cls->outbuf, cls->outcnt);
cls->outcnt = 0;
}
}
#endif
static
int
config_log_transaction(request_rec *r, config_log_state *cls,
array_header *default_format)
{
log_format_item *items;
char
*str, *s;
const
char
**strs;
int
*strl;
request_rec *orig;
int
i;
int
len = 0;
array_header *format;
char
*envar;
if
(cls->fname == NULL) {
return
DECLINED;
}
if
(cls->condition_var != NULL) {
envar = cls->condition_var;
if
(*envar !=
'!'
) {
if
(ap_table_get(r->subprocess_env, envar) == NULL) {
return
DECLINED;
}
}
else
{
if
(ap_table_get(r->subprocess_env, &envar[1]) != NULL) {
return
DECLINED;
}
}
}
format = cls->format ? cls->format : default_format;
strs = ap_palloc(r->pool,
sizeof
(
char
*) * (format->nelts));
strl = ap_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]);
}
#ifdef BUFFERED_LOGS
if
(len + cls->outcnt > LOG_BUFSIZE) {
flush_log(cls);
}
if
(len >= LOG_BUFSIZE) {
str = ap_palloc(r->pool, len + 1);
for
(i = 0, s = str; i < format->nelts; ++i) {
memcpy
(s, strs[i], strl[i]);
s += strl[i];
}
write(cls->log_fd, str, len);
}
else
{
for
(i = 0, s = &cls->outbuf[cls->outcnt]; i < format->nelts; ++i) {
memcpy
(s, strs[i], strl[i]);
s += strl[i];
}
cls->outcnt += len;
}
#else
str = ap_palloc(r->pool, len + 1);
for
(i = 0, s = str; i < format->nelts; ++i) {
memcpy
(s, strs[i], strl[i]);
s += strl[i];
}
write(cls->log_fd, str, len);
#endif
return
OK;
}
static
int
multi_log_transaction(request_rec *r)
{
multi_log_state *mls = ap_get_module_config(r->server->module_config,
&config_log_module);
config_log_state *clsarray;
int
i;
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;
}
static
void
*make_config_log_state(pool *p, server_rec *s)
{
multi_log_state *mls;
mls = (multi_log_state *) ap_palloc(p,
sizeof
(multi_log_state));
mls->config_logs = ap_make_array(p, 1,
sizeof
(config_log_state));
mls->default_format_string = NULL;
mls->default_format = NULL;
mls->server_config_logs = NULL;
mls->formats = ap_make_table(p, 4);
ap_table_setn(mls->formats,
"CLF"
, DEFAULT_LOG_FORMAT);
return
mls;
}
static
void
*merge_config_log_state(pool *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 = ap_overlay_tables(p, base->formats, add->formats);
return
add;
}
static
const
char
*log_format(cmd_parms *cmd,
void
*dummy,
char
*fmt,
char
*name)
{
const
char
*err_string = NULL;
multi_log_state *mls = ap_get_module_config(cmd->server->module_config,
&config_log_module);
if
(name != NULL) {
parse_log_string(cmd->pool, fmt, &err_string);
if
(err_string == NULL) {
ap_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,
char
*fn,
char
*fmt,
char
*envclause)
{
const
char
*err_string = NULL;
multi_log_state *mls = ap_get_module_config(cmd->server->module_config,
&config_log_module);
config_log_state *cls;
cls = (config_log_state *) ap_push_array(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 = ap_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_fd = -1;
return
err_string;
}
static
const
char
*set_transfer_log(cmd_parms *cmd,
void
*dummy,
char
*fn)
{
return
add_custom_log(cmd, dummy, fn, NULL, NULL);
}
static
const
char
*set_cookie_log(cmd_parms *cmd,
void
*dummy,
char
*fn)
{
return
add_custom_log(cmd, dummy, fn,
"%{Cookie}n \"%r\" %t"
, NULL);
}
static
const
command_rec config_log_cmds[] =
{
{
"CustomLog"
, add_custom_log, NULL, RSRC_CONF, TAKE23,
"a file name, a custom log format string or format name, "
"and an optional \"env=\" clause (see docs)"
},
{
"TransferLog"
, set_transfer_log, NULL, RSRC_CONF, TAKE1,
"the filename of the access log"
},
{
"LogFormat"
, log_format, NULL, RSRC_CONF, TAKE12,
"a log format string (see docs) and an optional format name"
},
{
"CookieLog"
, set_cookie_log, NULL, RSRC_CONF, TAKE1,
"the filename of the cookie log"
},
{NULL}
};
static
config_log_state *open_config_log(server_rec *s, pool *p,
config_log_state *cls,
array_header *default_format)
{
if
(cls->log_fd > 0) {
return
cls;
}
if
(cls->fname == NULL) {
return
cls;
}
if
(*cls->fname ==
'|'
) {
piped_log *pl;
pl = ap_open_piped_log(p, cls->fname + 1);
if
(pl == NULL) {
exit
(1);
}
cls->log_fd = ap_piped_log_write_fd(pl);
}
else
{
char
*fname = ap_server_root_relative(p, cls->fname);
if
((cls->log_fd = ap_popenf(p, fname, xfer_flags, xfer_mode)) < 0) {
ap_log_error(APLOG_MARK, APLOG_ERR, s,
"could not open transfer log file %s."
, fname);
exit
(1);
}
}
#ifdef BUFFERED_LOGS
cls->outcnt = 0;
#endif
return
cls;
}
static
config_log_state *open_multi_logs(server_rec *s, pool *p)
{
int
i;
multi_log_state *mls = ap_get_module_config(s->module_config,
&config_log_module);
config_log_state *clsarray;
const
char
*dummy;
const
char
*format;
if
(mls->default_format_string) {
format = ap_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 = ap_table_get(mls->formats, cls->format_string);
if
(format) {
cls->format = parse_log_string(p, format, &dummy);
}
}
cls = open_config_log(s, p, 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];
if
(cls->format_string) {
format = ap_table_get(mls->formats, cls->format_string);
if
(format) {
cls->format = parse_log_string(p, format, &dummy);
}
}
cls = open_config_log(s, p, cls, mls->default_format);
}
}
return
NULL;
}
static
void
init_config_log(server_rec *s, pool *p)
{
open_multi_logs(s, p);
for
(s = s->next; s; s = s->next) {
open_multi_logs(s, p);
}
}
#ifdef BUFFERED_LOGS
static
void
flush_all_logs(server_rec *s, pool *p)
{
multi_log_state *mls;
array_header *log_list;
config_log_state *clsarray;
int
i;
for
(; s; s = s->next) {
mls = ap_get_module_config(s->module_config, &config_log_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) {
flush_log(&clsarray[i]);
}
}
}
}
#endif
module MODULE_VAR_EXPORT config_log_module =
{
STANDARD_MODULE_STUFF,
init_config_log,
NULL,
NULL,
make_config_log_state,
merge_config_log_state,
config_log_cmds,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
multi_log_transaction,
NULL,
NULL,
#ifdef BUFFERED_LOGS
flush_all_logs,
#else
NULL,
#endif
NULL
};