/*
* 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.log4j2;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.logging.AbstractLoggingSystemTests;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggerConfiguration;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.boot.testutil.InternalOutputCapture;
import org.springframework.boot.testutil.Matched;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link Log4J2LoggingSystem}.
*
* @author Daniel Fullarton
* @author Phillip Webb
* @author Andy Wilkinson
* @author Ben Hale
*/
public class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
@Rule
public InternalOutputCapture output = new InternalOutputCapture();
private final TestLog4J2LoggingSystem loggingSystem = new TestLog4J2LoggingSystem();
private Logger logger;
@Before
public void setup() {
this.loggingSystem.cleanUp();
this.logger = LogManager.getLogger(getClass());
}
@Test
public void noFile() throws Exception {
this.loggingSystem.beforeInitialize();
this.logger.info("Hidden");
this.loggingSystem.initialize(null, null, null);
this.logger.info("Hello world");
String output = this.output.toString().trim();
Configuration configuration = this.loggingSystem.getConfiguration();
assertThat(output).contains("Hello world").doesNotContain("Hidden");
assertThat(new File(tmpDir() + "/spring.log").exists()).isFalse();
assertThat(configuration.getConfigurationSource().getFile()).isNotNull();
}
@Test
public void withFile() throws Exception {
this.loggingSystem.beforeInitialize();
this.logger.info("Hidden");
this.loggingSystem.initialize(null, null, getLogFile(null, tmpDir()));
this.logger.info("Hello world");
String output = this.output.toString().trim();
Configuration configuration = this.loggingSystem.getConfiguration();
assertThat(output).contains("Hello world").doesNotContain("Hidden");
assertThat(new File(tmpDir() + "/spring.log").exists()).isTrue();
assertThat(configuration.getConfigurationSource().getFile()).isNotNull();
}
@Test
public void testNonDefaultConfigLocation() throws Exception {
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, "classpath:log4j2-nondefault.xml",
getLogFile(tmpDir() + "/tmp.log", null));
this.logger.info("Hello world");
String output = this.output.toString().trim();
Configuration configuration = this.loggingSystem.getConfiguration();
assertThat(output).contains("Hello world").contains(tmpDir() + "/tmp.log");
assertThat(new File(tmpDir() + "/tmp.log").exists()).isFalse();
assertThat(configuration.getConfigurationSource().getFile().getAbsolutePath())
.contains("log4j2-nondefault.xml");
assertThat(configuration.getWatchManager().getIntervalSeconds()).isEqualTo(30);
}
@Test(expected = IllegalStateException.class)
public void testNonexistentConfigLocation() throws Exception {
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, "classpath:log4j2-nonexistent.xml", null);
}
@Test
public void getSupportedLevels() {
assertThat(this.loggingSystem.getSupportedLogLevels())
.isEqualTo(EnumSet.allOf(LogLevel.class));
}
@Test
public void setLevel() throws Exception {
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, null, null);
this.logger.debug("Hello");
this.loggingSystem.setLogLevel("org.springframework.boot", LogLevel.DEBUG);
this.logger.debug("Hello");
assertThat(StringUtils.countOccurrencesOf(this.output.toString(), "Hello"))
.isEqualTo(1);
}
@Test
public void getLoggingConfigurations() throws Exception {
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, null, null);
this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG);
List<LoggerConfiguration> configurations = this.loggingSystem
.getLoggerConfigurations();
assertThat(configurations).isNotEmpty();
assertThat(configurations.get(0).getName())
.isEqualTo(LoggingSystem.ROOT_LOGGER_NAME);
}
@Test
public void getLoggingConfiguration() throws Exception {
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, null, null);
this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG);
LoggerConfiguration configuration = this.loggingSystem
.getLoggerConfiguration(getClass().getName());
assertThat(configuration).isEqualTo(new LoggerConfiguration(getClass().getName(),
LogLevel.DEBUG, LogLevel.DEBUG));
}
@Test
public void setLevelOfUnconfiguredLoggerDoesNotAffectRootConfiguration()
throws Exception {
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, null, null);
LogManager.getRootLogger().debug("Hello");
this.loggingSystem.setLogLevel("foo.bar.baz", LogLevel.DEBUG);
LogManager.getRootLogger().debug("Hello");
assertThat(this.output.toString()).doesNotContain("Hello");
}
@Test
@Ignore("Fails on Bamboo")
public void loggingThatUsesJulIsCaptured() {
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, null, null);
java.util.logging.Logger julLogger = java.util.logging.Logger
.getLogger(getClass().getName());
julLogger.severe("Hello world");
String output = this.output.toString().trim();
assertThat(output).contains("Hello world");
}
@Test
public void configLocationsWithNoExtraDependencies() {
assertThat(this.loggingSystem.getStandardConfigLocations())
.contains("log4j2.xml");
}
@Test
public void configLocationsWithJacksonDatabind() {
this.loggingSystem.availableClasses(ObjectMapper.class.getName());
assertThat(this.loggingSystem.getStandardConfigLocations())
.contains("log4j2.json", "log4j2.jsn", "log4j2.xml");
}
@Test
public void configLocationsWithJacksonDataformatYaml() {
this.loggingSystem
.availableClasses("com.fasterxml.jackson.dataformat.yaml.YAMLParser");
assertThat(this.loggingSystem.getStandardConfigLocations())
.contains("log4j2.yaml", "log4j2.yml", "log4j2.xml");
}
@Test
public void configLocationsWithJacksonDatabindAndDataformatYaml() {
this.loggingSystem.availableClasses(
"com.fasterxml.jackson.dataformat.yaml.YAMLParser",
ObjectMapper.class.getName());
assertThat(this.loggingSystem.getStandardConfigLocations()).contains(
"log4j2.yaml", "log4j2.yml", "log4j2.json", "log4j2.jsn", "log4j2.xml");
}
@Test
public void springConfigLocations() throws Exception {
String[] locations = getSpringConfigLocations(this.loggingSystem);
assertThat(locations).isEqualTo(new String[] { "log4j2-spring.xml" });
}
@Test
public void exceptionsIncludeClassPackaging() throws Exception {
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, null, getLogFile(null, tmpDir()));
Matcher<String> expectedOutput = containsString("[junit-");
this.output.expect(expectedOutput);
this.logger.warn("Expected exception", new RuntimeException("Expected"));
String fileContents = FileCopyUtils
.copyToString(new FileReader(new File(tmpDir() + "/spring.log")));
assertThat(fileContents).is(Matched.by(expectedOutput));
}
@Test
public void beforeInitializeFilterDisablesErrorLogging() throws Exception {
this.loggingSystem.beforeInitialize();
assertThat(this.logger.isErrorEnabled()).isFalse();
this.loggingSystem.initialize(null, null, getLogFile(null, tmpDir()));
}
@Test
public void customExceptionConversionWord() throws Exception {
System.setProperty("LOG_EXCEPTION_CONVERSION_WORD", "%ex");
try {
this.loggingSystem.beforeInitialize();
this.logger.info("Hidden");
this.loggingSystem.initialize(null, null, getLogFile(null, tmpDir()));
Matcher<String> expectedOutput = Matchers.allOf(
containsString("java.lang.RuntimeException: Expected"),
not(containsString("Wrapped by:")));
this.output.expect(expectedOutput);
this.logger.warn("Expected exception",
new RuntimeException("Expected", new RuntimeException("Cause")));
String fileContents = FileCopyUtils
.copyToString(new FileReader(new File(tmpDir() + "/spring.log")));
assertThat(fileContents).is(Matched.by(expectedOutput));
}
finally {
System.clearProperty("LOG_EXCEPTION_CONVERSION_WORD");
}
}
@Test
public void initializationIsOnlyPerformedOnceUntilCleanedUp() throws Exception {
LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
PropertyChangeListener listener = mock(PropertyChangeListener.class);
loggerContext.addPropertyChangeListener(listener);
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, null, null);
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, null, null);
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, null, null);
verify(listener, times(2)).propertyChange(any(PropertyChangeEvent.class));
this.loggingSystem.cleanUp();
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, null, null);
verify(listener, times(4)).propertyChange(any(PropertyChangeEvent.class));
}
private static class TestLog4J2LoggingSystem extends Log4J2LoggingSystem {
private List<String> availableClasses = new ArrayList<>();
TestLog4J2LoggingSystem() {
super(TestLog4J2LoggingSystem.class.getClassLoader());
}
public Configuration getConfiguration() {
return ((org.apache.logging.log4j.core.LoggerContext) LogManager
.getContext(false)).getConfiguration();
}
@Override
protected boolean isClassAvailable(String className) {
return this.availableClasses.contains(className);
}
private void availableClasses(String... classNames) {
Collections.addAll(this.availableClasses, classNames);
}
}
}