diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java b/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java index e0ef048b13..dd2508a15d 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-2014 Basis Technology Corp. + * Copyright 2012-2015 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,17 +18,20 @@ */ package org.sleuthkit.autopsy.coreutils; -import java.io.File; import java.io.IOException; +import java.nio.file.Paths; import java.util.logging.FileHandler; import java.util.logging.Formatter; import java.util.logging.Handler; import java.sql.Timestamp; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import java.util.logging.LogRecord; /** * Autopsy specialization of the Java Logger class with custom file handlers. + * Note that the custom loggers are not obtained from the global log manager. */ public final class Logger extends java.util.logging.Logger { @@ -37,66 +40,81 @@ public final class Logger extends java.util.logging.Logger { private static final int LOG_FILE_COUNT = 10; private static final String LOG_WITHOUT_STACK_TRACES = "autopsy.log"; //NON-NLS private static final String LOG_WITH_STACK_TRACES = "autopsy_traces.log"; //NON-NLS - private static final Handler console = new java.util.logging.ConsoleHandler(); - private static final Object fileHandlerLock = new Object(); - private static FileHandler userFriendlyLogFile = createFileHandler(PlatformUtil.getLogDirectory(), LOG_WITHOUT_STACK_TRACES); - private static FileHandler developersLogFile = createFileHandler(PlatformUtil.getLogDirectory(), LOG_WITH_STACK_TRACES); + private static final Map namesToLoggers = new HashMap<>(); + private static final Handler consoleHandler = new java.util.logging.ConsoleHandler(); + private static FileHandler userFriendlyHandler = createFileHandlerWithoutTraces(PlatformUtil.getLogDirectory()); + private static FileHandler developerFriendlyHandler = createFileHandlerWithTraces(PlatformUtil.getLogDirectory()); - private static FileHandler createFileHandler(String logDirectory, String fileName) { + /** + * Creates a custom file handler with a custom message formatter that does + * not include stack traces. + * + * @param logDirectory The directory where the log files should reside. + * + * @return A custom file handler. + */ + private static FileHandler createFileHandlerWithoutTraces(String logDirectory) { + String logFilePath = Paths.get(logDirectory, LOG_WITHOUT_STACK_TRACES).toString(); try { - FileHandler f = new FileHandler(logDirectory + File.separator + fileName, LOG_SIZE, LOG_FILE_COUNT); - f.setEncoding(LOG_ENCODING); - switch (fileName) { - case LOG_WITHOUT_STACK_TRACES: - f.setFormatter(new Formatter() { - @Override - public String format(LogRecord record) { - synchronized (fileHandlerLock) { - return (new Date(record.getMillis())).toString() + " " - + record.getSourceClassName() + " " - + record.getSourceMethodName() + "\n" - + record.getLevel() + ": " - + this.formatMessage(record) + "\n"; - } - } - }); - break; - case LOG_WITH_STACK_TRACES: - f.setFormatter(new Formatter() { - @Override - public String format(LogRecord record) { - synchronized (fileHandlerLock) { - if (record.getThrown() != null) { + FileHandler fileHandler = new FileHandler(logFilePath, LOG_SIZE, LOG_FILE_COUNT); + fileHandler.setEncoding(LOG_ENCODING); + fileHandler.setFormatter(new Formatter() { + @Override + public String format(LogRecord record) { + return (new Date(record.getMillis())).toString() + " " + + record.getSourceClassName() + " " + + record.getSourceMethodName() + "\n" + + record.getLevel() + ": " + + this.formatMessage(record) + "\n"; + } + }); + return fileHandler; + } catch (IOException ex) { + throw new RuntimeException(String.format("Error initializing file handler for %s", logFilePath), ex); //NON-NLS + } + } - StackTraceElement ele[] = record.getThrown().getStackTrace(); - String StackTrace = ""; - for (StackTraceElement ele1 : ele) { - StackTrace += "\t" + ele1.toString() + "\n"; - } - - return (new Timestamp(record.getMillis())).toString() + " " - + record.getSourceClassName() + " " - + record.getSourceMethodName() + "\n" - + record.getLevel() + ": " - + this.formatMessage(record) + "\n" - + record.getThrown().toString() + ":\n" - + StackTrace - + "\n"; - } else { - return (new Timestamp(record.getMillis())).toString() + " " - + record.getSourceClassName() + " " - + record.getSourceMethodName() + "\n" - + record.getLevel() + ": " - + this.formatMessage(record) + "\n"; - } - } + /** + * Creates a custom file handler with a custom message formatter that + * incldues stack traces. + * + * @param logDirectory The directory where the log files should reside. + * + * @return A custom file handler. + */ + private static FileHandler createFileHandlerWithTraces(String logDirectory) { + String logFilePath = Paths.get(logDirectory, LOG_WITH_STACK_TRACES).toString(); + try { + FileHandler fileHandler = new FileHandler(logFilePath, LOG_SIZE, LOG_FILE_COUNT); + fileHandler.setEncoding(LOG_ENCODING); + fileHandler.setFormatter(new Formatter() { + @Override + public String format(LogRecord record) { + if (record.getThrown() != null) { + String stackTrace = ""; //NON-NLS + for (StackTraceElement traceElem : record.getThrown().getStackTrace()) { + stackTrace += "\t" + traceElem.toString() + "\n"; //NON-NLS } - }); - break; - } - return f; - } catch (IOException e) { - throw new RuntimeException("Error initializing " + fileName + " file handler", e); //NON-NLS + return (new Timestamp(record.getMillis())).toString() + " " //NON-NLS + + record.getSourceClassName() + " " //NON-NLS + + record.getSourceMethodName() + "\n" //NON-NLS + + record.getLevel() + ": " //NON-NLS + + this.formatMessage(record) + "\n" //NON-NLS + + record.getThrown().toString() + ":\n" //NON-NLS + + stackTrace + + "\n"; //NON-NLS + } else { + return (new Timestamp(record.getMillis())).toString() + " " //NON-NLS + + record.getSourceClassName() + " " //NON-NLS + + record.getSourceMethodName() + "\n" //NON-NLS + + record.getLevel() + ": " //NON-NLS + + this.formatMessage(record) + "\n"; //NON-NLS + } + } + }); + return fileHandler; + } catch (IOException ex) { + throw new RuntimeException(String.format("Error initializing file handler for %s", logFilePath), ex); //NON-NLS } } @@ -105,63 +123,88 @@ public final class Logger extends java.util.logging.Logger { * * @param directoryPath The path to the desired log directory as a string. */ - public static void setLogDirectory(String directoryPath) { - if (null != directoryPath && !directoryPath.isEmpty()) { - File directory = new File(directoryPath); - if (directory.exists() && directory.canWrite()) { - synchronized (fileHandlerLock) { - userFriendlyLogFile.close(); - userFriendlyLogFile = createFileHandler(directoryPath, LOG_WITHOUT_STACK_TRACES); - developersLogFile.close(); - developersLogFile = createFileHandler(directoryPath, LOG_WITH_STACK_TRACES); - } - } + synchronized public static void setLogDirectory(String directoryPath) { + /* + * Create file handlers for the new directory and swap them into all of + * the existing loggers using thread-safe Logger methods. The new + * handlers are added before the old handlers so that no messages will + * be lost, but this makes it possible for log messages to be written + * via the old handlers if logging calls are interleaved with the + * add/remove handler calls (currently, the base class handlers + * collection is a CopyOnWriteArrayList). + */ + FileHandler newUserFriendlyHandler = createFileHandlerWithoutTraces(directoryPath); + FileHandler newDeveloperFriendlyHandler = createFileHandlerWithTraces(directoryPath); + for (Logger logger : namesToLoggers.values()) { + logger.addHandler(newUserFriendlyHandler); + logger.addHandler(newDeveloperFriendlyHandler); + logger.removeHandler(userFriendlyHandler); + logger.removeHandler(userFriendlyHandler); } + + /* + * Close the old file handlers and save references to the new handlers + * so they can be added to any new loggers. This swap is why this method + * and the two overloads of getLogger() are synchronized, serializing + * access to userFriendlyHandler and developerFriendlyHandler. + */ + userFriendlyHandler.close(); + userFriendlyHandler = newUserFriendlyHandler; + developerFriendlyHandler.close(); + developerFriendlyHandler = newDeveloperFriendlyHandler; } /** - * Factory method to retrieve a org.sleuthkit.autopsy.coreutils.Logger - * instance derived from java.util.logging.Logger. Hides the base class - * factory method. + * Finds or creates a customized logger. Hides the base class factory + * method. * - * @param name A name for the logger. This should be a dot-separated name - * and should normally be based on the package name or class - * name. + * @param name A name for the logger. This should normally be a + * dot-separated name based on a package name or class name. * * @return org.sleuthkit.autopsy.coreutils.Logger instance */ - public static Logger getLogger(String name) { - return new Logger(name, null); + synchronized public static Logger getLogger(String name) { + return getLogger(name, null); } /** - * Factory method to retrieve a org.sleuthkit.autopsy.coreutils.Logger - * instance derived from java.util.logging.Logger. Hides the base class - * factory method. + * Finds or creates a customized logger. Hides the base class factory + * method. * - * @param name A name for the logger. This should be a - * dot-separated name and should normally be based - * on the package name or class name. - * @param resourceBundleName - name of ResourceBundle to be used for + * @param name A name for the logger. This should normally be + * a dot-separated name based on a package name or + * class name. + * @param resourceBundleName Name of ResourceBundle to be used for * localizing messages for this logger. May be - * null if none of the messages require - * localization. + * null. * * @return org.sleuthkit.autopsy.coreutils.Logger instance */ - public static Logger getLogger(String name, String resourceBundleName) { - return new Logger(name, resourceBundleName); + synchronized public static Logger getLogger(String name, String resourceBundleName) { + if (!namesToLoggers.containsKey(name)) { + Logger logger = new Logger(name, resourceBundleName); + logger.addHandler(userFriendlyHandler); + logger.addHandler(developerFriendlyHandler); + namesToLoggers.put(name, logger); + } + return namesToLoggers.get(name); } + /** + * Constructs a customized logger. + * + * @param name A name for the logger. This should normally be + * a dot-separated name based on a package name or + * class name. + * @param resourceBundleName Name of ResourceBundle to be used for + * localizing messages for this logger. May be + * null. + */ private Logger(String name, String resourceBundleName) { super(name, resourceBundleName); + super.setUseParentHandlers(false); if (Version.getBuildType() == Version.Type.DEVELOPMENT) { - super.addHandler(console); - } - synchronized (fileHandlerLock) { - super.setUseParentHandlers(false); - super.addHandler(userFriendlyLogFile); - super.addHandler(developersLogFile); + super.addHandler(consoleHandler); } } }