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.Appender;
import ch.qos.logback.core.ConsoleAppender;
import org.junit.rules.TemporaryFolder;
import org.sonar.process.Props;
import org.sonar.process.logging.LogbackHelper;
+import org.sonar.process.logging.PatternLayoutEncoder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.slf4j.Logger.ROOT_LOGGER_NAME;
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.FileAppender;
import org.sonar.process.ProcessId;
import org.sonar.process.logging.LogLevelConfig;
import org.sonar.process.logging.LogbackHelper;
+import org.sonar.process.logging.PatternLayoutEncoder;
import org.sonar.process.logging.RootLoggerConfig;
import static org.slf4j.Logger.ROOT_LOGGER_NAME;
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.Appender;
import ch.qos.logback.core.ConsoleAppender;
import org.sonar.application.config.TestAppSettings;
import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.logging.LogbackJsonLayout;
+import org.sonar.process.logging.PatternLayoutEncoder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.slf4j.Logger.ROOT_LOGGER_NAME;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.pattern.ClassicConverter;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import java.util.regex.Pattern;
+
+/**
+ * Escapes log message which contains CR LF sequence
+ */
+public class EscapedMessageConverter extends ClassicConverter {
+
+ private static final Pattern CR_PATTERN = Pattern.compile("\r");
+ private static final Pattern LF_PATTERN = Pattern.compile("\n");
+
+ public String convert(ILoggingEvent event) {
+ String formattedMessage = event.getFormattedMessage();
+ if (formattedMessage != null) {
+ String result = CR_PATTERN.matcher(formattedMessage).replaceAll("\\\\r");
+ result = LF_PATTERN.matcher(result).replaceAll("\\\\n");
+ return result;
+ }
+ return null;
+ }
+
+}
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;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.PatternLayout;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.pattern.PatternLayoutEncoderBase;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
+public class PatternLayoutEncoder extends PatternLayoutEncoderBase<ILoggingEvent> {
+
+ @Override
+ public void start() {
+ PatternLayout patternLayout = new PatternLayout();
+ patternLayout.getDefaultConverterMap().putAll(getEscapedMessageConverterConfig());
+ patternLayout.setContext(context);
+ patternLayout.setPattern(getPattern());
+ patternLayout.setOutputPatternAsHeader(outputPatternAsHeader);
+ patternLayout.start();
+ this.layout = patternLayout;
+ super.start();
+ }
+
+ private static Map<String, String> getEscapedMessageConverterConfig() {
+ return ImmutableMap.of(
+ "m", EscapedMessageConverter.class.getName(),
+ "msg", EscapedMessageConverter.class.getName(),
+ "message", EscapedMessageConverter.class.getName());
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EscapedMessageConverterTest {
+
+ private final EscapedMessageConverter underTest = new EscapedMessageConverter();
+
+ @Test
+ public void convert_null_message() {
+ ILoggingEvent event = createILoggingEvent(null);
+ assertThat(underTest.convert(event)).isNull();
+ }
+
+ @Test
+ public void convert_simple_message() {
+ ILoggingEvent event = createILoggingEvent("simple message");
+ assertThat(underTest.convert(event)).isEqualTo("simple message");
+ }
+
+ @Test
+ public void convert_message_with_CR() {
+ ILoggingEvent event = createILoggingEvent("simple\r message\r with\r CR\r");
+ assertThat(underTest.convert(event)).isEqualTo("simple\\r message\\r with\\r CR\\r");
+ }
+
+ @Test
+ public void convert_message_with_LF() {
+ ILoggingEvent event = createILoggingEvent("simple\n message\n with\n LF");
+ assertThat(underTest.convert(event)).isEqualTo("simple\\n message\\n with\\n LF");
+ }
+
+ @Test
+ public void convert_message_with_CRLF() {
+ ILoggingEvent event = createILoggingEvent("simple\n\r\n message\r with\r\n CR LF");
+ assertThat(underTest.convert(event)).isEqualTo("simple\\n\\r\\n message\\r with\\r\\n CR LF");
+ }
+
+ private static ILoggingEvent createILoggingEvent(String message) {
+ return new TestILoggingEvent(message);
+ }
+
+}
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
-import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggerContextListener;
import ch.qos.logback.core.Appender;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+
+public class PatternLayoutEncoderTest {
+
+ PatternLayoutEncoder underTest = new PatternLayoutEncoder();
+
+ @Before
+ public void before() {
+ underTest.start();
+ }
+
+ @Test
+ public void start_should_initialize_escaped_message_converter() {
+ assertThat(underTest.getLayout())
+ .isInstanceOf(ch.qos.logback.classic.PatternLayout.class);
+
+ assertThat(((ch.qos.logback.classic.PatternLayout) underTest.getLayout()).getDefaultConverterMap())
+ .contains(
+ entry("m", EscapedMessageConverter.class.getName()),
+ entry("msg", EscapedMessageConverter.class.getName()),
+ entry("message", EscapedMessageConverter.class.getName()));
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.IThrowableProxy;
+import ch.qos.logback.classic.spi.LoggerContextVO;
+import java.util.Map;
+import org.slf4j.Marker;
+
+public class TestILoggingEvent implements ILoggingEvent {
+ private String formattedMessage;
+
+ public TestILoggingEvent(String formattedMessage) {
+ this.formattedMessage = formattedMessage;
+ }
+
+ @Override
+ public String getThreadName() {
+ return null;
+ }
+
+ @Override
+ public Level getLevel() {
+ return null;
+ }
+
+ @Override
+ public String getMessage() {
+ return null;
+ }
+
+ @Override
+ public Object[] getArgumentArray() {
+ return null;
+ }
+
+ @Override
+ public String getFormattedMessage() {
+ return this.formattedMessage;
+ }
+
+ @Override
+ public String getLoggerName() {
+ return null;
+ }
+
+ @Override
+ public LoggerContextVO getLoggerContextVO() {
+ return null;
+ }
+
+ @Override
+ public IThrowableProxy getThrowableProxy() {
+ return null;
+ }
+
+ @Override
+ public StackTraceElement[] getCallerData() {
+ return new StackTraceElement[0];
+ }
+
+ @Override
+ public boolean hasCallerData() {
+ return false;
+ }
+
+ @Override
+ public Marker getMarker() {
+ return null;
+ }
+
+ @Override
+ public Map<String, String> getMDCPropertyMap() {
+ return null;
+ }
+
+ @Override
+ public Map<String, String> getMdc() {
+ return null;
+ }
+
+ @Override
+ public long getTimeStamp() {
+ return 0;
+ }
+
+ @Override
+ public void prepareForDeferredProcessing() {
+
+ }
+}
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.Appender;
import ch.qos.logback.core.AppenderBase;
import org.sonar.process.Props;
import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.logging.LogbackJsonLayout;
+import org.sonar.process.logging.PatternLayoutEncoder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.slf4j.Logger.ROOT_LOGGER_NAME;
LoggerContext context = underTest.configure(props);
Logger rootLogger = context.getLogger(ROOT_LOGGER_NAME);
- OutputStreamAppender appender = (OutputStreamAppender)rootLogger.getAppender("file_web");
+ OutputStreamAppender appender = (OutputStreamAppender) rootLogger.getAppender("file_web");
Encoder<ILoggingEvent> encoder = appender.getEncoder();
assertThat(encoder).isInstanceOf(LayoutWrappingEncoder.class);
- assertThat(((LayoutWrappingEncoder)encoder).getLayout()).isInstanceOf(LogbackJsonLayout.class);
+ assertThat(((LayoutWrappingEncoder) encoder).getLayout()).isInstanceOf(LogbackJsonLayout.class);
}
private void verifyRootLogLevel(LoggerContext ctx, Level expected) {