/*
* Copyright (C) 2012 Google Inc.
*
* 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 interactivespaces.launcher.bootstrap;
import interactivespaces.system.core.logging.LoggingProvider;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.Log4JLogger;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.RollingFileAppender;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* Logging provider using Log4J.
*
* @author Keith M. Hughes
*/
public class Log4jLoggingProvider implements LoggingProvider {
/**
* Where the logging properties file is kept in lib/system/java. This is deprecated.
*/
public static final String LOGGING_PROPERTIES_FILE_LIB_SYSTEM_JAVA = "lib/system/java/log4j.properties";
/**
* Where the logging properties file is kept in the config folder.
*/
public static final String LOGGING_PROPERTIES_FILE_CONFIG = "system/log4j.properties";
/**
* The file path to the logeer for the Interactive Spaces container.
*/
public static final String FILEPATH_CONTAINER_INTERACTIVESPACES_LOG = "logs/interactivespaces.log";
/**
* The Log4J property for specifying the file system location of the log folder.
*/
public static final String LOG4J_PROPERTY_FILEPATH_INTERACTIVESPACES_LOGGER =
"log4j.appender.interactivespaces.File";
/**
* The Log4J property for the logging conversion pattern.
*/
public static final String LOG4J_PROPERTY_CONVERSTION_PATTERN = "log4j.appender.stdout.layout.ConversionPattern";
/**
* The map of logging levels to their log4j level.
*/
public static final Map<String, Level> LOG_4J_LEVELS;
static {
Map<String, Level> levels = new HashMap<String, Level>();
levels.put(LOG_LEVEL_ERROR, Level.ERROR);
levels.put(LOG_LEVEL_FATAL, Level.FATAL);
levels.put(LOG_LEVEL_DEBUG, Level.DEBUG);
levels.put(LOG_LEVEL_INFO, Level.INFO);
levels.put(LOG_LEVEL_OFF, Level.OFF);
levels.put(LOG_LEVEL_TRACE, Level.TRACE);
levels.put(LOG_LEVEL_WARN, Level.WARN);
LOG_4J_LEVELS = Collections.unmodifiableMap(levels);
}
/**
* The root logger for the container.
*/
private Logger baseInteractiveSpacesLogger;
/**
* The base log for the container.
*/
private Log baseContainerLog;
/**
* The properties loaded from property file to use for the logger.
*/
private Properties loggingProperties;
/**
* The log data for all logs.
*/
private Map<Log, LogData> logDataMap = new HashMap<>();
/**
* Configure the provider.
*
* @param baseInstallDir
* base installation directory for IS
* @param configDir
* the configuration directory for IS
*/
public void configure(File baseInstallDir, File configDir) {
File loggingPropertiesFile = findLoggingConfiguration(baseInstallDir, configDir);
loggingProperties = new Properties();
try (FileInputStream fileInputStream = new FileInputStream(loggingPropertiesFile)) {
loggingProperties.load(fileInputStream);
loggingProperties.put(LOG4J_PROPERTY_FILEPATH_INTERACTIVESPACES_LOGGER, new File(baseInstallDir,
FILEPATH_CONTAINER_INTERACTIVESPACES_LOG).getAbsolutePath());
PropertyConfigurator.configure(loggingProperties);
baseInteractiveSpacesLogger = Logger.getLogger(LOGGER_BASE_NAME);
baseContainerLog = new Log4JLogger(baseInteractiveSpacesLogger);
} catch (FileNotFoundException e) {
throw new RuntimeException(String.format("Unable to find container configuration %s",
loggingPropertiesFile.getAbsolutePath()));
} catch (IOException e) {
throw new RuntimeException(String.format("Error while reading container configuration %s",
loggingPropertiesFile.getAbsolutePath()), e);
}
}
/**
* Locate the logging configuration for the container.
*
* @param baseInstallDir
* the base installation directory for the container
* @param configDir
* the configuration directory for the container
*
* @return the logging configuration file
*/
private File findLoggingConfiguration(File baseInstallDir, File configDir) {
// TODO(keith): Sort out all config locations so can have a base folder for just configs that this can look into.
File loggingPropertiesFile = new File(configDir, LOGGING_PROPERTIES_FILE_CONFIG);
if (loggingPropertiesFile.isFile()) {
return loggingPropertiesFile;
}
loggingPropertiesFile = new File(baseInstallDir, LOGGING_PROPERTIES_FILE_LIB_SYSTEM_JAVA);
if (loggingPropertiesFile.isFile()) {
return loggingPropertiesFile;
}
throw new RuntimeException("Could not locate log4j logging configuration file");
}
@Override
public Log getLog() {
return baseContainerLog;
}
@Override
public Log getLog(String logName, String level, String filename) {
synchronized (logDataMap) {
Level l = LOG_4J_LEVELS.get(level.toLowerCase());
boolean unknownLevel = false;
if (l == null) {
unknownLevel = true;
l = Level.ERROR;
}
Logger logger = Logger.getLogger("interactivespaces." + logName);
logger.setLevel(l);
Log4JLogger log = new Log4JLogger(logger);
LogData logData = new LogData();
logDataMap.put(log, logData);
if (filename != null) {
// Create pattern layout
PatternLayout layout = new PatternLayout();
layout.setConversionPattern(loggingProperties.getProperty(LOG4J_PROPERTY_CONVERSTION_PATTERN));
try {
RollingFileAppender fileAppender = new RollingFileAppender(layout, filename);
logger.addAppender(fileAppender);
logData.setFileAppender(fileAppender);
} catch (java.io.IOException e) {
throw new RuntimeException(String.format("Error while creating a RollingFileAppender for %s", filename), e);
}
}
if (unknownLevel) {
logger.error(String.format("Unknown log level %s, set to ERROR", level));
}
return log;
}
}
@Override
public boolean modifyLogLevel(Log log, String level) {
if (Log4JLogger.class.isAssignableFrom(log.getClass())) {
Level l = LOG_4J_LEVELS.get(level.toLowerCase());
if (l != null) {
((Log4JLogger) log).getLogger().setLevel(l);
return true;
} else {
log.error(String.format("Unknown log level %s", level));
}
} else {
log.error("Attempt to modify an unmodifiable logger");
}
return false;
}
@Override
public void releaseLog(Log log) {
if (log == baseContainerLog) {
throw new RuntimeException("Cannot release the base container log");
}
synchronized (logDataMap) {
LogData logData = logDataMap.remove(log);
if (logData != null) {
logData.release();
} else {
baseContainerLog.warn("Attempting to release a logger that was not allocated by the log provider");
}
}
}
/**
* Data for a log, including extra components added to the base logger.
*
* @author Keith M. Hughes
*/
private static class LogData {
/**
* The file appender for the logger.
*/
private FileAppender fileAppender;
/**
* Get the file appender added to the logger.
*
* @return the file appender, can be {@code null}
*/
public FileAppender getFileAppender() {
return fileAppender;
}
/**
* Set the file appender added to the logger.
*
* @param fileAppender
* the file appender, can be {@code null}
*/
public void setFileAppender(FileAppender fileAppender) {
this.fileAppender = fileAppender;
}
/**
* Release all components of the log.
*/
public void release() {
if (fileAppender != null) {
fileAppender.close();
}
}
}
}