Sophie

Sophie

distrib > Mandriva > 2010.1 > x86_64 > by-pkgid > 9bdf3aeaadef3c352c10bec2ec3fb275 > files > 3

apache-mod_log_rotate-1.00-10mdv2010.1.src.rpm

/* Copyright 1999-2004 The Apache Software Foundation
 *
 * Licensed 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.
 */

/* Adds RotateLogs and supporting directives that allow logs to be rotated by
 * the server without having to pipe them through rotatelogs.
 *
 * RotateLogs On|Off    Enable / disable automatic log rotation. Once enabled
 *                      mod_log_rotate takes responsibility for all log output
 *                      server wide even if RotateLogs Off is subsequently
 *                      used. That means that the BufferedLogs directive that
 *                      is implemented by mod_log_config will be ignored.
 *
 * RotateLogsLocalTime  Normally the log rotation interval is based on UTC.
 *                      For example an interval of 86400 (one day) will cause
 *                      the logs to rotate at UTC 00:00. When this option is
 *                      on, log rotation is timed relative to the local time.
 *
 * RotateInterval       Set the interval in seconds for log rotation. The
 *                      default is 86400 (one day). The shortest interval that
 *                      can be specified is 60 seconds. An optional second
 *                      argument specifies an offset in minutes which is
 *                      applied to UTC (or local time if RotateLogsLocalTime
 *                      is on). For example RotateInterval 86400 60 will
 *                      cause logs to be rotated at 23:00 UTC.
 *
 * The current version of this module is available at:
 *   http://www.hexten.net/sw/mod_log_rotate/index.mhtml
 */

/* 2004/12/02 1.00      andya@apache.org    Initial release.
 */
#include "apr_anylock.h"
#include "apr_file_io.h"
#include "apr_pools.h"
#include "apr_strings.h"
#include "apr_time.h"

#include "httpd.h"
#include "http_config.h"
#include "http_log.h"
#include "ap_mpm.h"

#include "mod_log_config.h"

#ifndef APR_LARGEFILE
#define APR_LARGEFILE 0
#endif

#define INTERVAL_DEFAULT    (APR_USEC_PER_SEC * APR_TIME_C(3600) * APR_TIME_C(24))
#define INTERVAL_MIN        (APR_USEC_PER_SEC * APR_TIME_C(60))

static int xfer_flags = (APR_WRITE | APR_APPEND | APR_CREATE | APR_LARGEFILE);
static apr_fileperms_t xfer_perms = APR_OS_DEFAULT;

module AP_MODULE_DECLARE_DATA log_rotate_module;

typedef struct {
    int             enabled;        /* Rotation enabled                     */
    apr_time_t      interval;       /* Rotation interval                    */
    apr_time_t      offset;         /* Offset from midnight                 */
    int             localt;         /* Use local time instead of GMT        */
} log_options;

typedef struct {
    apr_pool_t      *pool;          /* Our working pool                     */
    const char      *fname;         /* Basename for logs without extension  */
    apr_file_t      *fd;            /* Current open log file                */
    apr_time_t      logtime;        /* Quantised time of current log file   */
    apr_anylock_t   mutex;

    log_options     st;             /* Embedded config options              */
} rotated_log;

static const char *ap_pstrftime(apr_pool_t *p, const char *format, apr_time_exp_t *tm) {
    size_t got, len = strlen(format) + 1;
    const char *fp = strchr(format, '%');
    char *buf = NULL;
    while (NULL != fp) {
        len += 10;   /* approx only, will fail if anything generates a huge expansion */
        fp = strchr(fp + 1, '%');
    }

    buf = apr_palloc(p, len);
    apr_strftime(buf, &got, len, format, tm);
    return buf;
}

static apr_file_t *ap_open_log(apr_pool_t *p, server_rec *s, const char *base, log_options *ls, apr_time_t tm) {
    if (*base == '|') {
        /* We have piped log handling here because once log rotation has been
         * enabled we become responsible for /all/ transfer log output server
         * wide. That's a consequence of the way the log output hooks in
         * mod_log_config are implemented. Unfortunately this means we have to
         * duplicate functionality from mod_log_config. Note that we don't
         * support the buffered logging mode that mlc implements.
         */
        piped_log *pl;

        if (ls->enabled) {
            /* Can't rotate a piped log */
            ls->enabled = 0;
            ap_log_error(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, s,
                            "disabled log rotation for piped log %s.", base);
        }

        if (pl = ap_open_piped_log(p, base + 1), NULL == pl) {
           return NULL;
        }

        return ap_piped_log_write_fd(pl);
    } else {
        apr_file_t *fd;
        apr_status_t rv;
        const char *name = ap_server_root_relative(p, base);

        if (NULL == name) {
            ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s,
                            "invalid transfer log path %s.", base);
            return NULL;
        }

        if (ls->enabled) {
            apr_time_t log_time = tm - ls->offset;
            if (strchr(base, '%') != NULL) {
                apr_time_exp_t e;

                apr_time_exp_gmt(&e, log_time);
                name = ap_pstrftime(p, name, &e);
            } else {
                /* Synthesize the log name using the specified time in seconds as a
                 * suffix.  We subtract the offset here because it was added when
                 * quantizing the time but we want the name to reflect the actual
                 * time when the log rotated. We don't reverse the local time
                 * adjustment because, presumably, if you've specified local time
                 * logging you want the filenames to use local time.
                 */
                name = apr_psprintf(p, "%s.%" APR_TIME_T_FMT, name, apr_time_sec(log_time));
            }
        }

        if (rv = apr_file_open(&fd, name, xfer_flags, xfer_perms, p), APR_SUCCESS != rv) {
            ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                            "could not open transfer log file %s.", name);
            return NULL;
        }

        return fd;
    }
}

static apr_status_t ap_close_log(server_rec *s, apr_file_t *fd) {
    apr_status_t rv;

    if (rv = apr_file_close(fd), APR_SUCCESS != rv) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                        "error closing transfer log file.");
    }

    return rv;
}

/* Quantize the supplied time to the log rotation interval applying offsets as
 * specified in the config.
 */
static apr_time_t ap_get_quantized_time(rotated_log *rl, apr_time_t tm) {
    apr_time_t localadj = 0;

    if (rl->st.localt) {
        apr_time_exp_t lt;
        apr_time_exp_lt(&lt, tm);
        localadj = (apr_time_t) lt.tm_gmtoff * APR_USEC_PER_SEC;
    }

    return ((tm + rl->st.offset + localadj) / rl->st.interval) * rl->st.interval;
}

/* Called by mod_log_config to write a log file line.
 */
static apr_status_t ap_rotated_log_writer(request_rec *r, void *handle,
                                          const char **strs, int *strl,
                                          int nelts, apr_size_t len) {
    char *str;
    char *s;
    int i;
    apr_status_t rv = 0;
    rotated_log *rl = (rotated_log *) handle;

    if (NULL != rl && NULL != rl->fd) {
        if (rl->st.enabled) {
            apr_time_t logt = ap_get_quantized_time(rl, r->request_time);
            /*ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server, "New: %lu, old: %lu",
            (unsigned long) logt, (unsigned long) rl->logtime);*/
            /* Decide if the quantized time has rolled over into a new slot. */
            if (logt != rl->logtime) {
                /* Get the mutex */
                if (rv = APR_ANYLOCK_LOCK(&rl->mutex), APR_SUCCESS != rv) {
                    return rv;
                }

                /* Now check again in case someone else rotated the log while we waited
                 * for the mutex.
                 */
                if (logt != rl->logtime) {
                    apr_file_t *ofd = rl->fd;
                    apr_pool_t *par, *np;
                    rl->logtime = logt;

                    /* Create a new pool to provide storage for the new file.
                     * Once we have the new file open we'll destroy the old
                     * pool and make this one current.
                     */
                    par = apr_pool_parent_get(rl->pool);
                    if (rv = apr_pool_create(&np, par), APR_SUCCESS != rv) {
                        APR_ANYLOCK_UNLOCK(&rl->mutex);
                        return rv;
                    }

                    /* Atomically replace the current log file because other
                     * threads, perhaps those responsible for a long lived
                     * request, won't have blocked on the mutex.
                     */
                    if (rl->fd = ap_open_log(np, r->server, rl->fname, &rl->st, logt), NULL == rl->fd) {
                        /* Open failed so keep going with the old log... */
                        rl->fd = ofd;
                        /* ...and destroy the new pool. */
                        apr_pool_destroy(np);
                    } else {
                        /* Close the old log... */
                        ap_close_log(r->server, ofd);
                        /* ...and switch to the new pool. */
                        apr_pool_destroy(rl->pool);
                        rl->pool = np;
                    }
                }

                APR_ANYLOCK_UNLOCK(&rl->mutex);
            }
        }

        str = apr_palloc(r->pool, len + 1);

        for (i = 0, s = str; i < nelts; ++i) {
            memcpy(s, strs[i], strl[i]);
            s += strl[i];
        }

        rv = apr_file_write(rl->fd, str, &len);

        return rv;
    } else {
        return APR_SUCCESS; /* Should we complain? */
    }
}

/* Called my mod_log_config to initialise a log writer.
 */
static void *ap_rotated_log_writer_init(apr_pool_t *p, server_rec *s, const char* name) {
    rotated_log *rl;
    apr_status_t rv;
    log_options *ls = ap_get_module_config(s->module_config, &log_rotate_module);

    rl = apr_palloc(p, sizeof(rotated_log));
    rl->fname = apr_pstrdup(p, name);

    if (rv = apr_pool_create(&rl->pool, p), APR_SUCCESS != rv) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "can't make log rotation pool.");
        return NULL;
    }

    rl->mutex.type = apr_anylock_none;

#if APR_HAS_THREADS
    {
        int mpm_threads;

        ap_mpm_query(AP_MPMQ_MAX_THREADS, &mpm_threads);
        if (mpm_threads > 1) {
            apr_status_t rv;

            rl->mutex.type = apr_anylock_threadmutex;
            rv = apr_thread_mutex_create(&rl->mutex.lock.tm, APR_THREAD_MUTEX_DEFAULT, p);
            if (rv != APR_SUCCESS) {
                ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
                        "could not initialize log rotation mutex, "
                        "transfer log may become corrupted");
                rl->mutex.type = apr_anylock_none;
            }
        }
    }
#endif

    rl->st          = *ls;
    rl->logtime     = ap_get_quantized_time(rl, apr_time_now());

    if (rl->fd = ap_open_log(rl->pool, s, rl->fname, &rl->st, rl->logtime), NULL == rl->fd) {
        return NULL;
    }

    return rl;
}

static const char *set_rotated_logs(cmd_parms *cmd, void *dummy, int flag) {
    log_options *ls = ap_get_module_config(cmd->server->module_config, &log_rotate_module);
    if (flag) {
        /* Always hook the writer functions when we're enabled even if we've
         * done it already. We can't unhook which means that once we've been
         * enabled we become responsible for all transfer log output. Note that
         * a subsequent BufferedLogs On in conf will clobber these hooks and
         * disable us.
         */
        APR_OPTIONAL_FN_TYPE(ap_log_set_writer_init) *set_writer_init;
        APR_OPTIONAL_FN_TYPE(ap_log_set_writer)      *set_writer;

        set_writer_init = APR_RETRIEVE_OPTIONAL_FN(ap_log_set_writer_init);
        set_writer      = APR_RETRIEVE_OPTIONAL_FN(ap_log_set_writer);

        if (NULL != set_writer_init && NULL != set_writer) {
            set_writer_init(ap_rotated_log_writer_init);
            set_writer(ap_rotated_log_writer);
            ls->enabled = 1;
        } else {
            ap_log_error(APLOG_MARK, APLOG_ERR, APR_SUCCESS, cmd->server,
                    "can't install log rotator - ap_log_set_writer not available");
            ls->enabled = 0;
        }
    } else {
        ls->enabled = 0;
    }

    return NULL;
}

static const char *set_localtime(cmd_parms *cmd, void *dummy, int flag) {
    log_options *ls = ap_get_module_config(cmd->server->module_config, &log_rotate_module);
    ls->localt = flag;
    return NULL;
}

static const char *set_interval(cmd_parms *cmd, void *dummy,
                                const char *inte, const char *offs) {
    log_options *ls = ap_get_module_config(cmd->server->module_config, &log_rotate_module);
    if (NULL != inte) {
        /* Interval in seconds */
        ls->interval = APR_USEC_PER_SEC * (apr_time_t) atol(inte);
        if (ls->interval < INTERVAL_MIN) {
            ls->interval = INTERVAL_MIN;
        }
    }

    if (NULL != offs) {
        /* Offset in minutes */
        ls->offset = APR_USEC_PER_SEC * 60 * (apr_time_t) atol(offs);
    }

    return NULL;
}

static const command_rec rotate_log_cmds[] = {
    AP_INIT_FLAG(  "RotateLogs", set_rotated_logs, NULL, RSRC_CONF,
                   "Enable rotated logging"),
    AP_INIT_FLAG(  "RotateLogsLocalTime", set_localtime, NULL, RSRC_CONF,
                   "Rotate relative to local time"),
    AP_INIT_TAKE12("RotateInterval", set_interval, NULL, RSRC_CONF,
                   "Set rotation interval in seconds with"
                   " optional offset in minutes"),
    {NULL}
};

static void *make_log_options(apr_pool_t *p, server_rec *s) {
    log_options *ls;

    ls = (log_options *) apr_palloc(p, sizeof(log_options));
    ls->enabled     = 1;
    ls->interval    = INTERVAL_DEFAULT;
    ls->offset      = 0;
    ls->localt      = 0;

    return ls;
}

static void *merge_log_options(apr_pool_t *p, void *basev, void *addv) {
    log_options *base = (log_options *) basev;
    log_options *add  = (log_options *) addv;

    *add = *base;

    return add;
}

module AP_MODULE_DECLARE_DATA log_rotate_module = {
    STANDARD20_MODULE_STUFF,
    NULL,                       /* create per-dir config */
    NULL,                       /* merge per-dir config */
    make_log_options,           /* server config */
    merge_log_options,          /* merge server config */
    rotate_log_cmds,            /* command apr_table_t */
    NULL                        /* register hooks */
};