/*
* $Id: EvaLog.java 109 2007-03-24 14:55:03Z max $
*
* Copyright (c) 2007 Maximilian Antoni. All rights reserved.
*
* This software is licensed as described in the file LICENSE.txt, which you
* should have received as part of this distribution. The terms are also
* available at http://www.maxantoni.de/projects/eva-properties/license.txt.
*/
package com.eva.log;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Filter;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.eva.properties.DataSource;
import com.eva.properties.MapProperties;
import com.eva.properties.PropertiesException;
/**
* <p>
* provides factory methods for convenient logger creation. All methods require
* at least a package or package name that is used to find an optional
* configuration file in that package named "log.eva".
* </p>
* <p>
* If an external properties file is found, the default properties provided by
* this package are set as the super properties. The <code>bundle</code>
* filename can be specified in the properties file and the <code>package</code>
* property can be overriden.
* </p>
* <p>
* The default properties provide the following values:
* </p>
*
* <pre>
* encoding: ISO8859-1
* level: INFO
* dir: "${system.user.dir}/log"
* pattern: "eva%g.log"
* limit: 10000
* count: 5
* formatter: ( *com.eva.log.LogFormatter(${prefix})
* *com.eva.log.LogFormatter()
* )
* handler: ${console-handler}
* console-handler: *java.util.logging.ConsoleHandler()
* file-handler: *java.util.logging.FileHandler(
* "${dir}/${pattern}", ${limit}, ${count}
* )
* useParentHandlers: no
* </pre>
*
* <p>
* This setup allows you to switch to a console logger setup very easily. Simply
* provide the following in your custom log properties file:
* </p>
* <p>
* <code>handler: ${file-handler}</code>
* </p>
*
* @author max
* @version $Revision: 109 $
*/
public class EvaLog {
private static final Logger DEFAULT_LOGGER;
private static final MapProperties DEFAULT_PROPERTIES;
private static final String LOG_CONFIG_FILENAME = "log.eva";
static {
Exception exception = null;
MapProperties properties;
try {
properties = new MapProperties(
new DataSource("classpath://log.eva"));
}
catch(PropertiesException e) {
exception = e; // Log after initialization.
properties = loadDefaults();
}
catch(FileNotFoundException e) {
// Expected. Don't log.
properties = loadDefaults();
}
DEFAULT_PROPERTIES = properties;
DEFAULT_LOGGER = Logger.getLogger(EvaLog.class.getPackage().getName());
configure(DEFAULT_LOGGER, DEFAULT_PROPERTIES);
if(exception != null) {
DEFAULT_LOGGER.log(Level.WARNING,
"Custom log properties cannot be read. Using defaults.",
exception);
}
}
/**
* creates a new logger for the given class.
*
* @param inClass the class.
* @return the logger.
*/
public static Logger create(Class inClass) {
return create(inClass, (String) null);
}
/**
* creates a new logger for the given class and bundle name.
*
* @param inClass the class.
* @param inBundle the bundle name.
* @return the logger.
*/
public static Logger create(Class inClass, String inBundle) {
return create(inClass.getClassLoader(), inClass.getPackage().getName(),
inBundle);
}
/**
* creates a new logger for the given package name.
*
* @param inPackage the package name.
* @return the logger.
*/
public static Logger create(String inPackage) {
return create(null, inPackage, (String) null);
}
/**
* creates a new logger for the given package name and bundle name.
*
* @param inPackage the package name.
* @param inBundle the bundle name.
* @return the logger.
*/
public static Logger create(String inPackage, String inBundle) {
return create(null, inPackage, inBundle);
}
/**
* creates a new logger for the given package name and bundle name. If the
* class loader is not <code>null</code> it is used to find the properties
* file.
*
* @param inClassLoader the class loader.
* @param inPackage the package name.
* @param inBundle the bundle name.
* @return the logger.
*/
private static Logger create(ClassLoader inClassLoader, String inPackage,
String inBundle) {
/*
* Check for configuration for the given package in the default
* properties:
*/
MapProperties properties = (MapProperties) DEFAULT_PROPERTIES
.get(inPackage.replace('.', '-'));
if(properties == null) {
/*
* Try loading the properties for the given package form the
* classpath using the naming convention "<package>/log.eva":
*/
try {
properties = new MapProperties(new DataSource(inClassLoader,
"classpath://" + inPackage.replace('.', '/') + "/"
+ LOG_CONFIG_FILENAME));
}
catch(FileNotFoundException e) {
DEFAULT_LOGGER.log(Level.CONFIG,
"No logger configuration found for package \""
+ inPackage + "\". Using defaults.");
}
}
return create(inPackage, inBundle, properties);
}
/**
* creates a new logger for the given package and properties.
*
* @param inPackage the package.
* @param inProperties the properties.
* @return the logger.
*/
public static Logger create(Package inPackage, Map inProperties) {
return create(inPackage.getName(), null, inProperties);
}
/**
* creates a new logger for the given package name and properties.
*
* @param inPackage the package name.
* @param inProperties the properties.
* @return the logger.
*/
public static Logger create(String inPackage, Map inProperties) {
return create(inPackage, null, inProperties);
}
/**
* creates a new logger for the given package name, bundle name and
* properties.
*
* @param inPackage the package name.
* @param inBundle the bundle name.
* @param inProperties the properties.
* @return the logger.
*/
public static Logger create(String inPackage, String inBundle,
Map inProperties) {
String pkg;
String bundle = inBundle;
if(inProperties == null) {
pkg = inPackage;
}
else {
/*
* Set the default properties as the parent for the loaded
* properties. All properties will be inherited from the default
* configuration.
*/
inProperties.put(MapProperties.SUPER, DEFAULT_PROPERTIES);
pkg = (String) inProperties.get("package");
if(pkg == null) {
pkg = inPackage; // Use configured package instead.
}
if(bundle == null) {
bundle = (String) inProperties.get("bundle");
}
}
Logger logger;
if(bundle == null) {
logger = Logger.getLogger(pkg);
if(DEFAULT_LOGGER.isLoggable(Level.FINE)) {
DEFAULT_LOGGER.log(Level.FINE, "Logger for package \""
+ inPackage + "\" created without resource bundle.");
}
}
else {
// The bundle has to be in the package:
logger = Logger.getLogger(pkg, pkg + "." + bundle);
if(DEFAULT_LOGGER.isLoggable(Level.FINE)) {
DEFAULT_LOGGER.log(Level.FINE, "Logger for package \""
+ inPackage + "\" created with resource bundle \""
+ bundle + "\".");
}
}
if(logger.getParent() == DEFAULT_LOGGER) {
// No further configuration required. Logger already configured.
return logger;
}
logger.setParent(DEFAULT_LOGGER);
if(inProperties != null) {
configure(logger, inProperties);
}
return logger;
}
/**
* returns the default logger. The default logger is used as a parent for
* all loggers instantiated using this class.
*
* @return the default logger.
*/
public static Logger getDefault() {
return DEFAULT_LOGGER;
}
/**
* configures a logger with the information provided by a properties map.
*
* @param inoutLogger the logger to configure.
* @param inProperties the properties map.
*/
private static void configure(Logger inoutLogger, Map inProperties) {
if(DEFAULT_LOGGER.isLoggable(Level.FINER)) {
DEFAULT_LOGGER.finer("Configure logger \"" + inoutLogger.getName()
+ "\" with:");
Map properties;
if(inProperties instanceof MapProperties) {
properties = ((MapProperties) inProperties).getMap();
}
else {
properties = inProperties;
}
String[] keys = new String[] {
"handler", "handlers", "encoding", "level", "formatter",
"filter", "useParentHandlers"
};
boolean valuesPresent = false;
for(int i = 0; i < keys.length; i++) {
Object value = properties.get(keys[i]);
if(value != null) {
DEFAULT_LOGGER.finer("\t" + keys[i] + ": " + value);
valuesPresent = true;
}
}
if(!valuesPresent) {
DEFAULT_LOGGER.finer("\tNo properties found.");
}
}
Handler singleHandler = (Handler) inProperties.get("handler");
List handlers;
if(singleHandler == null) {
handlers = (List) inProperties.get("handlers");
}
else {
handlers = Collections.singletonList(singleHandler);
}
String encoding = (String) inProperties.get("encoding");
String l = (String) inProperties.get("level");
Level level = l == null ? null : Level.parse(l);
Formatter formatter = (Formatter) inProperties.get("formatter");
if(handlers != null) {
for(Iterator i = handlers.iterator(); i.hasNext();) {
Handler handler = (Handler) i.next();
if(encoding != null) {
try {
handler.setEncoding(encoding);
}
catch(SecurityException e) {
throw new RuntimeException(e);
}
catch(UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
if(level != null) {
handler.setLevel(level);
}
if(formatter != null) {
handler.setFormatter(formatter);
}
inoutLogger.addHandler(handler);
}
}
if(level != null) {
inoutLogger.setLevel(level);
}
Filter filter = (Filter) inProperties.get("filter");
if(filter != null) {
inoutLogger.setFilter(filter);
}
Boolean useParentHandlers = (Boolean) inProperties
.get("useParentHandlers");
if(useParentHandlers != null) {
inoutLogger.setUseParentHandlers(useParentHandlers.booleanValue());
}
}
/**
* loads the default properties from the classpath.
*
* @return the default properties.
*/
private static MapProperties loadDefaults() {
return new MapProperties(EvaLog.class
.getResourceAsStream(LOG_CONFIG_FILENAME));
}
/**
* Never instantiated.
*/
private EvaLog() {
// Never instantiated.
}
}