/* ==================================================================== * Copyright (c) 1995-1999 The Apache Group. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * 4. The names "Apache Server" and "Apache Group" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please contact * apache@apache.org. * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * 6. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Group and was originally based * on public domain software written at the National Center for * Supercomputing Applications, University of Illinois, Urbana-Champaign. * For more information on the Apache Group and the Apache HTTP server * project, please see . * */ /* * Modified by djm@va.pubnix.com: * If no TransferLog is given explicitly, decline to log. * * This is module implements the TransferLog directive (same as the * common log module), and additional directives, LogFormat and CustomLog. * * * Syntax: * * TransferLog fn Logs transfers to fn in standard log format, unless * a custom format is set with LogFormat * LogFormat format Set a log format from TransferLog files * CustomLog fn format * Log to file fn with format given by the format * argument * * CookieLog fn For backwards compatability with old Cookie * logging module - now deprecated. * * There can be any number of TransferLog and CustomLog * commands. Each request will be logged to _ALL_ the * named files, in the appropriate format. * * If no TransferLog or CustomLog directive appears in a VirtualHost, * the request will be logged to the log file(s) defined outside * the virtual host section. If a TransferLog or CustomLog directive * appears in the VirtualHost section, the log files defined outside * the VirtualHost will _not_ be used. This makes this module compatable * with the CLF and config log modules, where the use of TransferLog * inside the VirtualHost section overrides its use outside. * * Examples: * * TransferLog logs/access_log * * LogFormat "... custom format ..." * TransferLog log/virtual_only * CustomLog log/virtual_useragents "%t %{user-agent}i" * * * 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}e: The contents of the environment variable FOOBAR * %...f: filename * %...h: remote host * %...a: remote IP-address * %...A: local IP-address * %...{Foobar}i: The contents of Foobar: header line(s) in the request * sent to the client. * %...l: remote logname (from identd, if supplied) * %...{Foobar}n: The contents of note "Foobar" from another module. * %...{Foobar}o: The contents of Foobar: header line(s) in the reply. * %...p: the port the request was served to * %...P: the process ID of the child that serviced the request. * %...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. * %...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 * * 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 */ /* Modified by George Schlossnagle george@lethargy.org: * These modifications allow logging to a Spread group as a custom log * directive. * * Syntax: * SpreadDaemon port[@host] opens a connection to a spread daemon * listening on port. If host is omitted * then communictaion is performed through * a unix domain socket. The optional id field * assigns this daemon connection an identifier * that can be used in CustomLog $groupname's * to identify which daemon to log to. * CustomLog $groupname format Functions exactly like the classic CustomLog * except that a file starting with '$' causes * requests to be multicast to a spread group * thus named. * * If the group name is the magic name '$#hostname' * then the spread group is automatically set to * the Host: request-header value. This works * well for small vhost setups (up to ~100 hosts). * For larger vhost installs, there is the magic * name '$#vhost'. This sets hashes the Host: * request header to a small integer and uses that * as the group name. The actual Host: name * is prepended to the log line. This allows for * a number of daemons to be run for large vhost * installs. * If the groupname has a '#' symbol after the spread * group name, then after the '#' will be a number * indicating which daemon id to log to. So the name * '$mygroup#2' will log to group 'mygroup' on the * spread daemon identified by id '2'. This works * with the vhost support like '$#vhost#2'. * * If a second group name is listed before the format, * like '$groupname,$groupname2' then the second * group name is considered a failover group, and * the log messages will be logged to the second group * only if the first one fails. This can be used * either for reliability purposes or to ease migration * to a newer version of the spread daemon. * * VhostLogHashSize integer The size of the hash for $#vhost mass logging * option. Default value is 32. * * Spread is a project of the JHU Center for Networking and Distributed Systems * information on obtaining spread is available at http://www.spread.org. The * CNDS folks have been gracious enough to provide a free-use license for * use with this module. * -gs */ #define SpreadSpecial(a) ((((a)[0])=='#')?1:0) #define SpreadType(a) ((a)[1]) #define MAX_SPREAD_NAME 32 #define SP_TIMEOUT 2 #define SP_TRYAGAIN 15 #define MAX_SPREAD_DAEMONS 8 #define DEFAULT_LOG_FORMAT "%h %l %u %t \"%r\" %>s %b" #include "time.h" #include "httpd.h" #include "http_config.h" #include "http_core.h" /* For REMOTE_NAME */ #include "http_log.h" #include #include "sp.h" /* This should probably be moved to exterior module. The function hashblah was adapted from Kyle Loudon's Mastering Algorithms in C, credited to P.J. Weinberger. */ int ap_mls_hash(const void *key, const int size) { const char *ptr; unsigned int val = 0; int retval; ptr = key; while (*ptr != '\0') { int tmp; val = ((val << 1) + ((*ptr)*31 >> 5)) >> 1; if (tmp = (val & 0xf0000000)) { val = val ^ (tmp >> 24); val = val ^ tmp; } ptr++; } return (int) (val % size); } module MODULE_VAR_EXPORT log_spread_module; typedef struct { mailbox spread_fd; char spreaddaemon[256]; time_t lastconnect; char log_private_group[MAX_GROUP_NAME]; } spread_daemon_state; static NumSpreadDaemons = 0; static spread_daemon_state sds[MAX_SPREAD_DAEMONS]; static int hash_size = 32; static char tmpmessage[4096]; static int xfer_flags = (O_WRONLY | O_APPEND | O_CREAT); #if defined(OS2) || defined(WIN32) /* OS/2 dosen't support users and groups */ static mode_t xfer_mode = (S_IREAD | S_IWRITE); #else static mode_t xfer_mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); #endif /* 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 in 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 { char *default_format_string; array_header *default_format; array_header *config_logs; array_header *server_config_logs; table *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_fd is -1 before the log file is opened and * set to a valid fd after it is opened. */ typedef struct { char *fname; char *format_string; array_header *format; int log_fd; char *condition_var; char spread_group[MAX_SPREAD_NAME]; int spread_daemon; char failover_spread_group[MAX_SPREAD_NAME]; int failover_spread_daemon; #ifdef BUFFERED_LOGS int outcnt; char outbuf[LOG_BUFSIZE]; #endif } config_log_state; /* * Format items... * Note that many of these could have ap_sprintfs replaced with static buffers. */ 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) { /* 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 (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) { /* Custom format */ strftime(tstr, MAX_STRING_LEN, a, t); } else { /* CLF format */ 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); } /* 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 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)); } /* 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_get_server_name(r); } static const char *log_child_pid(request_rec *r, char *a) { return ap_psprintf(r->pool, "%ld", (long) getpid()); } /***************************************************************** * * Parsing the log format string */ 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 }, { '\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++; } /* * This might allocate a few chars extra if there's a backslash * escape in the format string. */ 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: /* 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(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 = ""; /* For safety's sake... */ 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; } /***************************************************************** * * 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 spread_init_connection(server_rec *s, int daemon_index) { int sperror; time_t nowtime; char private_name[MAX_GROUP_NAME]; nowtime = time(NULL); if ( nowtime < sds[daemon_index].lastconnect + SP_TRYAGAIN ) { sds[daemon_index].spread_fd = -1; } else { sds[daemon_index].lastconnect=nowtime; ap_snprintf(private_name, MAX_GROUP_NAME, "ap%05d", getpid()); // sds[daemon_index].spread_fd=(mailbox)-1; if((sperror=SP_connect( sds[daemon_index].spreaddaemon, private_name, 0, 0, &(sds[daemon_index].spread_fd), sds[daemon_index].log_private_group)) != ACCEPT_SESSION) { ap_log_error(APLOG_MARK, APLOG_ERR, s, "Could not connect to spread %s with private_name %s. Error %d", sds[daemon_index].spreaddaemon, private_name, sperror); } } } static void spread_init(server_rec *s, pool *p) { int i; for (i=0; i < NumSpreadDaemons; i++) { spread_init_connection(s, i); } } static int spread_multicast_log_to_daemon(request_rec *r, char *group_name, int daemon_index, char *str, int len) { int sperror; const char *src; char tmpgrp[MAX_GROUP_NAME]; char lchostname[1024]; char *dst = lchostname; if (SpreadSpecial(group_name)) { src = r->hostname; while(*src != '\0') { *dst = tolower(*src); dst++; src++; } *dst = '\0'; switch (SpreadType(group_name)) { case 'h': case 'H': sperror = SP_multicast( sds[daemon_index].spread_fd, RELIABLE_MESS, lchostname, 1, len, str); break; case 'v': case 'V': ap_snprintf(tmpgrp,MAX_GROUP_NAME,"apache-%04d", ap_mls_hash((void *)lchostname,hash_size)); ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, NULL, "Host: %s hashed to group %s.",lchostname, tmpgrp ); ap_snprintf(tmpmessage,strlen(lchostname)+len+2, "%s %s", lchostname, str); sperror = SP_multicast( sds[daemon_index].spread_fd, RELIABLE_MESS, tmpgrp, 1, strlen(tmpmessage), tmpmessage); /* FIXME: other version had 'len + strlen(lchostname) + 1' here */ break; default: ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, NULL, "Invalid Spread group %s.",group_name); /* FIXME: other version had exit(1); here */ break; } } else sperror = SP_multicast( sds[daemon_index].spread_fd, RELIABLE_MESS, group_name, 1, len, str); return(sperror); } static void spread_multicast_log(request_rec *r, config_log_state *cls, char *str, int len ) { int sperror; int temp; int try_failover = 0; /* We want to timeout if SP_multicast hangs */ temp = r->server->timeout; r->server->timeout=SP_TIMEOUT; ap_hard_timeout("multicasting logs", r); r->server->timeout = temp; sperror = spread_multicast_log_to_daemon(r, cls->spread_group, cls->spread_daemon, str, len); ap_kill_timeout(r); if(sperror < 0) { /* Try to connect again */ SP_disconnect( sds[cls->spread_daemon].spread_fd ); spread_init_connection(NULL, cls->spread_daemon); try_failover = 1; } if (try_failover) { temp = r->server->timeout; r->server->timeout=SP_TIMEOUT; ap_hard_timeout("multicasting logs", r); r->server->timeout = temp; sperror = spread_multicast_log_to_daemon(r, cls->failover_spread_group, cls->failover_spread_daemon, str, len); ap_kill_timeout(r); if(sperror < 0) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, NULL, "SP_multicast failover error(%d) in config_log_tranaction", sperror); SP_disconnect( sds[cls->failover_spread_daemon].spread_fd ); spread_init_connection(NULL, cls->failover_spread_daemon); } } return; } #ifdef BUFFERED_LOGS static void flush_log(config_log_state *cls) { if (cls->outcnt && cls->log_fd != -1) { /* if using spread, use spread, else don't. */ /* FIXME: This is buggy and doesn't compile */ if ( cls->spread_group[0] ){ int sperror; sperror = SP_multicast( spread_fd, RELIABLE_MESS, cls->spread_group, 1, len, str); if(sperror < 0) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, NULL, "SP_multicast error(%d) in process_item", sperror); SP_disconnect ( spread_fd ); spread_init(NULL,NULL); } } else { 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; } /* * 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 (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);*/ if (cls->spread_group[0]) { spread_multicast_log(r, cls, str, len); } else { 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);*/ if ( cls->spread_group[0] ) { spread_multicast_log(r, cls, str, len); } else { 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, &log_spread_module); config_log_state *clsarray; int i; /* * Log this transaction.. */ if (mls->config_logs->nelts) { clsarray = (config_log_state *) mls->config_logs->elts; for (i = 0; i < mls->config_logs->nelts; ++i) { config_log_state *cls = &clsarray[i]; config_log_transaction(r, cls, mls->default_format); } } else if (mls->server_config_logs) { clsarray = (config_log_state *) mls->server_config_logs->elts; for (i = 0; i < mls->server_config_logs->nelts; ++i) { config_log_state *cls = &clsarray[i]; config_log_transaction(r, cls, mls->default_format); } } return OK; } /***************************************************************** * * Module glue... */ static void *make_config_log_state(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; } /* * Use the mergeger 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(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; } /* * Set the default logfile format, or define a nickname for a format string. */ 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, &log_spread_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) { 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, &log_spread_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 char *set_spread_daemon(cmd_parms *cmd, void *dummy, char *dname, char *dnum) { int index; if (dnum != NULL) { index = atoi(dnum); } else { index = 0; } ap_snprintf(sds[index].spreaddaemon, 256, dname); NumSpreadDaemons++; ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, cmd->server, "set_spread_daemon(%s) for index %d", dname, index); return NULL; } static const char *set_hash_size(cmd_parms *cmd, void *dummy, char *param1) { hash_size = atoi(param1); return 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"}, {"SpreadDaemon", set_spread_daemon, NULL, RSRC_CONF, TAKE12, "the nme of the spread daemon"}, {"VhostLogHashSize", set_hash_size, NULL, RSRC_CONF, TAKE1, "size of logging hash"}, {NULL} }; static void parse_spread_groupname(server_rec *s, config_log_state *cls) { char *c, *ns, *fail_fname; int dnum; /* grab daemon index from name, and see if failover groups */ c = cls->fname; fail_fname = NULL; while ( *c != '\0' ) { if ( *c == ',' ) { *c = '\0'; fail_fname = c + 1; } c++; } c = cls->fname+2; /* skip first '#' character */ ns = NULL; while ( *c != '\0' ) { if ( *c == '#' ) { *c = '\0'; ns = c + 1; break; } c++; } if (ns == NULL) dnum = 0; else dnum = atoi(ns); cls->spread_daemon = dnum; switch(SpreadSpecial(cls->fname+1)){ case 1: switch ((cls->fname)[2]){ case 'h': ap_snprintf(cls->spread_group, MAX_SPREAD_NAME, cls->fname+1); break; case 'v': ap_snprintf(cls->spread_group, MAX_SPREAD_NAME, cls->fname+1); break; default: ap_log_error(APLOG_MARK, APLOG_ERR, s, "Spread group %s is not a valid group name", cls->fname+1); break; } default: ap_snprintf(cls->spread_group, MAX_SPREAD_NAME, cls->fname+1); } ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, NULL, "Create log to group %s for daemon %d", cls->spread_group, cls->spread_daemon); if (fail_fname != NULL) { c = fail_fname+2; /* skip first '#' character */ ns = NULL; while ( *c != '\0' ) { if ( *c == '#' ) { ns = c + 1; break; } c++; } if (ns == NULL) dnum = 0; else dnum = atoi(ns); cls->failover_spread_daemon = dnum; switch(SpreadSpecial(fail_fname+1)){ case 1: switch ((fail_fname)[2]){ case 'h': ap_snprintf(cls->failover_spread_group, MAX_SPREAD_NAME, fail_fname+1); break; case 'v': ap_snprintf(cls->failover_spread_group, MAX_SPREAD_NAME, fail_fname+1); break; default: ap_log_error(APLOG_MARK, APLOG_ERR, s, "Spread group %s is not a valid group name", fail_fname+1); break; } default: ap_snprintf(cls->failover_spread_group, MAX_SPREAD_NAME, fail_fname+1); } ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, NULL, "Create failover log to group %s for daemon %d", cls->failover_spread_group, cls->failover_spread_daemon); } } 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; /* virtual config shared w/main server */ } if (cls->fname == NULL) { return cls; /* Leave it NULL to decline. */ } 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); } /* Ignore spread descriptors now, pick them up in child init */ if (*cls->fname == '$') { parse_spread_groupname(s, cls); /* FIXME: bug why return here, why not wait for return at end of function? */ return cls; } 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, &log_spread_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) { /* First, do "physical" server, which gets default log fd and format * for the virtual servers, if they don't override... */ open_multi_logs(s, p); /* Then, virtual servers */ for (s = s->next; s; s = s->next) { open_multi_logs(s, p); } } static void close_spread(server_rec *s, pool *p) { int i; for (i=0; i< NumSpreadDaemons; i++) { if (sds[i].spread_fd != -1 ) SP_disconnect( sds[i].spread_fd ); } } #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, &log_spread_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]); } } } /*Add Spread disconnect*/ for (i=0; i < NumSpreadDaemons; i++) { if ( sds[i].spread_fd != -1 ) { SP_disconnect( sds[i].spread_fd ); } } } #endif module MODULE_VAR_EXPORT log_spread_module = { STANDARD_MODULE_STUFF, init_config_log, /* initializer */ 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 table */ NULL, /* handlers */ NULL, /* filename translation */ NULL, /* check_user_id */ NULL, /* check auth */ NULL, /* check access */ NULL, /* type_checker */ NULL, /* fixups */ multi_log_transaction, /* logger */ NULL, /* header parser */ spread_init, /* child_init */ #ifdef BUFFERED_LOGS flush_all_logs, /* child_exit */ #else close_spread, #endif NULL /* post read-request */ };