Skip to content

Commit

Permalink
SLING-11393 : Escape characters in log entries
Browse files Browse the repository at this point in the history
  • Loading branch information
cziegeler committed Jun 13, 2022
1 parent ad179db commit 6b1e837
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 12 deletions.
Expand Up @@ -145,10 +145,11 @@ public PatternLayout createLayout() {
}
}

PatternLayout pl = new PatternLayout();
final PatternLayout pl = new PatternLayout();
pl.setPattern(logBackPattern);
pl.setOutputPatternAsHeader(false);
pl.setContext(loggerContext);
MaskingMessageUtil.setMessageConverter(pl);

if (postProcessor != null) {
pl.setPostCompileProcessor(postProcessor);
Expand All @@ -171,5 +172,4 @@ public void setPostProcessor(PostCompileProcessor<ILoggingEvent> postProcessor)
public interface LogWriterProvider {
LogWriter getLogWriter(String writerName);
}

}
Expand Up @@ -32,7 +32,6 @@
import ch.qos.logback.classic.ClassicConstants;
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.core.Appender;
import ch.qos.logback.core.ConsoleAppender;
Expand Down Expand Up @@ -240,12 +239,7 @@ public Appender<ILoggingEvent> getDefaultAppender() {
appender.setName(DEFAULT_CONSOLE_APPENDER_NAME);
appender.setContext(loggerContext);

PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setPattern(LOG_PATTERN_DEFAULT);
encoder.setContext(loggerContext);
encoder.start();

appender.setEncoder(encoder);
appender.setEncoder(MaskingMessageUtil.getDefaultEncoder(loggerContext));

appender.start();
return appender;
Expand Down Expand Up @@ -274,7 +268,7 @@ public void onResetComplete(LoggerContext context) {
configuredAppenders = Collections.emptyMap();
}

Map<Appender, LoggerSpecificEncoder> encoders = new HashMap<Appender, LoggerSpecificEncoder>();
final Map<Appender<ILoggingEvent>, LoggerSpecificEncoder> encoders = new HashMap<>();

Set<String> configPids = new HashSet<>();
for (LogConfig config : getLogConfigs()) {
Expand Down
@@ -0,0 +1,212 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sling.commons.log.logback.internal;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.pattern.EnsureExceptionHandling;
import ch.qos.logback.classic.pattern.ExtendedThrowableProxyConverter;
import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.pattern.RootCauseFirstThrowableProxyConverter;
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
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.Context;
import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.pattern.Converter;
import ch.qos.logback.core.pattern.ConverterUtil;
import ch.qos.logback.core.pattern.PatternLayoutEncoderBase;

/**
* Converter util to mask certain characters in messages
*/
public class MaskingMessageUtil extends MessageConverter {

/**
* Set the message converter for the layout
* @param pl The layout
*/
public static void setMessageConverter(final PatternLayout pl) {
// need to overwrite all converter for messages and exceptions
// see https://logback.qos.ch/manual/layouts.html
pl.getInstanceConverterMap().put("m", MaskingMessageConverter.class.getName());
pl.getInstanceConverterMap().put("msg", MaskingMessageConverter.class.getName());
pl.getInstanceConverterMap().put("message", MaskingMessageConverter.class.getName());

pl.getInstanceConverterMap().put("ex", MaskingThrowableProxyConverter.class.getName());
pl.getInstanceConverterMap().put("exception", MaskingThrowableProxyConverter.class.getName());
pl.getInstanceConverterMap().put("rEx", MaskingRootCauseFirstThrowableProxyConverter.class.getName());
pl.getInstanceConverterMap().put("rootException", MaskingRootCauseFirstThrowableProxyConverter.class.getName());
pl.getInstanceConverterMap().put("throwable", MaskingThrowableProxyConverter.class.getName());

pl.getInstanceConverterMap().put("xEx", MaskingExtendedThrowableProxyConverter.class.getName());
pl.getInstanceConverterMap().put("xException", MaskingExtendedThrowableProxyConverter.class.getName());
pl.getInstanceConverterMap().put("xThrowable", MaskingExtendedThrowableProxyConverter.class.getName());

// override post processor for ensuring exception handling
pl.setPostCompileProcessor(new MaskingEnsureExceptionHandling());
}

/**
* Create an encoder with the default pattern
* @param loggerContext Logging context
* @return The encoder
*/
public static Encoder<ILoggingEvent> getDefaultEncoder(final Context loggerContext) {
final PatternLayoutEncoderBase<ILoggingEvent> encoder = new MaskingPatternLayoutEncoder();
encoder.setPattern(LogConfigManager.LOG_PATTERN_DEFAULT);
encoder.setContext(loggerContext);
encoder.start();
return encoder;
}

/**
* Replace any carriage returns and line feeds with an underscore
* @param msg The message
* @return converted string
*/
static String mask(final String msg) {
if ( msg == null ) {
return null;
}
return msg.replace('\n', '_').replace('\r', '_');
}

public static final class MaskingMessageConverter extends MessageConverter {
@Override
public String convert(final ILoggingEvent event) {
return mask(super.convert(event));
}
}

public static final class MaskingThrowableProxyConverter extends ThrowableProxyConverter {
@Override
protected String throwableProxyToString(final IThrowableProxy tp) {
return super.throwableProxyToString(new MaskingThrowableProxy(tp));
}
}

public static final class MaskingRootCauseFirstThrowableProxyConverter extends RootCauseFirstThrowableProxyConverter {
@Override
protected String throwableProxyToString(final IThrowableProxy tp) {
return super.throwableProxyToString(new MaskingThrowableProxy(tp));
}
}

public static class MaskingExtendedThrowableProxyConverter extends ExtendedThrowableProxyConverter {
@Override
protected String throwableProxyToString(final IThrowableProxy tp) {
return super.throwableProxyToString(new MaskingThrowableProxy(tp));
}
}

public static final class MaskingThrowableProxy implements IThrowableProxy {
private final IThrowableProxy proxied;

public MaskingThrowableProxy(final IThrowableProxy proxied) {
this.proxied = proxied;
}

@Override
public String getMessage() {
return mask(proxied.getMessage());
}

private IThrowableProxy getProxy(final IThrowableProxy p) {
if ( p == null ) {
return null;
}
if ( p == proxied || p == this ) {
return this;
}
return new MaskingThrowableProxy(p);
}

@Override
public IThrowableProxy getCause() {
return getProxy(proxied.getCause());
}

@Override
public String getClassName() {
return proxied.getClassName();
}

@Override
public StackTraceElementProxy[] getStackTraceElementProxyArray() {
return proxied.getStackTraceElementProxyArray();
}

@Override
public int getCommonFrames() {
return proxied.getCommonFrames();
}

@Override
public IThrowableProxy[] getSuppressed() {
final IThrowableProxy[] result = proxied.getSuppressed();
if ( result == null || result.length == 0 ) {
return result;
}
final IThrowableProxy[] proxies = new IThrowableProxy[result.length];
for(int i=0;i<proxies.length;i++) {
proxies[i] = getProxy(result[i]);
}
return proxies;
}
}

static final class MaskingEnsureExceptionHandling extends EnsureExceptionHandling {

public void process(Context context, Converter<ILoggingEvent> head) {
if (head == null) {
// this should never happen
throw new IllegalArgumentException("cannot process empty chain");
}
if (!chainHandlesThrowable(head)) {
Converter<ILoggingEvent> tail = ConverterUtil.findTail(head);
Converter<ILoggingEvent> exConverter = null;
LoggerContext loggerContext = (LoggerContext) context;
if (loggerContext.isPackagingDataEnabled()) {
exConverter = new MaskingExtendedThrowableProxyConverter();
} else {
exConverter = new MaskingThrowableProxyConverter();
}
tail.setNext(exConverter);
}
}
}

static final class MaskingPatternLayoutEncoder extends PatternLayoutEncoderBase<ILoggingEvent> {

@Override
public void start() {
PatternLayout patternLayout = new PatternLayout();
patternLayout.setContext(context);
patternLayout.setPattern(getPattern());
patternLayout.setOutputPatternAsHeader(outputPatternAsHeader);
MaskingMessageUtil.setMessageConverter(patternLayout);
patternLayout.start();
this.layout = patternLayout;
super.start();
}
}
}

Expand Up @@ -19,8 +19,9 @@

package org.apache.sling.commons.log.logback.internal.stacktrace;

import org.apache.sling.commons.log.logback.internal.MaskingMessageUtil;

import ch.qos.logback.classic.pattern.EnsureExceptionHandling;
import ch.qos.logback.classic.pattern.ExtendedThrowableProxyConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.core.Context;
Expand All @@ -47,7 +48,7 @@ public void process(Context context, Converter<ILoggingEvent> head) {
}
}

private class OSGiAwareConverter extends ExtendedThrowableProxyConverter {
private class OSGiAwareConverter extends MaskingMessageUtil.MaskingExtendedThrowableProxyConverter {
@Override
protected void extraData(StringBuilder builder, StackTraceElementProxy step) {
if (step != null) {
Expand Down
Expand Up @@ -20,14 +20,20 @@
package org.apache.sling.commons.log.logback.internal;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.util.Collections;

import org.junit.Test;
import org.mockito.Mockito;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.ThrowableProxy;

public class TestLogConfig {

Expand All @@ -47,6 +53,38 @@ public void testLayoutWithNewPattern() {
assertEquals(convertedPattern, logConfig.createLayout().getPattern());
}

@Test
public void testMessageEscaping() {
final LogConfig logConfig = createConfig("%message %m %msg");
final ILoggingEvent event = Mockito.mock(ILoggingEvent.class);
Mockito.when(event.getFormattedMessage()).thenReturn("foo\r\nbar");
final String result = logConfig.createLayout().doLayout(event);
assertEquals("foo__bar foo__bar foo__bar", result);
}

@Test
public void testExceptionMessageEscaping() {
final String[] patterns = new String[] {"%ex", "%exception", "%rEx", "%rootException", "%throwable", "%xEx", "%xException", "%xThrowable", "%m"};
for(final String p : patterns) {
final LogConfig logConfig = createConfig(p);
final PatternLayout layout = logConfig.createLayout();

final ILoggingEvent event = Mockito.mock(ILoggingEvent.class);
Mockito.when(event.getFormattedMessage()).thenReturn("message");

// single exception
Mockito.when(event.getThrowableProxy()).thenReturn(new ThrowableProxy(new RuntimeException("foo\r\nbar")));
String result = layout.doLayout(event);
assertTrue("pattern " + p + " : " + result, result.contains("foo__bar"));

// nested exception
Mockito.when(event.getThrowableProxy()).thenReturn(new ThrowableProxy(new RuntimeException("foo\r\nbar", new IOException("a\r\nb"))));
result = layout.doLayout(event);
assertTrue("pattern " + p + " : " + result, result.contains("foo__bar"));
assertTrue("pattern " + p + " : " + result, result.contains("a__b"));
}
}

private LogConfig createConfig(String pattern) {
return new LogConfig(new DummyLogWriterProvider(), pattern, Collections.<String> emptySet(), Level.DEBUG,
"test", false, null, (LoggerContext) LoggerFactory.getILoggerFactory(), false);
Expand Down

0 comments on commit 6b1e837

Please sign in to comment.