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

Commit 923f9fc

Browse files
Add Spring Integration. (#539)
1 parent 60af521 commit 923f9fc

38 files changed

+1036
-49
lines changed

buildSrc/src/main/java/Config.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ object Config {
4848
val log4j2Core = "org.apache.logging.log4j:log4j-core:$log4j2Version"
4949

5050
val springBootStarter = "org.springframework.boot:spring-boot-starter:$springBootVersion"
51+
val springBootStarterTest = "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
52+
val springBootStarterWeb = "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
53+
val springBootStarterSecurity = "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
5154

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

7676
object QualityPlugins {
7777
object Jacoco {
7878
val version = "0.8.5"
7979
val minimumCoverage = BigDecimal.valueOf(0.6)
8080
}
81-
val jacocoVersion = "0.8.5"
8281
val spotless = "com.diffplug.spotless"
8382
val spotlessVersion = "5.1.0"
8483
val errorProne = "net.ltgt.errorprone"
@@ -95,6 +94,7 @@ object Config {
9594
val SENTRY_ANDROID_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.android"
9695
val SENTRY_LOGBACK_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.logback"
9796
val SENTRY_LOG4J2_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.log4j2"
97+
val SENTRY_SPRING_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring"
9898
val SENTRY_SPRING_BOOT_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot"
9999
val group = "io.sentry"
100100
val description = "SDK for sentry.io"

sentry-core/src/main/java/io/sentry/core/Breadcrumb.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,22 @@ public final class Breadcrumb implements Cloneable, IUnknownPropertiesConsumer {
4343
this.timestamp = timestamp;
4444
}
4545

46+
/**
47+
* Creates HTTP breadcrumb.
48+
*
49+
* @param url - the request URL
50+
* @param method - the request method
51+
* @return the breadcrumb
52+
*/
53+
public static @NotNull Breadcrumb http(final @NotNull String url, final @NotNull String method) {
54+
final Breadcrumb breadcrumb = new Breadcrumb();
55+
breadcrumb.setType("http");
56+
breadcrumb.setCategory("http");
57+
breadcrumb.setData("url", url);
58+
breadcrumb.setData("method", method.toUpperCase(Locale.getDefault()));
59+
return breadcrumb;
60+
}
61+
4662
/** Breadcrumb ctor */
4763
public Breadcrumb() {
4864
this(DateUtils.getCurrentDateTimeOrNull());
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package io.sentry.core;
2+
3+
import io.sentry.core.exception.ExceptionMechanismException;
4+
import io.sentry.core.util.Objects;
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.WeakHashMap;
9+
import org.jetbrains.annotations.NotNull;
10+
import org.jetbrains.annotations.Nullable;
11+
12+
/** Deduplicates events containing throwable that has been already processed. */
13+
public final class DuplicateEventDetectionEventProcessor implements EventProcessor {
14+
private final WeakHashMap<Throwable, Object> capturedObjects = new WeakHashMap<>();
15+
private final SentryOptions options;
16+
17+
public DuplicateEventDetectionEventProcessor(final @NotNull SentryOptions options) {
18+
this.options = Objects.requireNonNull(options, "options are required");
19+
}
20+
21+
@Override
22+
public SentryEvent process(final @NotNull SentryEvent event, final @Nullable Object hint) {
23+
final Throwable throwable = event.getThrowable();
24+
if (throwable != null) {
25+
if (throwable instanceof ExceptionMechanismException) {
26+
final ExceptionMechanismException ex = (ExceptionMechanismException) throwable;
27+
if (capturedObjects.containsKey(ex.getThrowable())) {
28+
options
29+
.getLogger()
30+
.log(
31+
SentryLevel.DEBUG,
32+
"Duplicate Exception detected. Event %s will be discarded.",
33+
event.getEventId());
34+
return null;
35+
} else {
36+
capturedObjects.put(ex.getThrowable(), null);
37+
}
38+
} else {
39+
if (capturedObjects.containsKey(throwable)
40+
|| containsAnyKey(capturedObjects, allCauses(throwable))) {
41+
options
42+
.getLogger()
43+
.log(
44+
SentryLevel.DEBUG,
45+
"Duplicate Exception detected. Event %s will be discarded.",
46+
event.getEventId());
47+
return null;
48+
} else {
49+
capturedObjects.put(throwable, null);
50+
}
51+
}
52+
}
53+
return event;
54+
}
55+
56+
private static <T> boolean containsAnyKey(
57+
final @NotNull Map<T, Object> map, final @NotNull List<T> list) {
58+
for (T entry : list) {
59+
if (map.containsKey(entry)) {
60+
return true;
61+
}
62+
}
63+
return false;
64+
}
65+
66+
private static @NotNull List<Throwable> allCauses(final @NotNull Throwable throwable) {
67+
final List<Throwable> causes = new ArrayList<>();
68+
Throwable ex = throwable;
69+
while (ex.getCause() != null) {
70+
causes.add(ex.getCause());
71+
ex = ex.getCause();
72+
}
73+
return causes;
74+
}
75+
}

sentry-core/src/main/java/io/sentry/core/SentryOptions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,7 @@ public SentryOptions() {
10471047
integrations.add(new ShutdownHookIntegration());
10481048

10491049
eventProcessors.add(new MainEventProcessor(this));
1050+
eventProcessors.add(new DuplicateEventDetectionEventProcessor(this));
10501051

10511052
setSentryClientName(BuildConfig.SENTRY_JAVA_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
10521053
setSdkVersion(createSdkVersion());

sentry-core/src/test/java/io/sentry/core/BreadcrumbTest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,13 @@ class BreadcrumbTest {
9898
val breadcrumb = Breadcrumb("this is a test")
9999
assertEquals("this is a test", breadcrumb.message)
100100
}
101+
102+
@Test
103+
fun `creates HTTP breadcrumb`() {
104+
val breadcrumb = Breadcrumb.http("http://example.com", "POST")
105+
assertEquals("http://example.com", breadcrumb.data["url"])
106+
assertEquals("POST", breadcrumb.data["method"])
107+
assertEquals("http", breadcrumb.type)
108+
assertEquals("http", breadcrumb.category)
109+
}
101110
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package io.sentry.core
2+
3+
import io.sentry.core.exception.ExceptionMechanismException
4+
import io.sentry.core.protocol.Mechanism
5+
import java.lang.RuntimeException
6+
import kotlin.test.Test
7+
import kotlin.test.assertNotNull
8+
import kotlin.test.assertNull
9+
10+
class DuplicateEventDetectionEventProcessorTest {
11+
12+
val processor = DuplicateEventDetectionEventProcessor(SentryOptions())
13+
14+
@Test
15+
fun `does not drop event if no previous event with same exception was processed`() {
16+
processor.process(SentryEvent(), null)
17+
18+
val result = processor.process(SentryEvent(RuntimeException()), null)
19+
20+
assertNotNull(result)
21+
}
22+
23+
@Test
24+
fun `drops event with the same exception`() {
25+
val event = SentryEvent(RuntimeException())
26+
processor.process(event, null)
27+
28+
val result = processor.process(event, null)
29+
assertNull(result)
30+
}
31+
32+
@Test
33+
fun `drops event with mechanism exception having an exception that has already been processed`() {
34+
val event = SentryEvent(RuntimeException())
35+
processor.process(event, null)
36+
37+
val result = processor.process(SentryEvent(ExceptionMechanismException(Mechanism(), event.throwable, null)), null)
38+
assertNull(result)
39+
}
40+
41+
@Test
42+
fun `drops event with exception that has already been processed with event with mechanism exception`() {
43+
val sentryEvent = SentryEvent(ExceptionMechanismException(Mechanism(), RuntimeException(), null))
44+
processor.process(sentryEvent, null)
45+
46+
val result = processor.process(SentryEvent((sentryEvent.throwable as ExceptionMechanismException).throwable), null)
47+
48+
assertNull(result)
49+
}
50+
51+
@Test
52+
fun `drops event with the cause equal to exception in already processed event`() {
53+
val event = SentryEvent(RuntimeException())
54+
processor.process(event, null)
55+
56+
val result = processor.process(SentryEvent(RuntimeException(event.throwable)), null)
57+
58+
assertNull(result)
59+
}
60+
61+
@Test
62+
fun `drops event with any of the causes has been already processed`() {
63+
val event = SentryEvent(RuntimeException())
64+
processor.process(event, null)
65+
66+
val result = processor.process(SentryEvent(RuntimeException(RuntimeException(event.throwable))), null)
67+
68+
assertNull(result)
69+
}
70+
}

sentry-samples/sentry-samples-spring-boot/build.gradle.kts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import org.jetbrains.kotlin.config.KotlinCompilerVersion
12
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
23

34
plugins {
@@ -16,14 +17,14 @@ repositories {
1617
}
1718

1819
dependencies {
19-
implementation("org.springframework.boot:spring-boot-starter-security")
20-
implementation("org.springframework.boot:spring-boot-starter-web")
21-
implementation("org.springframework.boot:spring-boot-starter")
20+
implementation(Config.Libs.springBootStarterSecurity)
21+
implementation(Config.Libs.springBootStarterWeb)
22+
implementation(Config.Libs.springBootStarter)
2223
implementation("org.jetbrains.kotlin:kotlin-reflect")
23-
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
24+
implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION))
2425
implementation(project(":sentry-spring-boot-starter"))
2526
implementation(project(":sentry-logback"))
26-
testImplementation("org.springframework.boot:spring-boot-starter-test") {
27+
testImplementation(Config.Libs.springBootStarterTest) {
2728
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
2829
}
2930
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.sentry.samples.spring.boot;
2+
3+
import io.sentry.core.EventProcessor;
4+
import io.sentry.core.SentryEvent;
5+
import io.sentry.core.protocol.SentryRuntime;
6+
import org.jetbrains.annotations.Nullable;
7+
import org.springframework.stereotype.Component;
8+
9+
/**
10+
* Custom {@link EventProcessor} implementation lets modifying {@link SentryEvent}s before they are
11+
* sent to Sentry.
12+
*/
13+
@Component
14+
public class CustomEventProcessor implements EventProcessor {
15+
private final String javaVersion;
16+
private final String javaVendor;
17+
18+
public CustomEventProcessor(String javaVersion, String javaVendor) {
19+
this.javaVersion = javaVersion;
20+
this.javaVendor = javaVendor;
21+
}
22+
23+
public CustomEventProcessor() {
24+
this(System.getProperty("java.version"), System.getProperty("java.vendor"));
25+
}
26+
27+
@Override
28+
public SentryEvent process(SentryEvent event, @Nullable Object hint) {
29+
final SentryRuntime runtime = new SentryRuntime();
30+
runtime.setVersion(javaVersion);
31+
runtime.setName(javaVendor);
32+
event.getContexts().setRuntime(runtime);
33+
return event;
34+
}
35+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.sentry.samples.spring.boot;
2+
3+
public class Person {
4+
private final String firstName;
5+
private final String lastName;
6+
7+
public Person(String firstName, String lastName) {
8+
this.firstName = firstName;
9+
this.lastName = lastName;
10+
}
11+
12+
public String getFirstName() {
13+
return firstName;
14+
}
15+
16+
public String getLastName() {
17+
return lastName;
18+
}
19+
20+
@Override
21+
public String toString() {
22+
return "Person{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + '}';
23+
}
24+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.sentry.samples.spring.boot;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
import org.springframework.web.bind.annotation.PathVariable;
7+
import org.springframework.web.bind.annotation.PostMapping;
8+
import org.springframework.web.bind.annotation.RequestBody;
9+
import org.springframework.web.bind.annotation.RequestMapping;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
@RestController
13+
@RequestMapping("/person/")
14+
public class PersonController {
15+
private static final Logger LOGGER = LoggerFactory.getLogger(PersonController.class);
16+
17+
@GetMapping("{id}")
18+
Person person(@PathVariable Long id) {
19+
LOGGER.info("Loading person with id={}", id);
20+
throw new IllegalArgumentException("Something went wrong [id=" + id + "]");
21+
}
22+
23+
@PostMapping
24+
Person create(@RequestBody Person person) {
25+
LOGGER.warn("Creating person: {}", person);
26+
return person;
27+
}
28+
}

0 commit comments

Comments
 (0)