org.sonar.process.logging.Log4JPropertiesBuilderTest.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.process.logging.Log4JPropertiesBuilderTest.java

Source

/*
 * SonarQube
 * Copyright (C) 2009-2018 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.process.logging;

import ch.qos.logback.classic.Level;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.io.File;
import java.util.HashSet;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import org.apache.commons.lang.RandomStringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.sonar.process.MessageException;
import org.sonar.process.ProcessId;
import org.sonar.process.Props;

import static java.lang.String.valueOf;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder;

@RunWith(DataProviderRunner.class)
public class Log4JPropertiesBuilderTest {
    private static final String ROLLING_POLICY_PROPERTY = "sonar.log.rollingPolicy";
    private static final String PROPERTY_MAX_FILES = "sonar.log.maxFiles";

    @Rule
    public ExpectedException expectedException = ExpectedException.none();
    @Rule
    public TemporaryFolder temporaryFolder = new TemporaryFolder();

    private final RootLoggerConfig esRootLoggerConfig = newRootLoggerConfigBuilder()
            .setProcessId(ProcessId.ELASTICSEARCH).build();
    private final Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder();

    @Test
    public void constructor_fails_with_NPE_if_Props_is_null() {
        expectedException.expect(NullPointerException.class);
        expectedException.expectMessage("Props can't be null");

        new Log4JPropertiesBuilder(null);
    }

    @Test
    public void constructor_sets_status_to_ERROR() {
        Properties properties = underTest.get();

        assertThat(properties.getProperty("status")).isEqualTo("ERROR");
    }

    @Test
    public void getRootLoggerName_returns_rootLogger() {
        assertThat(underTest.getRootLoggerName()).isEqualTo("rootLogger");
    }

    @Test
    public void get_always_returns_a_new_object() {
        Properties previous = underTest.get();
        for (int i = 0; i < 2 + new Random().nextInt(5); i++) {
            Properties properties = underTest.get();
            assertThat(properties).isNotSameAs(previous);
            previous = properties;
        }
    }

    @Test
    public void buildLogPattern_puts_process_key_as_process_id() {
        String pattern = underTest
                .buildLogPattern(newRootLoggerConfigBuilder().setProcessId(ProcessId.ELASTICSEARCH).build());

        assertThat(pattern).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level es[][%logger{1.}] %msg%n");
    }

    @Test
    public void buildLogPattern_puts_threadIdFieldPattern_from_RootLoggerConfig_non_null() {
        String threadIdFieldPattern = RandomStringUtils.randomAlphabetic(5);

        String pattern = underTest.buildLogPattern(newRootLoggerConfigBuilder().setProcessId(ProcessId.APP)
                .setThreadIdFieldPattern(threadIdFieldPattern).build());

        assertThat(pattern).isEqualTo(
                "%d{yyyy.MM.dd HH:mm:ss} %-5level app[" + threadIdFieldPattern + "][%logger{1.}] %msg%n");
    }

    @Test
    public void buildLogPattern_does_not_put_threadIdFieldPattern_from_RootLoggerConfig_is_null() {
        String pattern = underTest
                .buildLogPattern(newRootLoggerConfigBuilder().setProcessId(ProcessId.COMPUTE_ENGINE).build());

        assertThat(pattern).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level ce[][%logger{1.}] %msg%n");
    }

    @Test
    public void buildLogPattern_does_not_put_threadIdFieldPattern_from_RootLoggerConfig_is_empty() {
        String pattern = underTest.buildLogPattern(newRootLoggerConfigBuilder().setProcessId(ProcessId.WEB_SERVER)
                .setThreadIdFieldPattern("").build());

        assertThat(pattern).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level web[][%logger{1.}] %msg%n");
    }

    @Test
    public void configureGlobalFileLog_sets_properties_for_daily_time_rolling_policy_with_max_7_files_for_empty_props()
            throws Exception {
        File logDir = temporaryFolder.newFolder();
        String logPattern = randomAlphanumeric(15);
        underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);

        verifyTimeRollingPolicy(underTest, logDir, logPattern, "yyyy-MM-dd", 7);
    }

    @Test
    public void time_rolling_policy_has_large_max_files_if_property_is_zero() throws Exception {
        File logDir = temporaryFolder.newFolder();
        String logPattern = "foo";
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(ROLLING_POLICY_PROPERTY, "time:yyyy-MM-dd",
                PROPERTY_MAX_FILES, "0");
        underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);

        verifyTimeRollingPolicy(underTest, logDir, logPattern, "yyyy-MM-dd", 100_000);
    }

    @Test
    public void time_rolling_policy_has_large_max_files_if_property_is_negative() throws Exception {
        File logDir = temporaryFolder.newFolder();
        String logPattern = "foo";
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(ROLLING_POLICY_PROPERTY, "time:yyyy-MM-dd",
                PROPERTY_MAX_FILES, "-2");
        underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);

        verifyTimeRollingPolicy(underTest, logDir, logPattern, "yyyy-MM-dd", 100_000);
    }

    @Test
    public void size_rolling_policy_has_large_max_files_if_property_is_zero() throws Exception {
        File logDir = temporaryFolder.newFolder();
        String logPattern = "foo";
        String sizePattern = "1KB";
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(ROLLING_POLICY_PROPERTY, "size:" + sizePattern,
                PROPERTY_MAX_FILES, "0");
        underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);

        verifySizeRollingPolicy(underTest, logDir, logPattern, sizePattern, 100_000);
    }

    @Test
    public void size_rolling_policy_has_large_max_files_if_property_is_negative() throws Exception {
        File logDir = temporaryFolder.newFolder();
        String logPattern = "foo";
        String sizePattern = "1KB";
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(ROLLING_POLICY_PROPERTY, "size:" + sizePattern,
                PROPERTY_MAX_FILES, "-2");
        underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);

        verifySizeRollingPolicy(underTest, logDir, logPattern, sizePattern, 100_000);
    }

    @Test
    public void configureGlobalFileLog_throws_MessageException_when_property_is_not_supported() throws Exception {
        File logDir = temporaryFolder.newFolder();
        String logPattern = randomAlphanumeric(15);
        String invalidPropertyValue = randomAlphanumeric(3);
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(ROLLING_POLICY_PROPERTY, invalidPropertyValue);

        expectedException.expect(MessageException.class);
        expectedException.expectMessage(
                "Unsupported value for property " + ROLLING_POLICY_PROPERTY + ": " + invalidPropertyValue);

        underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);
    }

    @Test
    public void configureGlobalFileLog_sets_properties_for_time_rolling_policy_with_max_7_files_when_property_starts_with_time_colon()
            throws Exception {
        File logDir = temporaryFolder.newFolder();
        String logPattern = randomAlphanumeric(15);
        String timePattern = randomAlphanumeric(6);
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(ROLLING_POLICY_PROPERTY,
                "time:" + timePattern);
        underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);

        verifyTimeRollingPolicy(underTest, logDir, logPattern, timePattern, 7);
    }

    @Test
    public void configureGlobalFileLog_sets_properties_for_time_rolling_policy_when_property_starts_with_time_colon_and_specified_max_number_of_files()
            throws Exception {
        File logDir = temporaryFolder.newFolder();
        String logPattern = randomAlphanumeric(15);
        String timePattern = randomAlphanumeric(6);
        int maxFile = 1 + new Random().nextInt(10);
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(ROLLING_POLICY_PROPERTY, "time:" + timePattern,
                PROPERTY_MAX_FILES, valueOf(maxFile));
        underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);

        verifyTimeRollingPolicy(underTest, logDir, logPattern, timePattern, maxFile);
    }

    @Test
    public void configureGlobalFileLog_sets_properties_for_size_rolling_policy_with_max_7_files_when_property_starts_with_size_colon()
            throws Exception {
        File logDir = temporaryFolder.newFolder();
        String logPattern = randomAlphanumeric(15);
        String sizePattern = randomAlphanumeric(6);
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(ROLLING_POLICY_PROPERTY,
                "size:" + sizePattern);
        underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);

        verifySizeRollingPolicy(underTest, logDir, logPattern, sizePattern, 7);
    }

    @Test
    public void configureGlobalFileLog_sets_properties_for_size_rolling_policy_when_property_starts_with_size_colon_and_specified_max_number_of_files()
            throws Exception {
        File logDir = temporaryFolder.newFolder();
        String logPattern = randomAlphanumeric(15);
        String sizePattern = randomAlphanumeric(6);
        int maxFile = 1 + new Random().nextInt(10);
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(ROLLING_POLICY_PROPERTY, "size:" + sizePattern,
                PROPERTY_MAX_FILES, valueOf(maxFile));
        underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);

        verifySizeRollingPolicy(underTest, logDir, logPattern, sizePattern, maxFile);
    }

    @Test
    public void configureGlobalFileLog_sets_properties_for_no_rolling_policy_when_property_is_none()
            throws Exception {
        File logDir = temporaryFolder.newFolder();
        String logPattern = randomAlphanumeric(15);
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(ROLLING_POLICY_PROPERTY, "none");
        underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);

        verifyPropertiesForConfigureGlobalFileLog(underTest.get(), "appender.file_es.type", "File",
                "appender.file_es.name", "file_es", "appender.file_es.fileName",
                new File(logDir, "es.log").getAbsolutePath(), "appender.file_es.layout.type", "PatternLayout",
                "appender.file_es.layout.pattern", logPattern, "rootLogger.appenderRef.file_es.ref", "file_es");
    }

    @Test
    public void apply_fails_with_IAE_if_LogLevelConfig_does_not_have_rootLoggerName_of_Log4J() {
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder();
        LogLevelConfig logLevelConfig = LogLevelConfig.newBuilder(randomAlphanumeric(2)).build();

        expectedException.expect(IllegalArgumentException.class);
        expectedException.expectMessage("Value of LogLevelConfig#rootLoggerName must be \"rootLogger\"");

        underTest.apply(logLevelConfig);
    }

    @Test
    public void apply_fails_with_IAE_if_global_property_has_unsupported_level() {
        LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();

        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", "ERROR");

        expectedException.expect(IllegalArgumentException.class);
        expectedException.expectMessage(
                "log level ERROR in property sonar.log.level is not a supported value (allowed levels are [TRACE, DEBUG, INFO])");

        underTest.apply(config);
    }

    @Test
    public void apply_fails_with_IAE_if_process_property_has_unsupported_level() {
        LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();

        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level.web", "ERROR");

        expectedException.expect(IllegalArgumentException.class);
        expectedException.expectMessage(
                "log level ERROR in property sonar.log.level.web is not a supported value (allowed levels are [TRACE, DEBUG, INFO])");

        underTest.apply(config);
    }

    @Test
    public void apply_sets_root_logger_to_INFO_if_no_property_is_set() {
        LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();

        underTest.apply(config);

        verifyRootLoggerLevel(underTest, Level.INFO);
    }

    @Test
    public void apply_sets_root_logger_to_global_property_if_set() {
        LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();

        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", "TRACE");

        underTest.apply(config);

        verifyRootLoggerLevel(underTest, Level.TRACE);
    }

    @Test
    public void apply_sets_root_logger_to_process_property_if_set() {
        LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();

        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level.web", "DEBUG");

        underTest.apply(config);

        verifyRootLoggerLevel(underTest, Level.DEBUG);
    }

    @Test
    public void apply_sets_root_logger_to_process_property_over_global_property_if_both_set() {
        LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", "DEBUG",
                "sonar.log.level.web", "TRACE");

        underTest.apply(config);

        verifyRootLoggerLevel(underTest, Level.TRACE);
    }

    @Test
    public void apply_sets_domain_property_over_process_and_global_property_if_all_set() {
        LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES)
                .build();
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", "DEBUG",
                "sonar.log.level.web", "DEBUG", "sonar.log.level.web.es", "TRACE");

        underTest.apply(config);

        verifyLoggerProperties(underTest.get(), "foo", Level.TRACE);
    }

    @Test
    public void apply_sets_domain_property_over_process_property_if_both_set() {
        LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES)
                .build();
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level.web", "DEBUG",
                "sonar.log.level.web.es", "TRACE");

        underTest.apply(config);

        verifyLoggerProperties(underTest.get(), "foo", Level.TRACE);
    }

    @Test
    public void apply_sets_domain_property_over_global_property_if_both_set() {
        LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES)
                .build();
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", "DEBUG",
                "sonar.log.level.web.es", "TRACE");

        underTest.apply(config);

        verifyLoggerProperties(underTest.get(), "foo", Level.TRACE);
    }

    @Test
    public void apply_fails_with_IAE_if_domain_property_has_unsupported_level() {
        LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.JMX)
                .build();

        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level.web.jmx", "ERROR");

        expectedException.expect(IllegalArgumentException.class);
        expectedException.expectMessage(
                "log level ERROR in property sonar.log.level.web.jmx is not a supported value (allowed levels are [TRACE, DEBUG, INFO])");

        underTest.apply(config);
    }

    @Test
    @UseDataProvider("logbackLevels")
    public void apply_accepts_any_level_as_hardcoded_level(Level level) {
        LogLevelConfig config = newLogLevelConfig().immutableLevel("bar", level).build();

        underTest.apply(config);

        verifyLoggerProperties(underTest.get(), "bar", level);
    }

    @Test
    public void apply_set_level_to_OFF_if_sonar_global_level_is_not_set() {
        underTest.apply(newLogLevelConfig().offUnlessTrace("fii").build());

        verifyLoggerProperties(underTest.get(), "fii", Level.OFF);
    }

    @Test
    public void apply_set_level_to_OFF_if_sonar_global_level_is_INFO() {
        setLevelToOff(Level.INFO);
    }

    @Test
    public void apply_set_level_to_OFF_if_sonar_global_level_is_DEBUG() {
        setLevelToOff(Level.DEBUG);
    }

    @Test
    public void apply_does_not_create_loggers_property_if_only_root_level_is_defined() {
        LogLevelConfig logLevelConfig = newLogLevelConfig().rootLevelFor(ProcessId.APP).build();

        underTest.apply(logLevelConfig);

        assertThat(underTest.get().getProperty("loggers")).isNull();
    }

    @Test
    public void apply_creates_loggers_property_with_logger_names_ordered_but_root() {
        LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER)
                .levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.JMX)
                .levelByDomain("bar", ProcessId.COMPUTE_ENGINE, LogDomain.ES).immutableLevel("doh", Level.ERROR)
                .immutableLevel("pif", Level.TRACE).offUnlessTrace("fii").build();

        underTest.apply(config);

        assertThat(underTest.get().getProperty("loggers")).isEqualTo("bar,doh,fii,foo,pif");
    }

    private void setLevelToOff(Level globalLogLevel) {
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", globalLogLevel.toString());

        underTest.apply(newLogLevelConfig().offUnlessTrace("fii").build());

        verifyLoggerProperties(underTest.get(), "fii", Level.OFF);
    }

    @Test
    public void apply_does_not_set_level_if_sonar_global_level_is_TRACE() {
        Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", Level.TRACE.toString());

        underTest.apply(newLogLevelConfig().offUnlessTrace("fii").build());

        verifyNoLoggerProperties(underTest.get(), "fii");
    }

    private static Log4JPropertiesBuilder newLog4JPropertiesBuilder(String... propertyKeysAndValues) {
        Properties properties = new Properties();
        assertThat(propertyKeysAndValues.length % 2).describedAs("propertyKeysAndValues must have even length")
                .isEqualTo(0);
        for (int i = 0; i < propertyKeysAndValues.length; i++) {
            properties.put(propertyKeysAndValues[i++], propertyKeysAndValues[i]);
        }
        return new Log4JPropertiesBuilder(new Props(properties));
    }

    private void verifyTimeRollingPolicy(Log4JPropertiesBuilder builder, File logDir, String logPattern,
            String datePattern, int maxFiles) {
        verifyPropertiesForConfigureGlobalFileLog(builder.get(), "appender.file_es.type", "RollingFile",
                "appender.file_es.name", "file_es", "appender.file_es.filePattern",
                new File(logDir, "es.%d{" + datePattern + "}.log").getAbsolutePath(), "appender.file_es.fileName",
                new File(logDir, "es.log").getAbsolutePath(), "appender.file_es.layout.type", "PatternLayout",
                "appender.file_es.layout.pattern", logPattern, "appender.file_es.policies.type", "Policies",
                "appender.file_es.policies.time.type", "TimeBasedTriggeringPolicy",
                "appender.file_es.policies.time.interval", "1", "appender.file_es.policies.time.modulate", "true",
                "appender.file_es.strategy.type", "DefaultRolloverStrategy", "appender.file_es.strategy.fileIndex",
                "nomax", "appender.file_es.strategy.action.type", "Delete",
                "appender.file_es.strategy.action.basepath", logDir.getAbsolutePath(),
                "appender.file_es.strategy.action.maxDepth", "1", "appender.file_es.strategy.action.condition.type",
                "IfFileName", "appender.file_es.strategy.action.condition.glob", "es*",
                "appender.file_es.strategy.action.condition.nested_condition.type", "IfAccumulatedFileCount",
                "appender.file_es.strategy.action.condition.nested_condition.exceeds", valueOf(maxFiles),
                "rootLogger.appenderRef.file_es.ref", "file_es");
    }

    private void verifySizeRollingPolicy(Log4JPropertiesBuilder builder, File logDir, String logPattern,
            String sizePattern, int maxFiles) {
        verifyPropertiesForConfigureGlobalFileLog(builder.get(), "appender.file_es.type", "RollingFile",
                "appender.file_es.name", "file_es", "appender.file_es.filePattern",
                new File(logDir, "es.%i.log").getAbsolutePath(), "appender.file_es.fileName",
                new File(logDir, "es.log").getAbsolutePath(), "appender.file_es.layout.type", "PatternLayout",
                "appender.file_es.layout.pattern", logPattern, "appender.file_es.policies.type", "Policies",
                "appender.file_es.policies.size.type", "SizeBasedTriggeringPolicy",
                "appender.file_es.policies.size.size", sizePattern, "appender.file_es.strategy.type",
                "DefaultRolloverStrategy", "appender.file_es.strategy.max", valueOf(maxFiles),
                "rootLogger.appenderRef.file_es.ref", "file_es");
    }

    private void verifyPropertiesForConfigureGlobalFileLog(Properties properties,
            String... expectedPropertyKeysAndValuesOrdered) {
        assertThat(properties.get("status")).isEqualTo("ERROR");
        if (expectedPropertyKeysAndValuesOrdered.length == 0) {
            assertThat(properties.size()).isEqualTo(1);
        } else {
            assertThat(expectedPropertyKeysAndValuesOrdered.length % 2)
                    .describedAs("Number of parameters must be even").isEqualTo(0);
            Set<String> keys = new HashSet<>(expectedPropertyKeysAndValuesOrdered.length / 2 + 1);
            keys.add("status");
            for (int i = 0; i < expectedPropertyKeysAndValuesOrdered.length; i++) {
                String key = expectedPropertyKeysAndValuesOrdered[i++];
                String value = expectedPropertyKeysAndValuesOrdered[i];
                assertThat(properties.get(key)).describedAs("Unexpected value for property " + key)
                        .isEqualTo(value);
                keys.add(key);
            }
            assertThat(properties.keySet()).containsOnly(keys.toArray());
        }
    }

    private LogLevelConfig.Builder newLogLevelConfig() {
        return LogLevelConfig.newBuilder("rootLogger");
    }

    private void verifyLoggerProperties(Properties properties, String loggerName, Level expectedLevel) {
        assertThat(properties.get("logger." + loggerName + ".name")).isEqualTo(loggerName);
        assertThat(properties.get("logger." + loggerName + ".level")).isEqualTo(expectedLevel.toString());
    }

    private void verifyNoLoggerProperties(Properties properties, String loggerName) {
        assertThat(properties.get("logger." + loggerName + ".name")).isNull();
        assertThat(properties.get("logger." + loggerName + ".level")).isNull();
    }

    private void verifyRootLoggerLevel(Log4JPropertiesBuilder underTest, Level expectedLevel) {
        assertThat(underTest.get().get("rootLogger.level")).isEqualTo(expectedLevel.toString());
    }

    @DataProvider
    public static Object[][] logbackLevels() {
        return new Object[][] { { Level.OFF }, { Level.ERROR }, { Level.WARN }, { Level.INFO }, { Level.DEBUG },
                { Level.TRACE }, { Level.ALL } };
    }
}