/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/
/* * http_log.c: Dealing with the logs and errors * * Rob McCool *
*/
#include"apr.h" #include"apr_general.h"/* for signal stuff */ #include"apr_strings.h" #include"apr_errno.h" #include"apr_thread_proc.h" #include"apr_lib.h" #include"apr_signal.h" #include"apr_portable.h" #include"apr_base64.h"
/* track pipe handles to close in child process */ typedefstruct read_handle_t { struct read_handle_t *next;
apr_file_t *handle;
} read_handle_t;
static read_handle_t *read_handles;
/** * @brief The piped logging structure. * * Piped logs are used to move functionality out of the main server. * For example, log rotation is done with piped logs.
*/ struct piped_log { /** The pool to use for the piped log */
apr_pool_t *p; /** The pipe between the server and the logging process */
apr_file_t *read_fd, *write_fd; #ifdef AP_HAVE_RELIABLE_PIPED_LOGS /** The name of the program the logging process is running */ char *program; /** The pid of the logging process */
apr_proc_t *pid; /** How to reinvoke program when it must be replaced */
apr_cmdtype_e cmdtype; #endif
};
/* remember to close this handle in the child process * * On Win32 this makes zero sense, because we don't * take the parent process's child procs. * If the win32 parent instead passed each and every * logger write handle from itself down to the child, * and the parent manages all aspects of keeping the * reliable pipe log children alive, this would still * make no sense :) Cripple it on Win32.
*/ staticvoid close_handle_in_child(apr_pool_t *p, apr_file_t *f)
{ #ifndef WIN32
read_handle_t *new_handle;
AP_DECLARE(apr_status_t) ap_replace_stderr_log(apr_pool_t *p, constchar *fname)
{
apr_file_t *stderr_file;
apr_status_t rc; char *filename = ap_server_root_relative(p, fname); if (!filename) {
ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT,
APR_EBADPATH, ap_server_conf, APLOGNO(00085) "Invalid -E error log file %s",
fname); return APR_EBADPATH;
} if ((rc = apr_file_open(&stderr_file, filename,
APR_APPEND | APR_WRITE | APR_CREATE | APR_LARGEFILE,
APR_OS_DEFAULT, p)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, ap_server_conf, APLOGNO(00086) "%s: could not open error log file %s.",
ap_server_argv0, fname); return rc;
} if (!stderr_pool) { /* This is safe provided we revert it when we are finished. * We don't manager the callers pool!
*/
stderr_pool = p;
} if ((rc = apr_file_open_stderr(&stderr_log, stderr_pool))
== APR_SUCCESS) {
apr_file_flush(stderr_log); if ((rc = apr_file_dup2(stderr_log, stderr_file, stderr_pool))
== APR_SUCCESS) {
apr_file_close(stderr_file); /* * You might ponder why stderr_pool should survive? * The trouble is, stderr_pool may have s_main->error_log, * so we aren't in a position to destroy stderr_pool until * the next recycle. There's also an apparent bug which * is not; if some folk decided to call this function before * the core open error logs hook, this pool won't survive. * Neither does the stderr logger, so this isn't a problem.
*/
}
} /* Revert, see above */ if (stderr_pool == p)
stderr_pool = NULL;
if (rc != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_CRIT, rc, NULL, APLOGNO(00087) "unable to replace stderr with error log file");
} return rc;
}
/* Create a child process running PROGNAME with a pipe connected to * the child's stdin. The write-end of the pipe will be placed in * *FPIN on successful return. If dummy_stderr is non-zero, the * stderr for the child will be the same as the stdout of the parent.
* Otherwise the child will inherit the stderr from the parent. */ staticint log_child(apr_pool_t *p, constchar *progname,
apr_file_t **fpin, apr_cmdtype_e cmdtype, int dummy_stderr)
{ /* Child process code for 'ErrorLog "|..."'; * may want a common framework for this, since I expect it will * be common for other foo-loggers to want this sort of thing...
*/
apr_status_t rc;
apr_procattr_t *procattr;
apr_proc_t *procnew;
apr_file_t *errfile;
if (rc == APR_SUCCESS) {
apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
(*fpin) = procnew->in; /* read handle to pipe not kept open, so no need to call * close_handle_in_child()
*/
}
}
return rc;
}
/* Open the error log for the given server_rec. If IS_MAIN is
* non-zero, s is the main server. */ staticint open_error_log(server_rec *s, int is_main, apr_pool_t *p)
{ constchar *fname; int rc;
/* In 2.4 favor PROGRAM_ENV, accept "||prog" syntax for compatibility * and "|$cmd" to override the default. * Any 2.2 backport would continue to favor SHELLCMD_ENV so there * accept "||prog" to override, and "|$cmd" to ease conversion.
*/ if (*fname == '|')
++fname; if (*fname == '$') {
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
* child inherits the parents stderr. */
rc = log_child(p, fname, &dummy, cmdtype, is_main); if (rc != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, ap_server_conf, APLOGNO(00089) "Couldn't start ErrorLog process '%s'.",
s->error_fname + 1); return DONE;
}
s->error_log = dummy;
}
#ifdef HAVE_SYSLOG elseif (strcmp(s->error_fname, "syslog") == 0
|| strncmp(s->error_fname, "syslog:", 7) == 0) { if ((fname = strchr(s->error_fname, ':'))) { /* s->error_fname could be [level]:[tag] (see #60525) */ constchar *tag;
apr_size_t flen; const TRANS *fac;
fname++;
tag = ap_strchr_c(fname, ':'); if (tag) {
flen = tag - fname;
tag++; if (*tag == '\0') {
tag = ap_server_argv0;
}
} else {
flen = strlen(fname);
tag = ap_server_argv0;
} if (flen == 0) { /* Was something like syslog::foobar */
openlog(tag, LOG_NDELAY|LOG_CONS|LOG_PID, LOG_LOCAL7);
} else { for (fac = facilities; fac->t_name; fac++) { if (!strncasecmp(fname, fac->t_name, flen)) {
openlog(tag, LOG_NDELAY|LOG_CONS|LOG_PID,
fac->t_val);
s->error_log = NULL; return OK;
}
} /* Huh? Invalid level name? */
ap_log_error(APLOG_MARK, APLOG_STARTUP, APR_EBADPATH, NULL, APLOGNO(10036) "%s: could not open syslog error log %s.",
ap_server_argv0, fname); return DONE;
}
} else {
openlog(ap_server_argv0, LOG_NDELAY|LOG_CONS|LOG_PID, LOG_LOCAL7);
}
int ap_open_logs(apr_pool_t *pconf, apr_pool_t *p /* plog */,
apr_pool_t *ptemp, server_rec *s_main)
{
apr_pool_t *stderr_p;
server_rec *virt, *q; int replace_stderr;
/* Register to throw away the read_handles list when we * cleanup plog. Upon fork() for the apache children, * this read_handles list is closed so only the parent * can relaunch a lost log child. These read handles * are always closed on exec. * We won't care what happens to our stderr log child * between log phases, so we don't mind losing stderr's * read_handle a little bit early.
*/
apr_pool_cleanup_register(p, &read_handles, ap_pool_cleanup_set_null,
apr_pool_cleanup_null);
/* HERE we need a stdout log that outlives plog. * We *presume* the parent of plog is a process * or global pool which spans server restarts. * Create our stderr_pool as a child of the plog's * parent pool.
*/
apr_pool_create(&stderr_p, apr_pool_parent_get(p));
apr_pool_tag(stderr_p, "stderr_pool");
if (open_error_log(s_main, 1, stderr_p) != OK) { return DONE;
}
replace_stderr = 1; if (s_main->error_log) {
apr_status_t rv;
/* Replace existing stderr with new log. */
apr_file_flush(s_main->error_log);
rv = apr_file_dup2(stderr_log, s_main->error_log, stderr_p); if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s_main, APLOGNO(00092) "unable to replace stderr with error_log");
} else { /* We are done with stderr_pool, close it, killing * the previous generation's stderr logger
*/ if (stderr_pool)
apr_pool_destroy(stderr_pool);
stderr_pool = stderr_p;
replace_stderr = 0; /* * Now that we have dup'ed s_main->error_log to stderr_log * close it and set s_main->error_log to stderr_log. This avoids * this fd being inherited by the next piped logger who would * keep open the writing end of the pipe that this one uses * as stdin. This in turn would prevent the piped logger from * exiting.
*/
apr_file_close(s_main->error_log);
s_main->error_log = stderr_log;
}
} /* note that stderr may still need to be replaced with something * because it points to the old error log, or back to the tty * of the submitter. * XXX: This is BS - /dev/null is non-portable * errno-as-apr_status_t is also non-portable
*/
/* In OSD/POSIX, the compiler returns for __FILE__ * a string like: __FILE__="*POSIX(/usr/include/stdio.h)" * (it even returns an absolute path for sources in * the current directory). Here we try to strip this * down to the basename.
*/ if (e != NULL && e[1] != '\0') {
apr_snprintf(tmp, sizeof(tmp), "%s", &e[1]);
e = &tmp[strlen(tmp)-1]; if (*e == ')') {
*e = '\0';
}
file = tmp;
} #else/* _OSD_POSIX || WIN32 */ constchar *p; /* On Unix, __FILE__ may be an absolute path in a
* VPATH build. */ if (file[0] == '/' && (p = ap_strrchr_c(file, '/')) != NULL) {
file = p + 1;
} #endif/*_OSD_POSIX || WIN32 */ return apr_snprintf(buf, buflen, "%s(%d)", file, info->line);
}
}
staticint log_apr_status(const ap_errorlog_info *info, constchar *arg, char *buf, int buflen)
{
apr_status_t status = info->status; int len; if (!status) return 0;
if (status < APR_OS_START_EAIERR) {
len = apr_snprintf(buf, buflen, "(%d)", status);
} elseif (status < APR_OS_START_SYSERR) {
len = apr_snprintf(buf, buflen, "(EAI %d)",
status - APR_OS_START_EAIERR);
} elseif (status < 100000 + APR_OS_START_SYSERR) {
len = apr_snprintf(buf, buflen, "(OS %d)",
status - APR_OS_START_SYSERR);
} else {
len = apr_snprintf(buf, buflen, "(os 0x%08x)",
status - APR_OS_START_SYSERR);
}
apr_strerror(status, buf + len, buflen - len);
len += strlen(buf + len); return len;
}
staticint log_server_name(const ap_errorlog_info *info, constchar *arg, char *buf, int buflen)
{ if (info->r) return cpystrn(buf, ap_get_server_name((request_rec *)info->r), buflen);
return 0;
}
staticint log_virtual_host(const ap_errorlog_info *info, constchar *arg, char *buf, int buflen)
{ if (info->s) return cpystrn(buf, info->s->server_hostname, buflen);
staticint log_header(const ap_errorlog_info *info, constchar *arg, char *buf, int buflen)
{ if (info->r) return log_table_entry(info->r->headers_in, arg, buf, buflen);
return 0;
}
staticint log_note(const ap_errorlog_info *info, constchar *arg, char *buf, int buflen)
{ /* XXX: maybe escaping the entry is not necessary for notes? */ if (info->r) return log_table_entry(info->r->notes, arg, buf, buflen);
return 0;
}
staticint log_env_var(const ap_errorlog_info *info, constchar *arg, char *buf, int buflen)
{ if (info->r) return log_table_entry(info->r->subprocess_env, arg, buf, buflen);
/* * This is used if no error log format is defined and during startup. * It automatically omits the timestamp if logging to syslog.
*/ staticint do_errorlog_default(const ap_errorlog_info *info, char *buf, int buflen, int *errstr_start, int *errstr_end, constchar *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
/* 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) {
AP_DEBUG_ASSERT(r->connection != NULL);
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;
/* Use the main ErrorLogFormat if any */ if (ap_server_conf) {
sconf = ap_get_core_module_config(ap_server_conf->module_config);
}
} 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;
}
}
/* the faked server_rec from mod_cgid does not have s->module_config */ if (s->module_config) {
sconf = ap_get_core_module_config(s->module_config); 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);
}
}
} elseif (ap_server_conf) { /* Use the main ErrorLogFormat if any */
sconf = ap_get_core_module_config(ap_server_conf->module_config);
}
}
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 (done) { /* * We don't call the error_log hook for per-request/per-conn * lines, and we only pass the actual log message, not the * prefix and suffix.
*/
errstr[errstr_end] = '\0';
ap_run_error_log(&info, errstr + errstr_start);
}
*errstr = '\0';
}
}
/* For internal calls to log_error_core with self-composed arg lists */ staticvoid log_error_va_glue(constchar *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, constchar *fmt, ...)
{
va_list args;
/* * IF APLOG_TOCLIENT is set, * AND the error level is 'warning' or more severe, * AND there isn't already error text associated with this request, * THEN make the message text available to ErrorDocument and * other error processors.
*/
va_end(args);
va_start(args,fmt); if ((level & APLOG_TOCLIENT)
&& ((level & APLOG_LEVELMASK) <= APLOG_WARNING)
&& (apr_table_get(r->notes, "error-notes") == NULL)) {
apr_table_setn(r->notes, "error-notes",
ap_escape_html(r->pool, apr_pvsprintf(r->pool, fmt,
args)));
}
va_end(args);
}
AP_DECLARE(void) ap_log_cserror_(constchar *file, int line, int module_index, int level, apr_status_t status, const conn_rec *c, const server_rec *s, constchar *fmt, ...)
{
va_list args;
AP_DECLARE(void) ap_log_command_line(apr_pool_t *plog, server_rec *s)
{ int i;
process_rec *process = s->process; char *result; int len_needed = 0;
/* Piece together the command line from the pieces * in process->argv, with spaces in between.
*/ for (i = 0; i < process->argc; i++) {
len_needed += strlen(process->argv[i]) + 1;
}
result = (char *) apr_palloc(plog, len_needed);
*result = '\0';
for (i = 0; i < process->argc; i++) {
strcat(result, process->argv[i]); if ((i+1)< process->argc) {
strcat(result, " ");
}
}
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(00094) "Command line: '%s'", result);
}
/* grab bag function to log commonly logged and shared info */
AP_DECLARE(void) ap_log_mpm_common(server_rec *s)
{
ap_log_error(APLOG_MARK, APLOG_DEBUG , 0, s, APLOGNO(02639) "Using SO_REUSEPORT: %s (%d)",
ap_have_so_reuseport ? "yes" : "no",
ap_num_listen_buckets);
}
mypid = getpid(); if (mypid != saved_pid
&& apr_stat(&finfo, fname, APR_FINFO_MTIME, p) == APR_SUCCESS) { /* AP_SIG_GRACEFUL and HUP call this on each restart. * Only warn on first time through for this pid. * * XXX: Could just write first time through too, although * that may screw up scripts written to do something * based on the last modification time of the pid file.
*/
ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, p, APLOGNO(00098) "pid file %s overwritten -- Unclean " "shutdown of previous Apache run?",
fname);
}
temp_fname = apr_pstrcat(p, fname, ".XXXXXX", NULL);
rv = apr_file_mktemp(&pid_file, temp_fname,
APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE, p); if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00099) "could not create %s", temp_fname);
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00100) "%s: could not log pid to file %s",
ap_server_argv0, fname); exit(1);
}
/* If we fill the buffer, we're probably reading a corrupt pid file.
* To be nice, let's also ensure the first char is a digit. */ if (bytes_read == 0 || bytes_read == BUFFER_SIZE - 1 || !apr_isdigit(*buf)) { return APR_EGENERAL;
}
if (status == APR_SUCCESS) {
pl->pid = procnew; /* procnew->in was dup2'd from pl->write_fd; * since the original fd is still valid, close the copy to
* avoid a leak. */
apr_file_close(procnew->in);
procnew->in = NULL;
apr_proc_other_child_register(procnew, piped_log_maintenance, pl,
pl->write_fd, pl->p);
close_handle_in_child(pl->p, pl->read_fd);
} else { /* Something bad happened, give up and go away. */
ap_log_error(APLOG_MARK, APLOG_STARTUP, status, ap_server_conf, APLOGNO(00104) "unable to start piped log program '%s'",
pl->program);
}
}
switch (reason) { case APR_OC_REASON_DEATH: case APR_OC_REASON_LOST:
pl->pid = NULL; /* in case we don't get it going again, this * tells other logic not to try to kill it
*/
apr_proc_other_child_unregister(pl);
rv = ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state); if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00105) "can't query MPM state; not restarting " "piped log program '%s'",
pl->program);
} elseif (mpm_state != AP_MPMQ_STOPPING) {
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00106) "piped log program '%s' failed unexpectedly",
pl->program); if ((rv = piped_log_spawn(pl)) != APR_SUCCESS) { /* what can we do? This could be the error log we're having
* problems opening up... */
ap_log_error(APLOG_MARK, APLOG_STARTUP, rv, NULL, APLOGNO(00107) "piped_log_maintenance: unable to respawn '%s'",
pl->program);
}
} break;
case APR_OC_REASON_UNWRITABLE: /* We should not kill off the pipe here, since it may only be full.
* If it really is locked, we should kill it off manually. */ break;
case APR_OC_REASON_RESTART: if (pl->pid != NULL) {
apr_proc_kill(pl->pid, SIGTERM);
pl->pid = NULL;
} break;
/* In 2.4 favor PROGRAM_ENV, accept "||prog" syntax for compatibility * and "|$cmd" to override the default. * Any 2.2 backport would continue to favor SHELLCMD_ENV so there * accept "||prog" to override, and "|$cmd" to ease conversion.
*/ if (*program == '|')
++program; if (*program == '$') {
cmdtype = APR_SHELLCMD_ENV;
++program;
}
AP_DECLARE(constchar *) ap_parse_log_level(constchar *str, int *val)
{ constchar *err = "Log level keyword must be one of emerg/alert/crit/error/" "warn/notice/info/debug/trace1/.../trace8"; int i = 0;
if (str == NULL) return err;
while (priorities[i].t_name != NULL) { if (!strcasecmp(str, priorities[i].t_name)) {
*val = priorities[i].t_val; return NULL;
}
i++;
} return err;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.