Add LevelRemappingAppender and remap thymeleaf

Add a LevelRemappingAppender that can remap the level of logback events
as they are written.

Also update the base configuration to change the somewhat noisy
Thymeleaf INFO logging to DEBUG.

Fixes gh-265
pull/276/head
Phillip Webb 11 years ago
parent b3f5d556bc
commit 932c3c206b

@ -0,0 +1,201 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* 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 org.springframework.boot.logging.logback;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Marker;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.LoggerContextVO;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.AppenderBase;
/**
* {@link Appender} that can remap {@link ILoggingEvent} {@link Level}s as they are
* written.
*
* @author Phillip Webb
* @see #setRemapLevels(String)
* @see #setDestinationLogger(String)
*/
public class LevelRemappingAppender extends AppenderBase<ILoggingEvent> {
private static final Map<Level, Level> DEFAULT_REMAPS = Collections.singletonMap(
Level.INFO, Level.DEBUG);
private String destinationLogger = Logger.ROOT_LOGGER_NAME;
private Map<Level, Level> remapLevels = DEFAULT_REMAPS;
@Override
protected void append(ILoggingEvent event) {
Level remappedLevel = this.remapLevels.get(event.getLevel());
if (remappedLevel != null) {
AppendableLogger logger = getLogger(this.destinationLogger);
logger.callAppenders(new RemappedLoggingEvent(event));
}
}
protected AppendableLogger getLogger(String name) {
LoggerContext loggerContext = (LoggerContext) this.context;
return new AppendableLogger(loggerContext.getLogger(name));
}
/**
* Sets the destination logger that will be used to send remapped events. If not
* specified the root logger is used.
* @param destinationLogger the destinationLogger name
*/
public void setDestinationLogger(String destinationLogger) {
Assert.hasLength(destinationLogger, "DestinationLogger must not be empty");
this.destinationLogger = destinationLogger;
}
/**
* Set the remapped level.
* @param remapLevels Comma separated String of remapped levels in the form
* {@literal "FROM->TO"}. For example, {@literal "DEBUG->TRACE,ERROR->WARN"}.
*/
public void setRemapLevels(String remapLevels) {
Assert.hasLength(remapLevels, "RemapLevels must not be empty");
this.remapLevels = new HashMap<Level, Level>();
for (String remap : StringUtils.commaDelimitedListToStringArray(remapLevels)) {
String[] split = StringUtils.split(remap, "->");
Assert.notNull(split, "Remap element '" + remap + "' must contain '->'");
this.remapLevels.put(Level.toLevel(split[0]), Level.toLevel(split[1]));
}
}
/**
* Simple wrapper around a logger that can have events appended.
*/
protected static class AppendableLogger {
private Logger logger;
public AppendableLogger(Logger logger) {
this.logger = logger;
}
public void callAppenders(ILoggingEvent event) {
if (this.logger.isEnabledFor(event.getLevel())) {
this.logger.callAppenders(event);
}
}
}
/**
* Decorate an existing {@link ILoggingEvent} changing the level to DEBUG.
*/
private class RemappedLoggingEvent implements ILoggingEvent {
private final ILoggingEvent event;
public RemappedLoggingEvent(ILoggingEvent event) {
this.event = event;
}
@Override
public String getThreadName() {
return this.event.getThreadName();
}
@Override
public Level getLevel() {
Level remappedLevel = LevelRemappingAppender.this.remapLevels.get(this.event
.getLevel());
return (remappedLevel == null ? this.event.getLevel() : remappedLevel);
}
@Override
public String getMessage() {
return this.event.getMessage();
}
@Override
public Object[] getArgumentArray() {
return this.event.getArgumentArray();
}
@Override
public String getFormattedMessage() {
return this.event.getFormattedMessage();
}
@Override
public String getLoggerName() {
return this.event.getLoggerName();
}
@Override
public LoggerContextVO getLoggerContextVO() {
return this.event.getLoggerContextVO();
}
@Override
public IThrowableProxy getThrowableProxy() {
return this.event.getThrowableProxy();
}
@Override
public StackTraceElement[] getCallerData() {
return this.event.getCallerData();
}
@Override
public boolean hasCallerData() {
return this.event.hasCallerData();
}
@Override
public Marker getMarker() {
return this.event.getMarker();
}
@Override
public Map<String, String> getMDCPropertyMap() {
return this.event.getMDCPropertyMap();
}
@Override
@Deprecated
public Map<String, String> getMdc() {
return this.event.getMdc();
}
@Override
public long getTimeStamp() {
return this.event.getTimeStamp();
}
@Override
public void prepareForDeferredProcessing() {
this.event.prepareForDeferredProcessing();
}
}
}

@ -28,6 +28,10 @@
</triggeringPolicy>
</appender>
<appender name="DEBUG_LEVEL_REMAPPER" class="org.springframework.boot.logging.logback.LevelRemappingAppender">
<destinationLogger>org.springframework.boot</destinationLogger>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
@ -38,5 +42,8 @@
<logger name="org.crsh.plugin" level="WARN"/>
<logger name="org.apache.tomcat.util.net.NioSelectorPool" level="WARN"/>
<logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR"/>
<logger name="org.thymeleaf" additivity="false">
<appender-ref ref="DEBUG_LEVEL_REMAPPER"/>
</logger>
</included>

@ -0,0 +1,103 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* 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 org.springframework.boot.logging.logback;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.logging.logback.LevelRemappingAppender.AppendableLogger;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link LevelRemappingAppender}.
*
* @author Phillip Webb
*/
public class LevelRemappingAppenderTests {
private TestableLevelRemappingAppender appender;
@Mock
private AppendableLogger logger;
@Captor
private ArgumentCaptor<ILoggingEvent> logCaptor;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.appender = spy(new TestableLevelRemappingAppender());
}
@Test
public void useRootLoggerIfNoDestination() throws Exception {
this.appender.append(mockLogEvent(Level.INFO));
verify(this.appender).getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
}
@Test
public void useSpecificDestination() throws Exception {
this.appender.setDestinationLogger("org.mine");
this.appender.append(mockLogEvent(Level.INFO));
verify(this.appender).getLogger("org.mine");
}
@Test
public void defaltRemapsInfo() throws Exception {
this.appender.append(mockLogEvent(Level.INFO));
verify(this.logger).callAppenders(this.logCaptor.capture());
assertThat(this.logCaptor.getValue().getLevel(), equalTo(Level.DEBUG));
}
@Test
public void customRemaps() throws Exception {
this.appender.setRemapLevels("DEBUG->TRACE,ERROR->WARN");
this.appender.append(mockLogEvent(Level.DEBUG));
this.appender.append(mockLogEvent(Level.ERROR));
verify(this.logger, times(2)).callAppenders(this.logCaptor.capture());
assertThat(this.logCaptor.getAllValues().get(0).getLevel(), equalTo(Level.TRACE));
assertThat(this.logCaptor.getAllValues().get(1).getLevel(), equalTo(Level.WARN));
}
private ILoggingEvent mockLogEvent(Level level) {
ILoggingEvent event = mock(ILoggingEvent.class);
given(event.getLevel()).willReturn(level);
return event;
}
private class TestableLevelRemappingAppender extends LevelRemappingAppender {
@Override
protected AppendableLogger getLogger(String name) {
return LevelRemappingAppenderTests.this.logger;
}
}
}
Loading…
Cancel
Save