4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk/*
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk *
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk *
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * The contents of this file are subject to the terms
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * of the Common Development and Distribution License
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * (the License). You may not use this file except in
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * compliance with the License.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk *
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * You can obtain a copy of the License at
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * https://opensso.dev.java.net/public/CDDLv1.0.html or
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * opensso/legal/CDDLv1.0.txt
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * See the License for the specific language governing
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * permission and limitations under the License.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk *
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * When distributing Covered Code, include this CDDL
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Header Notice in each file and include the License file
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * at opensso/legal/CDDLv1.0.txt.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * If applicable, add the following below the CDDL Header,
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * with the fields enclosed by brackets [] replaced by
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * your own identifying information:
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * "Portions Copyrighted [year] [name of copyright owner]"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk *
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * $Id: FileHandler.java,v 1.14 2009/07/27 19:50:55 bigfatrat Exp $
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk *
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Portions Copyrighted 2011-2016 ForgeRock AS.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Portions Copyrighted 2014 Nomura Research Institute, Ltd
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkpackage com.sun.identity.log.handlers;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport static org.forgerock.openam.utils.Time.*;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.io.BufferedOutputStream;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.io.File;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.io.FileInputStream;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.io.FileOutputStream;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.io.IOException;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.io.OutputStream;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.io.OutputStreamWriter;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.io.UnsupportedEncodingException;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.io.Writer;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.text.SimpleDateFormat;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.util.Iterator;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.util.LinkedList;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.util.Date;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.util.logging.Formatter;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.util.logging.Level;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.util.logging.LogRecord;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport com.iplanet.am.util.ThreadPoolException;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport com.iplanet.log.NullLocationException;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport com.sun.identity.common.GeneralTaskRunnable;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport com.sun.identity.common.SystemTimer;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport com.sun.identity.log.LogConstants;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport com.sun.identity.log.LogManager;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport com.sun.identity.log.LogManagerUtil;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport com.sun.identity.log.Logger;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport com.sun.identity.log.spi.Debug;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport com.sun.identity.monitoring.Agent;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport com.sun.identity.monitoring.MonitoringUtil;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport com.sun.identity.monitoring.SsoServerLoggingHdlrEntryImpl;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport com.sun.identity.monitoring.SsoServerLoggingSvcImpl;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.io.FileNotFoundException;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkimport java.util.Calendar;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk/**
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * This <tt> FileHandler </tt> is very much similar to the
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * <t> java.util.logging.FileHandler </tt>. <p> The <TT> FileHandler </TT>
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * can either write to a specified file, or it can write to a rotating set
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * of files. <P>
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * For a rotating set of files, as each file reaches the limit
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * (<i> LogConstants.MAX_FILE_SIZE</I>), it is closed, rotated out, and a
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * new file opened. Successively older files and named by adding "-1", "-2",
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * etc., * to the base filename. The Locking mechanism is much more relaxed
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * (in JDK's FileHandler an exclusive lock is created on the file till the
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * handler is closed which makes reading impossible)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkpublic class FileHandler extends java.util.logging.Handler {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private LogManager lmanager = LogManagerUtil.getLogManager();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private OutputStream output;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private Writer writer;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private MeteredStream meteredStream;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private File files[];
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private boolean headerWritten;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private int count; // count represent number of history files
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private long maxFileSize;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private String location;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private Formatter formatter;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private String fileName;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private int recCountLimit;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private LinkedList recordBuffer;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private TimeBufferingTask bufferTask;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private boolean timeBufferingEnabled = false;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private boolean rotateEnabled = true;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private static String headerString = null;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private SsoServerLoggingSvcImpl logServiceImplForMonitoring = null;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private SsoServerLoggingHdlrEntryImpl fileLogHandlerForMonitoring = null;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private int rotationInterval = -1;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private long lastRotation;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk /**
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * By default the size based rotation is enabled
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private boolean rotatingBySize = true;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private static final String DEFAULT_LOG_SUFFIX_FORMAT = "-MM.dd.yy-kk.mm";
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private class MeteredStream extends OutputStream {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk OutputStream out;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk String filename = null;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk MeteredStream(File fileName, boolean append) throws IOException {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk this.filename = fileName.toString();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk FileOutputStream fout = new FileOutputStream(filename, append);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk this.out = new BufferedOutputStream(fout);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk /**
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * writes a single integer to the outputstream and increments
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * the number of bytes written by one.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @param b integer value to be written.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @throws IOException if it fails to write out.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk public void write(int b) throws IOException {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk out.write(b);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk /**
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Writes the array of bytes to the output stream and increments
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * the number of bytes written by the size of the array.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @param b the byte array to be written.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @throws IOException if it fails to write out.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk public void write(byte[] b) throws IOException {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk out.write(b);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk /**
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Writes the array of bytes to the output stream and increments
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * the number of bytes written by the size of the array.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @param b the byte array to be written.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @param offset the offset of array to be written.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @param length the length of bytes to be written.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @throws IOException if it fails to write out.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk public void write(byte[] b, int offset, int length)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk throws IOException {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk out.write(b, offset, length);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk /**
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Flush any buffered messages.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @throws IOException if it fails to write out.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk public void flush() throws IOException {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk out.flush();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk /**
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * close the current output stream.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @throws IOException if it fails to close output stream.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk public void close() throws IOException {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk out.close();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk /**
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * sets the output stream to the specified output stream ..picked up from
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * StreamHandler.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private void setOutputStream(OutputStream out) throws SecurityException,
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk UnsupportedEncodingException {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (out == null) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (Debug.warningEnabled()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk Debug.warning(fileName + ":FileHandler: OutputStream is null");
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk output = out;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk headerWritten = false;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk String encoding = getEncoding();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (encoding == null) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk writer = new OutputStreamWriter(output);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } else {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk try {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk writer = new OutputStreamWriter(output, encoding);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } catch (UnsupportedEncodingException e) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk Debug.error(fileName + ":FileHandler: Unsupported Encoding", e);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk throw new UnsupportedEncodingException(e.getMessage());
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk /**
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Set (or change) the character encoding used by this <tt>Handler</tt>.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * The encoding should be set before any <tt>LogRecords</tt> are written
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * to the <tt>Handler</tt>.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk *
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @param encoding The name of a supported character encoding.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * May be null, to indicate the default platform encoding.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @exception SecurityException if a security manager exists and if
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * the caller does not have
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * <tt>LoggingPermission("control")</tt>.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @exception UnsupportedEncodingException if the named encoding is
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * not supported.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk public void setEncoding(String encoding) throws SecurityException,
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk UnsupportedEncodingException {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk super.setEncoding(encoding);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (output == null) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk return;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // Replace the current writer with a writer for the new encoding.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk cleanup();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (encoding == null) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk writer = new OutputStreamWriter(output);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } else {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk writer = new OutputStreamWriter(output, encoding);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk /**
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * This method is used for getting the properties from LogManager
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * and setting the private variables count, maxFileSize etc.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk private void configure()
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk throws NullLocationException, FormatterInitException {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk String bufferSize = lmanager.getProperty(LogConstants.BUFFER_SIZE);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (bufferSize != null && bufferSize.length() > 0) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk try {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk recCountLimit = Integer.parseInt(bufferSize);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } catch (NumberFormatException nfe) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk Debug.warning(fileName +
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk ":FileHandler: NumberFormatException ", nfe);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (Debug.messageEnabled()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk Debug.message(fileName +
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk ":FileHandler: Setting buffer size to 1");
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk recCountLimit = 1;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } else {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk Debug.warning(fileName +
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk ":FileHandler: Invalid buffer size: " + bufferSize);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (Debug.messageEnabled()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk Debug.message(fileName +
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk ":FileHandler: Setting buffer size to 1");
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk recCountLimit = 1;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
String status = lmanager.getProperty(
LogConstants.TIME_BUFFERING_STATUS);
if (status != null && status.equalsIgnoreCase("ON")) {
timeBufferingEnabled = true;
}
String strCount = lmanager.getProperty(LogConstants.NUM_HISTORY_FILES);
if ((strCount == null) || (strCount.length() == 0)) {
count = 0;
} else {
count = Integer.parseInt(strCount);
}
String strMaxFileSize = lmanager.getProperty(
LogConstants.MAX_FILE_SIZE);
if ((strMaxFileSize == null) || (strMaxFileSize.length() == 0)) {
maxFileSize = 0L;
} else {
maxFileSize = Long.parseLong(strMaxFileSize);
}
location = lmanager.getProperty(LogConstants.LOG_LOCATION);
if ((location == null) || (location.length() == 0)) {
throw new NullLocationException(
"Location Not Specified"); //localize
}
if (!location.endsWith(File.separator)) {
location += File.separator;
}
String strFormatter = lmanager.getProperty(LogConstants.ELF_FORMATTER);
try {
Class clz = Class.forName(strFormatter);
formatter = (Formatter) clz.newInstance();
} catch (Exception e) {
throw new FormatterInitException(
"Unable to initialize Formatter Class" + e);
}
String strRotateEnabled = lmanager.getProperty(LogConstants.ENABLE_ROTATION);
if (strRotateEnabled != null && !strRotateEnabled.isEmpty()) {
rotateEnabled = Boolean.parseBoolean(strRotateEnabled);
}
if (rotateEnabled) {
String rotation = lmanager.getProperty(LogConstants.LOGFILE_ROTATION);
try {
if (rotation != null) {
rotationInterval = Integer.parseInt(rotation);
}
} catch (NumberFormatException nfe) {
//if we cannot parse it, then we use the size based rotation
rotationInterval = -1;
}
if (rotationInterval > 0) {
lastRotation = currentTimeMillis();
rotatingBySize = false;
}
}
}
private void openFiles(String fileName) throws IOException {
if (rotateEnabled && rotatingBySize) {
// make sure that we have a valid maxFileSize
if (maxFileSize < 0) {
Debug.error(fileName
+ ":FileHandler: maxFileSize cannot be negative");
maxFileSize = 0L;
}
}
// make sure that we have a valid history count
if (count < 0) {
Debug.error(fileName
+ ":FileHandler: no. of history files negative " + count);
count = 0;
}
files = new File[count + 1]; // count is the number of history files
for (int i = 0; i < count + 1; i++) {
if (i != 0) {
files[i] = new File(fileName + "-" + i);
} else {
files[0] = new File(fileName);
}
}
open(files[0], true);
}
/**
* Algorithm: Check how many bytes have already been written to to that file
* Get an instance of MeteredStream and assign it as the output stream.
* Create a file of the name of FileOutputStream.
*/
private void open(File fileName, boolean append) throws IOException {
meteredStream = new MeteredStream(fileName, append);
setOutputStream(meteredStream);
checkForHeaderWritten(fileName.toString());
}
/**
* Creates a new FileHandler. It takes a string parameter which represents
* file name. When this constructor is called a new file to be created.
* Assuming that the fileName logger provides is the timestamped fileName.
* @param fileName The filename associate with file handler.
*/
public FileHandler(String fileName) {
if ((fileName == null) || (fileName.length() == 0)) {
return;
}
this.fileName = fileName;
try {
configure();
} catch (NullLocationException nle) {
Debug.error(fileName + ":FileHandler: Location not specified", nle);
} catch (FormatterInitException fie) {
Debug.error(fileName +
":FileHandler: could not instantiate Formatter", fie);
}
if (rotateEnabled && !rotatingBySize) {
fileName = wrapFilename(fileName);
}
fileName = location + fileName;
Logger logger = (Logger) Logger.getLogger(this.fileName);
if (logger.getLevel() != Level.OFF) {
try {
openFiles(fileName);
} catch (IOException ioe) {
Debug.error(fileName + ":FileHandler: Unable to open Files",
ioe);
}
}
logger.setCurrentFile(this.fileName);
recordBuffer = new LinkedList();
if (timeBufferingEnabled) {
startTimeBufferingThread();
}
if (MonitoringUtil.isRunning()) {
logServiceImplForMonitoring =
Agent.getLoggingSvcMBean();
fileLogHandlerForMonitoring =
logServiceImplForMonitoring.getHandler(
SsoServerLoggingSvcImpl.FILE_HANDLER_NAME);
}
}
private String wrapFilename(String fileName) {
String prefix = lmanager.getProperty(LogConstants.LOGFILE_PREFIX);
String suffixFormat = lmanager.getProperty(LogConstants.LOGFILE_SUFFIX);
StringBuilder newFileName = new StringBuilder();
if (prefix != null) {
newFileName.append(prefix);
}
newFileName.append(fileName);
SimpleDateFormat suffixDateFormat = null;
if (suffixFormat != null && suffixFormat.trim().length() > 0) {
try {
suffixDateFormat = new SimpleDateFormat(suffixFormat);
} catch (IllegalArgumentException iae) {
Debug.error("Date format invalid; " + suffixFormat, iae);
}
}
if (rotationInterval > 0 && suffixDateFormat == null) {
//fallback to a default dateformat, so the logfilenames will differ
suffixDateFormat = new SimpleDateFormat(DEFAULT_LOG_SUFFIX_FORMAT);
}
if (suffixDateFormat != null) {
newFileName.append(suffixDateFormat.format(newDate()));
}
return newFileName.toString();
}
private void cleanup() {
if (writer != null) {
try {
writer.flush();
} catch (Exception ex) {
Debug.error(fileName +
":FileHandler: Could not Flush Output", ex);
}
}
}
/**
* Flush any buffered messages and Close all the files.
*/
public void close() {
flush();
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
Debug.error(fileName + ":FileHandler: Error closing writer", e);
}
}
stopBufferTimer();
}
/**
* Format and publish a LogRecord.
* <p>
* This FileHandler is associated with a Formatter, which has to format the
* LogRecord according to ELF and return back the string formatted as per
* ELF. This method first checks if the header is already written to the
* file, if not, gets the header from the Formatter and writes it at the
* beginning of the file.
* @param lrecord the log record to be published.
*/
public void publish(LogRecord lrecord) {
if (MonitoringUtil.isRunning() && fileLogHandlerForMonitoring != null) {
fileLogHandlerForMonitoring.incHandlerRequestCount(1);
}
if (maxFileSize <= 0) {
return;
}
if (!isLoggable(lrecord)) {
return;
}
Formatter formatter = getFormatter();
String message = formatter.format(lrecord);
synchronized (this) {
recordBuffer.add(message);
if (recordBuffer.size() >= recCountLimit) {
if (Debug.messageEnabled()) {
Debug.message(fileName + ":FileHandler.publish(): got " +
recordBuffer.size() + " records, writing all");
}
nonBlockingFlush();
}
}
}
private String getHeaderString() {
if (headerString == null) {
headerString = getFormatter().getHead(this);
}
return headerString;
}
/**
* Flush any buffered messages.
*/
protected void nonBlockingFlush() {
LinkedList writeBuffer = null;
synchronized (this) {
if (recordBuffer.size() <= 0) {
if (Debug.messageEnabled()) {
Debug.message(fileName +
":FileHandler.flush: no records in buffer to write");
}
return;
}
writeBuffer = recordBuffer;
recordBuffer = new LinkedList();
}
LogTask task = new LogTask(writeBuffer);
try {
// Get an instance as required otherwise it can cause issues on container restart.
LoggingThread.getInstance().run(task);
} catch (ThreadPoolException ex) {
// use current thread to flush the data if ThreadPool is shutdown
synchronized (this) {
task.run();
}
}
}
public void flush() {
synchronized (this) {
if (recordBuffer.size() <= 0) {
return;
}
if (writer == null) {
int recordsToBeDropped = recordBuffer.size();
if (MonitoringUtil.isRunning() && fileLogHandlerForMonitoring !=
null) {
fileLogHandlerForMonitoring.incHandlerDroppedCount(
recordsToBeDropped);
}
recordBuffer.clear();
return;
}
for (Iterator iter = recordBuffer.iterator(); iter.hasNext();) {
String message = (String) iter.next();
if (needsRotation(message)) {
rotate();
}
try {
if (!headerWritten) {
writer.write(getHeaderString());
headerWritten = true;
}
writer.write(message);
if (MonitoringUtil.isRunning() &&
fileLogHandlerForMonitoring != null) {
fileLogHandlerForMonitoring.incHandlerSuccessCount(1);
}
} catch (IOException ex) {
}
cleanup();
}
recordBuffer.clear();
}
}
private boolean needsRotation(String message) {
if (rotateEnabled) {
if (rotatingBySize) {
if (!message.isEmpty()
&& new File(meteredStream.filename).length() >= maxFileSize
- message.length()) {
return true;
}
} else {
Calendar now = getCalendarInstance();
Calendar then = getCalendarInstance();
then.setTimeInMillis(lastRotation);
then.add(Calendar.MINUTE, rotationInterval);
if (now.after(then)) {
return true;
}
}
}
return false;
}
private void rotate() {
if (writer != null) {
try {
writer.flush();
writer.close();
} catch (Exception ex) {
Debug.error(fileName + ":FileHandler: " +
"Error closing writer", ex);
}
}
if (rotatingBySize) {
//
// delete file<n>; file<n-1> becomes file<n>; and so on.
//
for (int i = count - 1; i >= 0; i--) {
File f1 = files[i];
File f2 = files[i + 1];
if (f1.exists()) {
if (f2.exists()) {
try {
f2.delete();
} catch (SecurityException secex) {
Debug.error(fileName
+ ":FileHandler: could not delete file. msg = "
+ secex.getMessage());
}
}
boolean renameSuccess = f1.renameTo(f2);
// In case renaming fails, copy the contents of source file
// to destination file.
if (!renameSuccess) {
copyFile(f1.toString(), f2.toString());
}
}
}
} else {
// remember when we last rotated
lastRotation = currentTimeMillis();
// Delete the oldest file if it exists
if (files[count].exists()) {
try {
files[count].delete();
} catch (SecurityException secex) {
Debug.error(fileName
+ ":FileHandler: could not delete file. msg = "
+ secex.getMessage());
}
}
// Move each file up a slot and then replace 0th one with new file
for (int i = count - 1; i >= 0; i--) {
files[i + 1] = files[i];
}
// generate a new timestamp based filename
String wrappedFilename = wrapFilename(this.fileName);
File newLogFile = new File(location, wrappedFilename);
if (newLogFile.exists()) {
Debug.error(newLogFile.getName()
+ ":FileHandler: could not rotate file. msg = "
+ "file already exists!");
} else {
// swap across to the new file
files[0] = newLogFile;
}
}
if (Debug.messageEnabled()) {
Debug.message(fileName
+ ":FileHandler: rotate to file " + files[0].getName());
}
try {
open(files[0], false);
} catch (IOException ix) {
Debug.error(fileName + ":FileHandler: error opening file" + ix);
}
}
private void copyFile(String input, String output) {
if (Debug.messageEnabled()) {
Debug.message(fileName + ":FileHandler: CopyFile Method called");
}
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//input file
fis = new FileInputStream(input);
int s;
//output file
fos = new FileOutputStream(output);
while ((s = fis.read()) > -1) {
fos.write(s);
}
} catch (FileNotFoundException fnfe) {
Debug.error(fileName + ":FileHandler: copyFile: File not found: ",
fnfe);
} catch (IOException ioex) {
Debug.error(fileName + ":FileHandler: copyFile: IOException",
ioex);
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ex) {
Debug.error(fileName + ":FileHandler: copyFile: IOException while closing file", ex);
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException ex) {
Debug.error(fileName + ":FileHandler: copyFile: IOException while closing file", ex);
}
}
}
private void checkForHeaderWritten(String fileName) {
byte[] bytes = new byte[1024];
FileInputStream fins = null;
try {
fins = new FileInputStream(new File(fileName));
fins.read(bytes);
} catch (IOException ioe) {
Debug.error(fileName + ":FileHandler: couldnot read file content",
ioe);
} finally {
if (fins != null) {
try {
fins.close();
} catch (IOException ex) {
Debug.error(fileName +
":FileHandler: could not close file.", ex);
}
}
}
String fileContent = new String(bytes);
fileContent = fileContent.trim();
if (fileContent.startsWith("#Version")) {
headerWritten = true;
} else {
headerWritten = false;
}
}
private class LogTask implements Runnable {
private LinkedList buffer;
public LogTask(LinkedList buffer) {
this.buffer = buffer;
}
public void run() {
if (writer == null) {
Debug.error(fileName + ":FileHandler: Writer is null");
int recordsToBeDropped = buffer.size();
if (MonitoringUtil.isRunning() && fileLogHandlerForMonitoring !=
null) {
fileLogHandlerForMonitoring.incHandlerDroppedCount(
recordsToBeDropped);
}
buffer.clear();
return;
}
if (Debug.messageEnabled()) {
Debug.message(fileName + ":FileHandler.flush: writing " +
"buffered records (" +
buffer.size() + " records)");
}
for (Iterator iter = buffer.iterator(); iter.hasNext();) {
String message = (String) iter.next();
if (needsRotation(message)) {
rotate();
}
try {
if (!headerWritten) {
writer.write(getHeaderString());
headerWritten = true;
}
writer.write(message);
if (MonitoringUtil.isRunning() &&
fileLogHandlerForMonitoring != null) {
fileLogHandlerForMonitoring.incHandlerSuccessCount(1);
}
} catch (IOException ex) {
Debug.error(fileName +
":FileHandler: could not write to file: ", ex);
}
cleanup();
}
}
}
private class TimeBufferingTask extends GeneralTaskRunnable {
private long runPeriod;
public TimeBufferingTask(long runPeriod) {
this.runPeriod = runPeriod;
}
/**
* The method which implements the GeneralTaskRunnable.
*/
public void run() {
if (Debug.messageEnabled()) {
Debug.message(fileName +
":FileHandler:TimeBufferingTask.run() called");
}
nonBlockingFlush();
}
/**
* Methods that need to be implemented from GeneralTaskRunnable.
*/
public boolean isEmpty() {
return true;
}
public boolean addElement(Object obj) {
return false;
}
public boolean removeElement(Object obj) {
return false;
}
public long getRunPeriod() {
return runPeriod;
}
}
private void startTimeBufferingThread() {
String period = lmanager.getProperty(LogConstants.BUFFER_TIME);
long interval;
if ((period != null) && (period.length() != 0)) {
interval = Long.parseLong(period);
} else {
interval = LogConstants.BUFFER_TIME_DEFAULT;
}
interval *= 1000;
if (bufferTask == null) {
bufferTask = new TimeBufferingTask(interval);
try {
SystemTimer.getTimer().schedule(bufferTask, new Date(((currentTimeMillis() + interval) / 1000) * 1000));
} catch (IllegalArgumentException e) {
Debug.error(fileName + ":FileHandler:BuffTimeArg: " +
e.getMessage());
} catch (IllegalStateException e) {
if (Debug.messageEnabled()) {
Debug.message(fileName + ":FileHandler:BuffTimeState: " +
e.getMessage());
}
}
if (Debug.messageEnabled()) {
Debug.message(fileName +
":FileHandler: Time Buffering Thread Started");
}
}
}
private void stopBufferTimer() {
if (bufferTask != null) {
bufferTask.cancel();
bufferTask = null;
if (Debug.messageEnabled()) {
Debug.message(fileName + ":FileHandler: Buffer Timer Stopped");
}
}
}
}