Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
import io.sentry.Sentry;
Expand All @@ -16,6 +17,7 @@
import io.sentry.event.interfaces.SentryException;
import io.sentry.event.interfaces.StackTraceInterface;

import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Date;
Expand All @@ -38,6 +40,12 @@ public class SentryAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
* Name of the {@link Event#extra} property containing the Thread name.
*/
public static final String THREAD_NAME = "Sentry-Threadname";

/**
* Appender encoder.
*/
protected Encoder<ILoggingEvent> encoder;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be at least AtomicReference and CAS'd on the use sites so that the unsynchronized access is somewhat handled? Or is it only ever supposed to be mutated by logback during instantiation and before any logging can occur?

@jeacott1 jeacott1 Nov 26, 2019

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as far as I understand it this isn't an issue. And yes, I think you'd typically create and replace with a new appender than dynamically replace an encoder.

fyi fwiw logstashencoder is doing the same thing:
https://github.com/logstash/logstash-logback-encoder/blob/bbd8333120f274f4d9ef12e797eae2331fed1a52/src/main/java/net/logstash/logback/appender/AbstractLogstashTcpSocketAppender.java#L199


/**
* If set, only events with level = minLevel and up will be recorded.
*
Expand Down Expand Up @@ -91,6 +99,7 @@ protected static Event.Level formatLevel(Level level) {

@Override
protected void append(ILoggingEvent iLoggingEvent) {

// Do not log the event if the current thread is managed by sentry
if (isNotLoggable(iLoggingEvent) || SentryEnvironment.isManagingThread()) {
return;
Expand Down Expand Up @@ -122,10 +131,19 @@ private boolean isNotLoggable(ILoggingEvent iLoggingEvent) {
* @return EventBuilder containing details provided by the logging system.
*/
protected EventBuilder createEventBuilder(ILoggingEvent iLoggingEvent) {
final String formattedMessage;

if (this.encoder != null) {
byte[] byteArray = this.encoder.encode(iLoggingEvent);
formattedMessage = new String(byteArray, StandardCharsets.UTF_8);
} else {
formattedMessage = iLoggingEvent.getFormattedMessage();
}

EventBuilder eventBuilder = new EventBuilder()
.withSdkIntegration("logback")
.withTimestamp(new Date(iLoggingEvent.getTimeStamp()))
.withMessage(iLoggingEvent.getFormattedMessage())
.withMessage(formattedMessage)
.withLogger(iLoggingEvent.getLoggerName())
.withLevel(formatLevel(iLoggingEvent.getLevel()))
.withExtra(THREAD_NAME, iLoggingEvent.getThreadName());
Expand All @@ -144,7 +162,11 @@ protected EventBuilder createEventBuilder(ILoggingEvent iLoggingEvent) {
}

for (Map.Entry<String, String> contextEntry : iLoggingEvent.getLoggerContextVO().getPropertyMap().entrySet()) {
eventBuilder.withExtra(contextEntry.getKey(), contextEntry.getValue());
if (Sentry.getStoredClient().getMdcTags().contains(contextEntry.getKey())) {
eventBuilder.withTag(contextEntry.getKey(), contextEntry.getValue());
} else {
eventBuilder.withExtra(contextEntry.getKey(), contextEntry.getValue());
}
}

for (Map.Entry<String, String> mdcEntry : iLoggingEvent.getMDCPropertyMap().entrySet()) {
Expand Down Expand Up @@ -286,6 +308,19 @@ public void stop() {
}
}

public Encoder<ILoggingEvent> getEncoder() {
return encoder;
}

/**
* Set logback encoder.
*
* @param encoder logback encoder.
*/
public void setEncoder(Encoder<ILoggingEvent> encoder) {
this.encoder = encoder;
}

private class DropSentryFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
Expand All @@ -296,4 +331,5 @@ public FilterReply decide(ILoggingEvent event) {
return FilterReply.NEUTRAL;
}
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.sentry.logback;

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.BasicStatusManager;
import ch.qos.logback.core.Context;
Expand Down Expand Up @@ -41,7 +43,7 @@ public class SentryAppenderEventBuildingTest extends BaseTest {
@Before
public void setUp() throws Exception {
mockSentryClient = mock(SentryClient.class);
mockContext = mock(Context.class);
mockContext = mock(LoggerContext.class);

Sentry.setStoredClient(mockSentryClient);
sentryAppender = new SentryAppender();
Expand Down Expand Up @@ -205,6 +207,22 @@ public void testMdcTakesPrecedenceOverContextProperties() throws Exception {
assertThat(event.getExtra(), Matchers.<String, Object>hasEntry(mdcKey, mdcValue));
assertNoErrorsInStatusManager();
}

@Test
public void testGLobalAddedToExtra() throws Exception {
final String extraKey = "10e09b11-546f-4c57-99b2-cf3c627c8737";
final String extraValue = "5f7a53b1-4354-4120-a368-78a615705540";


sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, null, null, null,
null, null, null, 0, Collections.singletonMap(extraKey, extraValue)));

ArgumentCaptor<EventBuilder> eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class);
verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture());
Event event = eventBuilderArgumentCaptor.getValue().build();
assertThat(event.getExtra(), Matchers.<String, Object>hasEntry(extraKey, extraValue));
assertNoErrorsInStatusManager();
}

@Test
public void testSourceUsedAsStacktrace() throws Exception {
Expand Down Expand Up @@ -244,4 +262,47 @@ public void testExtraTagObtainedFromMdc() throws Exception {
assertThat(event.getExtra(), Matchers.<String, Object>hasEntry("other_property", "cb9c92a1-0182-4e9c-866f-b06b271cd196"));
assertNoErrorsInStatusManager();
}

@Test
public void testExtraTagObtainedFromGlobal() throws Exception {
Map<String, String> propertyMap = new HashMap<>();
propertyMap.put(mockExtraTag, "47008f35-50c8-4e40-94ca-c8c1a3ddb729");
propertyMap.put("other_property", "cb9c92a1-0182-4e9c-866f-b06b271cd196");

when(mockSentryClient.getMdcTags()).thenReturn(extraTags);

sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, null, null, null, null, null,
null, 0, propertyMap));

ArgumentCaptor<EventBuilder> eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class);
verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture());
Event event = eventBuilderArgumentCaptor.getValue().build();
assertThat(event.getTags().entrySet(), hasSize(1));
assertThat(event.getTags(), hasEntry(mockExtraTag, "47008f35-50c8-4e40-94ca-c8c1a3ddb729"));
assertThat(event.getExtra(), not(hasKey(mockExtraTag)));
assertThat(event.getExtra(), Matchers.<String, Object>hasEntry("other_property", "cb9c92a1-0182-4e9c-866f-b06b271cd196"));
assertNoErrorsInStatusManager();
}

@Test
public void testSetEncoder() throws Exception {
when(mockSentryClient.getMdcTags()).thenReturn(extraTags);

PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setPattern("%-5level : %message");
encoder.setContext(mockContext);
encoder.start();
sentryAppender.setEncoder(encoder);
sentryAppender.start();

String expectedMessage = "some message";
sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, expectedMessage, null, null, null, null,
null, 0));

ArgumentCaptor<EventBuilder> eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class);
verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture());
Event event = eventBuilderArgumentCaptor.getValue().build();
assertThat(event.getMessage(), is("INFO : " + expectedMessage));
assertNoErrorsInStatusManager();
}
}