diff options
-rw-r--r-- | CHANGES | 5 | ||||
-rw-r--r-- | STATUS | 8 | ||||
-rw-r--r-- | docs/manual/logs.xml | 1 | ||||
-rw-r--r-- | docs/manual/mod/core.xml | 147 | ||||
-rw-r--r-- | docs/manual/mod/mod_log_config.xml | 4 | ||||
-rw-r--r-- | include/ap_mmn.h | 7 | ||||
-rw-r--r-- | include/http_config.h | 2 | ||||
-rw-r--r-- | include/http_core.h | 65 | ||||
-rw-r--r-- | include/httpd.h | 11 | ||||
-rw-r--r-- | include/util_time.h | 2 | ||||
-rw-r--r-- | modules/loggers/mod_log_config.c | 18 | ||||
-rw-r--r-- | server/config.c | 6 | ||||
-rw-r--r-- | server/core.c | 243 | ||||
-rw-r--r-- | server/log.c | 711 | ||||
-rw-r--r-- | server/util_time.c | 58 |
15 files changed, 1116 insertions, 172 deletions
@@ -2,6 +2,11 @@ Changes with Apache 2.3.9 + *) core: Add ErrorLogFormat to allow configuring error log format, including + additional information that is logged once per connection or request. Add + error log IDs for connections and request to allow correlating error log + lines and the corresponding access log entry. + *) core: Disable sendfile by default. [Stefan Fritsch] *) mod_cache: Check the request to determine whether we are allowed @@ -64,11 +64,6 @@ RELEASE SHOWSTOPPERS: - MPM simple - mod_serf - * Error log format should be configurable. The current format is - too verbose: The prefix length ranges from 70 chars (server-scope, - level <= info) to 120 chars (conn-scope, level >= debug). - (sf is working on this). - FOR BETA: * Modules without documentation need to be moved to experimental or be @@ -141,9 +136,6 @@ CURRENT VOTES: RELEASE NON-SHOWSTOPPERS BUT WOULD BE REAL NICE TO WRAP THESE UP: - * Allow to log module name in error log format. - (sf is working on this) - * Add mod_allow_method or some other (usable) functionality to replace Limit/LimitExcept. diff --git a/docs/manual/logs.xml b/docs/manual/logs.xml index d6d8a2768b..1c00a1df95 100644 --- a/docs/manual/logs.xml +++ b/docs/manual/logs.xml @@ -92,6 +92,7 @@ </modulelist> <directivelist> <directive module="core">ErrorLog</directive> + <directive module="core">ErrorLogFormat</directive> <directive module="core">LogLevel</directive> </directivelist> </related> diff --git a/docs/manual/mod/core.xml b/docs/manual/mod/core.xml index 4811f6a0e8..6aa6333c2e 100644 --- a/docs/manual/mod/core.xml +++ b/docs/manual/mod/core.xml @@ -1044,6 +1044,153 @@ in case of an error</description> </directivesynopsis> <directivesynopsis> +<name>ErrorLogFormat</name> +<description>Format specification for error log entries</description> +<syntax> ErrorLog [connection|request] <var>format</var></syntax> +<contextlist><context>server config</context><context>virtual host</context> +</contextlist> +<compatibility>Available in Apache httpd 2.3.9 and later</compatibility> + +<usage> + <p><directive>ErrorLogFormat</directive> allows to specify what + supplementary information is logged in the error log in addition to the + actual log message.</p> + + <example><title>Simple example</title> + ErrorLogFormat "[%t] [%l] [pid %P] %F: %E: [client %a] %M" + </example> + + <p>Specifying <code>connection</code> or <code>request</code> as first + paramter allows to specify additional formats, causing additional + information to be logged when the first message is logged for a specific + connection or request, respectivly. This additional information is only + logged once per connection/request. If a connection or request is processed + without causing any log message, the additional information is not logged + either.</p> + + <p>It can happen that some format string items do not produce output. For + example, the Referer header is only present if the log message is + associated to a request and the log message happens at a time when the + Referer header has already been read from the client. If no output is + produced, the default behaviour is to delete everything from the preceeding + space character to the next space character. This means the log line is + implicitly divided into fields on non-whitespace to whitespace transitions. + If a format string item does not produce output, the whole field is + ommitted. For example, if the remote address <code>%a</code> in the log + format <code>[%t] [%l] [%a] %M </code> is not available, the surrounding + brackets are not logged either. Space characters can be escaped with a + backslash to prevent them from delimiting a field. The combination '% ' + (percent space) is a zero-witdh field delimiter that does not produce any + output.</p> + + <p>The above behaviour can be changed by adding flags to the format string + item. A <code>-</code> (minus) flag causes a minus to be logged if the + respective item does not produce any output. In once-per-connection/request + formats, it is also possible to use the <code>+</code> (plus) flag. If an + item with the plus flag does not produce any output, the whole line is + ommitted.</p> + + <p>Some format string items accept additional parameters in braces.</p> + + <table border="1" style="zebra"> + <columnspec><column width=".2"/><column width=".8"/></columnspec> + + <tr><th>Format String</th> <th>Description</th></tr> + + <tr><td><code>%%</code></td> + <td>The percent sign</td></tr> + + <tr><td><code>%...a</code></td> + <td>Remote IP-address and port</td></tr> + + <tr><td><code>%...A</code></td> + <td>Local IP-address and port</td></tr> + + <tr><td><code>%...E</code></td> + <td>APR/OS error status code and string</td></tr> + + <tr><td><code>%...F</code></td> + <td>Source file name and line number of the log call</td></tr> + + <tr><td><code>%...{name}i</code></td> + <td>Request header <code>name</code></td></tr> + + <tr><td><code>%...k</code></td> + <td>Number of keep-alive requests on this connection</td></tr> + + <tr><td><code>%...l</code></td> + <td>Loglevel of the message</td></tr> + + <tr><td><code>%...L</code></td> + <td>Log ID of the request</td></tr> + + <tr><td><code>%...{c}L</code></td> + <td>Log ID of the connection</td></tr> + + <tr><td><code>%...{C}L</code></td> + <td>Log ID of the connection if used in connection scope, empty otherwise</td></tr> + + <tr><td><code>%...m</code></td> + <td>Name of the module logging the message</td></tr> + + <tr><td><code>%M</code></td> + <td>The actual log message</td></tr> + + <tr><td><code>%...P</code></td> + <td>Process ID of current process</td></tr> + + <tr><td><code>%...T</code></td> + <td>Thread ID of current thread</td></tr> + + <tr><td><code>%...t</code></td> + <td>The current time</td></tr> + + <tr><td><code>%...{u}t</code></td> + <td>The current time including micro-seconds</td></tr> + + <tr><td><code>%...{cu}t</code></td> + <td>The current time in compact ISO 8601 format, including + micro-seconds</td></tr> + + <tr><td><code>\ </code> (backslash space)</td> + <td>Non-field delimiting space</td></tr> + + <tr><td><code>% </code> (percent space)</td> + <td>Field delimiter (no output)</td></tr> + </table> + + <p>The log ID format <code>%L</code> produces a unique id for a connection + or request. This can be used to correlate which log lines belong to the + same connection or request, which request happens on which connection. + A <code>%L</code> format string is also available in + <module>mod_log_config</module>, to allow to correlate access log entries + with error log lines.</p> + + <example><title>Example (somewhat similar to default format)</title> + ErrorLogFormat "[%{u}t] [%-m:%l] [pid %P] %F: %E: [client\ %a] + %M% ,\ referer\ %{Referer}i" + </example> + + <example><title>Example (similar to the 2.2.x format)</title> + ErrorLogFormat "[%t] [%l] %F: %E: [client\ %a] + %M% ,\ referer\ %{Referer}i" + </example> + + <example><title>Advanced example with request/connection log IDs</title> + ErrorLogFormat "[%{uc}t] [%-m:%-l] [R:%L] [C:%{C}L] %M"<br/> + ErrorLogFormat request "[%{uc}t] [R:%L] Request %k on C:%{c}L pid:%P tid:%T"<br/> + ErrorLogFormat request "[%{uc}t] [R:%L] UA:'%+{User-Agent}i'"<br/> + ErrorLogFormat request "[%{uc}t] [R:%L] Referer:'%+{Referer}i'"<br/> + ErrorLogFormat connection "[%{uc}t] [C:%{c}L] local\ %a remote\ %A"<br/> + </example> + +</usage> +<seealso><directive module="core">ErrorLog</directive></seealso> +<seealso><directive module="core">LogLevel</directive></seealso> +<seealso><a href="../logs.html">Apache HTTP Server Log Files</a></seealso> +</directivesynopsis> + +<directivesynopsis> <name>ExtendedStatus</name> <description>Keep track of extended status information for each request</description> diff --git a/docs/manual/mod/mod_log_config.xml b/docs/manual/mod/mod_log_config.xml index 7761b69edc..7ac233ab83 100644 --- a/docs/manual/mod/mod_log_config.xml +++ b/docs/manual/mod/mod_log_config.xml @@ -122,6 +122,10 @@ module="mod_ident">IdentityCheck</directive> is set <code>On</code>.</td></tr> + <tr><td><code>%L</code></td> + <td>The request log ID from the error log (or '-' if nothing has been + logged to the error log for this request)</td></tr> + <tr><td><code>%m</code></td> <td>The request method</td></tr> diff --git a/include/ap_mmn.h b/include/ap_mmn.h index 010484725d..a066a83a2d 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -245,14 +245,17 @@ * proxy worker structs * 20100723.2 (2.3.7-dev) Add ap_request_has_body() * 20100723.3 (2.3.8-dev) Add ap_check_mpm() + * 20100905.0 (2.3.9-dev) Add log_id to conn and req recs. Add error log + * format handlers. Support AP_CTIME_OPTION_COMPACT in + * ap_recent_ctime_ex(). */ #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */ #ifndef MODULE_MAGIC_NUMBER_MAJOR -#define MODULE_MAGIC_NUMBER_MAJOR 20100723 +#define MODULE_MAGIC_NUMBER_MAJOR 20100905 #endif -#define MODULE_MAGIC_NUMBER_MINOR 3 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 0 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a diff --git a/include/http_config.h b/include/http_config.h index c0b2dd7b44..095880d918 100644 --- a/include/http_config.h +++ b/include/http_config.h @@ -724,7 +724,7 @@ AP_DECLARE(const char *) ap_find_module_name(module *m); /** * Find the short name of the module identified by the specified module index * @param module_index The module index to get the name for - * @return the name of the module + * @return the name of the module, NULL if not found */ AP_DECLARE(const char *) ap_find_module_short_name(int module_index); /** diff --git a/include/http_core.h b/include/http_core.h index 7951978759..affd42a6ad 100644 --- a/include/http_core.h +++ b/include/http_core.h @@ -569,6 +569,15 @@ typedef struct { const char *protocol; apr_table_t *accf_map; + /* array of ap_errorlog_format_item for error log format string */ + apr_array_header_t *error_log_format; + /* + * two arrays of arrays of ap_errorlog_format_item for additional information + * logged to the error log once per connection/request + */ + apr_array_header_t *error_log_conn; + apr_array_header_t *error_log_req; + /* TRACE control */ #define AP_TRACE_UNSET -1 #define AP_TRACE_DISABLE 0 @@ -663,6 +672,62 @@ APR_DECLARE_OPTIONAL_FN(apr_off_t, ap_logio_get_last_bytes, (conn_rec *c)); /* ---------------------------------------------------------------------- * + * Error log formats + */ + +/** + * info structure passed to callback functions of errorlog handlers + */ +typedef struct ap_errorlog_info { + const server_rec *s; + const conn_rec *c; + const request_rec *r; + const request_rec *rmain; + const char *file; + int line; + int module_index; + int level; + apr_status_t status; + int using_syslog; + int startup; +} ap_errorlog_info; + +/** + * callback function prototype for a external errorlog handler + */ +typedef int ap_errorlog_handler_fn_t(const ap_errorlog_info *info, + const char *arg, char *buf, int buflen); + +/** + * Register external errorlog handler + * @param p config pool to use + * @param tag the new format specifier (i.e. the letter after the %) + * @param handler the handler function + * @param flags flags (reserved, set to 0) + */ +AP_DECLARE(void) ap_register_errorlog_handler(apr_pool_t *p, char *tag, + ap_errorlog_handler_fn_t *handler, + int flags); + +typedef struct ap_errorlog_handler { + ap_errorlog_handler_fn_t *func; + int flags; +} ap_errorlog_handler; + +typedef struct { + ap_errorlog_handler_fn_t *func; + const char *arg; +#define AP_ERRORLOG_FLAG_FIELD_SEP 1 +#define AP_ERRORLOG_FLAG_MESSAGE 2 +#define AP_ERRORLOG_FLAG_REQUIRED 4 +#define AP_ERRORLOG_FLAG_NULL_AS_HYPHEN 8 + unsigned int flags; +} ap_errorlog_format_item; + +AP_DECLARE(void) ap_register_builtin_errorlog_handlers(apr_pool_t *p); + +/* ---------------------------------------------------------------------- + * * ident lookups with mod_ident */ diff --git a/include/httpd.h b/include/httpd.h index 0447866cf3..c275132d26 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -962,6 +962,11 @@ struct request_rec { * modifying */ const struct ap_logconf *log; + /** Id to identify request in access and error log. Set when the first + * error log entry for this request is generated. + */ + const char *log_id; + /** * A linked list of the .htaccess configuration directives * accessed by this request. @@ -1103,6 +1108,12 @@ struct conn_rec { * per_dir config, i.e. must be copied before modifying */ const struct ap_logconf *log; + /** Id to identify this connection in error log. Set when the first + * error log entry for this connection is generated. + */ + const char *log_id; + + /** This points to the current thread being used to process this request, * over the lifetime of a request, the value may change. Users of the connection * record should not rely upon it staying the same between calls that invole diff --git a/include/util_time.h b/include/util_time.h index 16706cff86..e4e02dbbfd 100644 --- a/include/util_time.h +++ b/include/util_time.h @@ -45,6 +45,8 @@ extern "C" { #define AP_CTIME_OPTION_NONE 0x0 /* Add sub second timestamps with micro second resolution */ #define AP_CTIME_OPTION_USEC 0x1 +/* Use more compact ISO 8601 format */ +#define AP_CTIME_OPTION_COMPACT 0x2 /** diff --git a/modules/loggers/mod_log_config.c b/modules/loggers/mod_log_config.c index 7f0085f030..54acb20021 100644 --- a/modules/loggers/mod_log_config.c +++ b/modules/loggers/mod_log_config.c @@ -116,9 +116,11 @@ * '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.) -* + * (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 @@ -369,6 +371,15 @@ 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); @@ -1613,6 +1624,7 @@ static int log_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) 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); diff --git a/server/config.c b/server/config.c index ea54280ea4..dc9b3b64a4 100644 --- a/server/config.c +++ b/server/config.c @@ -747,7 +747,7 @@ AP_DECLARE(const char *) ap_setup_prelinked_modules(process_rec *process) ap_loaded_modules = (module **)apr_palloc(process->pool, sizeof(module *) * conf_vector_length); if (!ap_module_short_names) - ap_module_short_names = malloc(sizeof(char *) * conf_vector_length); + ap_module_short_names = calloc(sizeof(char *), conf_vector_length); if (ap_loaded_modules == NULL || ap_module_short_names == NULL) { return "Ouch! Out of memory in ap_setup_prelinked_modules()!"; @@ -780,8 +780,8 @@ AP_DECLARE(const char *) ap_find_module_name(module *m) AP_DECLARE(const char *) ap_find_module_short_name(int module_index) { - if (module_index < 0) - return "-"; + if (module_index < 0 || module_index >= conf_vector_length) + return NULL; return ap_module_short_names[module_index]; } diff --git a/server/core.c b/server/core.c index 47129cea43..25f307f5b0 100644 --- a/server/core.c +++ b/server/core.c @@ -42,6 +42,7 @@ #include "util_filter.h" #include "util_ebcdic.h" #include "util_mutex.h" +#include "util_time.h" #include "mpm_common.h" #include "scoreboard.h" #include "mod_core.h" @@ -53,6 +54,9 @@ #if defined(RLIMIT_CPU) || defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined(RLIMIT_AS) || defined (RLIMIT_NPROC) #include "unixd.h" #endif +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif /* LimitRequestBody handling */ #define AP_LIMIT_REQ_BODY_UNSET ((apr_off_t) -1) @@ -460,6 +464,15 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv) if (virt->gprof_dir) conf->gprof_dir = virt->gprof_dir; + if (virt->error_log_format) + conf->error_log_format = virt->error_log_format; + + if (virt->error_log_conn) + conf->error_log_conn = virt->error_log_conn; + + if (virt->error_log_req) + conf->error_log_req = virt->error_log_req; + return conf; } @@ -3100,6 +3113,229 @@ static const char *set_trace_enable(cmd_parms *cmd, void *dummy, return NULL; } +static apr_hash_t *errorlog_hash; + +static int log_constant_item(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + char *end = apr_cpystrn(buf, arg, buflen); + return end - buf; +} + +static char *parse_errorlog_misc_string(apr_pool_t *p, + ap_errorlog_format_item *it, + const char **sa) +{ + const char *s; + char scratch[MAX_STRING_LEN]; + char *d = scratch; + /* + * non-leading white space terminates this string to allow the next field + * separator to be inserted + */ + int at_start = 1; + + it->func = log_constant_item; + s = *sa; + + while (*s && *s != '%' && (*s != ' ' || at_start) && d < scratch + MAX_STRING_LEN) { + if (*s != '\\') { + if (*s != ' ') { + at_start = 0; + } + *d++ = *s++; + } + else { + s++; + switch (*s) { + case 'r': + *d++ = '\r'; + s++; + break; + case 'n': + *d++ = '\n'; + s++; + break; + case 't': + *d++ = '\t'; + s++; + break; + case '\0': + /* handle end of string */ + *d++ = '\\'; + break; + default: + /* copy next char verbatim */ + *d++ = *s++; + break; + } + } + } + *d = '\0'; + it->arg = apr_pstrdup(p, scratch); + + *sa = s; + return NULL; +} + +static char *parse_errorlog_item(apr_pool_t *p, ap_errorlog_format_item *it, + const char **sa) +{ + const char *s = *sa; + ap_errorlog_handler *handler; + + if (*s != '%') { + if (*s == ' ') { + it->flags |= AP_ERRORLOG_FLAG_FIELD_SEP; + } + return parse_errorlog_misc_string(p, it, sa); + } + + ++s; + + if (*s == ' ') { + /* percent-space (% ) is a field separator */ + it->flags |= AP_ERRORLOG_FLAG_FIELD_SEP; + *sa = ++s; + /* recurse */ + return parse_errorlog_item(p, it, sa); + } + else if (*s == '%') { + it->arg = "%"; + it->func = log_constant_item; + *sa = ++s; + return NULL; + } + + while (*s) { + switch (*s) { + case '{': + ++s; + it->arg = ap_getword(p, &s, '}'); + break; + case '+': + ++s; + it->flags |= AP_ERRORLOG_FLAG_REQUIRED; + break; + case '-': + ++s; + it->flags |= AP_ERRORLOG_FLAG_NULL_AS_HYPHEN; + break; + case 'M': + it->func = NULL; + it->flags |= AP_ERRORLOG_FLAG_MESSAGE; + *sa = ++s; + return NULL; + default: + handler = (ap_errorlog_handler *)apr_hash_get(errorlog_hash, s, 1); + if (!handler) { + char dummy[2]; + + dummy[0] = *s; + dummy[1] = '\0'; + return apr_pstrcat(p, "Unrecognized error log format directive %", + dummy, NULL); + } + it->func = handler->func; + *sa = ++s; + return NULL; + } + } + + return "Ran off end of error log format parsing args to some directive"; +} + +static apr_array_header_t *parse_errorlog_string(apr_pool_t *p, + const char *s, + const char **err, + int want_msg_fmt) +{ + apr_array_header_t *a = apr_array_make(p, 30, + sizeof(ap_errorlog_format_item)); + char *res; + int seen_msg_fmt = 0; + + while (s && *s) { + ap_errorlog_format_item *item = + (ap_errorlog_format_item *)apr_array_push(a); + memset(item, 0, sizeof(*item)); + res = parse_errorlog_item(p, item, &s); + if (res) { + *err = res; + return NULL; + } + if (item->flags & AP_ERRORLOG_FLAG_MESSAGE) { + if (!want_msg_fmt) { + *err = "%M cannot be used in once-per-request or " + "once-per-connection formats"; + return NULL; + } + seen_msg_fmt = 1; + } + } + + if (want_msg_fmt && !seen_msg_fmt) { + *err = "main ErrorLogFormat must contain message format string '%M'"; + return NULL; + } + + return a; +} + +static const char *set_errorlog_format(cmd_parms *cmd, void *dummy, + const char *arg1, const char *arg2) +{ + const char *err_string = NULL; + core_server_config *conf = ap_get_module_config(cmd->server->module_config, + &core_module); + + if (!arg2) { + conf->error_log_format = parse_errorlog_string(cmd->pool, arg1, + &err_string, 1); + } + else if (!strcasecmp(arg1, "connection")) { + if (!conf->error_log_conn) { + conf->error_log_conn = apr_array_make(cmd->pool, 5, + sizeof(apr_array_header_t *)); + } + + if (arg2 && *arg2) { + apr_array_header_t **e; + e = (apr_array_header_t **) apr_array_push(conf->error_log_conn); + *e = parse_errorlog_string(cmd->pool, arg2, &err_string, 0); + } + } + else if (!strcasecmp(arg1, "request")) { + if (!conf->error_log_req) { + conf->error_log_req = apr_array_make(cmd->pool, 5, + sizeof(apr_array_header_t *)); + } + + if (arg2 && *arg2) { + apr_array_header_t **e; + e = (apr_array_header_t **) apr_array_push(conf->error_log_req); + *e = parse_errorlog_string(cmd->pool, arg2, &err_string, 0); + } + } + else { + err_string = "ErrorLogFormat type must be one of request, connection"; + } + + return err_string; +} + +AP_DECLARE(void) ap_register_errorlog_handler(apr_pool_t *p, char *tag, + ap_errorlog_handler_fn_t *handler, + int flags) +{ + ap_errorlog_handler *log_struct = apr_palloc(p, sizeof(*log_struct)); + log_struct->func = handler; + log_struct->flags = flags; + + apr_hash_set(errorlog_hash, tag, 1, (const void *)log_struct); +} + + /* Note --- ErrorDocument will now work from .htaccess files. * The AllowOverride of Fileinfo allows webmasters to turn it off */ @@ -3199,6 +3435,8 @@ AP_INIT_TAKE1("ServerRoot", set_server_root, NULL, RSRC_CONF | EXEC_ON_READ, AP_INIT_TAKE1("ErrorLog", set_server_string_slot, (void *)APR_OFFSETOF(server_rec, error_fname), RSRC_CONF, "The filename of the error log"), +AP_INIT_TAKE12("ErrorLogFormat", set_errorlog_format, NULL, RSRC_CONF, + "Format string for the ErrorLog"), AP_INIT_RAW_ARGS("ServerAlias", set_server_alias, NULL, RSRC_CONF, "A name or names alternately used to access the server"), AP_INIT_TAKE1("ServerPath", set_serverpath, NULL, RSRC_CONF, @@ -3632,6 +3870,9 @@ AP_DECLARE(int) ap_sys_privileges_handlers(int inc) static int core_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) { ap_mutex_init(pconf); + + ap_register_builtin_errorlog_handlers(pconf); + return APR_SUCCESS; } @@ -3843,6 +4084,8 @@ static int core_pre_connection(conn_rec *c, void *csd) static void register_hooks(apr_pool_t *p) { + errorlog_hash = apr_hash_make(p); + /* create_connection and pre_connection should always be hooked * APR_HOOK_REALLY_LAST by core to give other modules the opportunity * to install alternate network transports and stop other functions diff --git a/server/log.c b/server/log.c index a5ba651b59..42d711744a 100644 --- a/server/log.c +++ b/server/log.c @@ -29,6 +29,7 @@ #include "apr_lib.h" #include "apr_signal.h" #include "apr_portable.h" +#include "apr_base64.h" #define APR_WANT_STDIO #define APR_WANT_STRFUNC @@ -378,7 +379,7 @@ static int open_error_log(server_rec *s, int is_main, apr_pool_t *p) cmdtype = APR_SHELLCMD_ENV; ++fname; } - + /* Spawn a new child logger. If this is the main server_rec, * the new child must use a dummy stderr since the current * stderr might be a pipe to the old logger. Otherwise, the @@ -546,106 +547,132 @@ AP_DECLARE(void) ap_error_log2stderr(server_rec *s) { } } -static void log_error_core(const char *file, int line, int module_index, - int level, - apr_status_t status, const server_rec *s, - const conn_rec *c, - const request_rec *r, apr_pool_t *pool, - const char *fmt, va_list args) +static int cpystrn(char *buf, const char *arg, int buflen) { - char errstr[MAX_STRING_LEN]; -#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED - char scratch[MAX_STRING_LEN]; -#endif - apr_size_t len, errstrlen; - apr_file_t *logf = NULL; - const char *referer; - int level_and_mask = level & APLOG_LEVELMASK; + char *end; + if (!arg) + return 0; + end = apr_cpystrn(buf, arg, buflen); + return end - buf; +} - if (r && r->connection) { - c = r->connection; - } - if (s == NULL) { - /* - * If we are doing stderr logging (startup), don't log messages that are - * above the default server log level unless it is a startup/shutdown - * notice - */ -#ifndef DEBUG - if ((level_and_mask != APLOG_NOTICE) - && (level_and_mask > ap_default_loglevel)) { - return; - } -#endif +static int log_remote_address(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->c) + return apr_snprintf(buf, buflen, "%s:%d", info->c->remote_ip, + info->c->remote_addr->port); + else + return 0; +} - logf = stderr_log; +static int log_local_address(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->c) + return apr_snprintf(buf, buflen, "%s:%d", info->c->local_ip, + info->c->local_addr->port); + else + return 0; +} + +static int log_pid(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + pid_t pid = getpid(); + return apr_snprintf(buf, buflen, "%" APR_PID_T_FMT, pid); +} + +static int log_tid(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ +#if APR_HAS_THREADS + int result; + + if (ap_mpm_query(AP_MPMQ_IS_THREADED, &result) == APR_SUCCESS + && result != AP_MPMQ_NOT_SUPPORTED) + { + apr_os_thread_t tid = apr_os_thread_current(); + return apr_snprintf(buf, buflen, "%pT", &tid); } - else { - int configured_level = r ? ap_get_request_module_loglevel(r, module_index) : - c ? ap_get_conn_server_module_loglevel(c, s, module_index) : - ap_get_server_module_loglevel(s, module_index); - if (s->error_log) { - /* - * If we are doing normal logging, don't log messages that are - * above the module's log level unless it is a startup/shutdown notice - */ - if ((level_and_mask != APLOG_NOTICE) - && (level_and_mask > configured_level)) { - return; - } +#endif + return 0; +} - logf = s->error_log; - } - else { - /* - * If we are doing syslog logging, don't log messages that are - * above the module's log level (including a startup/shutdown notice) - */ - if (level_and_mask > configured_level) { - return; - } +static int log_ctime(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + int time_len = buflen; + int option = AP_CTIME_OPTION_NONE; + + while(arg && *arg) { + switch (*arg) { + case 'u': option |= AP_CTIME_OPTION_USEC; + break; + case 'c': option |= AP_CTIME_OPTION_COMPACT; + break; } + arg++; } - if (logf && ((level & APLOG_STARTUP) != APLOG_STARTUP)) { - int time_len; + ap_recent_ctime_ex(buf, apr_time_now(), option, &time_len); - errstr[0] = '['; - len = 1; - time_len = MAX_STRING_LEN - len; - ap_recent_ctime_ex(errstr + len, apr_time_now(), - AP_CTIME_OPTION_USEC, &time_len); - len += time_len -1; - errstr[len++] = ']'; - errstr[len++] = ' '; - } else { - len = 0; - } - - if ((level & APLOG_STARTUP) != APLOG_STARTUP) { - len += apr_snprintf(errstr + len, MAX_STRING_LEN - len, - "[%s] ", priorities[level_and_mask].t_name); + /* ap_recent_ctime_ex includes the trailing \0 in time_len */ + return time_len - 1; +} - len += apr_snprintf(errstr + len, MAX_STRING_LEN - len, - "[pid %" APR_PID_T_FMT, getpid()); -#if APR_HAS_THREADS - { - int result; +static int log_loglevel(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->level < 0) + return 0; + else + return cpystrn(buf, priorities[info->level].t_name, buflen); +} - if (ap_mpm_query(AP_MPMQ_IS_THREADED, &result) == APR_SUCCESS - && result != AP_MPMQ_NOT_SUPPORTED) { - apr_os_thread_t tid = apr_os_thread_current(); - len += apr_snprintf(errstr + len, MAX_STRING_LEN - len, - ":tid %pT", &tid); - } +static int log_log_id(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + /* + * C: log conn log_id if available, + * c: log conn log id if available and not a once-per-request log line + * else: log request log id if available + */ + if (arg && !strcasecmp(arg, "c")) { + if (info->c && (*arg != 'C' || !info->r)) { + return cpystrn(buf, info->c->log_id, buflen); } -#endif - errstr[len++] = ']'; - errstr[len++] = ' '; } + else if (info->r) { + return cpystrn(buf, info->r->log_id, buflen); + } + return 0; +} + +static int log_keepalives(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (!info->c) + return 0; + + return apr_snprintf(buf, buflen, "%d", info->c->keepalives); +} - if (file && level_and_mask >= APLOG_DEBUG) { +static int log_module_name(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + return cpystrn(buf, ap_find_module_short_name(info->module_index), buflen); +} + +static int log_file_line(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->file == NULL) { + return 0; + } + else { + const char *file = info->file; #if defined(_OSD_POSIX) || defined(WIN32) || defined(__MVS__) char tmp[256]; char *e = strrchr(file, '/'); @@ -677,75 +704,303 @@ static void log_error_core(const char *file, int line, int module_index, file = p + 1; } #endif /*_OSD_POSIX || WIN32 */ - len += apr_snprintf(errstr + len, MAX_STRING_LEN - len, - "%s(%d): ", file, line); + return apr_snprintf(buf, buflen, "%s(%d)", file, info->line); + } +} + +static int log_apr_status(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + apr_status_t status = info->status; + int len = 0; + if (!status) + return 0; + + if (status < APR_OS_START_EAIERR) { + len += apr_snprintf(buf + len, buflen - len, + "(%d)", status); + } + else if (status < APR_OS_START_SYSERR) { + len += apr_snprintf(buf + len, buflen - len, + "(EAI %d)", status - APR_OS_START_EAIERR); + } + else if (status < 100000 + APR_OS_START_SYSERR) { + len += apr_snprintf(buf + len, buflen - len, + "(OS %d)", status - APR_OS_START_SYSERR); + } + else { + len += apr_snprintf(buf + len, buflen - len, + "(os 0x%08x)", status - APR_OS_START_SYSERR); } + apr_strerror(status, buf + len, buflen - len); + len += strlen(buf + len); + return len; +} +static int log_header(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + const char *header; + int len = 0; +#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED + char scratch[MAX_STRING_LEN]; +#endif + + if ( info->r && (header = apr_table_get(info->r->headers_in, arg)) +#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED + && ap_escape_errorlog_item(scratch, header, MAX_STRING_LEN) +#endif + ) { + len = cpystrn(buf, +#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED + scratch, +#else + header, +#endif + buflen); + } + return len; +} + + + + +AP_DECLARE(void) ap_register_builtin_errorlog_handlers(apr_pool_t *p) +{ + ap_register_errorlog_handler(p, "a", log_remote_address, 0); + ap_register_errorlog_handler(p, "A", log_local_address, 0); + ap_register_errorlog_handler(p, "E", log_apr_status, 0); + ap_register_errorlog_handler(p, "F", log_file_line, 0); + ap_register_errorlog_handler(p, "i", log_header, 0); + ap_register_errorlog_handler(p, "k", log_keepalives, 0); + ap_register_errorlog_handler(p, "l", log_loglevel, 0); + ap_register_errorlog_handler(p, "L", log_log_id, 0); + ap_register_errorlog_handler(p, "m", log_module_name, 0); + ap_register_errorlog_handler(p, "P", log_pid, 0); + ap_register_errorlog_handler(p, "t", log_ctime, 0); + ap_register_errorlog_handler(p, "T", log_tid, 0); + + /* XXX: TODO: envvars, notes */ +} + +static void add_log_id(const conn_rec *c, const request_rec *r) +{ + apr_uint64_t id, tmp; + pid_t pid; + int len; + char *encoded; + + if (r && r->request_time) { + id = (apr_uint64_t)r->request_time; + } + else { + id = (apr_uint64_t)apr_time_now(); + } + + pid = getpid(); + if (sizeof(pid_t) > 2) { + tmp = pid; + tmp = tmp << 40; + id ^= tmp; + pid = pid >> 24; + tmp = pid; + tmp = tmp << 56; + id ^= tmp; + } + else { + tmp = pid; + tmp = tmp << 40; + id ^= tmp; + } +#if APR_HAS_THREADS if (c) { - /* XXX: TODO: add a method of selecting whether logged remote - * addresses are in dotted quad or resolved form... dotted - * quad is the most secure, which is why I'm implementing it - * first. -djg - */ + apr_uintptr_t tmp2 = (apr_uintptr_t)c->current_thread; + tmp = tmp2; + tmp = tmp << 32; + id ^= tmp; + } +#endif + + /* + * The apr-util docs wrongly states encoded strings are not 0-terminated. + * Let's be save and allocate an additional byte. + */ + len = 1 + apr_base64_encode_len(sizeof(id)); + encoded = apr_palloc(r ? r->pool : c->pool, len); + apr_base64_encode(encoded, (char *)&id, sizeof(id)); + encoded[11] = '\0'; /* omit last char which is always '=' */ + + /* need to cast const away */ + if (r) { + ((request_rec *)r)->log_id = encoded; + } + else { + ((conn_rec *)c)->log_id = encoded; + } +} + +/* + * This is used if no error log format is defined and during startup. + * It automatically omits the timestamp if logging to syslog. + */ +static int do_errorlog_default(const ap_errorlog_info *info, char *buf, + int buflen, int *errstr_start, int *errstr_end, + const char *errstr_fmt, va_list args) +{ + int len = 0; + int field_start = 0; + int item_len; +#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED + char scratch[MAX_STRING_LEN]; +#endif + + if (!info->using_syslog && !info->startup) { + buf[len++] = '['; + len += log_ctime(info, "u", buf + len, buflen - len); + buf[len++] = ']'; + buf[len++] = ' '; + } + + if (!info->startup) { + buf[len++] = '['; + len += log_module_name(info, NULL, buf + len, buflen - len); + buf[len++] = ':'; + len += log_loglevel(info, NULL, buf + len, buflen - len); + len += cpystrn(buf + len, "] [pid ", buflen - len); + + len += log_pid(info, NULL, buf + len, buflen - len); +#if APR_HAS_THREADS + field_start = len; + len += cpystrn(buf + len, ":tid ", buflen - len); + item_len = log_tid(info, NULL, buf + len, buflen - len); + if (!item_len) + len = field_start; + else + len += item_len; +#endif + buf[len++] = ']'; + buf[len++] = ' '; + } + + if (info->level >= APLOG_DEBUG) { + item_len = log_file_line(info, NULL, buf + len, buflen - len); + if (item_len) { + len += item_len; + len += cpystrn(buf + len, ": ", buflen - len); + } + } + + if (info->status) { + item_len = log_apr_status(info, NULL, buf + len, buflen - len); + if (item_len) { + len += item_len; + len += cpystrn(buf + len, ": ", buflen - len); + } + } + + if (info->c) { /* * remote_ip can be client or backend server. If we have a scoreboard * handle, it is likely a client. */ - len += apr_snprintf(errstr + len, MAX_STRING_LEN - len, - c->sbh ? "[client %s:%d] " : "[remote %s:%d] ", - c->remote_ip, c->remote_addr->port); - } - if (status != 0) { - if (status < APR_OS_START_EAIERR) { - len += apr_snprintf(errstr + len, MAX_STRING_LEN - len, - "(%d)", status); - } - else if (status < APR_OS_START_SYSERR) { - len += apr_snprintf(errstr + len, MAX_STRING_LEN - len, - "(EAI %d)", status - APR_OS_START_EAIERR); - } - else if (status < 100000 + APR_OS_START_SYSERR) { - len += apr_snprintf(errstr + len, MAX_STRING_LEN - len, - "(OS %d)", status - APR_OS_START_SYSERR); - } - else { - len += apr_snprintf(errstr + len, MAX_STRING_LEN - len, - "(os 0x%08x)", status - APR_OS_START_SYSERR); - } - apr_strerror(status, errstr + len, MAX_STRING_LEN - len); - len += strlen(errstr + len); - if (MAX_STRING_LEN - len > 2) { - errstr[len++] = ':'; - errstr[len++] = ' '; - errstr[len] = '\0'; - } + len += apr_snprintf(buf + len, buflen - len, + info->c->sbh ? "[client %s:%d] " : "[remote %s:%d] ", + info->c->remote_ip, info->c->remote_addr->port); } - errstrlen = len; + /* the actual error message */ + *errstr_start = len; #ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED - if (apr_vsnprintf(scratch, MAX_STRING_LEN - len, fmt, args)) { - len += ap_escape_errorlog_item(errstr + len, scratch, - MAX_STRING_LEN - len); + if (apr_vsnprintf(scratch, MAX_STRING_LEN, errstr_fmt, args)) { + len += ap_escape_errorlog_item(buf + len, scratch, + buflen - len); + } #else - len += apr_vsnprintf(errstr + len, MAX_STRING_LEN - len, fmt, args); + len += apr_vsnprintf(buf + len, buflen - len, errstr_fmt, args); #endif + *errstr_end = len; + + field_start = len; + len += cpystrn(buf + len, ", referer: ", buflen - len); + item_len = log_header(info, "Referer", buf + len, buflen - len); + if (item_len) + len += item_len; + else + len = field_start; + + return len; +} - if ( r && (referer = apr_table_get(r->headers_in, "Referer")) +static int do_errorlog_format(apr_array_header_t *fmt, ap_errorlog_info *info, + char *buf, int buflen, int *errstr_start, + int *errstr_end, const char *err_fmt, va_list args) +{ #ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED - && ap_escape_errorlog_item(scratch, referer, MAX_STRING_LEN - len) + char scratch[MAX_STRING_LEN]; #endif - ) { - len += apr_snprintf(errstr + len, MAX_STRING_LEN - len, - ", referer: %s", + int i; + int len = 0; + int field_start = 0; + int skipping = 0; + ap_errorlog_format_item *items = (ap_errorlog_format_item *)fmt->elts; + + for (i = 0; i < fmt->nelts; ++i) { + ap_errorlog_format_item *item = &items[i]; + if (item->flags & AP_ERRORLOG_FLAG_FIELD_SEP) { + if (skipping) { + skipping = 0; + } + else { + field_start = len; + } + } + + if (item->flags & AP_ERRORLOG_FLAG_MESSAGE) { + /* the actual error message */ + *errstr_start = len; #ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED - scratch + if (apr_vsnprintf(scratch, MAX_STRING_LEN, err_fmt, args)) { + len += ap_escape_errorlog_item(buf + len, scratch, + buflen - len); + + } #else - referer + len += apr_vsnprintf(buf + len, buflen - len, err_fmt, args); #endif - ); + *errstr_end = len; + } + else if (skipping) { + continue; + } + else { + int item_len = (*item->func)(info, item->arg, buf + len, + buflen - len); + if (!item_len) { + if (item->flags & AP_ERRORLOG_FLAG_REQUIRED) { + /* required item is empty. skip whole line */ + buf[0] = '\0'; + return 0; + } + else if (item->flags & AP_ERRORLOG_FLAG_NULL_AS_HYPHEN) { + buf[len++] = '-'; + } + else { + len = field_start; + skipping = 1; + } + } + else { + len += item_len; + } + } } + return len; +} +static void write_logline(char *errstr, apr_size_t len, apr_file_t *logf, + int level) +{ /* NULL if we are logging to syslog */ if (logf) { /* Truncate for the terminator (as apr_snprintf does) */ @@ -758,13 +1013,193 @@ static void log_error_core(const char *file, int line, int module_index, } #ifdef HAVE_SYSLOG else { - syslog(level_and_mask < LOG_PRIMASK ? level_and_mask : APLOG_DEBUG, - "%s", errstr); + syslog(level < LOG_PRIMASK ? level : APLOG_DEBUG, "%s", errstr); } #endif +} - ap_run_error_log(file, line, module_index, level, status, s, r, pool, - errstr + errstrlen); +static void log_error_core(const char *file, int line, int module_index, + int level, + apr_status_t status, const server_rec *s, + const conn_rec *c, + const request_rec *r, apr_pool_t *pool, + const char *fmt, va_list args) +{ + char errstr[MAX_STRING_LEN]; + apr_file_t *logf = NULL; + int level_and_mask = level & APLOG_LEVELMASK; + const request_rec *rmain = NULL; + core_server_config *sconf = NULL; + ap_errorlog_info info; + + /* do we need to log once-per-req or once-per-conn info? */ + int log_conn_info = 0, log_req_info = 0; + apr_array_header_t **lines = NULL; + int done = 0; + int line_number = 0; + + if (r && r->connection) { + c = r->connection; + } + + if (s == NULL) { + /* + * If we are doing stderr logging (startup), don't log messages that are + * above the default server log level unless it is a startup/shutdown + * notice + */ +#ifndef DEBUG + if ((level_and_mask != APLOG_NOTICE) + && (level_and_mask > ap_default_loglevel)) { + return; + } +#endif + + logf = stderr_log; + } + else { + int configured_level = r ? ap_get_request_module_loglevel(r, module_index) : + c ? ap_get_conn_server_module_loglevel(c, s, module_index) : + ap_get_server_module_loglevel(s, module_index); + if (s->error_log) { + /* + * If we are doing normal logging, don't log messages that are + * above the module's log level unless it is a startup/shutdown notice + */ + if ((level_and_mask != APLOG_NOTICE) + && (level_and_mask > configured_level)) { + return; + } + + logf = s->error_log; + } + else { + /* + * If we are doing syslog logging, don't log messages that are + * above the module's log level (including a startup/shutdown notice) + */ + if (level_and_mask > configured_level) { + return; + } + } + + sconf = ap_get_module_config(s->module_config, &core_module); + if (c && !c->log_id) { + add_log_id(c, NULL); + if (sconf->error_log_conn && sconf->error_log_conn->nelts > 0) + log_conn_info = 1; + } + if (r) { + if (r->main) + rmain = r->main; + else + rmain = r; + + if (!rmain->log_id) { + /* XXX: do we need separate log ids for subrequests? */ + if (sconf->error_log_req && sconf->error_log_req->nelts > 0) + log_req_info = 1; + /* + * XXX: potential optimization: only create log id if %L is + * XXX: actually used + */ + add_log_id(c, rmain); + } + } + } + + info.s = s; + info.c = c; + info.file = file; + info.line = line; + info.status = status; + info.using_syslog = (logf == NULL); + info.startup = ((level & APLOG_STARTUP) == APLOG_STARTUP); + + + while (!done) { + apr_array_header_t *log_format; + int len = 0, errstr_start = 0, errstr_end = 0; + /* XXX: potential optimization: format common prefixes only once */ + if (log_conn_info) { + /* once-per-connection info */ + if (line_number == 0) { + lines = (apr_array_header_t **)sconf->error_log_conn->elts; + info.r = NULL; + info.rmain = NULL; + info.level = -1; + info.module_index = APLOG_NO_MODULE; + } + + log_format = lines[line_number++]; + + if (line_number == sconf->error_log_conn->nelts) { + /* this is the last line of once-per-connection info */ + line_number = 0; + log_conn_info = 0; + } + } + else if (log_req_info) { + /* once-per-request info */ + if (line_number == 0) { + lines = (apr_array_header_t **)sconf->error_log_req->elts; + info.r = rmain; + info.rmain = rmain; + info.level = -1; + info.module_index = APLOG_NO_MODULE; + } + + log_format = lines[line_number++]; + + if (line_number == sconf->error_log_req->nelts) { + /* this is the last line of once-per-request info */ + line_number = 0; + log_req_info = 0; + } + } + else { + /* the actual error message */ + info.r = r; + info.rmain = rmain; + info.level = level_and_mask; + info.module_index = module_index; + log_format = sconf ? sconf->error_log_format : NULL; + done = 1; + } + + /* + * prepare and log one line + */ + + if (log_format) { + len += do_errorlog_format(log_format, &info, errstr + len, + MAX_STRING_LEN - len, + &errstr_start, &errstr_end, fmt, args); + } + else { + len += do_errorlog_default(&info, errstr + len, MAX_STRING_LEN - len, + &errstr_start, &errstr_end, fmt, args); + } + + if (!*errstr) + { + /* + * Don't log empty lines. This can happen with once-per-conn/req + * info if an item with AP_ERRORLOG_FLAG_REQUIRED is NULL. + */ + continue; + } + write_logline(errstr, len, logf, level_and_mask); + + if (!log_format) { + /* only pass the real error string to the hook */ + errstr[errstr_end] = '\0'; + ap_run_error_log(file, line, module_index, level, status, s, r, pool, + errstr + errstr_start); + } + + *errstr = '\0'; + } } AP_DECLARE(void) ap_log_error_(const char *file, int line, int module_index, diff --git a/server/util_time.c b/server/util_time.c index 4e0df9912e..3632d0d583 100644 --- a/server/util_time.c +++ b/server/util_time.c @@ -22,6 +22,9 @@ * */ #define AP_CTIME_USEC_LENGTH 7 +/* Length of ISO 8601 date/time */ +#define AP_CTIME_COMPACT_LEN 20 + /* Cache for exploded values of recent timestamps */ @@ -169,7 +172,11 @@ AP_DECLARE(apr_status_t) ap_recent_ctime_ex(char *date_str, apr_time_t t, /* Calculate the needed buffer length */ - needed = APR_CTIME_LEN; + if (option & AP_CTIME_OPTION_COMPACT) + needed = AP_CTIME_COMPACT_LEN; + else + needed = APR_CTIME_LEN; + if (option & AP_CTIME_OPTION_USEC) { needed += AP_CTIME_USEC_LENGTH; } @@ -187,18 +194,34 @@ AP_DECLARE(apr_status_t) ap_recent_ctime_ex(char *date_str, apr_time_t t, /* example without options: "Wed Jun 30 21:49:08 1993" */ /* 123456789012345678901234 */ + /* example for compact format: "1993-06-30 21:49:08" */ + /* 1234567890123456789 */ ap_explode_recent_localtime(&xt, t); - s = &apr_day_snames[xt.tm_wday][0]; - *date_str++ = *s++; - *date_str++ = *s++; - *date_str++ = *s++; - *date_str++ = ' '; - s = &apr_month_snames[xt.tm_mon][0]; - *date_str++ = *s++; - *date_str++ = *s++; - *date_str++ = *s++; - *date_str++ = ' '; + real_year = 1900 + xt.tm_year; + if (option & AP_CTIME_OPTION_COMPACT) { + int real_month = xt.tm_mon + 1; + *date_str++ = real_year / 1000 + '0'; + *date_str++ = real_year % 1000 / 100 + '0'; + *date_str++ = real_year % 100 / 10 + '0'; + *date_str++ = real_year % 10 + '0'; + *date_str++ = '-'; + *date_str++ = real_month / 10 + '0'; + *date_str++ = real_month % 10 + '0'; + *date_str++ = '-'; + } + else { + s = &apr_day_snames[xt.tm_wday][0]; + *date_str++ = *s++; + *date_str++ = *s++; + *date_str++ = *s++; + *date_str++ = ' '; + s = &apr_month_snames[xt.tm_mon][0]; + *date_str++ = *s++; + *date_str++ = *s++; + *date_str++ = *s++; + *date_str++ = ' '; + } *date_str++ = xt.tm_mday / 10 + '0'; *date_str++ = xt.tm_mday % 10 + '0'; *date_str++ = ' '; @@ -219,12 +242,13 @@ AP_DECLARE(apr_status_t) ap_recent_ctime_ex(char *date_str, apr_time_t t, usec = usec % div; } } - *date_str++ = ' '; - real_year = 1900 + xt.tm_year; - *date_str++ = real_year / 1000 + '0'; - *date_str++ = real_year % 1000 / 100 + '0'; - *date_str++ = real_year % 100 / 10 + '0'; - *date_str++ = real_year % 10 + '0'; + if (!(option & AP_CTIME_OPTION_COMPACT)) { + *date_str++ = ' '; + *date_str++ = real_year / 1000 + '0'; + *date_str++ = real_year % 1000 / 100 + '0'; + *date_str++ = real_year % 100 / 10 + '0'; + *date_str++ = real_year % 10 + '0'; + } *date_str++ = 0; return APR_SUCCESS; |