From bb03cec435d188cc7fd5576eced84eaa2c634212 Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 5 Aug 2021 09:30:32 +0200 Subject: [PATCH] SONAR-15243 Enable JSON logging on Elasticsearch process --- .../org/sonar/application/es/EsLogging.java | 34 +- .../org/sonar/application/AppLoggingTest.java | 2 +- .../org/sonar/process/ProcessProperties.java | 14 +- .../logging/Log4JPropertiesBuilder.java | 181 ++++--- .../sonar/process/logging/LogbackHelper.java | 3 +- .../process/logging/LogbackJsonLayout.java | 5 +- .../logging/Log4JPropertiesBuilderTest.java | 449 +++++++++++++----- .../process/logging/LogbackHelperTest.java | 2 +- .../logging/LogbackJsonLayoutTest.java | 26 +- .../app/WebServerProcessLoggingTest.java | 2 +- 10 files changed, 474 insertions(+), 244 deletions(-) diff --git a/server/sonar-main/src/main/java/org/sonar/application/es/EsLogging.java b/server/sonar-main/src/main/java/org/sonar/application/es/EsLogging.java index 47c8a675a99..75e105fa18b 100644 --- a/server/sonar-main/src/main/java/org/sonar/application/es/EsLogging.java +++ b/server/sonar-main/src/main/java/org/sonar/application/es/EsLogging.java @@ -30,6 +30,7 @@ import org.sonar.process.logging.LogLevelConfig; import org.sonar.process.logging.RootLoggerConfig; import static org.sonar.process.ProcessProperties.Property.LOG_CONSOLE; +import static org.sonar.process.ProcessProperties.Property.LOG_JSON_OUTPUT; import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder; public class EsLogging { @@ -39,21 +40,26 @@ public class EsLogging { RootLoggerConfig config = newRootLoggerConfigBuilder().setProcessId(ProcessId.ELASTICSEARCH).build(); String logPattern = log4JPropertiesBuilder.buildLogPattern(config); - log4JPropertiesBuilder.internalLogLevel(Level.ERROR); - log4JPropertiesBuilder.configureGlobalFileLog(config, logDir, logPattern); - if (isAllLogsToConsoleEnabled(props)) { - log4JPropertiesBuilder.configureGlobalStdoutLog(logPattern); - } - log4JPropertiesBuilder.apply( - LogLevelConfig.newBuilder(log4JPropertiesBuilder.getRootLoggerName()) - .rootLevelFor(ProcessId.ELASTICSEARCH) - // turn off ES type deprecation logging to not flood logs - .immutableLevel("DEPRECATION", Level.ERROR) - .immutableLevel("org.elasticsearch.deprecation", Level.ERROR) - .immutableLevel("org.elasticsearch.client.RestClient", Level.ERROR) - .build()); + return log4JPropertiesBuilder.internalLogLevel(Level.ERROR) + .rootLoggerConfig(config) + .logPattern(logPattern) + .enableAllLogsToConsole(isAllLogsToConsoleEnabled(props)) + .jsonOutput(isJsonOutput(props)) + .logDir(logDir) + .logLevelConfig( + LogLevelConfig.newBuilder(log4JPropertiesBuilder.getRootLoggerName()) + .rootLevelFor(ProcessId.ELASTICSEARCH) + // turn off ES type deprecation logging to not flood logs + .immutableLevel("DEPRECATION", Level.ERROR) + .immutableLevel("org.elasticsearch.deprecation", Level.ERROR) + .immutableLevel("org.elasticsearch.client.RestClient", Level.ERROR) + .build()) + .build(); + } - return log4JPropertiesBuilder.get(); + private static boolean isJsonOutput(Props props) { + return props.valueAsBoolean(LOG_JSON_OUTPUT.getKey(), + Boolean.parseBoolean(LOG_JSON_OUTPUT.getDefaultValue())); } /** diff --git a/server/sonar-main/src/test/java/org/sonar/application/AppLoggingTest.java b/server/sonar-main/src/test/java/org/sonar/application/AppLoggingTest.java index 4c639f80f94..9da2b90899c 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/AppLoggingTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/AppLoggingTest.java @@ -259,7 +259,7 @@ public class AppLoggingTest { @Test public void use_json_output() { - settings.getProps().set("sonar.log.useJsonOutput", "true"); + settings.getProps().set("sonar.log.jsonOutput", "true"); LoggerContext ctx = underTest.configure(); Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME); diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java index 72c99d1b2d0..fe9e97951e8 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java @@ -45,6 +45,7 @@ import static org.sonar.process.ProcessProperties.Property.SEARCH_PORT; * They are almost all the properties defined in conf/sonar.properties. */ public class ProcessProperties { + private static final String DEFAULT_FALSE = Boolean.FALSE.toString(); private final ServiceLoaderWrapper serviceLoaderWrapper; @@ -75,6 +76,7 @@ public class ProcessProperties { LOG_ROLLING_POLICY("sonar.log.rollingPolicy"), LOG_MAX_FILES("sonar.log.maxFiles"), LOG_CONSOLE("sonar.log.console"), + LOG_JSON_OUTPUT("sonar.log.jsonOutput", DEFAULT_FALSE), SEARCH_HOST("sonar.search.host"), SEARCH_PORT("sonar.search.port"), @@ -113,8 +115,8 @@ public class ProcessProperties { SOCKS_PROXY_HOST("socksProxyHost"), SOCKS_PROXY_PORT("socksProxyPort"), - CLUSTER_ENABLED("sonar.cluster.enabled", "false"), - CLUSTER_KUBERNETES("sonar.cluster.kubernetes", "false"), + CLUSTER_ENABLED("sonar.cluster.enabled", DEFAULT_FALSE), + CLUSTER_KUBERNETES("sonar.cluster.kubernetes", DEFAULT_FALSE), CLUSTER_NODE_TYPE("sonar.cluster.node.type"), CLUSTER_SEARCH_HOSTS("sonar.cluster.search.hosts"), CLUSTER_HZ_HOSTS("sonar.cluster.hosts"), @@ -140,14 +142,14 @@ public class ProcessProperties { CLUSTER_NODE_ES_PORT("sonar.cluster.node.es.port"), AUTH_JWT_SECRET("sonar.auth.jwtBase64Hs256Secret"), - SONAR_WEB_SSO_ENABLE("sonar.web.sso.enable", "false"), + SONAR_WEB_SSO_ENABLE("sonar.web.sso.enable", DEFAULT_FALSE), SONAR_WEB_SSO_LOGIN_HEADER("sonar.web.sso.loginHeader", "X-Forwarded-Login"), SONAR_WEB_SSO_NAME_HEADER("sonar.web.sso.nameHeader", "X-Forwarded-Name"), SONAR_WEB_SSO_EMAIL_HEADER("sonar.web.sso.emailHeader", "X-Forwarded-Email"), SONAR_WEB_SSO_GROUPS_HEADER("sonar.web.sso.groupsHeader", "X-Forwarded-Groups"), SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES("sonar.web.sso.refreshIntervalInMinutes", "5"), SONAR_SECURITY_REALM("sonar.security.realm"), - SONAR_AUTHENTICATOR_IGNORE_STARTUP_FAILURE("sonar.authenticator.ignoreStartupFailure", "false"), + SONAR_AUTHENTICATOR_IGNORE_STARTUP_FAILURE("sonar.authenticator.ignoreStartupFailure", DEFAULT_FALSE), LDAP_SERVERS("ldap.servers"), LDAP_URL("ldap.url"), @@ -172,7 +174,7 @@ public class ProcessProperties { SONAR_UPDATECENTER_ACTIVATE("sonar.updatecenter.activate", "true"), - SONARCLOUD_ENABLED("sonar.sonarcloud.enabled", "false"), + SONARCLOUD_ENABLED("sonar.sonarcloud.enabled", DEFAULT_FALSE), SONARCLOUD_HOMEPAGE_URL("sonar.homepage.url", ""), SONAR_PRISMIC_ACCESS_TOKEN("sonar.prismic.accessToken", ""), SONAR_ANALYTICS_GTM_TRACKING_ID("sonar.analytics.gtm.trackingId", ""), @@ -184,7 +186,7 @@ public class ProcessProperties { ENABLE_STOP_COMMAND("sonar.enableStopCommand"), // whether the blue/green deployment of server is enabled - BLUE_GREEN_ENABLED("sonar.blueGreenEnabled", "false"); + BLUE_GREEN_ENABLED("sonar.blueGreenEnabled", DEFAULT_FALSE); /** * Properties that are defined for each LDAP server from the `ldap.servers` property diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/Log4JPropertiesBuilder.java b/server/sonar-process/src/main/java/org/sonar/process/logging/Log4JPropertiesBuilder.java index 10ec6e88c02..c450d940d62 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/logging/Log4JPropertiesBuilder.java +++ b/server/sonar-process/src/main/java/org/sonar/process/logging/Log4JPropertiesBuilder.java @@ -26,23 +26,34 @@ import java.util.Objects; import java.util.Properties; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.annotation.Nullable; import org.apache.commons.lang.StringUtils; import org.sonar.process.MessageException; import org.sonar.process.ProcessProperties; import org.sonar.process.Props; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; import static java.lang.String.format; import static java.lang.String.valueOf; +import static java.util.Optional.ofNullable; import static org.sonar.process.ProcessProperties.Property.LOG_LEVEL; import static org.sonar.process.ProcessProperties.Property.LOG_MAX_FILES; import static org.sonar.process.ProcessProperties.Property.LOG_ROLLING_POLICY; public class Log4JPropertiesBuilder extends AbstractLogHelper { + private static final String PATTERN_LAYOUT = "PatternLayout"; private static final String ROOT_LOGGER_NAME = "rootLogger"; private static final int UNLIMITED_MAX_FILES = 100_000; private final Properties log4j2Properties = new Properties(); private final Props props; + private RootLoggerConfig config; + private String logPattern; + private boolean allLogsToConsole; + private File logDir; + private LogLevelConfig logLevelConfig; + private boolean jsonOutput; public Log4JPropertiesBuilder(Props props) { super("%logger{1.}"); @@ -55,20 +66,64 @@ public class Log4JPropertiesBuilder extends AbstractLogHelper { return ROOT_LOGGER_NAME; } - public Properties get() { + public Log4JPropertiesBuilder rootLoggerConfig(RootLoggerConfig config) { + this.config = config; + return this; + } + + public Log4JPropertiesBuilder logPattern(String logPattern) { + this.logPattern = logPattern; + return this; + } + + public Log4JPropertiesBuilder enableAllLogsToConsole(boolean allLogsToConsoleEnabled) { + allLogsToConsole = allLogsToConsoleEnabled; + return this; + } + + public Log4JPropertiesBuilder jsonOutput(boolean jsonOutput) { + this.jsonOutput = jsonOutput; + return this; + } + + public Log4JPropertiesBuilder logDir(File logDir) { + this.logDir = logDir; + return this; + } + + public Log4JPropertiesBuilder logLevelConfig(LogLevelConfig logLevelConfig) { + this.logLevelConfig = logLevelConfig; + return this; + } + + public Properties build() { + checkNotNull(logDir, config); + checkState(jsonOutput || (logPattern != null), "log pattern must be specified if not using json output"); + configureGlobalFileLog(); + if (allLogsToConsole) { + configureGlobalStdoutLog(); + } + + ofNullable(logLevelConfig).ifPresent(this::applyLogLevelConfiguration); + Properties res = new Properties(); res.putAll(log4j2Properties); return res; } - public void internalLogLevel(Level level) { + public Log4JPropertiesBuilder internalLogLevel(Level level) { putProperty("status", level.toString()); + return this; } private void putProperty(String key, String value) { log4j2Properties.put(key, value); } + private void putProperty(String prefix, String key, @Nullable String value) { + log4j2Properties.put(prefix + key, value); + } + /** * Make log4j2 configuration for a process to push all its logs to a log file. *

@@ -82,31 +137,17 @@ public class Log4JPropertiesBuilder extends AbstractLogHelper { * * @see #buildLogPattern(RootLoggerConfig) */ - public void configureGlobalFileLog(RootLoggerConfig config, File logDir, String logPattern) { - String appenderRef = writeFileAppender(config, logDir, logPattern); - - putProperty(ROOT_LOGGER_NAME + ".appenderRef." + appenderRef + ".ref", appenderRef); - } - - private String writeFileAppender(RootLoggerConfig config, File logDir, String logPattern) { + private void configureGlobalFileLog() { String appenderName = "file_" + config.getProcessId().getLogFilenamePrefix(); RollingPolicy rollingPolicy = createRollingPolicy(logDir, config.getProcessId().getLogFilenamePrefix()); - FileAppender appender = new FileAppender(appenderName, rollingPolicy, logPattern); - appender.writeAppenderProperties(); - return appender.getAppenderRef(); + writeFileAppender(appenderName, rollingPolicy, logPattern, jsonOutput); + putProperty(ROOT_LOGGER_NAME + ".appenderRef." + appenderName + ".ref", appenderName); } - public void configureGlobalStdoutLog(String logPattern) { - String appenderRef = writeStdoutAppender(logPattern); - - putProperty(ROOT_LOGGER_NAME + ".appenderRef." + appenderRef + ".ref", appenderRef); - } - - private String writeStdoutAppender(String logPattern) { + private void configureGlobalStdoutLog() { String appenderName = "stdout"; - ConsoleAppender appender = new ConsoleAppender(appenderName, logPattern); - appender.writeAppenderProperties(); - return appender.getAppenderRef(); + writeConsoleAppender(appenderName, logPattern, jsonOutput); + putProperty(ROOT_LOGGER_NAME + ".appenderRef." + appenderName + ".ref", appenderName); } private RollingPolicy createRollingPolicy(File logDir, String filenamePrefix) { @@ -127,7 +168,7 @@ public class Log4JPropertiesBuilder extends AbstractLogHelper { } } - public void apply(LogLevelConfig logLevelConfig) { + private void applyLogLevelConfiguration(LogLevelConfig logLevelConfig) { if (!ROOT_LOGGER_NAME.equals(logLevelConfig.getRootLoggerName())) { throw new IllegalArgumentException("Value of LogLevelConfig#rootLoggerName must be \"" + ROOT_LOGGER_NAME + "\""); } @@ -170,61 +211,59 @@ public class Log4JPropertiesBuilder extends AbstractLogHelper { } } - private class FileAppender { - private final String prefix; - private final String appenderName; - private final RollingPolicy rollingPolicy; - private final String logPattern; - - private FileAppender(String appenderName, RollingPolicy rollingPolicy, String logPattern) { - this.prefix = "appender." + appenderName + "."; - this.appenderName = appenderName; - this.rollingPolicy = rollingPolicy; - this.logPattern = logPattern; - } - - void writeAppenderProperties() { - put("name", appenderName); - put("layout.type", "PatternLayout"); - put("layout.pattern", logPattern); - - rollingPolicy.writePolicy(this.prefix); - } - - void put(String key, String value) { - Log4JPropertiesBuilder.this.putProperty(this.prefix + key, value); - } - - String getAppenderRef() { - return appenderName; - } + private void writeFileAppender(String appenderName, RollingPolicy rollingPolicy, @Nullable String logPattern, boolean jsonOutput) { + String prefix = "appender." + appenderName + "."; + putProperty(prefix, "name", appenderName); + writeAppenderLayout(logPattern, jsonOutput, prefix); + rollingPolicy.writePolicy(prefix); } - private class ConsoleAppender { - private final String prefix; - private final String appenderName; - private final String logPattern; + private void writeConsoleAppender(String appenderName, @Nullable String logPattern, boolean jsonOutput) { + String prefix = "appender." + appenderName + "."; + putProperty(prefix, "type", "Console"); + putProperty(prefix, "name", appenderName); + writeAppenderLayout(logPattern, jsonOutput, prefix); + } - private ConsoleAppender(String appenderName, String logPattern) { - this.prefix = "appender." + appenderName + "."; - this.appenderName = appenderName; - this.logPattern = logPattern; + private void writeAppenderLayout(@Nullable String logPattern, boolean jsonOutput, String prefix) { + putProperty(prefix, "layout.type", PATTERN_LAYOUT); + if (!jsonOutput) { + putProperty(prefix, "layout.pattern", logPattern); + } else { + putProperty(prefix, "layout.pattern", getJsonPattern()); } + } - void writeAppenderProperties() { - put("type", "Console"); - put("name", appenderName); - put("layout.type", "PatternLayout"); - put("layout.pattern", logPattern); - } + /** + * json pattern based on https://github.com/elastic/elasticsearch/blob/7.13/server/src/main/java/org/elasticsearch/common/logging/ESJsonLayout.java + */ + private String getJsonPattern() { + return "{" + + jsonKey("process") + + inQuotes(config.getProcessId().getKey()) + + "," + + jsonKey("timestamp") + + inQuotes("%d{yyyy-MM-dd'T'HH:mm:ss.SSSZZ}") + + "," + + jsonKey("severity") + + inQuotes("%p") + + "," + + jsonKey("logger") + + inQuotes("%c{1.}") + + "," + + jsonKey("message") + + inQuotes("%notEmpty{%enc{%marker}{JSON} }%enc{%.-10000m}{JSON}") + + "%exceptionAsJson " + + "}" + + System.lineSeparator(); + } - void put(String key, String value) { - Log4JPropertiesBuilder.this.putProperty(this.prefix + key, value); - } + private static CharSequence jsonKey(String s) { + return inQuotes(s) + ": "; + } - String getAppenderRef() { - return appenderName; - } + private static String inQuotes(String s) { + return "\"" + s + "\""; } private abstract class RollingPolicy { diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java index becaf3d7bfa..e95fb20eda0 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java +++ b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java @@ -55,6 +55,7 @@ import static org.sonar.process.ProcessProperties.Property.LOG_CONSOLE; import static org.sonar.process.ProcessProperties.Property.LOG_LEVEL; import static org.sonar.process.ProcessProperties.Property.LOG_MAX_FILES; import static org.sonar.process.ProcessProperties.Property.LOG_ROLLING_POLICY; +import static org.sonar.process.ProcessProperties.Property.LOG_JSON_OUTPUT; import static org.sonar.process.ProcessProperties.Property.PATH_LOGS; /** @@ -229,7 +230,7 @@ public class LogbackHelper extends AbstractLogHelper { } public Encoder createEncoder(Props props, RootLoggerConfig config, LoggerContext context) { - if (props.valueAsBoolean("sonar.log.useJsonOutput", false)) { + if (props.valueAsBoolean(LOG_JSON_OUTPUT.getKey(), Boolean.parseBoolean(LOG_JSON_OUTPUT.getDefaultValue()))) { LayoutWrappingEncoder encoder = new LayoutWrappingEncoder<>(); encoder.setLayout(new LogbackJsonLayout(config.getProcessId().getKey())); encoder.setContext(context); diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackJsonLayout.java b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackJsonLayout.java index ef4be5bb329..d0ed49a5f1d 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackJsonLayout.java +++ b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackJsonLayout.java @@ -45,7 +45,7 @@ import static java.util.Objects.requireNonNull; */ public class LogbackJsonLayout extends LayoutBase { - private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME + static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME .withLocale(Locale.US) .withZone(ZoneId.systemDefault()); private static final Pattern NEWLINE_REGEXP = Pattern.compile("\n"); @@ -72,8 +72,7 @@ public class LogbackJsonLayout extends LayoutBase { } } json - .name("instant").value(event.getTimeStamp()) - .name("date").value(DATE_FORMATTER.format(Instant.ofEpochMilli(event.getTimeStamp()))) + .name("timestamp").value(DATE_FORMATTER.format(Instant.ofEpochMilli(event.getTimeStamp()))) .name("severity").value(event.getLevel().toString()) .name("logger").value(event.getLoggerName()) .name("message").value(NEWLINE_REGEXP.matcher(event.getFormattedMessage()).replaceAll("\r")); diff --git a/server/sonar-process/src/test/java/org/sonar/process/logging/Log4JPropertiesBuilderTest.java b/server/sonar-process/src/test/java/org/sonar/process/logging/Log4JPropertiesBuilderTest.java index d6429fae4a2..975eba70c3f 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/logging/Log4JPropertiesBuilderTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/logging/Log4JPropertiesBuilderTest.java @@ -24,6 +24,7 @@ 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.io.IOException; import java.util.HashSet; import java.util.Properties; import java.util.Random; @@ -31,7 +32,6 @@ 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; @@ -41,6 +41,8 @@ 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.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertThrows; import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder; @RunWith(DataProviderRunner.class) @@ -48,39 +50,40 @@ 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); + assertThatThrownBy(() -> new Log4JPropertiesBuilder(null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("Props can't be null"); } @Test - public void constructor_sets_status_to_ERROR() { - Properties properties = underTest.get(); + public void constructor_sets_status_to_ERROR() throws IOException { + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); + Properties properties = newLog4JPropertiesBuilder().rootLoggerConfig(esRootLoggerConfig).logDir(logDir).logPattern(logPattern).build(); assertThat(properties.getProperty("status")).isEqualTo("ERROR"); } @Test public void getRootLoggerName_returns_rootLogger() { - assertThat(underTest.getRootLoggerName()).isEqualTo("rootLogger"); + assertThat(newLog4JPropertiesBuilder().getRootLoggerName()).isEqualTo("rootLogger"); } @Test - public void get_always_returns_a_new_object() { - Properties previous = underTest.get(); + public void get_always_returns_a_new_object() throws IOException { + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); + + Properties previous = newLog4JPropertiesBuilder().rootLoggerConfig(esRootLoggerConfig).logDir(logDir).logPattern(logPattern).build(); for (int i = 0; i < 2 + new Random().nextInt(5); i++) { - Properties properties = underTest.get(); + Properties properties = newLog4JPropertiesBuilder().rootLoggerConfig(esRootLoggerConfig).logDir(logDir).logPattern(logPattern).build(); assertThat(properties).isNotSameAs(previous); previous = properties; } @@ -88,7 +91,7 @@ public class Log4JPropertiesBuilderTest { @Test public void buildLogPattern_puts_process_key_as_process_id() { - String pattern = underTest.buildLogPattern(newRootLoggerConfigBuilder() + String pattern = newLog4JPropertiesBuilder().buildLogPattern(newRootLoggerConfigBuilder() .setProcessId(ProcessId.ELASTICSEARCH) .build()); @@ -99,7 +102,7 @@ public class Log4JPropertiesBuilderTest { public void buildLogPattern_puts_threadIdFieldPattern_from_RootLoggerConfig_non_null() { String threadIdFieldPattern = RandomStringUtils.randomAlphabetic(5); - String pattern = underTest.buildLogPattern( + String pattern = newLog4JPropertiesBuilder().buildLogPattern( newRootLoggerConfigBuilder() .setProcessId(ProcessId.APP) .setThreadIdFieldPattern(threadIdFieldPattern) @@ -110,7 +113,7 @@ public class Log4JPropertiesBuilderTest { @Test public void buildLogPattern_does_not_put_threadIdFieldPattern_from_RootLoggerConfig_is_null() { - String pattern = underTest.buildLogPattern( + String pattern = newLog4JPropertiesBuilder().buildLogPattern( newRootLoggerConfigBuilder() .setProcessId(ProcessId.COMPUTE_ENGINE) .build()); @@ -120,7 +123,7 @@ public class Log4JPropertiesBuilderTest { @Test public void buildLogPattern_does_not_put_threadIdFieldPattern_from_RootLoggerConfig_is_empty() { - String pattern = underTest.buildLogPattern( + String pattern = newLog4JPropertiesBuilder().buildLogPattern( newRootLoggerConfigBuilder() .setProcessId(ProcessId.WEB_SERVER) .setThreadIdFieldPattern("") @@ -133,7 +136,10 @@ public class Log4JPropertiesBuilderTest { 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); + var underTest = newLog4JPropertiesBuilder() + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern); verifyTimeRollingPolicy(underTest, logDir, logPattern, "yyyy-MM-dd", 7); } @@ -142,10 +148,13 @@ public class Log4JPropertiesBuilderTest { 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( + + var underTest = newLog4JPropertiesBuilder( ROLLING_POLICY_PROPERTY, "time:yyyy-MM-dd", - PROPERTY_MAX_FILES, "0"); - underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern); + PROPERTY_MAX_FILES, "0") + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern); verifyTimeRollingPolicy(underTest, logDir, logPattern, "yyyy-MM-dd", 100_000); } @@ -154,10 +163,13 @@ public class Log4JPropertiesBuilderTest { 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( + + var underTest = newLog4JPropertiesBuilder( ROLLING_POLICY_PROPERTY, "time:yyyy-MM-dd", - PROPERTY_MAX_FILES, "-2"); - underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern); + PROPERTY_MAX_FILES, "-2") + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern); verifyTimeRollingPolicy(underTest, logDir, logPattern, "yyyy-MM-dd", 100_000); } @@ -167,10 +179,13 @@ public class Log4JPropertiesBuilderTest { File logDir = temporaryFolder.newFolder(); String logPattern = "foo"; String sizePattern = "1KB"; - Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder( + + var underTest = newLog4JPropertiesBuilder( ROLLING_POLICY_PROPERTY, "size:" + sizePattern, - PROPERTY_MAX_FILES, "0"); - underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern); + PROPERTY_MAX_FILES, "0") + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern); verifySizeRollingPolicy(underTest, logDir, logPattern, sizePattern, 100_000); } @@ -180,10 +195,13 @@ public class Log4JPropertiesBuilderTest { File logDir = temporaryFolder.newFolder(); String logPattern = "foo"; String sizePattern = "1KB"; - Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder( + + var underTest = newLog4JPropertiesBuilder( ROLLING_POLICY_PROPERTY, "size:" + sizePattern, - PROPERTY_MAX_FILES, "-2"); - underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern); + PROPERTY_MAX_FILES, "-2") + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern); verifySizeRollingPolicy(underTest, logDir, logPattern, sizePattern, 100_000); } @@ -194,12 +212,14 @@ public class Log4JPropertiesBuilderTest { 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); + ROLLING_POLICY_PROPERTY, invalidPropertyValue) + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern); - underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern); + assertThatThrownBy(underTest::build) + .isInstanceOf(MessageException.class) + .hasMessage("Unsupported value for property " + ROLLING_POLICY_PROPERTY + ": " + invalidPropertyValue); } @Test @@ -207,9 +227,12 @@ public class Log4JPropertiesBuilderTest { 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); + ROLLING_POLICY_PROPERTY, "time:" + timePattern) + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern); verifyTimeRollingPolicy(underTest, logDir, logPattern, timePattern, 7); } @@ -220,10 +243,13 @@ public class Log4JPropertiesBuilderTest { 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); + PROPERTY_MAX_FILES, valueOf(maxFile)) + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern); verifyTimeRollingPolicy(underTest, logDir, logPattern, timePattern, maxFile); } @@ -234,8 +260,10 @@ public class Log4JPropertiesBuilderTest { String logPattern = randomAlphanumeric(15); String sizePattern = randomAlphanumeric(6); Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder( - ROLLING_POLICY_PROPERTY, "size:" + sizePattern); - underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern); + ROLLING_POLICY_PROPERTY, "size:" + sizePattern) + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern); verifySizeRollingPolicy(underTest, logDir, logPattern, sizePattern, 7); } @@ -248,8 +276,10 @@ public class Log4JPropertiesBuilderTest { 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); + PROPERTY_MAX_FILES, valueOf(maxFile)) + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern); verifySizeRollingPolicy(underTest, logDir, logPattern, sizePattern, maxFile); } @@ -259,10 +289,12 @@ public class Log4JPropertiesBuilderTest { File logDir = temporaryFolder.newFolder(); String logPattern = randomAlphanumeric(15); Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder( - ROLLING_POLICY_PROPERTY, "none"); - underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern); + ROLLING_POLICY_PROPERTY, "none") + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern); - verifyProperties(underTest.get(), + verifyProperties(underTest.build(), "appender.file_es.type", "File", "appender.file_es.name", "file_es", "appender.file_es.fileName", new File(logDir, "es.log").getAbsolutePath(), @@ -272,183 +304,323 @@ public class Log4JPropertiesBuilderTest { } @Test - public void name() { + public void enable_all_logs_to_stdout_write_additionally_Console_appender() throws IOException { + File logDir = temporaryFolder.newFolder(); String logPattern = randomAlphanumeric(15); - Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(); - underTest.configureGlobalStdoutLog(logPattern); - verifyProperties(underTest.get(), + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(ROLLING_POLICY_PROPERTY, "none") + .enableAllLogsToConsole(true) + .rootLoggerConfig(esRootLoggerConfig) + .logPattern(logPattern) + .logDir(logDir); + verifyProperties(underTest.build(), "appender.stdout.type", "Console", "appender.stdout.name", "stdout", "appender.stdout.layout.type", "PatternLayout", "appender.stdout.layout.pattern", logPattern, - "rootLogger.appenderRef.stdout.ref", "stdout"); + "rootLogger.appenderRef.stdout.ref", "stdout", + "appender.file_es.layout.pattern", logPattern, + "appender.file_es.layout.type", "PatternLayout", + "appender.file_es.fileName", new File(logDir, "es.log").getAbsolutePath(), + "appender.file_es.name", "file_es", + "rootLogger.appenderRef.file_es.ref", "file_es", + "appender.file_es.type", "File"); + } + + @Test + public void enable_json_output_should_change_pattern_for_console_and_file_appender() throws IOException { + File logDir = temporaryFolder.newFolder(); + + String expectedPattern = "{\"process\": \"es\",\"timestamp\": \"%d{yyyy-MM-dd'T'HH:mm:ss.SSSZZ}\"," + + "\"severity\": \"%p\",\"logger\": \"%c{1.}\",\"message\": \"%notEmpty{%enc{%marker}{JSON} }%enc{%.-10000m}{JSON}\"%exceptionAsJson }" + System.lineSeparator(); + + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(ROLLING_POLICY_PROPERTY, "none") + .enableAllLogsToConsole(true) + .rootLoggerConfig(esRootLoggerConfig) + .jsonOutput(true) + .logDir(logDir); + verifyProperties(underTest.build(), + "appender.stdout.type", "Console", + "appender.stdout.name", "stdout", + "appender.stdout.layout.type", "PatternLayout", + "appender.stdout.layout.pattern", expectedPattern, + "rootLogger.appenderRef.stdout.ref", "stdout", + "appender.file_es.layout.type", "PatternLayout", + "appender.file_es.layout.pattern", expectedPattern, + "appender.file_es.fileName", new File(logDir, "es.log").getAbsolutePath(), + "appender.file_es.name", "file_es", + "rootLogger.appenderRef.file_es.ref", "file_es", + "appender.file_es.type", "File"); } @Test - public void apply_fails_with_IAE_if_LogLevelConfig_does_not_have_rootLoggerName_of_Log4J() { - Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(); + public void apply_fails_with_IAE_if_LogLevelConfig_does_not_have_rootLoggerName_of_Log4J() throws IOException { LogLevelConfig logLevelConfig = LogLevelConfig.newBuilder(randomAlphanumeric(2)).build(); + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Value of LogLevelConfig#rootLoggerName must be \"rootLogger\""); + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder() + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(logLevelConfig); - underTest.apply(logLevelConfig); + assertThatThrownBy(underTest::build) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Value of LogLevelConfig#rootLoggerName must be \"rootLogger\""); } @Test - public void apply_fails_with_IAE_if_global_property_has_unsupported_level() { + public void apply_fails_with_IAE_if_global_property_has_unsupported_level() throws IOException { LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build(); + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); - 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])"); + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", "ERROR") + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(config); - underTest.apply(config); + assertThatThrownBy(underTest::build) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("log level ERROR in property sonar.log.level is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); } @Test - public void apply_fails_with_IAE_if_process_property_has_unsupported_level() { + public void apply_fails_with_IAE_if_process_property_has_unsupported_level() throws IOException { LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build(); + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); - 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])"); + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level.web", "ERROR") + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(config); - underTest.apply(config); + assertThatThrownBy(underTest::build) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("log level ERROR in property sonar.log.level.web is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); } @Test - public void apply_sets_root_logger_to_INFO_if_no_property_is_set() { + public void apply_sets_root_logger_to_INFO_if_no_property_is_set() throws IOException { LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build(); + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); + + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder() + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(config); - underTest.apply(config); + underTest.build(); verifyRootLoggerLevel(underTest, Level.INFO); } @Test - public void apply_sets_root_logger_to_global_property_if_set() { + public void apply_sets_root_logger_to_global_property_if_set() throws IOException { LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build(); + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); - Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", "TRACE"); - - underTest.apply(config); + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", "TRACE") + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(config); verifyRootLoggerLevel(underTest, Level.TRACE); } @Test - public void apply_sets_root_logger_to_process_property_if_set() { + public void apply_sets_root_logger_to_process_property_if_set() throws IOException { LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build(); + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); - Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level.web", "DEBUG"); - - underTest.apply(config); + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level.web", "DEBUG") + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(config); verifyRootLoggerLevel(underTest, Level.DEBUG); } @Test - public void apply_sets_root_logger_to_process_property_over_global_property_if_both_set() { + public void apply_sets_root_logger_to_process_property_over_global_property_if_both_set() throws IOException { LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build(); - Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", "DEBUG", - "sonar.log.level.web", "TRACE"); + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); - underTest.apply(config); + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", "DEBUG", + "sonar.log.level.web", "TRACE") + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(config); verifyRootLoggerLevel(underTest, Level.TRACE); } @Test - public void apply_sets_domain_property_over_process_and_global_property_if_all_set() { + public void apply_sets_domain_property_over_process_and_global_property_if_all_set() throws IOException { LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES).build(); + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder( "sonar.log.level", "DEBUG", "sonar.log.level.web", "DEBUG", - "sonar.log.level.web.es", "TRACE"); - - underTest.apply(config); + "sonar.log.level.web.es", "TRACE") + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(config); - verifyLoggerProperties(underTest.get(), "foo", Level.TRACE); + verifyLoggerProperties(underTest.build(), "foo", Level.TRACE); } @Test - public void apply_sets_domain_property_over_process_property_if_both_set() { + public void apply_sets_domain_property_over_process_property_if_both_set() throws IOException { LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES).build(); + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder( "sonar.log.level.web", "DEBUG", - "sonar.log.level.web.es", "TRACE"); + "sonar.log.level.web.es", "TRACE") + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(config); - underTest.apply(config); - - verifyLoggerProperties(underTest.get(), "foo", Level.TRACE); + verifyLoggerProperties(underTest.build(), "foo", Level.TRACE); } @Test - public void apply_sets_domain_property_over_global_property_if_both_set() { + public void apply_sets_domain_property_over_global_property_if_both_set() throws IOException { LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES).build(); + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder( "sonar.log.level", "DEBUG", - "sonar.log.level.web.es", "TRACE"); - - underTest.apply(config); + "sonar.log.level.web.es", "TRACE") + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(config); - verifyLoggerProperties(underTest.get(), "foo", Level.TRACE); + verifyLoggerProperties(underTest.build(), "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(); + public void apply_fails_with_IAE_if_domain_property_has_unsupported_level() throws IOException { + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); - Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level.web.jmx", "ERROR"); + LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.JMX).build(); - 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])"); + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level.web.jmx", "ERROR") + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(config); - underTest.apply(config); + assertThatThrownBy(underTest::build) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("log level ERROR in property sonar.log.level.web.jmx is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); } @Test @UseDataProvider("logbackLevels") - public void apply_accepts_any_level_as_hardcoded_level(Level level) { + public void apply_accepts_any_level_as_hardcoded_level(Level level) throws IOException { LogLevelConfig config = newLogLevelConfig().immutableLevel("bar", level).build(); + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); + + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder() + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(config); - underTest.apply(config); + verifyLoggerProperties(underTest.build(), "bar", level); + } - verifyLoggerProperties(underTest.get(), "bar", level); + @Test + public void apply_set_level_to_OFF_if_sonar_global_level_is_not_set() throws IOException { + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); + + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder() + .rootLoggerConfig(esRootLoggerConfig) + .logLevelConfig(newLogLevelConfig().offUnlessTrace("fii").build()) + .logDir(logDir) + .logPattern(logPattern); + + verifyLoggerProperties(underTest.build(), "fii", Level.OFF); } @Test - public void apply_set_level_to_OFF_if_sonar_global_level_is_not_set() { - underTest.apply(newLogLevelConfig().offUnlessTrace("fii").build()); + public void fail_if_pattern_not_provided() { + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder() + .rootLoggerConfig(esRootLoggerConfig) + .logDir(new File("dir")); - verifyLoggerProperties(underTest.get(), "fii", Level.OFF); + assertThrows(IllegalStateException.class, underTest::build); } @Test - public void apply_set_level_to_OFF_if_sonar_global_level_is_INFO() { + public void fail_if_root_logger_config_not_provided() { + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder() + .logPattern("pattern") + .logDir(new File("dir")); + + assertThrows(NullPointerException.class, underTest::build); + } + + @Test + public void fail_if_logDir_not_provided() { + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder() + .logPattern("pattern") + .rootLoggerConfig(esRootLoggerConfig); + + assertThrows(NullPointerException.class, underTest::build); + } + + @Test + public void apply_set_level_to_OFF_if_sonar_global_level_is_INFO() throws Exception { setLevelToOff(Level.INFO); } @Test - public void apply_set_level_to_OFF_if_sonar_global_level_is_DEBUG() { + public void apply_set_level_to_OFF_if_sonar_global_level_is_DEBUG() throws Exception { setLevelToOff(Level.DEBUG); } @Test - public void apply_does_not_create_loggers_property_if_only_root_level_is_defined() { + public void apply_does_not_create_loggers_property_if_only_root_level_is_defined() throws IOException { LogLevelConfig logLevelConfig = newLogLevelConfig().rootLevelFor(ProcessId.APP).build(); + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); - underTest.apply(logLevelConfig); + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder() + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(logLevelConfig); - assertThat(underTest.get().getProperty("loggers")).isNull(); + assertThat(underTest.build().getProperty("loggers")).isNull(); } @Test - public void apply_creates_loggers_property_with_logger_names_ordered_but_root() { + public void apply_creates_loggers_property_with_logger_names_ordered_but_root() throws IOException { LogLevelConfig config = newLogLevelConfig() .rootLevelFor(ProcessId.WEB_SERVER) .levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.JMX) @@ -458,26 +630,43 @@ public class Log4JPropertiesBuilderTest { .offUnlessTrace("fii") .build(); - underTest.apply(config); + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); + + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder() + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(config); - assertThat(underTest.get().getProperty("loggers")).isEqualTo("bar,doh,fii,foo,pif"); + assertThat(underTest.build().getProperty("loggers")).isEqualTo("bar,doh,fii,foo,pif"); } - private void setLevelToOff(Level globalLogLevel) { - Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", globalLogLevel.toString()); + @Test + public void apply_does_not_set_level_if_sonar_global_level_is_TRACE() throws IOException { + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); - underTest.apply(newLogLevelConfig().offUnlessTrace("fii").build()); + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", Level.TRACE.toString()) + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(newLogLevelConfig().offUnlessTrace("fii").build()); - verifyLoggerProperties(underTest.get(), "fii", Level.OFF); + verifyNoLoggerProperties(underTest.build(), "fii"); } - @Test - public void apply_does_not_set_level_if_sonar_global_level_is_TRACE() { - Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", Level.TRACE.toString()); + private void setLevelToOff(Level globalLogLevel) throws IOException { + File logDir = temporaryFolder.newFolder(); + String logPattern = randomAlphanumeric(15); - underTest.apply(newLogLevelConfig().offUnlessTrace("fii").build()); + Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", globalLogLevel.toString()) + .rootLoggerConfig(esRootLoggerConfig) + .logDir(logDir) + .logPattern(logPattern) + .logLevelConfig(newLogLevelConfig().offUnlessTrace("fii").build()); - verifyNoLoggerProperties(underTest.get(), "fii"); + verifyLoggerProperties(underTest.build(), "fii", Level.OFF); } private static Log4JPropertiesBuilder newLog4JPropertiesBuilder(String... propertyKeysAndValues) { @@ -490,7 +679,7 @@ public class Log4JPropertiesBuilderTest { } private void verifyTimeRollingPolicy(Log4JPropertiesBuilder builder, File logDir, String logPattern, String datePattern, int maxFiles) { - verifyProperties(builder.get(), + verifyProperties(builder.build(), "appender.file_es.type", "RollingFile", "appender.file_es.name", "file_es", "appender.file_es.filePattern", new File(logDir, "es.%d{" + datePattern + "}.log").getAbsolutePath(), @@ -514,7 +703,7 @@ public class Log4JPropertiesBuilderTest { } private void verifySizeRollingPolicy(Log4JPropertiesBuilder builder, File logDir, String logPattern, String sizePattern, int maxFiles) { - verifyProperties(builder.get(), + verifyProperties(builder.build(), "appender.file_es.type", "RollingFile", "appender.file_es.name", "file_es", "appender.file_es.filePattern", new File(logDir, "es.%i.log").getAbsolutePath(), @@ -562,7 +751,7 @@ public class Log4JPropertiesBuilderTest { } private void verifyRootLoggerLevel(Log4JPropertiesBuilder underTest, Level expectedLevel) { - assertThat(underTest.get().get("rootLogger.level")).isEqualTo(expectedLevel.toString()); + assertThat(underTest.build().get("rootLogger.level")).isEqualTo(expectedLevel.toString()); } @DataProvider diff --git a/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java b/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java index bfa2ddd318e..7806aaa0a78 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java @@ -491,7 +491,7 @@ public class LogbackHelperTest { @Test public void createEncoder_uses_json_output() { - props.set("sonar.log.useJsonOutput", "true"); + props.set("sonar.log.jsonOutput", "true"); RootLoggerConfig config = newRootLoggerConfigBuilder() .setProcessId(ProcessId.WEB_SERVER) .build(); diff --git a/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackJsonLayoutTest.java b/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackJsonLayoutTest.java index 3dcf0c48a83..6a207d6a746 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackJsonLayoutTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackJsonLayoutTest.java @@ -24,17 +24,16 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.LoggingEvent; import com.google.gson.Gson; import java.time.Instant; -import java.time.format.DateTimeFormatter; -import org.assertj.core.data.Offset; import org.junit.Test; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.process.logging.LogbackJsonLayout.DATE_FORMATTER; public class LogbackJsonLayoutTest { - private LogbackJsonLayout underTest = new LogbackJsonLayout("web"); + private final LogbackJsonLayout underTest = new LogbackJsonLayout("web"); @Test public void test_simple_log() { @@ -44,12 +43,10 @@ public class LogbackJsonLayoutTest { JsonLog json = new Gson().fromJson(log, JsonLog.class); assertThat(json.process).isEqualTo("web"); - assertThat(json.instant).isCloseTo(System.currentTimeMillis(), Offset.offset(10_000L)); - assertThat(Instant.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(json.date)).toEpochMilli()).isEqualTo(json.instant); + assertThat(json.timestamp).isEqualTo(DATE_FORMATTER.format(Instant.ofEpochMilli(event.getTimeStamp()))); assertThat(json.severity).isEqualTo("WARN"); assertThat(json.logger).isEqualTo("the.logger"); assertThat(json.message).isEqualTo("the message"); - assertThat(json.message).isEqualTo("the message"); assertThat(json.stacktrace).isNull(); assertThat(json.fromMdc).isNull(); } @@ -63,12 +60,11 @@ public class LogbackJsonLayoutTest { JsonLog json = new Gson().fromJson(log, JsonLog.class); assertThat(json.process).isEqualTo("web"); - assertThat(json.instant).isCloseTo(System.currentTimeMillis(), Offset.offset(10_000L)); - assertThat(Instant.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(json.date)).toEpochMilli()).isEqualTo(json.instant); + assertThat(json.timestamp).isEqualTo(DATE_FORMATTER.format(Instant.ofEpochMilli(event.getTimeStamp()))); assertThat(json.severity).isEqualTo("WARN"); assertThat(json.logger).isEqualTo("the.logger"); assertThat(json.message).isEqualTo("the message"); - assertThat(json.stacktrace.length).isGreaterThan(5); + assertThat(json.stacktrace).hasSizeGreaterThan(5); assertThat(json.stacktrace[0]).isEqualTo("java.lang.IllegalStateException: BOOM"); assertThat(json.stacktrace[1]).startsWith("at ").contains("LogbackJsonLayoutTest.test_log_with_throwable"); assertThat(json.fromMdc).isNull(); @@ -83,7 +79,7 @@ public class LogbackJsonLayoutTest { String log = underTest.doLayout(event); JsonLog json = new Gson().fromJson(log, JsonLog.class); - assertThat(json.stacktrace.length).isGreaterThan(5); + assertThat(json.stacktrace).hasSizeGreaterThan(5); assertThat(json.stacktrace[0]).isEqualTo("java.lang.IllegalStateException: BOOM"); assertThat(json.stacktrace[1]).startsWith("at org.sonar.process.logging.LogbackJsonLayoutTest.test_log_with_throwable_and_cause(LogbackJsonLayoutTest.java:"); assertThat(json.stacktrace) @@ -100,21 +96,20 @@ public class LogbackJsonLayoutTest { String log = underTest.doLayout(event); JsonLog json = new Gson().fromJson(log, JsonLog.class); - assertThat(json.stacktrace.length).isGreaterThan(5); + assertThat(json.stacktrace).hasSizeGreaterThan(5); assertThat(json.stacktrace[0]).isEqualTo("java.lang.Exception: BOOM"); assertThat(json.stacktrace).contains("Suppressed: java.lang.IllegalStateException: foo"); } @Test public void test_log_with_message_arguments() { - LoggingEvent event = new LoggingEvent("org.foundation.Caller", (Logger) LoggerFactory.getLogger("the.logger"), Level.WARN, "the {}", null, new Object[]{"message"}); + LoggingEvent event = new LoggingEvent("org.foundation.Caller", (Logger) LoggerFactory.getLogger("the.logger"), Level.WARN, "the {}", null, new Object[] {"message"}); String log = underTest.doLayout(event); JsonLog json = new Gson().fromJson(log, JsonLog.class); assertThat(json.process).isEqualTo("web"); - assertThat(json.instant).isCloseTo(System.currentTimeMillis(), Offset.offset(10_000L)); - assertThat(Instant.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(json.date)).toEpochMilli()).isEqualTo(json.instant); + assertThat(json.timestamp).isEqualTo(DATE_FORMATTER.format(Instant.ofEpochMilli(event.getTimeStamp()))); assertThat(json.severity).isEqualTo("WARN"); assertThat(json.logger).isEqualTo("the.logger"); assertThat(json.message).isEqualTo("the message"); @@ -139,8 +134,7 @@ public class LogbackJsonLayoutTest { private static class JsonLog { private String process; - private long instant; - private String date; + private String timestamp; private String severity; private String logger; private String message; diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java index e66ae989b13..8b44a82f0e3 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java @@ -503,7 +503,7 @@ public class WebServerProcessLoggingTest { @Test public void use_json_output() { - props.set("sonar.log.useJsonOutput", "true"); + props.set("sonar.log.jsonOutput", "true"); LoggerContext context = underTest.configure(props); -- 2.39.5