Skip to content

Commit a7e9275

Browse files
committed
Create cursor rules file explaining our OpenTelemetry integration
1 parent d2c7e95 commit a7e9275

File tree

1 file changed

+84
-0
lines changed

1 file changed

+84
-0
lines changed

.cursor/rules/opentelemetry.mdc

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Java SDK OpenTelemetry Integration
2+
3+
## Overview
4+
5+
The Sentry Java SDK provides comprehensive OpenTelemetry integration through multiple modules:
6+
7+
- `sentry-opentelemetry-core`: Core OpenTelemetry integration functionality
8+
- `sentry-opentelemetry-agent`: Agent-based integration for automatic instrumentation
9+
- `sentry-opentelemetry-agentless`: Manual instrumentation without Java agent
10+
- `sentry-opentelemetry-agentless-spring`: Spring-specific agentless integration
11+
- `sentry-opentelemetry-bootstrap`: Bootstrap classes for agent initialization
12+
- `sentry-opentelemetry-agentcustomization`: Agent customization utilities
13+
14+
## Advantages over using Sentry without OpenTelemetry
15+
16+
- Support for more libraries and frameworks
17+
- See https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation for a list of supported libraries and frameworks
18+
- More automated Performance instrumentation (spans) created
19+
- Using `sentry-opentelemetry-agent` offers most support
20+
- Using `sentry-opentelemetry-agentless-spring` for Spring Boot also has a lot of supported libraries, altough fewer than the agent does
21+
- Note that `sentry-opentelemetry-agentless` will not have any OpenTelemetry auto instrumentation
22+
- 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.
23+
- 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.
24+
25+
## Key Components
26+
27+
### Agent vs Agentless
28+
29+
**Agent-based integration**:
30+
- Automatic instrumentation via Java agent
31+
- Can be added to any JAR when starting, no extra dependencies or code changes required. Just add the agent to the `java` command.
32+
- Uses OpenTelemetry Java agent with Sentry extensions
33+
- Uses bytecode manipulation
34+
35+
**Agentless-Spring integration**:
36+
- Automatic instrumentation setup via Spring Boot
37+
- Dependency needs to be added to the project.
38+
39+
**Agentless integration**:
40+
- Manual instrumentation setup
41+
- Dependency needs to be added to the project.
42+
43+
**Manual Integration**:
44+
While it's possible to manually wire up all the required classes to make Sentry and OpenTelemetry work together, we do not recommend this.
45+
It is instead preferrable to go through `SentryAutoConfigurationCustomizerProvider` so the Sentry SDK has a place to manage required classes and update it when changes are needed.
46+
This way customers receive the updated config automatically as oppposed to having to update manually, wire in new classes, remove old ones etc.
47+
48+
### Integration Architecture
49+
50+
Sentry will try to locate certain classes that come with the Sentry OpenTelemetry integration to:
51+
- Determine whether any Sentry OpenTelemetry integration is present
52+
- Determine which mode to use and in turn which Sentry auto instrumentation to suppress
53+
54+
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`.
55+
56+
OpenTelemetry SDK makes use of `io.opentelemetry.context.Scope` in `try` with 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 statement and not calling `close` (e.g. not putting it in a `finally` block in that case).
57+
58+
`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.
59+
60+
`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`.
61+
62+
`DefaultSpanFactory` creates a `SentryTracer` instance when creating a transaction and spans are then created directly on the transaction via `startChild`.
63+
`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.
64+
65+
## Configuration
66+
67+
We use `SentryAutoConfigurationCustomizerProvider` to configure OpenTelemetry for use with Sentry and register required classes, hooks etc.
68+
69+
## Span Processing
70+
71+
Both Sentry and OpenTelemetry API can be used to create spans. When using Sentry API, `OtelSpanFactory` is used to indirectly create a OpenTelemetry span.
72+
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.
73+
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`.
74+
75+
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`.
76+
77+
`SentrySpanExporter` collects spans, then structures them to create a transaction for the local root span and attaches child spans to form a span tree.
78+
Some OpenTelemetry attributes are transformed into their corresponding Sentry data structure or format.
79+
80+
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.
81+
82+
## Troubleshooting
83+
84+
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.

0 commit comments

Comments
 (0)