* It creates a dedicated appender to the System.out which applies no formatting the logs it receives.
*/
private void configureConsole(LoggerContext loggerContext) {
- Encoder<ILoggingEvent> encoder = newPatternLayoutEncoder(loggerContext, "%msg%n");
+ Encoder<ILoggingEvent> encoder = createGobblerEncoder(loggerContext);
ConsoleAppender<ILoggingEvent> consoleAppender = helper.newConsoleAppender(loggerContext, CONSOLE_PLAIN_APPENDER, encoder);
Logger consoleLogger = loggerContext.getLogger(CONSOLE_LOGGER);
// logs are written to the console because we want them to be in sonar.log and the wrapper will write any log
// from APP's System.out and System.err to sonar.log
Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
- Encoder<ILoggingEvent> encoder = newPatternLayoutEncoder(ctx, helper.buildLogPattern(APP_ROOT_LOGGER_CONFIG));
+ Encoder<ILoggingEvent> encoder = helper.createEncoder(appSettings.getProps(), APP_ROOT_LOGGER_CONFIG, ctx);
rootLogger.addAppender(createAppConsoleAppender(ctx, encoder));
// in regular configuration, sub processes are not copying their logs to their System.out, so, the only logs to be
private void configureRootWithLogbackWritingToFile(LoggerContext ctx) {
Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
- Encoder<ILoggingEvent> encoder = newPatternLayoutEncoder(ctx, helper.buildLogPattern(APP_ROOT_LOGGER_CONFIG));
+ Encoder<ILoggingEvent> encoder = helper.createEncoder(appSettings.getProps(), APP_ROOT_LOGGER_CONFIG, ctx);
FileAppender<ILoggingEvent> fileAppender = helper.newFileAppender(ctx, appSettings.getProps(), APP_ROOT_LOGGER_CONFIG, encoder);
rootLogger.addAppender(fileAppender);
rootLogger.addAppender(createAppConsoleAppender(ctx, encoder));
private void configureGobbler(LoggerContext ctx) {
Logger gobblerLogger = ctx.getLogger(LOGGER_GOBBLER);
gobblerLogger.setAdditive(false);
- Encoder<ILoggingEvent> encoder = newPatternLayoutEncoder(ctx, "%msg%n");
+ Encoder<ILoggingEvent> encoder = createGobblerEncoder(ctx);
gobblerLogger.addAppender(helper.newConsoleAppender(ctx, GOBBLER_PLAIN_CONSOLE, encoder));
}
return helper.newConsoleAppender(ctx, APP_CONSOLE_APPENDER, encoder);
}
- private static Encoder<ILoggingEvent> newPatternLayoutEncoder(LoggerContext context, String pattern) {
+ /**
+ * Simply displays the message received as input.
+ */
+ private static Encoder<ILoggingEvent> createGobblerEncoder(LoggerContext context) {
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(context);
- encoder.setPattern(pattern);
+ encoder.setPattern("%msg%n");
encoder.start();
return encoder;
}
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.encoder.Encoder;
+import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.joran.spi.ConsoleTarget;
import ch.qos.logback.core.rolling.RollingFileAppender;
import java.io.File;
import org.sonar.application.config.AppSettings;
import org.sonar.application.config.TestAppSettings;
import org.sonar.process.logging.LogbackHelper;
+import org.sonar.process.logging.LogbackJsonLayout;
import static org.assertj.core.api.Assertions.assertThat;
import static org.slf4j.Logger.ROOT_LOGGER_NAME;
LoggerFactory.getLogger("com.hazelcast").isInfoEnabled()).isEqualTo(false);
}
+ @Test
+ public void use_json_output() {
+ settings.getProps().set("sonar.log.useJsonOutput", "true");
+
+ LoggerContext ctx = underTest.configure();
+ Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
+ ConsoleAppender appender = (ConsoleAppender<ILoggingEvent>)rootLogger.getAppender("APP_CONSOLE");
+ Encoder<ILoggingEvent> encoder = appender.getEncoder();
+ assertThat(encoder).isInstanceOf(LayoutWrappingEncoder.class);
+ assertThat(((LayoutWrappingEncoder)encoder).getLayout()).isInstanceOf(LogbackJsonLayout.class);
+ }
+
private void emulateRunFromSonarScript() {
settings.getProps().set("sonar.wrapped", "true");
}
compile 'commons-codec:commons-codec'
compile 'commons-io:commons-io'
compile 'commons-lang:commons-lang'
+ compile 'com.google.code.gson:gson'
compile 'com.google.guava:guava'
compile 'com.hazelcast:hazelcast'
compile 'org.slf4j:jul-to-slf4j'
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.classic.jul.LevelChangePropagator;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.encoder.Encoder;
+import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
import ch.qos.logback.core.rolling.RollingFileAppender;
configurator.doConfigure(LogbackHelper.class.getResource(xmlResourcePath));
}
+ public Encoder<ILoggingEvent> createEncoder(Props props, RootLoggerConfig config, LoggerContext context) {
+ if (props.valueAsBoolean("sonar.log.useJsonOutput", false)) {
+ LayoutWrappingEncoder encoder = new LayoutWrappingEncoder<>();
+ encoder.setLayout(new LogbackJsonLayout(config.getProcessId().getKey()));
+ encoder.setContext(context);
+ encoder.start();
+ return encoder;
+ }
+ PatternLayoutEncoder encoder = new PatternLayoutEncoder();
+ encoder.setContext(context);
+ encoder.setPattern(buildLogPattern(config));
+ encoder.start();
+ return encoder;
+ }
+
public RollingPolicy createRollingPolicy(Context ctx, Props props, String filenamePrefix) {
String rollingPolicy = props.value(ROLLING_POLICY_PROPERTY, "time:yyyy-MM-dd");
int maxFiles = props.valueAsInt(MAX_FILES_PROPERTY, 7);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.IThrowableProxy;
+import ch.qos.logback.classic.spi.StackTraceElementProxy;
+import ch.qos.logback.core.CoreConstants;
+import ch.qos.logback.core.LayoutBase;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Pattern;
+import org.apache.commons.lang.StringUtils;
+
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Formats logs in JSON.
+ * <p>
+ * Strongly inspired by https://github.com/qos-ch/logback/blob/master/logback-classic/src/main/java/ch/qos/logback/classic/html/DefaultThrowableRenderer.java
+ */
+public class LogbackJsonLayout extends LayoutBase<ILoggingEvent> {
+
+ private 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");
+
+ private final String processKey;
+
+ public LogbackJsonLayout(String processKey) {
+ this.processKey = requireNonNull(processKey);
+ }
+
+ String getProcessKey() {
+ return processKey;
+ }
+
+ @Override
+ public String doLayout(ILoggingEvent event) {
+ StringWriter output = new StringWriter();
+ try (JsonWriter json = new JsonWriter(output)) {
+ json.beginObject();
+ json.name("process").value(processKey);
+ for (Map.Entry<String, String> entry : event.getMDCPropertyMap().entrySet()) {
+ if (entry.getValue() != null) {
+ json.name(entry.getKey()).value(entry.getValue());
+ }
+ }
+ json
+ .name("instant").value(event.getTimeStamp())
+ .name("date").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"));
+ IThrowableProxy tp = event.getThrowableProxy();
+ if (tp != null) {
+ json.name("stacktrace").beginArray();
+ int nbOfTabs = 0;
+ while (tp != null) {
+ printFirstLine(json, tp, nbOfTabs);
+ render(json, tp, nbOfTabs);
+ tp = tp.getCause();
+ nbOfTabs++;
+ }
+ json.endArray();
+
+ }
+ json.endObject();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new IllegalStateException("BUG - fail to create JSON", e);
+ }
+ output.write(System.lineSeparator());
+ return output.toString();
+ }
+
+ private static void render(JsonWriter output, IThrowableProxy tp, int nbOfTabs) throws IOException {
+ String tabs = StringUtils.repeat("\t", nbOfTabs);
+ int commonFrames = tp.getCommonFrames();
+ StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();
+
+ for (int i = 0; i < stepArray.length - commonFrames; i++) {
+ StackTraceElementProxy step = stepArray[i];
+ output.value(tabs + step.toString());
+ }
+
+ if (commonFrames > 0) {
+ output.value(tabs + "... " + commonFrames + " common frames omitted");
+ }
+
+ for (IThrowableProxy suppressed : tp.getSuppressed()) {
+ output.value(format("%sSuppressed: %s: %s", tabs, suppressed.getClassName(), suppressed.getMessage()));
+ render(output, suppressed, nbOfTabs + 1);
+ }
+ }
+
+ private static void printFirstLine(JsonWriter output, IThrowableProxy tp, int nbOfTabs) throws IOException {
+ String tabs = StringUtils.repeat("\t", nbOfTabs);
+ int commonFrames = tp.getCommonFrames();
+ if (commonFrames > 0) {
+ output.value(tabs + CoreConstants.CAUSED_BY);
+ }
+ output.value(tabs + tp.getClassName() + ": " + tp.getMessage());
+ }
+}
return new Builder();
}
- ProcessId getProcessId() {
+ public ProcessId getProcessId() {
return processId;
}
- String getThreadIdFieldPattern() {
+ public String getThreadIdFieldPattern() {
return threadIdFieldPattern;
}
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
+import ch.qos.logback.core.Layout;
+import ch.qos.logback.core.encoder.Encoder;
+import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
assertThat(context.getLogger("fii").getLevel()).isNull();
}
+ @Test
+ public void createEncoder_uses_pattern_by_default() {
+ RootLoggerConfig config = newRootLoggerConfigBuilder()
+ .setProcessId(ProcessId.WEB_SERVER)
+ .build();
+
+ Encoder<ILoggingEvent> encoder = underTest.createEncoder(props, config, underTest.getRootContext());
+
+ assertThat(encoder).isInstanceOf(PatternLayoutEncoder.class);
+ }
+
+ @Test
+ public void createEncoder_uses_json_output() {
+ props.set("sonar.log.useJsonOutput", "true");
+ RootLoggerConfig config = newRootLoggerConfigBuilder()
+ .setProcessId(ProcessId.WEB_SERVER)
+ .build();
+
+ Encoder<ILoggingEvent> encoder = underTest.createEncoder(props, config, underTest.getRootContext());
+
+ assertThat(encoder).isInstanceOf(LayoutWrappingEncoder.class);
+ Layout layout = ((LayoutWrappingEncoder) encoder).getLayout();
+ assertThat(layout).isInstanceOf(LogbackJsonLayout.class);
+ assertThat(((LogbackJsonLayout)layout).getProcessKey()).isEqualTo("web");
+ }
+
private LogLevelConfig.Builder newLogLevelConfig() {
return LogLevelConfig.newBuilder(ROOT_LOGGER_NAME);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 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;
+
+public class LogbackJsonLayoutTest {
+
+ private LogbackJsonLayout underTest = new LogbackJsonLayout("web");
+
+ @Test
+ public void test_simple_log() {
+ LoggingEvent event = new LoggingEvent("org.foundation.Caller", (Logger) LoggerFactory.getLogger("the.logger"), Level.WARN, "the message", null, new Object[0]);
+
+ 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.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();
+ }
+
+ @Test
+ public void test_log_with_throwable_and_no_cause() {
+ Throwable exception = new IllegalStateException("BOOM");
+ LoggingEvent event = new LoggingEvent("org.foundation.Caller", (Logger) LoggerFactory.getLogger("the.logger"), Level.WARN, "the message", exception, new Object[0]);
+
+ 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.severity).isEqualTo("WARN");
+ assertThat(json.logger).isEqualTo("the.logger");
+ assertThat(json.message).isEqualTo("the message");
+ assertThat(json.stacktrace.length).isGreaterThan(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();
+ }
+
+ @Test
+ public void test_log_with_throwable_and_cause() {
+ Throwable rootCause = new IllegalArgumentException("Root cause");
+ Throwable exception = new IllegalStateException("BOOM", rootCause);
+ LoggingEvent event = new LoggingEvent("org.foundation.Caller", (Logger) LoggerFactory.getLogger("the.logger"), Level.WARN, "the message", exception, new Object[0]);
+
+ String log = underTest.doLayout(event);
+
+ JsonLog json = new Gson().fromJson(log, JsonLog.class);
+ assertThat(json.stacktrace.length).isGreaterThan(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)
+ .contains("\tCaused by: ")
+ .contains("\tjava.lang.IllegalArgumentException: Root cause");
+ }
+
+ @Test
+ public void test_log_with_suppressed_throwable() {
+ Exception exception = new Exception("BOOM");
+ exception.addSuppressed(new IllegalStateException("foo"));
+ LoggingEvent event = new LoggingEvent("org.foundation.Caller", (Logger) LoggerFactory.getLogger("the.logger"), Level.WARN, "the message", exception, new Object[0]);
+
+ String log = underTest.doLayout(event);
+
+ JsonLog json = new Gson().fromJson(log, JsonLog.class);
+ assertThat(json.stacktrace.length).isGreaterThan(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"});
+
+ 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.severity).isEqualTo("WARN");
+ assertThat(json.logger).isEqualTo("the.logger");
+ assertThat(json.message).isEqualTo("the message");
+ assertThat(json.stacktrace).isNull();
+ assertThat(json.fromMdc).isNull();
+ }
+
+ @Test
+ public void test_log_with_MDC() {
+ try {
+ LoggingEvent event = new LoggingEvent("org.foundation.Caller", (Logger) LoggerFactory.getLogger("the.logger"), Level.WARN, "the message", null, new Object[0]);
+ MDC.put("fromMdc", "foo");
+
+ String log = underTest.doLayout(event);
+
+ JsonLog json = new Gson().fromJson(log, JsonLog.class);
+ assertThat(json.fromMdc).isEqualTo("foo");
+ } finally {
+ MDC.clear();
+ }
+ }
+
+ private static class JsonLog {
+ private String process;
+ private long instant;
+ private String date;
+ private String severity;
+ private String logger;
+ private String message;
+ private String fromMdc;
+ private String[] stacktrace;
+ }
+}
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
-import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.encoder.Encoder;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
import org.sonar.process.ProcessId;
configureRootLogger(props);
helper.apply(logLevelConfig, props);
- configureDirectToConsoleLoggers(ctx, STARTUP_LOGGER_NAME);
+ configureDirectToConsoleLoggers(props, ctx, STARTUP_LOGGER_NAME);
extendConfigure();
helper.enableJulChangePropagation(ctx);
.setProcessId(processId)
.setThreadIdFieldPattern(threadIdFieldPattern)
.build();
- PatternLayoutEncoder encoder = new PatternLayoutEncoder();
- encoder.setContext(helper.getRootContext());
- encoder.setPattern(helper.buildLogPattern(config));
- encoder.start();
-
+ Encoder<ILoggingEvent> encoder = helper.createEncoder(props, config, helper.getRootContext());
helper.configureGlobalFileLog(props, config, encoder);
helper.configureForSubprocessGobbler(props, encoder);
}
* Setup one or more specified loggers to be non additive and to print to System.out which will be caught by the Main
* Process and written to sonar.log.
*/
- private void configureDirectToConsoleLoggers(LoggerContext context, String... loggerNames) {
+ private void configureDirectToConsoleLoggers(Props props, LoggerContext context, String... loggerNames) {
RootLoggerConfig config = newRootLoggerConfigBuilder()
.setProcessId(ProcessId.APP)
.setThreadIdFieldPattern("")
.build();
- PatternLayoutEncoder encoder = new PatternLayoutEncoder();
- encoder.setContext(context);
- encoder.setPattern(helper.buildLogPattern(config));
- encoder.start();
+ Encoder<ILoggingEvent> encoder = helper.createEncoder(props, config, context);
ConsoleAppender<ILoggingEvent> consoleAppender = helper.newConsoleAppender(context, "CONSOLE", encoder);
for (String loggerName : loggerNames) {
consoleLogger.addAppender(consoleAppender);
}
}
-
}
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
+import ch.qos.logback.core.OutputStreamAppender;
+import ch.qos.logback.core.encoder.Encoder;
+import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.joran.spi.JoranException;
import com.google.common.collect.ImmutableList;
import java.io.File;
import org.junit.rules.TemporaryFolder;
import org.sonar.process.Props;
import org.sonar.process.logging.LogbackHelper;
+import org.sonar.process.logging.LogbackJsonLayout;
import static org.assertj.core.api.Assertions.assertThat;
import static org.slf4j.Logger.ROOT_LOGGER_NAME;
.forEach(loggerName -> assertThat(context.getLogger(loggerName).getLevel()).isEqualTo(Level.OFF));
}
+ @Test
+ public void use_json_output() {
+ props.set("sonar.log.useJsonOutput", "true");
+
+ LoggerContext context = underTest.configure(props);
+
+ Logger rootLogger = context.getLogger(ROOT_LOGGER_NAME);
+ OutputStreamAppender appender = (OutputStreamAppender)rootLogger.getAppender("file_web");
+ Encoder<ILoggingEvent> encoder = appender.getEncoder();
+ assertThat(encoder).isInstanceOf(LayoutWrappingEncoder.class);
+ assertThat(((LayoutWrappingEncoder)encoder).getLayout()).isInstanceOf(LogbackJsonLayout.class);
+ }
+
private void verifyRootLogLevel(LoggerContext ctx, Level expected) {
Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
assertThat(rootLogger.getLevel()).isEqualTo(expected);