blob: c2f5aaf13e6ccf9e72c7d08c9c08a98038b6894a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.log4j;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.or.ObjectRenderer;
import org.apache.log4j.or.RendererMap;
import org.apache.log4j.plugins.Plugin;
import org.apache.log4j.plugins.PluginRegistry;
import org.apache.log4j.scheduler.Scheduler;
import org.apache.log4j.spi.ErrorItem;
import org.apache.log4j.spi.HierarchyEventListener;
import org.apache.log4j.spi.LoggerEventListener;
import org.apache.log4j.spi.LoggerFactory;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.LoggerRepositoryEventListener;
import org.apache.log4j.spi.LoggerRepositoryEx;
import org.apache.log4j.spi.RendererSupport;
import org.apache.log4j.xml.UnrecognizedElementHandler;
import org.apache.log4j.xml.DOMConfigurator;
import org.w3c.dom.Element;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
/**
* This class implements LoggerRepositoryEx by
* wrapping an existing LoggerRepository implementation
* and implementing the newly added capabilities.
*/
public final class LoggerRepositoryExImpl
implements LoggerRepositoryEx,
RendererSupport,
UnrecognizedElementHandler {
/**
* Wrapped logger repository.
*/
private final LoggerRepository repo;
/**
* Logger factory. Does not affect class of logger
* created by underlying repository.
*/
private LoggerFactory loggerFactory;
/**
* Renderer support.
*/
private final RendererSupport rendererSupport;
/**
* List of repository event listeners.
*/
private final ArrayList repositoryEventListeners = new ArrayList();
/**
* Map of HierarchyEventListener keyed by LoggingEventListener.
*/
private final Map loggerEventListeners = new HashMap();
/**
* Name of hierarchy.
*/
private String name;
/**
* Plug in registry.
*/
private PluginRegistry pluginRegistry;
/**
* Properties.
*/
private final Map properties = new Hashtable();
/**
* Scheduler.
*/
private Scheduler scheduler;
/** The repository can also be used as an object store
* for various objects used by log4j components.
*/
private Map objectMap = new HashMap();
/**
* Error list.
*/
private List errorList = new Vector();
/**
* True if hierarchy has not been modified.
*/
private boolean pristine = true;
/**
Constructs a new logger hierarchy.
@param repository Base implementation of repository.
*/
public LoggerRepositoryExImpl(final LoggerRepository repository) {
super();
if (repository == null) {
throw new NullPointerException("repository");
}
repo = repository;
if (repository instanceof RendererSupport) {
rendererSupport = (RendererSupport) repository;
} else {
rendererSupport = new RendererSupportImpl();
}
}
/**
Add a {@link LoggerRepositoryEventListener} to the repository. The
listener will be called when repository events occur.
@param listener listener
*/
public void addLoggerRepositoryEventListener(
final LoggerRepositoryEventListener listener) {
synchronized (repositoryEventListeners) {
if (repositoryEventListeners.contains(listener)) {
LogLog.warn(
"Ignoring attempt to add a previously "
+ "registered LoggerRepositoryEventListener.");
} else {
repositoryEventListeners.add(listener);
}
}
}
/**
Remove a {@link LoggerRepositoryEventListener} from the repository.
@param listener listener
*/
public void removeLoggerRepositoryEventListener(
final LoggerRepositoryEventListener listener) {
synchronized (repositoryEventListeners) {
if (!repositoryEventListeners.contains(listener)) {
LogLog.warn(
"Ignoring attempt to remove a "
+ "non-registered LoggerRepositoryEventListener.");
} else {
repositoryEventListeners.remove(listener);
}
}
}
/**
Add a {@link LoggerEventListener} to the repository. The listener
will be called when repository events occur.
@param listener listener
*/
public void addLoggerEventListener(final LoggerEventListener listener) {
synchronized (loggerEventListeners) {
if (loggerEventListeners.get(listener) != null) {
LogLog.warn(
"Ignoring attempt to add a previously registerd LoggerEventListener.");
} else {
HierarchyEventListenerProxy proxy =
new HierarchyEventListenerProxy(listener);
loggerEventListeners.put(listener, proxy);
repo.addHierarchyEventListener(proxy);
}
}
}
/**
Add a {@link org.apache.log4j.spi.HierarchyEventListener}
event to the repository.
@param listener listener
@deprecated Superceded by addLoggerEventListener
*/
public
void addHierarchyEventListener(final HierarchyEventListener listener) {
repo.addHierarchyEventListener(listener);
}
/**
Remove a {@link LoggerEventListener} from the repository.
@param listener listener to be removed
*/
public void removeLoggerEventListener(final LoggerEventListener listener) {
synchronized (loggerEventListeners) {
HierarchyEventListenerProxy proxy =
(HierarchyEventListenerProxy) loggerEventListeners.get(listener);
if (proxy == null) {
LogLog.warn(
"Ignoring attempt to remove a non-registered LoggerEventListener.");
} else {
loggerEventListeners.remove(listener);
proxy.disable();
}
}
}
/**
* Issue warning that there are no appenders in hierarchy.
* @param cat logger, not currently used.
*/
public void emitNoAppenderWarning(final Category cat) {
repo.emitNoAppenderWarning(cat);
}
/**
Check if the named logger exists in the hierarchy. If so return
its reference, otherwise returns <code>null</code>.
@param loggerName The name of the logger to search for.
@return true if logger exists.
*/
public Logger exists(final String loggerName) {
return repo.exists(loggerName);
}
/**
* Return the name of this hierarchy.
* @return name of hierarchy
*/
public String getName() {
return name;
}
/**
* Set the name of this repository.
*
* Note that once named, a repository cannot be rerenamed.
* @param repoName name of hierarchy
*/
public void setName(final String repoName) {
if (name == null) {
name = repoName;
} else if (!name.equals(repoName)) {
throw new IllegalStateException(
"Repository [" + name + "] cannot be renamed as [" + repoName + "].");
}
}
/**
* {@inheritDoc}
*/
public Map getProperties() {
return properties;
}
/**
* {@inheritDoc}
*/
public String getProperty(final String key) {
return (String) properties.get(key);
}
/**
* Set a property by key and value. The property will be shared by all
* events in this repository.
* @param key property name
* @param value property value
*/
public void setProperty(final String key,
final String value) {
properties.put(key, value);
}
/**
The string form of {@link #setThreshold(Level)}.
@param levelStr symbolic name for level
*/
public void setThreshold(final String levelStr) {
repo.setThreshold(levelStr);
}
/**
Enable logging for logging requests with level <code>l</code> or
higher. By default all levels are enabled.
@param l The minimum level for which logging requests are sent to
their appenders. */
public void setThreshold(final Level l) {
repo.setThreshold(l);
}
/**
* {@inheritDoc}
*/
public PluginRegistry getPluginRegistry() {
if (pluginRegistry == null) {
pluginRegistry = new PluginRegistry(this);
}
return pluginRegistry;
}
/**
Requests that a appender added event be sent to any registered
{@link LoggerEventListener}.
@param logger The logger to which the appender was added.
@param appender The appender added to the logger.
*/
public void fireAddAppenderEvent(final Category logger,
final Appender appender) {
repo.fireAddAppenderEvent(logger, appender);
}
/**
Requests that a appender removed event be sent to any registered
{@link LoggerEventListener}.
@param logger The logger from which the appender was removed.
@param appender The appender removed from the logger.
*/
public void fireRemoveAppenderEvent(final Category logger,
final Appender appender) {
if (repo instanceof Hierarchy) {
((Hierarchy) repo).fireRemoveAppenderEvent(logger, appender);
}
}
/**
Requests that a level changed event be sent to any registered
{@link LoggerEventListener}.
@param logger The logger which changed levels.
*/
public void fireLevelChangedEvent(final Logger logger) {
}
/**
*
* Requests that a configuration changed event be sent to any registered
* {@link LoggerRepositoryEventListener}.
*
*/
public void fireConfigurationChangedEvent() {
}
/**
Returns the current threshold.
@return current threshold level
@since 1.2 */
public Level getThreshold() {
return repo.getThreshold();
}
/**
Return a new logger instance named as the first parameter using
the default factory.
<p>If a logger of that name already exists, then it will be
returned. Otherwise, a new logger will be instantiated and
then linked with its existing ancestors as well as children.
@param loggerName The name of the logger to retrieve.
@return logger
*/
public Logger getLogger(final String loggerName) {
return repo.getLogger(loggerName);
}
/**
Return a new logger instance named as the first parameter using
<code>factory</code>.
<p>If a logger of that name already exists, then it will be
returned. Otherwise, a new logger will be instantiated by the
<code>factory</code> parameter and linked with its existing
ancestors as well as children.
@param loggerName The name of the logger to retrieve.
@param factory The factory that will make the new logger instance.
@return logger
*/
public Logger getLogger(final String loggerName,
final LoggerFactory factory) {
return repo.getLogger(loggerName, factory);
}
/**
Returns all the currently defined categories in this hierarchy as
an {@link java.util.Enumeration Enumeration}.
<p>The root logger is <em>not</em> included in the returned
{@link Enumeration}.
@return enumerator of current loggers
*/
public Enumeration getCurrentLoggers() {
return repo.getCurrentLoggers();
}
/**
* Return the the list of previously encoutered {@link ErrorItem error items}.
* @return list of errors
*/
public List getErrorList() {
return errorList;
}
/**
* Add an error item to the list of previously encountered errors.
* @param errorItem error to add to list of errors.
*/
public void addErrorItem(final ErrorItem errorItem) {
getErrorList().add(errorItem);
}
/**
* Get enumerator over current loggers.
* @return enumerator over current loggers
@deprecated Please use {@link #getCurrentLoggers} instead.
*/
public Enumeration getCurrentCategories() {
return repo.getCurrentCategories();
}
/**
Get the renderer map for this hierarchy.
@return renderer map
*/
public RendererMap getRendererMap() {
return rendererSupport.getRendererMap();
}
/**
Get the root of this hierarchy.
@since 0.9.0
@return root of hierarchy
*/
public Logger getRootLogger() {
return repo.getRootLogger();
}
/**
This method will return <code>true</code> if this repository is
disabled for <code>level</code> value passed as parameter and
<code>false</code> otherwise. See also the {@link
#setThreshold(Level) threshold} method.
@param level numeric value for level.
@return true if disabled for specified level
*/
public boolean isDisabled(final int level) {
return repo.isDisabled(level);
}
/**
Reset all values contained in this hierarchy instance to their
default. This removes all appenders from all categories, sets
the level of all non-root categories to <code>null</code>,
sets their additivity flag to <code>true</code> and sets the level
of the root logger to DEBUG. Moreover,
message disabling is set its default "off" value.
<p>Existing categories are not removed. They are just reset.
<p>This method should be used sparingly and with care as it will
block all logging until it is completed.</p>
@since 0.8.5 */
public void resetConfiguration() {
repo.resetConfiguration();
}
/**
Used by subclasses to add a renderer to the hierarchy passed as parameter.
@param renderedClass class
@param renderer object used to render class.
*/
public void setRenderer(final Class renderedClass,
final ObjectRenderer renderer) {
rendererSupport.setRenderer(renderedClass, renderer);
}
/**
* {@inheritDoc}
*/
public boolean isPristine() {
return pristine;
}
/**
* {@inheritDoc}
*/
public void setPristine(final boolean state) {
pristine = state;
}
/**
Shutting down a hierarchy will <em>safely</em> close and remove
all appenders in all categories including the root logger.
<p>Some appenders such as org.apache.log4j.net.SocketAppender
and AsyncAppender need to be closed before the
application exists. Otherwise, pending logging events might be
lost.
<p>The <code>shutdown</code> method is careful to close nested
appenders before closing regular appenders. This is allows
configurations where a regular appender is attached to a logger
and again to a nested appender.
@since 1.0 */
public void shutdown() {
repo.shutdown();
}
/**
* Return this repository's own scheduler.
* The scheduler is lazily instantiated.
* @return this repository's own scheduler.
*/
public Scheduler getScheduler() {
if (scheduler == null) {
scheduler = new Scheduler();
scheduler.setDaemon(true);
scheduler.start();
}
return scheduler;
}
/**
* Puts object by key.
* @param key key, may not be null.
* @param value object to associate with key.
*/
public void putObject(final String key,
final Object value) {
objectMap.put(key, value);
}
/**
* Get object by key.
* @param key key, may not be null.
* @return object associated with key or null.
*/
public Object getObject(final String key) {
return objectMap.get(key);
}
/**
* Set logger factory.
* @param factory logger factory.
*/
public void setLoggerFactory(final LoggerFactory factory) {
if (factory == null) {
throw new NullPointerException();
}
this.loggerFactory = factory;
}
/**
* Get logger factory.
* @return logger factory.
*/
public LoggerFactory getLoggerFactory() {
return loggerFactory;
}
/** {@inheritDoc} */
public boolean parseUnrecognizedElement(
final Element element,
final Properties props) throws Exception {
if ("plugin".equals(element.getNodeName())) {
Object instance =
DOMConfigurator.parseElement(element, props, Plugin.class);
if (instance instanceof Plugin) {
Plugin plugin = (Plugin) instance;
String pluginName = DOMConfigurator.subst(element.getAttribute("name"), props);
if (pluginName.length() > 0) {
plugin.setName(pluginName);
}
getPluginRegistry().addPlugin(plugin);
plugin.setLoggerRepository(this);
LogLog.debug("Pushing plugin on to the object stack.");
plugin.activateOptions();
return true;
}
}
return false;
}
/**
* Implementation of RendererSupportImpl if not
* provided by LoggerRepository.
*/
private static final class RendererSupportImpl implements RendererSupport {
/**
* Renderer map.
*/
private final RendererMap renderers = new RendererMap();
/**
* Create new instance.
*/
public RendererSupportImpl() {
super();
}
/** {@inheritDoc} */
public RendererMap getRendererMap() {
return renderers;
}
/** {@inheritDoc} */
public void setRenderer(final Class renderedClass,
final ObjectRenderer renderer) {
renderers.put(renderedClass, renderer);
}
}
/**
* Proxy that implements HierarchyEventListener
* and delegates to LoggerEventListener.
*/
private static final class HierarchyEventListenerProxy
implements HierarchyEventListener {
/**
* Wrapper listener.
*/
private LoggerEventListener listener;
/**
* Creates new instance.
* @param l listener
*/
public HierarchyEventListenerProxy(final LoggerEventListener l) {
super();
if (l == null) {
throw new NullPointerException("l");
}
listener = l;
}
/** {@inheritDoc} */
public void addAppenderEvent(final Category cat,
final Appender appender) {
if (isEnabled() && cat instanceof Logger) {
listener.appenderAddedEvent((Logger) cat, appender);
}
}
/** {@inheritDoc} */
public void removeAppenderEvent(final Category cat,
final Appender appender) {
if (isEnabled() && cat instanceof Logger) {
listener.appenderRemovedEvent((Logger) cat, appender);
}
}
/**
* Disable forwarding of notifications to
* simulate removal of listener.
*/
public synchronized void disable() {
listener = null;
}
/**
* Gets whether proxy is enabled.
* @return true if proxy is enabled.
*/
private synchronized boolean isEnabled() {
return listener != null;
}
}
}