Skip to content
This repository has been archived by the owner on Aug 30, 2023. It is now read-only.

Commit

Permalink
Add Spring Integration. (#539)
Browse files Browse the repository at this point in the history
  • Loading branch information
maciejwalkowiak authored Sep 8, 2020
1 parent 60af521 commit 923f9fc
Show file tree
Hide file tree
Showing 38 changed files with 1,036 additions and 49 deletions.
8 changes: 4 additions & 4 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ object Config {
val log4j2Core = "org.apache.logging.log4j:log4j-core:$log4j2Version"

val springBootStarter = "org.springframework.boot:spring-boot-starter:$springBootVersion"
val springBootStarterTest = "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
val springBootStarterWeb = "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
val springBootStarterSecurity = "org.springframework.boot:spring-boot-starter-security:$springBootVersion"

val springWeb = "org.springframework:spring-webmvc"
val servletApi = "javax.servlet:javax.servlet-api"
Expand All @@ -68,17 +71,13 @@ object Config {
val robolectric = "org.robolectric:robolectric:4.3.1"
val mockitoKotlin = "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
val awaitility = "org.awaitility:awaitility-kotlin:4.0.3"
val springBootStarterTest = "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
val springBootStarterWeb = "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
val springBootStarterSecurity = "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
}

object QualityPlugins {
object Jacoco {
val version = "0.8.5"
val minimumCoverage = BigDecimal.valueOf(0.6)
}
val jacocoVersion = "0.8.5"
val spotless = "com.diffplug.spotless"
val spotlessVersion = "5.1.0"
val errorProne = "net.ltgt.errorprone"
Expand All @@ -95,6 +94,7 @@ object Config {
val SENTRY_ANDROID_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.android"
val SENTRY_LOGBACK_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.logback"
val SENTRY_LOG4J2_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.log4j2"
val SENTRY_SPRING_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring"
val SENTRY_SPRING_BOOT_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot"
val group = "io.sentry"
val description = "SDK for sentry.io"
Expand Down
16 changes: 16 additions & 0 deletions sentry-core/src/main/java/io/sentry/core/Breadcrumb.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@ public final class Breadcrumb implements Cloneable, IUnknownPropertiesConsumer {
this.timestamp = timestamp;
}

/**
* Creates HTTP breadcrumb.
*
* @param url - the request URL
* @param method - the request method
* @return the breadcrumb
*/
public static @NotNull Breadcrumb http(final @NotNull String url, final @NotNull String method) {
final Breadcrumb breadcrumb = new Breadcrumb();
breadcrumb.setType("http");
breadcrumb.setCategory("http");
breadcrumb.setData("url", url);
breadcrumb.setData("method", method.toUpperCase(Locale.getDefault()));
return breadcrumb;
}

/** Breadcrumb ctor */
public Breadcrumb() {
this(DateUtils.getCurrentDateTimeOrNull());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package io.sentry.core;

import io.sentry.core.exception.ExceptionMechanismException;
import io.sentry.core.util.Objects;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/** Deduplicates events containing throwable that has been already processed. */
public final class DuplicateEventDetectionEventProcessor implements EventProcessor {
private final WeakHashMap<Throwable, Object> capturedObjects = new WeakHashMap<>();
private final SentryOptions options;

public DuplicateEventDetectionEventProcessor(final @NotNull SentryOptions options) {
this.options = Objects.requireNonNull(options, "options are required");
}

@Override
public SentryEvent process(final @NotNull SentryEvent event, final @Nullable Object hint) {
final Throwable throwable = event.getThrowable();
if (throwable != null) {
if (throwable instanceof ExceptionMechanismException) {
final ExceptionMechanismException ex = (ExceptionMechanismException) throwable;
if (capturedObjects.containsKey(ex.getThrowable())) {
options
.getLogger()
.log(
SentryLevel.DEBUG,
"Duplicate Exception detected. Event %s will be discarded.",
event.getEventId());
return null;
} else {
capturedObjects.put(ex.getThrowable(), null);
}
} else {
if (capturedObjects.containsKey(throwable)
|| containsAnyKey(capturedObjects, allCauses(throwable))) {
options
.getLogger()
.log(
SentryLevel.DEBUG,
"Duplicate Exception detected. Event %s will be discarded.",
event.getEventId());
return null;
} else {
capturedObjects.put(throwable, null);
}
}
}
return event;
}

private static <T> boolean containsAnyKey(
final @NotNull Map<T, Object> map, final @NotNull List<T> list) {
for (T entry : list) {
if (map.containsKey(entry)) {
return true;
}
}
return false;
}

private static @NotNull List<Throwable> allCauses(final @NotNull Throwable throwable) {
final List<Throwable> causes = new ArrayList<>();
Throwable ex = throwable;
while (ex.getCause() != null) {
causes.add(ex.getCause());
ex = ex.getCause();
}
return causes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,7 @@ public SentryOptions() {
integrations.add(new ShutdownHookIntegration());

eventProcessors.add(new MainEventProcessor(this));
eventProcessors.add(new DuplicateEventDetectionEventProcessor(this));

setSentryClientName(BuildConfig.SENTRY_JAVA_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
setSdkVersion(createSdkVersion());
Expand Down
9 changes: 9 additions & 0 deletions sentry-core/src/test/java/io/sentry/core/BreadcrumbTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,13 @@ class BreadcrumbTest {
val breadcrumb = Breadcrumb("this is a test")
assertEquals("this is a test", breadcrumb.message)
}

@Test
fun `creates HTTP breadcrumb`() {
val breadcrumb = Breadcrumb.http("http://example.com", "POST")
assertEquals("http://example.com", breadcrumb.data["url"])
assertEquals("POST", breadcrumb.data["method"])
assertEquals("http", breadcrumb.type)
assertEquals("http", breadcrumb.category)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package io.sentry.core

import io.sentry.core.exception.ExceptionMechanismException
import io.sentry.core.protocol.Mechanism
import java.lang.RuntimeException
import kotlin.test.Test
import kotlin.test.assertNotNull
import kotlin.test.assertNull

class DuplicateEventDetectionEventProcessorTest {

val processor = DuplicateEventDetectionEventProcessor(SentryOptions())

@Test
fun `does not drop event if no previous event with same exception was processed`() {
processor.process(SentryEvent(), null)

val result = processor.process(SentryEvent(RuntimeException()), null)

assertNotNull(result)
}

@Test
fun `drops event with the same exception`() {
val event = SentryEvent(RuntimeException())
processor.process(event, null)

val result = processor.process(event, null)
assertNull(result)
}

@Test
fun `drops event with mechanism exception having an exception that has already been processed`() {
val event = SentryEvent(RuntimeException())
processor.process(event, null)

val result = processor.process(SentryEvent(ExceptionMechanismException(Mechanism(), event.throwable, null)), null)
assertNull(result)
}

@Test
fun `drops event with exception that has already been processed with event with mechanism exception`() {
val sentryEvent = SentryEvent(ExceptionMechanismException(Mechanism(), RuntimeException(), null))
processor.process(sentryEvent, null)

val result = processor.process(SentryEvent((sentryEvent.throwable as ExceptionMechanismException).throwable), null)

assertNull(result)
}

@Test
fun `drops event with the cause equal to exception in already processed event`() {
val event = SentryEvent(RuntimeException())
processor.process(event, null)

val result = processor.process(SentryEvent(RuntimeException(event.throwable)), null)

assertNull(result)
}

@Test
fun `drops event with any of the causes has been already processed`() {
val event = SentryEvent(RuntimeException())
processor.process(event, null)

val result = processor.process(SentryEvent(RuntimeException(RuntimeException(event.throwable))), null)

assertNull(result)
}
}
11 changes: 6 additions & 5 deletions sentry-samples/sentry-samples-spring-boot/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
Expand All @@ -16,14 +17,14 @@ repositories {
}

dependencies {
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter")
implementation(Config.Libs.springBootStarterSecurity)
implementation(Config.Libs.springBootStarterWeb)
implementation(Config.Libs.springBootStarter)
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION))
implementation(project(":sentry-spring-boot-starter"))
implementation(project(":sentry-logback"))
testImplementation("org.springframework.boot:spring-boot-starter-test") {
testImplementation(Config.Libs.springBootStarterTest) {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.sentry.samples.spring.boot;

import io.sentry.core.EventProcessor;
import io.sentry.core.SentryEvent;
import io.sentry.core.protocol.SentryRuntime;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Component;

/**
* Custom {@link EventProcessor} implementation lets modifying {@link SentryEvent}s before they are
* sent to Sentry.
*/
@Component
public class CustomEventProcessor implements EventProcessor {
private final String javaVersion;
private final String javaVendor;

public CustomEventProcessor(String javaVersion, String javaVendor) {
this.javaVersion = javaVersion;
this.javaVendor = javaVendor;
}

public CustomEventProcessor() {
this(System.getProperty("java.version"), System.getProperty("java.vendor"));
}

@Override
public SentryEvent process(SentryEvent event, @Nullable Object hint) {
final SentryRuntime runtime = new SentryRuntime();
runtime.setVersion(javaVersion);
runtime.setName(javaVendor);
event.getContexts().setRuntime(runtime);
return event;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.sentry.samples.spring.boot;

public class Person {
private final String firstName;
private final String lastName;

public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

public String getFirstName() {
return firstName;
}

public String getLastName() {
return lastName;
}

@Override
public String toString() {
return "Person{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + '}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.sentry.samples.spring.boot;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/person/")
public class PersonController {
private static final Logger LOGGER = LoggerFactory.getLogger(PersonController.class);

@GetMapping("{id}")
Person person(@PathVariable Long id) {
LOGGER.info("Loading person with id={}", id);
throw new IllegalArgumentException("Something went wrong [id=" + id + "]");
}

@PostMapping
Person create(@RequestBody Person person) {
LOGGER.warn("Creating person: {}", person);
return person;
}
}
Loading

0 comments on commit 923f9fc

Please sign in to comment.