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-265pull/276/head
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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…
Reference in New Issue