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
89 changes: 89 additions & 0 deletions .cursor/rules/api.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
alwaysApply: false
description: Public API surface, binary compatibility, and common classes to modify
---
# Java SDK Public API

## API Compatibility

Public API is tracked via `.api` files generated by the [Binary Compatibility Validator](https://github.com/Kotlin/binary-compatibility-validator) Gradle plugin. Each module has its own file at `<module>/api/<module>.api`.

- **Never edit `.api` files manually.** Run `./gradlew apiDump` to regenerate them.
- `./gradlew check` validates current code against `.api` files and fails on unintended changes.
- `@ApiStatus.Internal` marks classes/methods as internal โ€” they still appear in `.api` files but are not part of the public contract.
- `@ApiStatus.Experimental` marks API that may change in future versions.

## Key Public API Classes

### Entry Point

`Sentry` (`sentry` module) is the static entry point. Most public API methods on `Sentry` delegate to `getCurrentScopes()`. When adding a new method to `Sentry`, it typically calls through to `IScopes`.

### Interfaces

| Interface | Description |
|-----------|-------------|
| `IScope` | Single scope โ€” holds data (tags, extras, breadcrumbs, attributes, user, contexts, etc.) |
| `IScopes` | Multi-scope container โ€” manages global, isolation, and current scope; delegates capture calls to `SentryClient` |
| `ISpan` | Performance span โ€” timing, tags, data, measurements |
| `ITransaction` | Top-level transaction โ€” extends `ISpan` |

### Configuration

`SentryOptions` is the base configuration class. Platform-specific subclasses:
- `SentryAndroidOptions` โ€” Android-specific options
- Integration modules may add their own (e.g. `SentrySpringProperties`)

New features must be **opt-in by default** โ€” add a getter/setter pair to the appropriate options class.

### Internal Classes (Not Public API)

| Class | Description |
|-------|-------------|
| `SentryClient` | Sends events/envelopes to Sentry โ€” receives captured data from `Scopes` |
| `SentryEnvelope` / `SentryEnvelopeItem` | Low-level envelope serialization |
| `Scope` | Concrete implementation of `IScope` |
| `Scopes` | Concrete implementation of `IScopes` |

## Adding New Public API

When adding a new method that users can call (e.g. a new scope operation), these classes typically need changes:

### Interfaces and Static API
1. `IScope` โ€” add the method signature
2. `IScopes` โ€” add the method signature (usually delegates to a scope)
3. `Sentry` โ€” add static method that calls `getCurrentScopes()`

### Implementations
4. `Scope` โ€” actual implementation with data storage
5. `Scopes` โ€” delegates to the appropriate scope (global, isolation, or current based on `defaultScopeType`)
6. `CombinedScopeView` โ€” defines how the three scope types combine for reads (merge, first-wins, or specific scope)

### No-Op and Adapter Classes
7. `NoOpScope` โ€” no-op stub for `IScope`
8. `NoOpScopes` โ€” no-op stub for `IScopes`
9. `ScopesAdapter` โ€” delegates to `Sentry` static API
10. `HubAdapter` โ€” deprecated bridge from old `IHub` API
11. `HubScopesWrapper` โ€” wraps `IScopes` as `IHub`

### Serialization (if the data is sent to Sentry)
12. Add serialization/deserialization in the relevant data class or create a new one implementing `JsonSerializable` and `JsonDeserializer`

### Tests
13. Write tests for all implementations, especially `Scope`, `Scopes`, `SentryTest`, and any new data classes
14. No-op classes typically don't need separate tests unless they have non-trivial logic

## Protocol / Data Model Classes

Classes in the `io.sentry.protocol` package represent the Sentry event protocol. They implement `JsonSerializable` for serialization and have a companion `Deserializer` class implementing `JsonDeserializer`. When adding new fields to protocol classes, update both serialization and deserialization.

## Namespaced APIs

Newer features are namespaced under `Sentry.<feature>()` rather than added directly to `Sentry`. Each namespaced API has an interface, implementation, and no-op. Examples:

- `Sentry.logger()` โ†’ `ILoggerApi` / `LoggerApi` / `NoOpLoggerApi` (structured logging, `io.sentry.logger` package)
- `Sentry.metrics()` โ†’ `IMetricsApi` / `MetricsApi` / `NoOpMetricsApi` (metrics)

Options for namespaced features are similarly nested under `SentryOptions`, e.g. `SentryOptions.getMetrics()`, `SentryOptions.getLogs()`.

These APIs may share infrastructure like the type system (`SentryAttributeType.inferFrom()`) โ€” changes to shared components (e.g. attribute types) may require updates across multiple namespaced APIs.
115 changes: 115 additions & 0 deletions .cursor/rules/options.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
alwaysApply: false
description: Adding and modifying SDK options
---
# Adding Options to the SDK

New features must be **opt-in by default**. Options control whether a feature is enabled and how it behaves.

## Namespaced Options

Newer features use namespaced option classes nested inside `SentryOptions`, e.g.:
- `SentryOptions.getLogs()` โ†’ `SentryOptions.Logs`
- `SentryOptions.getMetrics()` โ†’ `SentryOptions.Metrics`

Each namespaced options class is a `public static final class` inside `SentryOptions` with its own fields, getters/setters, and callbacks (e.g. `BeforeSendLogCallback`, `BeforeSendMetricCallback`).

A typical namespaced options class contains:
- `enabled` boolean (default `false` for opt-in)
- `sampleRate` double (if the feature supports sampling)
- `beforeSend` callback interface (nested inside the options class)

To add a new namespaced options class:
1. Create the `public static final class` inside `SentryOptions` with fields, getters/setters, and any callback interfaces
2. Add a private field on `SentryOptions` initialized with `new SentryOptions.MyFeature()`
3. Add getter/setter on `SentryOptions` annotated with `@ApiStatus.Experimental`

## Direct (Non-Namespaced) Options

Options that apply globally across the SDK (e.g. `dsn`, `environment`, `release`, `sampleRate`, `maxBreadcrumbs`) live as direct fields on `SentryOptions` with getter/setter pairs. Use this pattern for options that aren't tied to a specific feature namespace.

## Configuration Layers

Options can be set through multiple layers. When adding a new option, consider which layers apply:

### 1. SentryOptions (always required)

The core options class. Add the field (or nested class) with getter/setter here.

**File:** `sentry/src/main/java/io/sentry/SentryOptions.java`

**Tests:** `sentry/src/test/java/io/sentry/SentryOptionsTest.kt`
- Test the default value
- Test merge behavior (see layer 2)

### 2. ExternalOptions (sentry.properties / environment variables)

Allows setting options via `sentry.properties` file or system properties. Fields use nullable wrapper types (`@Nullable Boolean`, `@Nullable Double`) since unset means "don't override the default."

**File:** `sentry/src/main/java/io/sentry/ExternalOptions.java`
- Add `@Nullable` fields with getter/setter for each externally configurable option (e.g. `enableMetrics`, `logsSampleRate`)
- Wire them in the static `from(PropertiesProvider)` method:
- Boolean: `propertiesProvider.getBooleanProperty("metrics.enabled")`
- Double: `propertiesProvider.getDoubleProperty("logs.sample-rate")`

**File:** `sentry/src/main/java/io/sentry/SentryOptions.java` โ€” `merge()` method
- Add null-check blocks to apply each external option onto the namespaced options class:
```java
if (options.isEnableMetrics() != null) {
getMetrics().setEnabled(options.isEnableMetrics());
}
if (options.getLogsSampleRate() != null) {
getLogs().setSampleRate(options.getLogsSampleRate());
}
```

**Tests:**
- `sentry/src/test/java/io/sentry/ExternalOptionsTest.kt` โ€” test true/false/null for booleans, valid values and null for doubles
- `sentry/src/test/java/io/sentry/SentryOptionsTest.kt` โ€” test merge applies values and test merge preserves defaults when unset

### 3. Android Manifest Metadata (Android only)

Allows setting options via `AndroidManifest.xml` `<meta-data>` tags.

**File:** `sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java`
- Add a `static final String` constant for the key (e.g. `"io.sentry.metrics.enabled"`)
- Read it in `applyMetadata()` using `readBool(metadata, logger, CONSTANT, defaultValue)`
- Apply to the namespaced options, e.g. `options.getMetrics().setEnabled(...)`

**Tests:** `sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt`
- Test default value preserved when not in manifest
- Test explicit true
- Test explicit false

### 4. Spring Boot Properties (Spring Boot only)

`SentryProperties` extends `SentryOptions`, so namespaced options (nested classes) are automatically available as Spring Boot properties without extra code. For example, `SentryOptions.Logs` is automatically mapped to `sentry.logs.enabled` in `application.properties`.

No additional code is needed for namespaced options โ€” Spring Boot auto-configuration handles this via property binding on the `SentryOptions` class hierarchy.

**Tests:** `sentry-spring-boot*/src/test/kotlin/.../SentryAutoConfigurationTest.kt`
- Add the property (e.g. `"sentry.logs.enabled=true"`) to the existing `resolves all properties` test
- Assert the value is set on the resolved `SentryProperties` bean
- There are three Spring Boot modules with separate test files: `sentry-spring-boot`, `sentry-spring-boot-jakarta`, `sentry-spring-boot-4`

### 5. Reading Options at Runtime

Features check their options at usage time. For namespaced features the check typically happens in the feature's API class (e.g. `LoggerApi`, `MetricsApi`):
- Check `options.getLogs().isEnabled()` early and return if disabled
- Apply sampling via `options.getLogs().getSampleRate()` if applicable
- Apply `beforeSend` callback in `SentryClient` before sending

When a feature has its own capture path (e.g. `captureLog`), the relevant classes are:
- `ISentryClient` โ€” add the capture method signature
- `SentryClient` โ€” implement capture, including `beforeSend` callback execution
- `NoOpSentryClient` โ€” add no-op stub

## Checklist for Adding a New Namespaced Option

1. `SentryOptions.java` โ€” nested options class + getter/setter on `SentryOptions`
2. `ExternalOptions.java` โ€” `@Nullable` fields + wiring in `from()`
3. `SentryOptions.java` `merge()` โ€” apply external options to namespaced class
4. `ManifestMetadataReader.java` โ€” Android manifest support (if Android-relevant)
5. `SentryAutoConfigurationTest.kt` โ€” Spring Boot property binding tests (all three Spring Boot modules)
6. Tests for all of the above (`SentryOptionsTest`, `ExternalOptionsTest`, `ManifestMetadataReaderTest`)
7. Run `./gradlew apiDump` โ€” the nested class and its methods appear in `sentry.api`
23 changes: 23 additions & 0 deletions .cursor/rules/overview_dev.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ These rules are automatically included in every conversation:
Use the `fetch_rules` tool to include these rules when working on specific areas:

### Core SDK Functionality
- **`api`**: Use when working with:
- Adding or modifying public API surface
- Binary compatibility, `.api` files, `apiDump`
- Understanding which classes to modify for new API (interfaces, implementations, no-ops, adapters)
- `IScope`, `IScopes`, `Sentry` static API
- Attributes, logging API, protocol classes

- **`options`**: Use when working with:
- Adding or modifying SDK options (`SentryOptions`, namespaced options)
- External options (`ExternalOptions`, `sentry.properties`, environment variables)
- Android manifest metadata (`ManifestMetadataReader`)
- Spring Boot properties (`SentryProperties`)

- **`scopes`**: Use when working with:
- Hub/Scope management, forking, or lifecycle
- `Sentry.getCurrentScopes()`, `pushScope()`, `withScope()`
Expand Down Expand Up @@ -63,6 +76,13 @@ Use the `fetch_rules` tool to include these rules when working on specific areas

- **`new_module`**: Use when adding a new integration or sample module

### Workflow
- **`pr`**: Use when working with:
- Creating pull requests
- Stacked PRs, PR naming, stack comments
- PR changelog entries
- Merging or syncing stacked branches

### Testing
- **`e2e_tests`**: Use when working with:
- System tests, sample applications
Expand All @@ -76,6 +96,8 @@ Use the `fetch_rules` tool to include these rules when working on specific areas
2. **Fetch on-demand**: Use `fetch_rules ["rule_name"]` when you identify specific domain work
3. **Multiple rules**: Fetch multiple rules if task spans domains (e.g., `["scopes", "opentelemetry"]` for tracing scope issues)
4. **Context clues**: Look for these keywords in requests to determine relevant rules:
- Public API/apiDump/.api files/binary compatibility/new method โ†’ `api`
- Options/SentryOptions/ExternalOptions/ManifestMetadataReader/sentry.properties โ†’ `options`
- Scope/Hub/forking โ†’ `scopes`
- Duplicate/dedup โ†’ `deduplication`
- OpenTelemetry/tracing/spans โ†’ `opentelemetry`
Expand All @@ -84,3 +106,4 @@ Use the `fetch_rules` tool to include these rules when working on specific areas
- System test/e2e/sample โ†’ `e2e_tests`
- Feature flag/addFeatureFlag/flag evaluation โ†’ `feature_flags`
- Metrics/count/distribution/gauge โ†’ `metrics`
- PR/pull request/stacked PR/stack โ†’ `pr`
Loading
Loading