/* * 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. */ package com.addthis.basis.util; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.zip.GZIPOutputStream; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class RollingLog extends OutputStream implements FileLogger { protected static final Logger log = LoggerFactory.getLogger(RollingLog.class); private final DateTimeFormatter format = DateTimeFormat.forPattern("yyyyMMdd-HHmmss"); // The heritage of IFileLogger is that multiple threads might want // to write to this logger, and should also be able to start and // stop the logger. No need for CAS (AtomicBoolean) in this case. // Note also that the default of 'true' is different from FileLogger private volatile boolean loggingEnabled = true; private File dir; private String prefix = ""; private String suffix = ""; private boolean compress; private long maxSize; private long maxAge; private long open; private File file; private File fileRename; private File cFile; private OutputStream out; public RollingLog(File dir, String prefix, boolean compress, long maxSize, long maxAge) { this(dir, prefix, "", compress, maxSize, maxAge); } public RollingLog(File dir, String pre, String suf, boolean compress, long maxSize, long maxAge) { dir.mkdirs(); this.dir = LessFiles.initDirectory(dir); this.prefix = pre != null ? pre : ""; this.suffix = suf != null ? suf : ""; if (prefix != "" && !prefix.endsWith("-")) { prefix = prefix + "-"; } if (suffix != "" && !suffix.startsWith(".")) { suffix = "." + suffix; } this.compress = compress; this.maxSize = maxSize; this.maxAge = maxAge; } private void openNext() throws IOException { close(); open = System.currentTimeMillis(); String fname = prefix + format.print(open) + suffix; file = new File(dir, fname + ".tmp"); out = new FileOutputStream(file); fileRename = new File(dir, fname); if (compress) { cFile = new File(dir, fname + ".gz"); } } private void checkNext() throws IOException { if ((out == null) || (maxAge > 0 && (System.currentTimeMillis() - open) > maxAge) || (maxSize > 0 && file.length() > maxSize)) { openNext(); } } @Override public synchronized void writeLine(String line) { if (loggingEnabled) { try { write(LessBytes.toBytes(line)); write('\n'); } catch (IOException e) { log.debug("error writing to log file", e); } } } @Override public synchronized void write(int b) throws IOException { checkNext(); out.write(b); } @Override public synchronized void write(byte b[]) throws IOException { checkNext(); out.write(b); } @Override public synchronized void write(byte b[], int off, int len) throws IOException { checkNext(); out.write(b, off, len); } @Override public synchronized void close() throws IOException { if (out != null) { out.flush(); out.close(); if (!file.renameTo(fileRename)) { log.info("file rename failed :: " + file + " --> " + fileRename); } else if (compress) { final File from = fileRename, to = cFile; Thread t = new Thread(new Runnable() { public void run() { compressFile(from, to); } }); t.setDaemon(true); t.start(); } out = null; } } // Interface stop/start niceties @Override public boolean isLogging() { return loggingEnabled; } @Override public void startLogging() { loggingEnabled = true; } @Override public void stopLogging() { loggingEnabled = false; } private void compressFile(File current, File compressed) { try { GZIPOutputStream cStream = new GZIPOutputStream(new FileOutputStream(compressed)); BufferedInputStream in = new BufferedInputStream(new FileInputStream(current)); byte[] buffer = new byte[4096]; int read; while ((read = in.read(buffer)) != -1) { cStream.write(buffer, 0, read); } in.close(); cStream.finish(); cStream.close(); if (!current.delete()) { log.info("file delete failed :: " + current); } } catch (IOException ioe) { log.debug("error compressing file", ioe); compressed.delete(); } } }