Skip to content

Commit 36e68d4

Browse files
authored
Merge branch 'main' into 08-04-add_new_modules_for_spring_7_and_spring_boot_4
2 parents 47b3629 + c626414 commit 36e68d4

File tree

35 files changed

+920
-57
lines changed

35 files changed

+920
-57
lines changed

.cursor/rules/offline.mdc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
alwaysApply: true
2+
alwaysApply: false
33
description: Java SDK Offline behaviour
44
---
55
# Java SDK Offline behaviour

.cursor/rules/opentelemetry.mdc

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
---
2+
alwaysApply: false
3+
description: Java SDK OpenTelemetry Integration
4+
---
5+
# Java SDK OpenTelemetry Integration
6+
7+
## Overview
8+
9+
The Sentry Java SDK provides comprehensive OpenTelemetry integration through multiple modules:
10+
11+
- `sentry-opentelemetry-core`: Core OpenTelemetry integration functionality
12+
- `sentry-opentelemetry-agent`: Java Agent-based integration for automatic instrumentation
13+
- `sentry-opentelemetry-agentless`: Manual instrumentation without Java agent
14+
- `sentry-opentelemetry-agentless-spring`: Spring-specific agentless integration
15+
- `sentry-opentelemetry-bootstrap`: Classes that go into the bootstrap classloader when the agent is used. For agentless they are simply used in the applications classloader.
16+
- `sentry-opentelemetry-agentcustomization`: Classes that help wire up Sentry in OpenTelemetry. These land in the agent classloader when the agent is used. For agentless they are simply used in the application classloader.
17+
18+
## Advantages over using Sentry without OpenTelemetry
19+
20+
- Support for more libraries and frameworks
21+
- See https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation for a list of supported libraries and frameworks
22+
- More automated Performance instrumentation (spans) created
23+
- Using `sentry-opentelemetry-agent` offers most support
24+
- Using `sentry-opentelemetry-agentless-spring` for Spring Boot also has a lot of supported libraries, altough fewer than the agent does
25+
- Note that `sentry-opentelemetry-agentless` will not have any OpenTelemetry auto instrumentation
26+
- Sentry also relies on OpenTelemetry `Context` propagation to propagate Sentry `Scopes`, ensuring e.g. that execution flow for a request shares data and does not leak data into other requests.
27+
- OpenTelemetry also offers better support for distributed tracing since more libraries are supported for attaching tracing information to outgoing requests and picking up incoming tracing information.
28+
29+
## Key Components
30+
31+
### Agent vs Agentless
32+
33+
**Java Agent-based integration**:
34+
- Automatic instrumentation via Java agent
35+
- Can be added to any JAR when starting, no extra dependencies or code changes required. Just add the agent when running the application, e.g. `SENTRY_PROPERTIES_FILE=sentry.properties JAVA_TOOL_OPTIONS="-javaagent:sentry-opentelemetry-agent.jar" java -jar your-application.jar`.
36+
- Uses OpenTelemetry Java agent with Sentry extensions
37+
- Uses bytecode manipulation
38+
39+
**Agentless-Spring integration**:
40+
- Automatic instrumentation setup via Spring Boot
41+
- Dependency needs to be added to the project.
42+
43+
**Agentless integration**:
44+
- Manual instrumentation setup
45+
- Dependency needs to be added to the project.
46+
47+
**Manual Integration**:
48+
While it's possible to manually wire up all the required classes to make Sentry and OpenTelemetry work together, we do not recommend this.
49+
It is instead preferrable to use `SentryAutoConfigurationCustomizerProvider` so the Sentry SDK has a place to manage required classes and update it when changes are needed.
50+
This way customers receive the updated config automatically as oppposed to having to update manually, wire in new classes, remove old ones etc.
51+
52+
### Integration Architecture
53+
54+
Sentry will try to locate certain classes that come with the Sentry OpenTelemetry integration to:
55+
- Determine whether any Sentry OpenTelemetry integration is present
56+
- Determine which mode to use and in turn which Sentry auto instrumentation to suppress
57+
58+
Reflection is used to search for `io.sentry.opentelemetry.OtelContextScopesStorage` and use it instead of `DefaultScopesStorage` when a Sentry OpenTelemetry integration is present at runtime. `IScopesStorage` is used to store Sentry `Scopes` instances. `DefaultScopesStorage` will use a thread local variable to store the current threads' `Scopes` whereas `OtelContextScopesStorage` makes use of OpenTelemetry SDKs `Context`. Sentry OpenTelemetry integrations configure OpenTelemetry to use `SentryOtelThreadLocalStorage` to customize restoring of the previous `Context`.
59+
60+
OpenTelemetry SDK makes use of `io.opentelemetry.context.Scope` in `try-with-resources` statements that call `close` when a code block is finished. Without customization, it would refuse to restore the previous `Context` onto the `ThreadLocal` if the current state of the `ThreadLocal` isn't the same as the one this scope was created for. Sentry changes this behaviour in `SentryScopeImpl` to restore the previous `Context` onto the `ThreadLocal` even if an inner `io.opentelemetry.context.Scope` wasn't properly cleaned up. Our thinking here is to prefer returning to a clean state as opposed to propagating the problem. The unclean state could happen, if `io.opentelemetry.context.Scope` isn't closed, e.g. when forgetting to put it in a `try-with-resources` statement and not calling `close` (e.g. not putting it in a `finally` block in that case).
61+
62+
`SentryContextStorageProvider` looks for any other `ContextStorageProvider` and forwards to that to not override any customized `ContextStorage`. If no other provider is found, `SentryOtelThreadLocalStorage` is used.
63+
64+
`SpanFactoryFactory` is used to configure Sentry to use `io.sentry.opentelemetry.OtelSpanFactory` if the class is present at runtime. Reflection is used to search for it. If the class is not available, we fall back to `DefaultSpanFactory`.
65+
66+
`DefaultSpanFactory` creates a `SentryTracer` instance when creating a transaction and spans are then created directly on the transaction via `startChild`.
67+
`OtelSpanFactory` instead creates an OpenTelemetry span and wraps it using `OtelTransactionSpanForwarder` to simulate a transaction. The `startChild` invocations on `OtelTransactionSpanForwarder` go through `OtelSpanFactory` again to create the child span.
68+
69+
## Configuration
70+
71+
We use `SentryAutoConfigurationCustomizerProvider` to configure OpenTelemetry for use with Sentry and register required classes, hooks etc.
72+
73+
## Span Processing
74+
75+
Both Sentry and OpenTelemetry API can be used to create spans. When using Sentry API, `OtelSpanFactory` is used to indirectly create a OpenTelemetry span.
76+
Regardless of API used, when an OpenTelemetry span is created, it goes through `SentrySampler` for sampling and `OtelSentrySpanProcessor` for `Scopes` forking and ensuring the trace is continued.
77+
When Sentry API is used, sampling is performed in `Scopes.createTransaction` before forwarding the call to `OtelSpanFactory`. The sampling decision and other sampling details are forwarded to `SentrySampler` and `OtelSentrySpanProcessor`.
78+
79+
When a span is finished, regardless of whether Sentry or OpenTelemetry API is used, it goes through `OtelSentrySpanProcessor` to set the end date and then through `BatchSpanProcessor` which will batch spans and then forward them to `SentrySpanExporter`.
80+
81+
`SentrySpanExporter` collects spans, then structures them to create a transaction for the local root span and attaches child spans to form a span tree.
82+
Some OpenTelemetry attributes are transformed into their corresponding Sentry data structure or format.
83+
84+
After creating the transaction with child spans `SentrySpanExporter` uses Sentry API to send the transaction to Sentry. This API call however forces the use of `DefaultSpanFactory` in order to create the required Sentry classes for sending and also to not create an infinite loop where any span created will cause a new span to be created recursively.
85+
86+
## Troubleshooting
87+
88+
To debug forking of `Scopes`, we added a reference to `parent` `Scopes` and a `creator` String to store the reason why `Scopes` were created or forked.

.github/workflows/agp-matrix.yml

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,36 @@ jobs:
4343
with:
4444
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
4545

46-
- name: Setup KVM
47-
shell: bash
46+
- name: Enable KVM
4847
run: |
49-
# check if virtualization is supported...
50-
sudo apt install -y --no-install-recommends cpu-checker coreutils && echo "CPUs=$(nproc --all)" && kvm-ok
51-
# allow access to KVM to run the emulator
52-
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
53-
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
48+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
5449
sudo udevadm control --reload-rules
5550
sudo udevadm trigger --name-match=kvm
5651
52+
- name: AVD cache
53+
uses: actions/cache@v4
54+
id: avd-cache
55+
with:
56+
path: |
57+
~/.android/avd/*
58+
~/.android/adb*
59+
key: avd-api-30-x86_64-aosp_atd
60+
61+
- name: Create AVD and generate snapshot for caching
62+
if: steps.avd-cache.outputs.cache-hit != 'true'
63+
uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # pin@v2
64+
with:
65+
api-level: 30
66+
target: aosp_atd
67+
channel: canary # Necessary for ATDs
68+
arch: x86_64
69+
force-avd-creation: false
70+
disable-animations: true
71+
disable-spellchecker: true
72+
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
73+
disk-size: 4096M
74+
script: echo "Generated AVD snapshot for caching."
75+
5776
# Clean, build and release a test apk
5877
- name: Make assembleUiTests
5978
run: make assembleUiTests
@@ -63,13 +82,13 @@ jobs:
6382
uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # pin@v2
6483
with:
6584
api-level: 30
85+
target: aosp_atd
86+
channel: canary # Necessary for ATDs
87+
arch: x86_64
6688
force-avd-creation: false
67-
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
6889
disable-animations: true
6990
disable-spellchecker: true
70-
target: 'aosp_atd'
71-
arch: x86
72-
channel: canary # Necessary for ATDs
91+
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
7392
disk-size: 4096M
7493
script: ./gradlew sentry-android-integration-tests:sentry-uitest-android:connectedReleaseAndroidTest -DtestBuildType=release -Denvironment=github --daemon
7594

.github/workflows/integration-tests-ui-critical.yml

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,36 @@ jobs:
7979
- name: Checkout code
8080
uses: actions/checkout@v4
8181

82-
- name: Setup KVM
83-
shell: bash
82+
- name: Enable KVM
8483
run: |
85-
# check if virtualization is supported...
86-
sudo apt install -y --no-install-recommends cpu-checker coreutils && echo "CPUs=$(nproc --all)" && kvm-ok
87-
# allow access to KVM to run the emulator
88-
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
89-
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
84+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
9085
sudo udevadm control --reload-rules
9186
sudo udevadm trigger --name-match=kvm
9287
88+
- name: AVD cache
89+
uses: actions/cache@v4
90+
id: avd-cache
91+
with:
92+
path: |
93+
~/.android/avd/*
94+
~/.android/adb*
95+
key: avd-api-${{ matrix.api-level }}-${{ matrix.arch }}-${{ matrix.target }}
96+
97+
- name: Create AVD and generate snapshot for caching
98+
if: steps.avd-cache.outputs.cache-hit != 'true'
99+
uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # pin@v2
100+
with:
101+
api-level: ${{ matrix.api-level }}
102+
target: ${{ matrix.target }}
103+
channel: ${{ matrix.channel }}
104+
arch: ${{ matrix.arch }}
105+
force-avd-creation: false
106+
disable-animations: true
107+
disable-spellchecker: true
108+
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
109+
disk-size: 4096M
110+
script: echo "Generated AVD snapshot for caching."
111+
93112
- name: Download APK artifact
94113
uses: actions/download-artifact@v5
95114
with:
@@ -104,21 +123,13 @@ jobs:
104123
uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # pin@v2.34.0
105124
with:
106125
api-level: ${{ matrix.api-level }}
107-
force-avd-creation: false
108-
disable-animations: true
109-
disable-spellchecker: true
110126
target: ${{ matrix.target }}
111127
channel: ${{ matrix.channel }}
112128
arch: ${{ matrix.arch }}
113-
emulator-options: >
114-
-no-window
115-
-no-snapshot-save
116-
-gpu swiftshader_indirect
117-
-noaudio
118-
-no-boot-anim
119-
-camera-back none
120-
-camera-front none
121-
-timezone US/Pacific
129+
force-avd-creation: false
130+
disable-animations: true
131+
disable-spellchecker: true
132+
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -no-snapshot-save
122133
script: |
123134
adb install -r -d "${{env.APK_NAME}}"
124135
maestro test "${{env.BASE_PATH}}/maestro" --debug-output "${{env.BASE_PATH}}/maestro-logs"

.github/workflows/system-tests-backend.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ jobs:
5454
- sample: "sentry-samples-console"
5555
agent: "false"
5656
agent-auto-init: "true"
57+
- sample: "sentry-samples-logback"
58+
agent: "false"
59+
agent-auto-init: "true"
60+
- sample: "sentry-samples-log4j2"
61+
agent: "false"
62+
agent-auto-init: "true"
63+
- sample: "sentry-samples-jul"
64+
agent: "false"
65+
agent-auto-init: "true"
66+
- sample: "sentry-samples-spring-boot-4"
67+
agent: "false"
68+
agent-auto-init: "true"
5769
- sample: "sentry-samples-spring-boot-4-webflux"
5870
agent: "false"
5971
agent-auto-init: "true"

CHANGELOG.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
# Changelog
22

3-
### Unreleased
3+
## Unreleased
4+
5+
### Features
6+
7+
- Add onDiscard to enable users to track the type and amount of data discarded before reaching Sentry ([#4612](https://github.com/getsentry/sentry-java/pull/4612))
8+
- Stub for setting the callback on `Sentry.init`:
9+
```java
10+
Sentry.init(options -> {
11+
...
12+
options.setOnDiscard(
13+
(reason, category, number) -> {
14+
// Your logic to process discarded data
15+
});
16+
});
17+
```
18+
19+
## 8.19.1
420

521
### Fixes
622

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
1111
android.useAndroidX=true
1212

1313
# Release information
14-
versionName=8.19.0
14+
versionName=8.19.1
1515

1616
# Override the SDK name on native crashes on Android
1717
sentryAndroidSdkName=sentry.native.android

sentry-samples/sentry-samples-console/src/main/java/io/sentry/samples/console/Main.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
package io.sentry.samples.console;
22

3-
import io.sentry.Breadcrumb;
4-
import io.sentry.EventProcessor;
5-
import io.sentry.Hint;
6-
import io.sentry.ISpan;
7-
import io.sentry.ITransaction;
8-
import io.sentry.Sentry;
9-
import io.sentry.SentryEvent;
10-
import io.sentry.SentryLevel;
11-
import io.sentry.SpanStatus;
3+
import io.sentry.*;
4+
import io.sentry.clientreport.DiscardReason;
125
import io.sentry.protocol.Message;
136
import io.sentry.protocol.User;
147
import java.util.Collections;
158

169
public class Main {
1710

11+
private static long numberOfDiscardedSpansDueToOverflow = 0;
12+
1813
public static void main(String[] args) throws InterruptedException {
1914
Sentry.init(
2015
options -> {
@@ -59,6 +54,18 @@ public static void main(String[] args) throws InterruptedException {
5954
return breadcrumb;
6055
});
6156

57+
// Record data being discarded, including the reason, type of data, and the number of
58+
// items dropped
59+
options.setOnDiscard(
60+
(reason, category, number) -> {
61+
// Only record the number of lost spans due to overflow conditions
62+
if ((reason.equals(DiscardReason.CACHE_OVERFLOW)
63+
|| reason.equals(DiscardReason.QUEUE_OVERFLOW))
64+
&& category.equals(DataCategory.Span)) {
65+
numberOfDiscardedSpansDueToOverflow += number;
66+
}
67+
});
68+
6269
// Configure the background worker which sends events to sentry:
6370
// Wait up to 5 seconds before shutdown while there are events to send.
6471
options.setShutdownTimeoutMillis(5000);

0 commit comments

Comments
 (0)