Skip to content

Commit

Permalink
fix(logging): add ability to filter MDC labels (googleapis#6430)
Browse files Browse the repository at this point in the history
Currently all MDC values are being written as labels in a LogEntry.
This is not desirable for everyone. By adding MDCEventEnhancer a user
now has the ability to easily filter which MDC entries get converted
into labels. To maintain backwards compatability the default
implementation is to write all entries as labels. If a user wants to
filter these, they just need to extend MDCEventEnhancer, override the
filtering, and add the new classpath to their logback.xml.

Fixes googleapis#6320
  • Loading branch information
codyoss committed Nov 14, 2019
1 parent 740c755 commit 6c40d9b
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@
import com.google.cloud.logging.Payload;
import com.google.cloud.logging.Severity;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
Expand Down Expand Up @@ -72,6 +72,8 @@ public class LoggingAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
private static final String LEVEL_NAME_KEY = "levelName";
private static final String LEVEL_VALUE_KEY = "levelValue";
private static final String LOGGER_NAME_KEY = "loggerName";
private static final List<LoggingEventEnhancer> DEFAULT_LOGGING_EVENT_ENHANCERS =
ImmutableList.<LoggingEventEnhancer>of(new MDCEventEnhancer());

private volatile Logging logging;
private LoggingOptions loggingOptions;
Expand Down Expand Up @@ -157,7 +159,11 @@ List<LoggingEnhancer> getLoggingEnhancers() {
}

List<LoggingEventEnhancer> getLoggingEventEnhancers() {
return getEnhancers(loggingEventEnhancerClassNames);
if (loggingEventEnhancerClassNames.isEmpty()) {
return DEFAULT_LOGGING_EVENT_ENHANCERS;
} else {
return getEnhancers(loggingEventEnhancerClassNames);
}
}

<T> List<T> getEnhancers(Set<String> classNames) {
Expand Down Expand Up @@ -278,12 +284,6 @@ private LogEntry logEntryFor(ILoggingEvent e) {
.addLabel(LEVEL_VALUE_KEY, String.valueOf(level.toInt()))
.addLabel(LOGGER_NAME_KEY, e.getLoggerName());

for (Map.Entry<String, String> entry : e.getMDCPropertyMap().entrySet()) {
if (null != entry.getKey() && null != entry.getValue()) {
builder.addLabel(entry.getKey(), entry.getValue());
}
}

if (loggingEnhancers != null) {
for (LoggingEnhancer enhancer : loggingEnhancers) {
enhancer.enhanceLogEntry(builder);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2019 Google LLC
*
* Licensed 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 com.google.cloud.logging.logback;

import ch.qos.logback.classic.spi.ILoggingEvent;
import com.google.cloud.logging.LogEntry;
import java.util.Map;

/**
* MDCEventEnhancer takes values found in the MDC property map and adds them as labels to the {@link
* LogEntry}. This {@link LoggingEventEnhancer} is turned on by default. If you wish to filter which
* MDC values get added as labels to your {@link LogEntry}, implement a {@link LoggingEventEnhancer}
* and add its classpath to your {@code logback.xml}. If any {@link LoggingEventEnhancer} is added
* this class is no longer registered.
*/
final class MDCEventEnhancer implements LoggingEventEnhancer {

@Override
public final void enhanceLogEntry(LogEntry.Builder builder, ILoggingEvent e) {
for (Map.Entry<String, String> entry : e.getMDCPropertyMap().entrySet()) {
if (null != entry.getKey() && null != entry.getValue()) {
builder.addLabel(entry.getKey(), entry.getValue());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.filter.ThresholdFilter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggingEvent;
import com.google.cloud.MonitoredResource;
import com.google.cloud.Timestamp;
Expand All @@ -48,8 +49,8 @@
@RunWith(EasyMockRunner.class)
public class LoggingAppenderTest {
private final String projectId = "test-project";
private final Logging logging = EasyMock.createStrictMock(Logging.class);
private LoggingAppender loggingAppender = new TestLoggingAppender();
private Logging logging;
private LoggingAppender loggingAppender;

class TestLoggingAppender extends LoggingAppender {
@Override
Expand All @@ -64,7 +65,10 @@ Logging getLogging() {
}

@Before
public void setUp() {}
public void setUp() {
logging = EasyMock.createStrictMock(Logging.class);
loggingAppender = new TestLoggingAppender();
}

private final WriteOption[] defaultWriteOptions =
new WriteOption[] {
Expand Down Expand Up @@ -277,4 +281,43 @@ public void testMdcValuesAreConvertedToLabelsWithPassingNullValues() {
assertThat(capturedArgumentMap.get("mdc2")).isNull();
assertThat(capturedArgumentMap.get("mdc3")).isEqualTo("value3");
}

@Test
public void testAddCustomLoggingEventEnhancers() {
MDC.put("mdc1", "value1");
logging.setFlushSeverity(Severity.ERROR);
Capture<Iterable<LogEntry>> capturedArgument = Capture.newInstance();
logging.write(capture(capturedArgument), (WriteOption) anyObject(), (WriteOption) anyObject());
expectLastCall().once();
replay(logging);
Timestamp timestamp = Timestamp.ofTimeSecondsAndNanos(100000, 0);
LoggingEvent loggingEvent = createLoggingEvent(Level.INFO, timestamp.getSeconds());
loggingAppender.addLoggingEventEnhancer(CustomLoggingEventEnhancer1.class.getName());
loggingAppender.addLoggingEventEnhancer(CustomLoggingEventEnhancer2.class.getName());
loggingAppender.start();
loggingAppender.doAppend(loggingEvent);
verify(logging);
MDC.remove("mdc1");
Map<String, String> capturedArgumentMap =
capturedArgument.getValue().iterator().next().getLabels();
assertThat(capturedArgumentMap.get("mdc1")).isNull();
assertThat(capturedArgumentMap.get("foo")).isEqualTo("foo");
assertThat(capturedArgumentMap.get("bar")).isEqualTo("bar");
}

static class CustomLoggingEventEnhancer1 implements LoggingEventEnhancer {

@Override
public void enhanceLogEntry(LogEntry.Builder builder, ILoggingEvent e) {
builder.addLabel("foo", "foo");
}
}

static class CustomLoggingEventEnhancer2 implements LoggingEventEnhancer {

@Override
public void enhanceLogEntry(LogEntry.Builder builder, ILoggingEvent e) {
builder.addLabel("bar", "bar");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2019 Google LLC
*
* Licensed 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 com.google.cloud.logging.logback;

import static com.google.common.truth.Truth.assertThat;

import ch.qos.logback.classic.spi.LoggingEvent;
import com.google.cloud.logging.LogEntry;
import com.google.cloud.logging.Payload.StringPayload;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;

public class MDCEventEnhancerTest {
private MDCEventEnhancer classUnderTest;

@Before
public void setUp() {
classUnderTest = new MDCEventEnhancer();
}

@Test
public void testEnhanceLogEntry() {
LoggingEvent loggingEvent = new LoggingEvent();
loggingEvent.setMessage("this is a test");
loggingEvent.setMDCPropertyMap(Collections.singletonMap("foo", "bar"));
LogEntry.Builder builder = LogEntry.newBuilder(StringPayload.of("this is a test"));

classUnderTest.enhanceLogEntry(builder, loggingEvent);
LogEntry logEntry = builder.build();

assertThat(logEntry.getLabels().get("foo")).isEqualTo("bar");
}
}

0 comments on commit 6c40d9b

Please sign in to comment.