/*
* Copyright 2012-2017 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 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;
import org.slf4j.Marker;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@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;
/**
* Create a new {@link LevelRemappingAppender}.
*/
public LevelRemappingAppender() {
}
/**
* Create a new {@link LevelRemappingAppender} with a specific destination logger.
* @param destinationLogger the destination logger
*/
public LevelRemappingAppender(String destinationLogger) {
this.destinationLogger = destinationLogger;
}
@Override
protected void append(ILoggingEvent event) {
AppendableLogger logger = getLogger(this.destinationLogger);
Level remapped = this.remapLevels.get(event.getLevel());
logger.callAppenders(remapped == null ? event : 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<>();
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;
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.getMDCPropertyMap();
}
@Override
public long getTimeStamp() {
return this.event.getTimeStamp();
}
@Override
public void prepareForDeferredProcessing() {
this.event.prepareForDeferredProcessing();
}
}
}