# AccessLogger.java

    /*
     *  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.
     */
    package org.apache.synapse.transport.http.access;

    import java.io.BufferedWriter;
    import java.io.File;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Properties;

    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.apache.synapse.commons.util.MiscellaneousUtil;

    /**
     * Class that logs the Http Accesses to the access log files. Code segment borrowed from
     * Apache Tomcat's org.apache.catalina.valves.AccessLogValve with thanks.
     */
    public class AccessLogger {

        /** The name of the system property used to specify/override the nhttp properties location */
        public static final String NHTTP_PROPERTIES = "nhttp.properties";

        //property name of nhttp log directory
        public static final String NHTTP_LOG_DIRECTORY = "nhttp.log.directory";

        public final static String ACCESS_LOG_ID = "org.apache.synapse.transport.nhttp.access";

        private static Log log = LogFactory.getLog(ACCESS_LOG_ID);

        public AccessLogger(final Log log) {
            super();
            this.initOpen();
            AccessLogger.log = log;
            buffered = true;
            checkExists = false;
        }

        /**
         * A date formatter to format a Date into a date in the given file format
         */
        protected SimpleDateFormat fileDateFormatter =
                new SimpleDateFormat(AccessConstants.getFileDateFormat());

        /**
         * The PrintWriter to which we are currently logging, if any.
         */
        protected PrintWriter writer;

        /**
         * The as-of date for the currently open log file, or a zero-length
         * string if there is no open log file.
         */
        private volatile String dateStamp = "";

        /**
         * Instant when the log daily rotation was last checked.
         */
        private volatile long rotationLastChecked = 0L;

        /**
         * Buffered logging.
         */
        private boolean buffered = true;

        /**
         * Do we check for log file existence? Helpful if an external
         * agent renames the log file so we can automatically recreate it.
         */
        private boolean checkExists = false;

        /**
         * The current log file we are writing to. Helpful when checkExists
         * is true.
         */
        protected File currentLogFile = null;

        /**
         * Can the log file be rotated.
         */
        protected boolean isRotatable = true;

        /**
         * Log the specified message to the log file, switching files if the date
         * has changed since the previous log call.
         *
         * @param message Message to be logged
         */
        public void log(String message) {
            if (isRotatable) {
                // Only do a logfile switch check once a second, max.
                long systime = System.currentTimeMillis();
                if ((systime - rotationLastChecked) > 1000) {
                    synchronized (this) {
                        if ((systime - rotationLastChecked) > 1000) {
                            rotationLastChecked = systime;

                            String tsDate;
                            // Check for a change of date
                            tsDate = fileDateFormatter.format(new Date(systime));

                            // If the date has changed, switch log files
                            if (!dateStamp.equals(tsDate)) {
                                close();
                                dateStamp = tsDate;
                                open();
                            }
                        }
                    }
                }
            }

            /* In case something external rotated the file instead */
            if (checkExists) {
                synchronized (this) {
                    if (currentLogFile != null && !currentLogFile.exists()) {
                        try {
                            close();
                        } catch (Throwable e) {
                            handleThrowable(e);
                            log.info("Access Log file Close failed");
                        }

                        /* Make sure date is correct */
                        dateStamp = fileDateFormatter.format(
                                new Date(System.currentTimeMillis()));

                        open();
                    }
                }
            }

            // Log this message
            synchronized (this) {
                if (writer != null) {
                    writer.println(message);

                    if (!buffered) {
                        writer.flush();
                    }
                }
            }
        }

        protected synchronized void initOpen() {
            /* Make sure date is correct */
            dateStamp = fileDateFormatter.format(
                    new Date(System.currentTimeMillis()));
            this.open();
        }

        /**
         * Open the new log file for the date specified by <code>dateStamp</code>.
         */
        protected synchronized void open() {
            // Create the directory if necessary
            File dir;
            Properties synapseProps = MiscellaneousUtil.loadProperties(NHTTP_PROPERTIES);
            String nhttpLogDir =  synapseProps.getProperty(NHTTP_LOG_DIRECTORY);
            if (nhttpLogDir != null) {
                dir = new File(nhttpLogDir);
            } else {
                dir = new File(AccessConstants.getDirectory());
            }
            if (!dir.exists()) {
                if (!dir.mkdirs()) {
                    log.error("Access Log Open Directory Failed");
                }
            }

            // Open the current log file
            try {
                String pathname;
                // If no rotate - no need for dateStamp in fileName
                if (isRotatable) {
                    pathname = dir.getAbsolutePath() + File.separator + AccessConstants.getPrefix() +
                               dateStamp + AccessConstants.getSuffix();
                } else {
                    pathname = dir.getAbsolutePath() + File.separator + AccessConstants.getPrefix() +
                               AccessConstants.getSuffix();
                }

                writer = new PrintWriter(new BufferedWriter(new FileWriter(
                        pathname, true), 128000), true);

                currentLogFile = new File(pathname);
            } catch (IOException e) {
                log.warn("Unable to open the print writer", e);
                writer = null;
                currentLogFile = null;
            }
        }

        /**
         * Close the currently open log file (if any)
         */
        synchronized void close() {
            if (writer == null) {
                return;
            }
            writer.flush();
            writer.close();
            writer = null;
            dateStamp = "";
            currentLogFile = null;
        }

        /**
         * Checks whether the supplied Throwable is one that needs to be
         * re-thrown and swallows all others.
         *
         * @param t the Throwable to check
         */
        public static void handleThrowable(Throwable t) {
            if (t instanceof ThreadDeath) {
                throw (ThreadDeath) t;
            }
            if (t instanceof VirtualMachineError) {
                throw (VirtualMachineError) t;
            }
            // All other instances of Throwable will be silently swallowed
        }
    }