ch.qos.logback.ext.spring.DelegatingLogbackAppender Maven / Gradle / Ivy
/**
* Copyright (C) 2014 The logback-extensions developers ([email protected])
*
* 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 ch.qos.logback.ext.spring;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import java.util.List;
/**
* A Logback {@code Appender} implementation which delegates the actual appending to a named bean contained in a Spring
* {@code ApplicationContext}.
*
* This appender is similar in spirit to Spring's {@code DelegatingFilterProxy}, which allows servlet filters to be
* created and wired in the ApplicationContext and then accessed in the filter chain. As with the filter proxy, the
* delegating appender uses its own name to find the target appender in the context.
*
* Because the logging framework is usually started before the Spring context, this appender supports caching for
* {@code ILoggingEvent}s which are received before the {@code ApplicationContext} is available. This caching has
* 3 possible modes:
*
* - off - Events are discarded until the {@code ApplicationContext} is available.
* - on - Events are cached with strong references until the {@code ApplicationContext} is available, at
* which time they will be forwarded to the delegate appender. In systems which produce substantial amounts of log
* events while starting up the {@code ApplicationContext} this mode may result in heavy memory usage.
* - soft - Events are wrapped in {@code SoftReference}s and cached until the {@code ApplicationContext}
* is available. Memory pressure may cause the garbage collector to collect some or all of the cached events before
* the {@code ApplicationContext} is available, so some or all events may be lost. However, in systems with heavy
* logging, this mode may result in more efficient memory usage.
*
* Caching is {@code on} by default, so strong references will be used for all events.
*
* An example of how to use this appender in {@code logback.xml}:
*
* <appender name="appenderBeanName" class="ch.qos.logback.ext.spring.DelegatingLogbackAppender"/>
*
*
* Or, if specifying a different cache mode, e.g.:
*
* <appender name="appenderBeanName" class="ch.qos.logback.ext.spring.DelegatingLogbackAppender">
* <cacheMode>soft</cacheMode>
* </appender>
*
* Using this appender requires that the {@link ApplicationContextHolder} be included in the {@code ApplicationContext}.
*
* @author Bryan Turner
* @since 0.1
*/
public class DelegatingLogbackAppender extends UnsynchronizedAppenderBase {
private final Object lock;
private String beanName;
private ILoggingEventCache cache;
private EventCacheMode cacheMode;
private volatile Appender delegate;
public DelegatingLogbackAppender() {
cacheMode = EventCacheMode.ON;
lock = new Object();
}
public void setCacheMode(String mode) {
cacheMode = Enum.valueOf(EventCacheMode.class, mode.toUpperCase());
}
@Override
public void start() {
if (isStarted()) {
return;
}
if (beanName == null || beanName.trim().isEmpty()) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalStateException("A 'name' or 'beanName' is required for DelegatingLogbackAppender");
}
beanName = name;
}
cache = cacheMode.createCache();
super.start();
}
@Override
public void stop() {
super.stop();
if (cache != null) {
cache = null;
}
if (delegate != null) {
delegate.stop();
delegate = null;
}
}
@Override
protected void append(ILoggingEvent event) {
//Double-check locking here to optimize out the synchronization after the delegate is in place. This also has
//the benefit of dealing with the race condition where 2 threads are trying to log and one gets the lock with
//the other waiting and the lead thread sets the delegate, logs all cached events and then returns, allowing
//the blocked thread to acquire the lock. At that time, the delegate is no longer null and the event is logged
//directly to it, rather than being cached.
if (delegate == null) {
synchronized (lock) {
//Note the isStarted() check here. If multiple threads are logging at the time the ApplicationContext
//becomes available, the first thread to acquire the lock _may_ stop this appender if the context does
//not contain an Appender with the expected name. If that happens, when the lock is released and other
//threads acquire it, isStarted() will return false and those threads should return without trying to
//use either the delegate or the cache--both of which will be null.
if (!isStarted()) {
return;
}
//If we're still started either no thread has attempted to load the delegate yet, or the delegate has
//been loaded successfully. If the latter, the delegate will no longer be null
if (delegate == null) {
if (ApplicationContextHolder.hasApplicationContext()) {
//First, load the delegate Appender from the ApplicationContext. If it cannot be loaded, this
//appender will be stopped and null will be returned.
Appender appender = getDelegate();
if (appender == null) {
return;
}
//Once we have the appender, unload the cache to it.
List cachedEvents = cache.get();
for (ILoggingEvent cachedEvent : cachedEvents) {
appender.doAppend(cachedEvent);
}
//If we've found our delegate appender, we no longer need the cache.
cache = null;
delegate = appender;
} else {
//Otherwise, if the ApplicationContext is not ready yet, cache this event and wait
cache.put(event);
return;
}
}
}
}
//If we make it here, the delegate should always be non-null and safe to append to.
delegate.doAppend(event);
}
private Appender getDelegate() {
ApplicationContext context = ApplicationContextHolder.getApplicationContext();
try {
@SuppressWarnings("unchecked")
Appender appender = context.getBean(beanName, Appender.class);
appender.setContext(getContext());
if (!appender.isStarted()) {
appender.start();
}
return appender;
} catch (NoSuchBeanDefinitionException e) {
stop();
addError("The ApplicationContext does not contain an Appender named [" + beanName +
"]. This delegating appender will now stop processing events.", e);
}
return null;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy