diff --git a/.ci/agent-build.sh b/.ci/agent-build.sh new file mode 100755 index 00000000000..b88658dba3c --- /dev/null +++ b/.ci/agent-build.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -ex + +pushd agent + +DEST=libsentry_agent_linux-$TARGET.so + +cmake CMakeLists.txt +make + +mv libsentry_agent.so $DEST +file $DEST + +popd \ No newline at end of file diff --git a/.ci/agent-deploy.sh b/.ci/agent-deploy.sh new file mode 100755 index 00000000000..c36507b150e --- /dev/null +++ b/.ci/agent-deploy.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -ex + +[[ $TRAVIS_LANGUAGE == "cpp" ]] || (echo "Not a C++ run, exiting." && exit 0;) + +pushd agent + +pip install --user requests==2.18.2 +python ../.ci/agent-upload-release.py + +popd \ No newline at end of file diff --git a/.ci/agent-install.sh b/.ci/agent-install.sh new file mode 100755 index 00000000000..f00fd4a9363 --- /dev/null +++ b/.ci/agent-install.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -ex + +sudo apt-get -qq update +sudo apt-get install -y g++-multilib diff --git a/.ci/agent-upload-release.py b/.ci/agent-upload-release.py new file mode 100644 index 00000000000..0018b40deaf --- /dev/null +++ b/.ci/agent-upload-release.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python + +import os +import sys +import urlparse +import requests + +try: + from requests.packages import urllib3 + urllib3.disable_warnings() +except ImportError: + pass + + +AUTH_USERNAME = 'getsentry-bot' +AUTH_TOKEN = os.environ['GITHUB_AUTH_TOKEN'] +AUTH = (AUTH_USERNAME, AUTH_TOKEN) +TAG = os.environ.get('TRAVIS_TAG') +TARGET = os.environ.get('TARGET') +LIB = 'libsentry_agent_linux-%(target)s.%(ext)s' +EXT = 'so' +BIN_TYPE = os.environ.get('BIN_TYPE', 'release') +REPO = 'getsentry/sentry-java' + + +def log(message, *args): + if args: + message = message % args + print >> sys.stderr, message + + +def api_request(method, path, **kwargs): + url = urlparse.urljoin('https://api.github.com/', path.lstrip('/')) + # default travis python does not have SNI + return requests.request(method, url, auth=AUTH, verify=False, **kwargs) + + +def find_executable(): + path = LIB % {'target': TARGET, 'ext': EXT} + log("Checking for executable: " + path) + if os.path.isfile(path): + return path + + +def ensure_release(): + resp = api_request('GET', 'repos/%s/releases' % REPO) + resp.raise_for_status() + for release in resp.json(): + if release['tag_name'] == TAG: + log('Found already existing release %s' % release['id']) + return release + resp = api_request('POST', 'repos/%s/releases' % REPO, json={ + 'tag_name': TAG, + 'name': 'sentry-java-agent %s' % TAG, + 'draft': True, + }) + resp.raise_for_status() + release = resp.json() + log('Created new release %s' % release['id']) + return release + + +def upload_asset(release, executable, target_name): + resp = api_request('GET', release['assets_url']) + resp.raise_for_status() + for asset in resp.json(): + if asset['name'] == target_name: + log('Already have release asset %s. Skipping' % target_name) + return + + upload_url = release['upload_url'].split('{')[0] + with open(executable, 'rb') as f: + log('Creating new release asset %s.' % target_name) + resp = api_request('POST', upload_url, + params={'name': target_name}, + headers={'Content-Type': 'application/octet-stream'}, + data=f) + resp.raise_for_status() + + +def main(): + if not TAG: + return log('No tag specified. Doing nothing.') + executable = find_executable() + if executable is None: + return log('Could not locate executable. Doing nothing.') + + release = ensure_release() + upload_asset(release, executable, executable) + + +if __name__ == '__main__': + main() diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index ac6b69b1435..00000000000 --- a/.claude/settings.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(find:*)", - "Bash(ls:*)", - "Bash(git:*)", - "Bash(git status:*)", - "Bash(git log:*)", - "Bash(git diff:*)", - "Bash(git show:*)", - "Bash(git branch:*)", - "Bash(git remote:*)", - "Bash(git tag:*)", - "Bash(git stash list:*)", - "Bash(git rev-parse:*)", - "Bash(gh pr view:*)", - "Bash(gh pr list:*)", - "Bash(gh pr checks:*)", - "Bash(gh pr diff:*)", - "Bash(gh issue view:*)", - "Bash(gh issue list:*)", - "Bash(gh run view:*)", - "Bash(gh run list:*)", - "Bash(gh run logs:*)", - "Bash(gh repo view:*)", - "WebFetch(domain:github.com)", - "WebFetch(domain:docs.sentry.io)", - "WebFetch(domain:develop.sentry.dev)", - "Bash(grep:*)", - "Bash(mv:*)" - ], - "deny": [] - } -} diff --git a/.craft.yml b/.craft.yml deleted file mode 100644 index 6f52255dd63..00000000000 --- a/.craft.yml +++ /dev/null @@ -1,69 +0,0 @@ -minVersion: 0.29.3 -changelogPolicy: auto -targets: - - name: maven - includeNames: /^sentry.*$/ - gradleCliPath: ./gradlew - mavenCliPath: scripts/mvnw - mavenSettingsPath: scripts/settings.xml - mavenRepoId: ossrh-staging-api - mavenRepoUrl: https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/ - android: - distDirRegex: /^(sentry-android-|.*-android).*$/ - fileReplaceeRegex: /\d+\.\d+\.\d+(-\w+(\.\d+)?)?(-SNAPSHOT)?/ - fileReplacerStr: release.aar - - name: github - excludeNames: /^libsentry.*\.so$/ - - name: registry - sdks: - maven:io.sentry:sentry: - maven:io.sentry:sentry-spring: - maven:io.sentry:sentry-spring-jakarta: - maven:io.sentry:sentry-spring-7: - maven:io.sentry:sentry-spring-boot: - maven:io.sentry:sentry-spring-boot-jakarta: - maven:io.sentry:sentry-spring-boot-starter: - maven:io.sentry:sentry-spring-boot-starter-jakarta: - maven:io.sentry:sentry-spring-boot-4: - maven:io.sentry:sentry-spring-boot-4-starter: - maven:io.sentry:sentry-servlet: - maven:io.sentry:sentry-servlet-jakarta: - maven:io.sentry:sentry-logback: - maven:io.sentry:sentry-log4j2: - maven:io.sentry:sentry-jul: - maven:io.sentry:sentry-apache-http-client-5: - maven:io.sentry:sentry-android: - maven:io.sentry:sentry-android-core: - maven:io.sentry:sentry-android-distribution: - maven:io.sentry:sentry-android-ndk: - maven:io.sentry:sentry-android-timber: - maven:io.sentry:sentry-kotlin-extensions: - maven:io.sentry:sentry-android-fragment: - maven:io.sentry:sentry-bom: - maven:io.sentry:sentry-openfeign: - maven:io.sentry:sentry-openfeature: - maven:io.sentry:sentry-opentelemetry-agent: - maven:io.sentry:sentry-opentelemetry-agentcustomization: - maven:io.sentry:sentry-opentelemetry-agentless: - maven:io.sentry:sentry-opentelemetry-agentless-spring: - maven:io.sentry:sentry-opentelemetry-bootstrap: - maven:io.sentry:sentry-opentelemetry-core: - maven:io.sentry:sentry-apollo: - maven:io.sentry:sentry-jdbc: - maven:io.sentry:sentry-graphql: - maven:io.sentry:sentry-graphql-22: - maven:io.sentry:sentry-graphql-core: - maven:io.sentry:sentry-quartz: - maven:io.sentry:sentry-okhttp: - maven:io.sentry:sentry-android-navigation: - maven:io.sentry:sentry-compose: - maven:io.sentry:sentry-compose-android: - maven:io.sentry:sentry-compose-desktop: - maven:io.sentry:sentry-apollo-3: - maven:io.sentry:sentry-android-sqlite: - maven:io.sentry:sentry-android-replay: - maven:io.sentry:sentry-apollo-4: - maven:io.sentry:sentry-reactor: - maven:io.sentry:sentry-ktor-client: - maven:io.sentry:sentry-async-profiler: - maven:io.sentry:sentry-spotlight: diff --git a/.cursor/rules/coding.mdc b/.cursor/rules/coding.mdc deleted file mode 100644 index e7af7273f15..00000000000 --- a/.cursor/rules/coding.mdc +++ /dev/null @@ -1,53 +0,0 @@ ---- -alwaysApply: true -description: Cursor Coding Rules ---- - -# Contributing Rules for Agents - -## Overview - -sentry-java is the Java and Android SDK for Sentry. This repository contains the source code and examples for SDK usage. - -## Tech Stack - -- **Language**: Java and Kotlin -- **Build Framework**: Gradle - -## Key Commands - -```bash -# Format code and regenerate .api files -./gradlew spotlessApply apiDump - -# Run all tests and linter -./gradlew check - -# Run unit tests for a specific file -./gradle '::testDebugUnitTest' --tests="**" --info -``` - -## Contributing Guidelines - -1. Follow existing code style and language -2. Do not modify the API files (e.g. sentry.api) manually, instead run `./gradlew apiDump` to regenerate them -3. Write comprehensive tests -4. New features should always be opt-in by default, extend `SentryOptions` or similar Option classes with getters and setters to enable/disable a new feature -5. Consider backwards compatibility - -## Coding rules - -1. First think through the problem, read the codebase for relevant files, and propose a plan -2. Before you begin working, check in with me and I will verify the plan -3. Then, begin working on the todo items, marking them as complete as you go -4. Please do not describe every step of the way and just give me a high level explanation of what changes you made -5. Make every task and code change you do as simple as possible. We want to avoid making any massive or complex changes. Every change should impact as little code as possible. Everything is about simplicity. -6. Once you're done, format the code and regenerate the .api files using the following command `./gradlew spotlessApply apiDump` -7. As a last step, git stage the relevant files and propose (but not execute) a single git commit command (e.g. `git commit -m ""`) - - -## Useful Resources - -- Main SDK documentation: https://develop.sentry.dev/sdk/overview/ -- Internal contributing guide: https://docs.sentry.io/internal/contributing/ -- Git commit messages conventions: https://develop.sentry.dev/engineering-practices/commit-messages/ diff --git a/.cursor/rules/deduplication.mdc b/.cursor/rules/deduplication.mdc deleted file mode 100644 index b48516dae44..00000000000 --- a/.cursor/rules/deduplication.mdc +++ /dev/null @@ -1,13 +0,0 @@ ---- -alwaysApply: false -description: Java SDK Event deduplication ---- - -# Java SDK Event deduplication - -To avoid sending the same error multiple times, there is deduplication logic in place in the SDK. -Duplicate captures can happen due to multiple integrations capturing the exception as well as additional manual calls to `Sentry.captureException`. - -Deduplication is performed in `DuplicateEventDetectionEventProcessor` which returns `null` when it detects a duplicate event causing it to be dropped. - -The `enableDeduplication` option can be used to opt out of deduplication. It is enabled by default. diff --git a/.cursor/rules/e2e_tests.mdc b/.cursor/rules/e2e_tests.mdc deleted file mode 100644 index 17e088774b5..00000000000 --- a/.cursor/rules/e2e_tests.mdc +++ /dev/null @@ -1,28 +0,0 @@ ---- -alwaysApply: false -description: Java SDK End to End Tests ---- - -# Java SDK End to End Tests (System Tests) - -The samples in the `sentry-samples` directory are used to run end to end tests against them. - -There is a python script (`system-test-runner.py`) that can be used to run one (using `--module SAMPLE_NAME`) or all (using `--all`) system tests. - -The script has an interactive mode (`-i`) which allows selection of test setups to execute, whether to run the tests or just prepare infrastructure for testing from IDE. - -The tests run a mock Sentry server via `system-test-sentry-server.py`. Any system under test will then have a DSN set that reflects this local mock server like `http://502f25099c204a2fbf4cb16edc5975d1@localhost:8000/0`. -By using this local DSN, the system under test sends events to the local mock server. -The tests can then use `TestHelper` to assert envelopes that were received by the mock server. -`TestHelper` uses HTTP requests to retrieve the JSON payload of the received events and deserialize them back to objects for easier assertion. -Tests can then assert events, transactions, logs etc. similar to how they would appear in `beforeSend` and similar callbacks. - -`TestHelper` has a lot of helper methods for asserting, e.g. by span name, log body etc. - -The end to end tests either expect the system under test to either be running on a server or call `java -jar` to execute a CLI system under test. - -For Spring Boot, we spin up the Spring Boot server. The tests then send requests to that server and assert what is sent to Sentry. - -End to end tests are also executed on CI using a matrix build, as defined in `.github/workflows/system-tests-backend.yml`. - -Some of the samples are tested in multiple ways, e.g. with OpenTelemetry Agent auto init turned on and off. diff --git a/.cursor/rules/feature_flags.mdc b/.cursor/rules/feature_flags.mdc deleted file mode 100644 index f2a78bc71cf..00000000000 --- a/.cursor/rules/feature_flags.mdc +++ /dev/null @@ -1,44 +0,0 @@ ---- -alwaysApply: false -description: Feature Flags ---- -# Java SDK Feature Flags - -There is a scope based and a span based API for tracking feature flag evaluations. - -## Scope Based API - -The `addFeatureFlag` method can be used to track feature flag evaluations. It exists on `Sentry` static API as well as `IScopes` and `IScope`. - -When using static API, `IScopes` or COMBINED scope type, Sentry will also invoke `addFeatureFlag` on the current span. This does not happen, when directly invoking `addFeatureFlag` on `IScope` (except for COMBINED scope type). - -The `maxFeatureFlags` option controls how many flags are tracked per scope and also how many are sent to Sentry as part of events. -Scope based feature flags can also be disabled by setting the value to 0. Defaults to 100 feature flag evaluations. - -Order of feature flag evaluations is important as we only keep track of the last {maxFeatureFlag} items. - -When a feature flag evaluation with the same name is added, the previous one is removed and the new one is stored so that it'll be dropped last. -Refer to `FeatureFlagBuffer` fore more details. `FeatureFlagBuffer` has been optimized for storing scope based feature flag evaluations, especially clone performance. - -When sending out an error event, feature flag buffers from all three scope types (global, isolation and current scope) are merged, choosing the newest {maxFeatureFlag} entries across all scope types. Feature flags are sent as part of the `flags` context. - -## Span Based API - -It's also possible to use the `addFeatureFlag` method on `ISpan` (and by extension `ITransaction`). Feature flag evaluations tracked this way -will not be added to the scope and thus won't be added to error events. - -Each span has its own `SpanFeatureFlagBuffer`. When starting a child span, feature flag evaluations are NOT copied from the parent. Each span starts out with an empty buffer and has its own limit. -`SpanFeatureFlagBuffer` has been optimized for storing feature flag evaluations on spans. - -Spans have a hard coded limit of 10 feature flag evaluations. When full, new entries are rejected. Updates to existing entries are still allowed even if full. - -## Integrations - -We offer integrations that automatically track feature flag evaluations. - -Android: -- LaunchDarkly (`SentryLaunchDarklyAndroidHook`) - -JVM (non Android): -- LaunchDarkly (`SentryLaunchDarklyServerHook`) -- OpenFeature (`SentryOpenFeatureHook`) diff --git a/.cursor/rules/metrics.mdc b/.cursor/rules/metrics.mdc deleted file mode 100644 index 93c82ecb467..00000000000 --- a/.cursor/rules/metrics.mdc +++ /dev/null @@ -1,26 +0,0 @@ ---- -alwaysApply: false -description: Metrics API ---- -# Java SDK Metrics API - -Metrics are enabled by default. - -API has been namespaced under `Sentry.metrics()` and `IScopes.metrics()` using the `IMetricsApi` interface and `MetricsApi` implementation. - -Options are namespaced under `SentryOptions.getMetrics()`. - -Three different APIs exist: -- `count`: Counters are one of the more basic types of metrics and can be used to count certain event occurrences. -- `distribution`: Distributions help you get the most insights from your data by allowing you to obtain aggregations such as p90, min, max, and avg. -- `gauge`: Gauges let you obtain aggregates like min, max, avg, sum, and count. They can be represented in a more space-efficient way than distributions, but they can't be used to get percentiles. If percentiles aren't important to you, we recommend using gauges. - -Refer to `SentryMetricsEvent` for details about available fields. - -`MetricsBatchProcessor` handles batching (`MAX_BATCH_SIZE`), automatic sending of metrics after a timeout (`FLUSH_AFTER_MS`) and rejecting if `MAX_QUEUE_SIZE` has been hit. - -The flow is `IMetricsApi` -> `IMetricsBatchProcessor` -> `SentryClient.captureBatchedMetricsEvents` -> `ITransport`. - -Each `SentryMetricsEvent` goes through `SentryOptions.metrics.beforeSend` (if configured) and can be modified or dropped. - -For sending, a batch of `SentryMetricsEvent` objects is sent inside a `SentryMetricsEvents` object. diff --git a/.cursor/rules/new_module.mdc b/.cursor/rules/new_module.mdc deleted file mode 100644 index 5bf2c70c2f7..00000000000 --- a/.cursor/rules/new_module.mdc +++ /dev/null @@ -1,88 +0,0 @@ ---- -description: Module Addition Rules for sentry-java -alwaysApply: false ---- -# Module Addition Rules for sentry-java - -## Overview - -This document outlines the complete process for adding a new module to the sentry-java repository. Follow these steps in order to ensure proper integration and release management. - -## Step-by-Step Process - -### 1. Create the Module Structure - -1. Create the new module, conforming to the existing naming conventions and build scripts - -2. Add the module to the include list in `settings.gradle.kts` - -If adding a `sentry-samples` module, also add it to the `ignoredProjects` list in the root `build.gradle.kts`: - -```kotlin -ignoredProjects.addAll( - listOf( - // ... existing projects ... - "sentry-samples-{module-name}" - ) -) -``` - -3. If adding a JVM sample, add E2E (system) tests, following the structure we have in the existing JVM examples. - The test should then be added to `test/system-test-runner.py` and `.github/workflows/system-tests-backend.yml`. - -### 2. Create Module Documentation - -Create a `README.md` in the module directory with the following structure: - -```markdown -# sentry-{module-name} - -This module provides an integration for [Technology/Framework Name]. - -Please consult the documentation on how to install and use this integration in the Sentry Docs for [Android](https://docs.sentry.io/platforms/android/integrations/{module-name}/) or [Java](https://docs.sentry.io/platforms/java/tracing/instrumentation/{module-name}/). -``` - -The following tasks are required only when adding a module that isn't a sample. - -### 3. Update Main README.md - -Add the new module to the packages table in the main `README.md` with a placeholder link to the badge: - -```markdown -| sentry-{module-name} | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-{module-name}/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-{module-name}) | | -``` - -Note that the badge will only work after the module is released to Maven Central. - -### 4. Add Documentation to docs.sentry.io - -Add the necessary documentation to [docs.sentry.io](https://docs.sentry.io): -- For Java modules: Add to Java platform docs, usually in integrations section -- For Android modules: Add to Android platform docs, usually in integrations section -- Include installation instructions, configuration options, and usage examples - -### 5. Post release tasks - -Remind the user to perform the following tasks after the module is merged and released: - -1. Add the SDK to the Sentry release registry, following the instructions in the [sentry-release-registry README](https://github.com/getsentry/sentry-release-registry#adding-new-sdks) - -2. Add the module to `.craft.yml` in the `sdks` section: - ```yaml - sdks: - # ... existing modules ... - maven:io.sentry:sentry-{module-name}: - ``` - -## Module Naming Conventions - -- Use kebab-case for module names: `sentry-{module-name}` -- Follow existing patterns: `sentry-okhttp`, `sentry-apollo-4`, `sentry-spring-boot` -- For version-specific modules, include the version: `sentry-apollo-3`, `sentry-apollo-4` - -## Important Notes - -1. **API Files**: Do not modify `.api` files manually. Run `./gradlew apiDump` to regenerate them -2. **Backwards Compatibility**: Ensure new features are opt-in by default -3. **Testing**: Write comprehensive tests for all new functionality -4. **Documentation**: Always include proper documentation and examples diff --git a/.cursor/rules/offline.mdc b/.cursor/rules/offline.mdc deleted file mode 100644 index 14e9419b4de..00000000000 --- a/.cursor/rules/offline.mdc +++ /dev/null @@ -1,87 +0,0 @@ ---- -alwaysApply: false -description: Java SDK Offline behaviour ---- -# Java SDK Offline behaviour - -By default offline caching is enabled for Android but disabled for JVM. -It can be enabled by setting SentryOptions.cacheDirPath. - -For Android, AndroidEnvelopeCache is used. For JVM, if cache path has been configured, EnvelopeCache will be used. - -Any error, event, transaction, profile, replay etc. is turned into an envelope and then sent into ITransport.send. -The default implementation is AsyncHttpTransport. - -If an envelope is dropped due to rate limit and has previously been cached (Cached hint) it will be discarded from the IEnvelopeCache. - -AsyncHttpTransport.send will enqueue an AsyncHttpTransport.EnvelopeSender task onto an executor. - -Any envelope that doesn't have the Cached hint will be stored in IEnvelopeCache by the EventSender task. Previously cached envelopes (Cached hint) will have a noop cache passed to AsyncHttpTransport.EnvelopeSender and thus not cache again. It is also possible cache is disabled in general. - -An envelope being sent directly from SDK API like Sentry.captureException will not have the Retryable hint. - -In case the SDK is offline, it'll mark the envelope to be retried if it has the Retryable hint. -If the envelope is not retryable and hasn't been sent to offline cache, it's recorded as lost in a client report. - -In case the envelope can't be sent due to an error or network connection problems it'll be marked for retry if it has the Retryable hint. -If it's not retryable and hasn't been cached, it's recorded as lost in a client report. - -In case the envelope is sent successfully, it'll be discarded from cache. - -The SDK has multiple mechanisms to deal with envelopes on disk. -- OutboxSender: Sends events coming from other SDKs like NDK that wrote them to disk. -- io.sentry.EnvelopeSender: This is the offline cache. - -Both of these are set up through an integration (SendCachedEnvelopeIntegration) which is configured to use SendFireAndForgetOutboxSender or SendFireAndForgetEnvelopeSender. - -io.sentry.EnvelopeSender is able to pick up files in the cache directory and send them. -It will trigger sending envelopes in cache dir on init and when the connection status changes (e.g. the SDK comes back online, meaning it has Internet connection again). - -## When Envelope Files Are Removed From Cache - -Envelope files are removed from the cache directory in the following scenarios: - -### 1. Successful Send to Sentry Server -When `AsyncHttpTransport` successfully sends an envelope to the Sentry server, it calls `envelopeCache.discard(envelope)` to remove the cached file. This happens in `AsyncHttpTransport.EnvelopeSender.flush()` when `result.isSuccess()` is true. - -### 2. Rate Limited Previously Cached Envelopes -If an envelope is dropped due to rate limiting **and** has previously been cached (indicated by the `Cached` hint), it gets discarded immediately via `envelopeCache.discard(envelope)` in `AsyncHttpTransport.send()`. -In this case the discarded envelope is recorded as lost in client reports. - -### 3. Offline Cache Processing (EnvelopeSender) -When the SDK processes cached envelope files from disk (via `EnvelopeSender`), files are deleted after processing **unless** they are marked for retry. In `EnvelopeSender.processFile()`, the file is deleted with `safeDelete(file)` if `!retryable.isRetry()`. - -### 4. Session File Management -Session-related files (session.json, previous_session.json) are removed during session lifecycle events like session start/end and abnormal exits. - -### 5. Cache rotation -If the number of files in the cache directory has reached the configured limit (SentryOptions.maxCacheItems), the oldest file will be deleted to make room. -This happens in `CacheStrategy.rotateCacheIfNeeded`. The deleted envelope will be recorded as lost in client reports. - -## Retry Mechanism - -**Important**: The SDK does NOT implement a traditional "max retry count" mechanism. Instead: - -### Infinite Retry Approach -- **Retryable envelopes**: Stay in cache indefinitely and are retried when conditions improve (network connectivity restored, rate limits expire, etc.) -- **Non-retryable envelopes**: If they fail to send, they're immediately recorded as lost (not cached for retry) - -### When Envelopes Are Permanently Lost (Not Due to Retry Limits) - -1. **Queue Overflow**: When the transport executor queue is full - recorded as `DiscardReason.QUEUE_OVERFLOW` - -2. **Network Errors (Non-Retryable)**: When an envelope isn't marked as retryable and fails due to network issues - recorded as `DiscardReason.NETWORK_ERROR` - -3. **Rate Limiting**: When envelope items are dropped due to active rate limits - recorded as `DiscardReason.RATELIMIT_BACKOFF` - -4. **Cache Overflow**: When the cache directory has reached maxCacheItems, old files are deleted - recorded as `DiscardReason.CACHE_OVERFLOW` - -### Cache Processing Triggers -Cached envelopes are processed when: -- Network connectivity is restored (via connection status observer) -- SDK initialization occurs -- Rate limits expire -- Manual flush operations - -### File Deletion Implementation -The actual file deletion is handled by `EnvelopeCache.discard()` which calls `envelopeFile.delete()` and logs errors if deletion fails. diff --git a/.cursor/rules/opentelemetry.mdc b/.cursor/rules/opentelemetry.mdc deleted file mode 100644 index 7a94dcf58f4..00000000000 --- a/.cursor/rules/opentelemetry.mdc +++ /dev/null @@ -1,88 +0,0 @@ ---- -alwaysApply: false -description: Java SDK OpenTelemetry Integration ---- -# Java SDK OpenTelemetry Integration - -## Overview - -The Sentry Java SDK provides comprehensive OpenTelemetry integration through multiple modules: - -- `sentry-opentelemetry-core`: Core OpenTelemetry integration functionality -- `sentry-opentelemetry-agent`: Java Agent-based integration for automatic instrumentation -- `sentry-opentelemetry-agentless`: Manual instrumentation without Java agent -- `sentry-opentelemetry-agentless-spring`: Spring-specific agentless integration -- `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. -- `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. - -## Advantages over using Sentry without OpenTelemetry - -- Support for more libraries and frameworks - - See https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation for a list of supported libraries and frameworks -- More automated Performance instrumentation (spans) created - - Using `sentry-opentelemetry-agent` offers most support - - Using `sentry-opentelemetry-agentless-spring` for Spring Boot also has a lot of supported libraries, altough fewer than the agent does - - Note that `sentry-opentelemetry-agentless` will not have any OpenTelemetry auto instrumentation -- 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. -- 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. - -## Key Components - -### Agent vs Agentless - -**Java Agent-based integration**: -- Automatic instrumentation via Java agent -- 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`. -- Uses OpenTelemetry Java agent with Sentry extensions -- Uses bytecode manipulation - -**Agentless-Spring integration**: -- Automatic instrumentation setup via Spring Boot -- Dependency needs to be added to the project. - -**Agentless integration**: -- Manual instrumentation setup -- Dependency needs to be added to the project. - -**Manual Integration**: -While it's possible to manually wire up all the required classes to make Sentry and OpenTelemetry work together, we do not recommend this. -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. -This way customers receive the updated config automatically as oppposed to having to update manually, wire in new classes, remove old ones etc. - -### Integration Architecture - -Sentry will try to locate certain classes that come with the Sentry OpenTelemetry integration to: -- Determine whether any Sentry OpenTelemetry integration is present -- Determine which mode to use and in turn which Sentry auto instrumentation to suppress - -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`. - -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). - -`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. - -`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`. - -`DefaultSpanFactory` creates a `SentryTracer` instance when creating a transaction and spans are then created directly on the transaction via `startChild`. -`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. - -## Configuration - -We use `SentryAutoConfigurationCustomizerProvider` to configure OpenTelemetry for use with Sentry and register required classes, hooks etc. - -## Span Processing - -Both Sentry and OpenTelemetry API can be used to create spans. When using Sentry API, `OtelSpanFactory` is used to indirectly create a OpenTelemetry span. -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. -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`. - -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`. - -`SentrySpanExporter` collects spans, then structures them to create a transaction for the local root span and attaches child spans to form a span tree. -Some OpenTelemetry attributes are transformed into their corresponding Sentry data structure or format. - -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. - -## Troubleshooting - -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. diff --git a/.cursor/rules/overview_dev.mdc b/.cursor/rules/overview_dev.mdc deleted file mode 100644 index f05d2992d40..00000000000 --- a/.cursor/rules/overview_dev.mdc +++ /dev/null @@ -1,86 +0,0 @@ ---- -alwaysApply: true -description: Sentry Java SDK - Development Rules Overview ---- - -# Sentry Java SDK Development Rules - -## Always Applied Rules - -These rules are automatically included in every conversation: -- **coding.mdc**: General contributing guidelines, build commands, and workflow rules - -## Domain-Specific Rules (Fetch Only When Needed) - -Use the `fetch_rules` tool to include these rules when working on specific areas: - -### Core SDK Functionality -- **`scopes`**: Use when working with: - - Hub/Scope management, forking, or lifecycle - - `Sentry.getCurrentScopes()`, `pushScope()`, `withScope()` - - `ScopeType` (GLOBAL, ISOLATION, CURRENT) - - Thread-local storage, scope bleeding issues - - Migration from Hub API (v7 → v8) - -- **`deduplication`**: Use when working with: - - Duplicate event detection/prevention - - `DuplicateEventDetectionEventProcessor` - - `enableDeduplication` option - -- **`offline`**: Use when working with: - - Caching, envelope storage/retrieval - - Network failure handling, retry logic - - `AsyncHttpTransport`, `EnvelopeCache` - - Rate limiting, cache rotation - - Android vs JVM caching differences - -- **`feature_flags`**: Use when working with: - - Feature flag tracking and evaluation - - `addFeatureFlag()`, `getFeatureFlags()` methods - - `FeatureFlagBuffer`, `SpanFeatureFlagBuffer`, `FeatureFlag` protocol - - `maxFeatureFlags` option and buffer management - - Feature flag merging across scope types - - Scope-based vs span-based feature flag APIs - - Scope-based API: `Sentry`, `IScopes`, `IScope` APIs - - Span-based API: `ISpan`, `ITransaction` APIs - - Integrations: LaunchDarkly (Android/JVM), OpenFeature (JVM) - -- **`metrics`**: Use when working with: - - Metrics API (`Sentry.metrics()`, `IScopes.metrics()`) - - `IMetricsApi`, `MetricsApi` implementation - - Metrics types: `count`, `distribution`, `gauge` - - `MetricsBatchProcessor`, batching and queue management - - `SentryMetricsEvent`, `SentryMetricsEvents` - - `SentryOptions.getMetrics()`, `beforeSend` callback - -### Integration & Infrastructure -- **`opentelemetry`**: Use when working with: - - OpenTelemetry modules (`sentry-opentelemetry-*`) - - Agent vs agentless configurations - - Span processing, sampling, context propagation - - `OtelSpanFactory`, `SentrySpanExporter` - - Tracing, distributed tracing - -- **`new_module`**: Use when adding a new integration or sample module - -### Testing -- **`e2e_tests`**: Use when working with: - - System tests, sample applications - - `system-test-runner.py`, mock Sentry server - - End-to-end test infrastructure - - CI system test workflows - -## Usage Guidelines - -1. **Start minimal**: Only include `coding.mdc` (auto-applied) for general tasks -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: - - Scope/Hub/forking → `scopes` - - Duplicate/dedup → `deduplication` - - OpenTelemetry/tracing/spans → `opentelemetry` - - new module/integration/sample → `new_module` - - Cache/offline/network → `offline` - - System test/e2e/sample → `e2e_tests` - - Feature flag/addFeatureFlag/flag evaluation → `feature_flags` - - Metrics/count/distribution/gauge → `metrics` diff --git a/.cursor/rules/scopes.mdc b/.cursor/rules/scopes.mdc deleted file mode 100644 index e054755d4f5..00000000000 --- a/.cursor/rules/scopes.mdc +++ /dev/null @@ -1,121 +0,0 @@ ---- -alwaysApply: false -description: Java SDK Hubs and Scopes ---- -# Java SDK Hubs and Scopes - -## `Scopes` - -`Scopes` implements `IScopes` and manages three `Scope` instances, `global`, `isolation` and `current` scope. -For some data, all three `Scope` instances are combined, for others, a certain one is used exclusively and for some we look at each scope in a certain order and use the data of the first scope that has the data set. This logic is contained in `CombinedScopeView`. -Data itself is stored on `Scope` instances. -`Scopes` also has a `parent` field, linking the `Scopes` it was forked off of and a `creator` String, explaining why it was forked. - -## `Hub` - -Up until major version 7 of the Java SDK the `IHub` interface was a central part of the SDK. -In major version 8 we replaced the `IHub` interface with `IScopes`. `IHub` has been deprecated. -While there is some bridging code in place to allow for easier migration, we are planning to remove it in an upcoming major. - -## Scope Types - -We have introduced some new Scope types in the SDK, allowing for better control over what data is attached where. -Previously there was a stack of scopes that was pushed and popped. -Instead we now fork scopes for a given lifecycle and then restore the previous scopes. -Since Hub is gone, it is also never cloned anymore. -Separation of data now happens through the different scope types while making it easier to manipulate exactly what you need without having to attach data at the right time to have it apply where wanted. - -### Global Scope - -Global scope is attached to all events created by the SDK. -It can also be modified before Sentry.init has been called. -It can be manipulated using `Sentry.configureScope(ScopeType.GLOBAL, (scope) -> { ... })`. - -Global scope can be retrieved from `Scopes` via `getGlobalScope`. It can also be retrieved directly via `Sentry.getGlobalScope`. - -### Isolation Scope - -Isolation scope can be used e.g. to attach data to all events that come up while handling an incoming request. -It can also be used for other isolation purposes. -It can be manipulated using `Sentry.configureScope(ScopeType.ISOLATION, (scope) -> { ... })`. -The SDK automatically forks isolation scope in certain cases like incoming requests, CRON jobs, Spring `@Async` and more. - -Isolation scope can be retrieved from `Scopes` via `getIsolationScope`. - -### Current scope - -Current scope is forked often and data added to it is only added to events that are created while this scope is active. -Data is also passed on to newly forked child scopes but not to parents. - -Current scope can be retrieved from `Scopes` via `getScope`. - -### Combined Scope - -This is a special scope type that combines global, isolation and current scope. - -Refer to `CombinedScopeView` for each field of interest to see whether values from the three individual scopes are merged, -whether a specific one is used or whether we're simply using the first one that has a value. - -Also see the section about `defaultScopeType` further down. - -## Storage of `Scopes` - -`Scopes` are stored in a `ThreadLocal` by default (NOTE: this is different for OpenTelemetry, see opentelemetry.mdc). -This happens through `Sentry.scopesStorage` and `DefaultScopesStorage`. - -The lifetime of `Scopes` in the thread local is managed by `ISentryLifecycleToken`. -When the scopes are forked, they are stored into the `ThreadLocal` and a `ISentryLifecycleToken` is returned. -When the `Scopes` are no longer needed, e.g. because a request is finished, `ISentryLifecycleToken.close` can be called to restore the previous state of the `ThreadLocal`. - -## Old versions of the Java SDK - -There were several implementations of the `IHub` interface: -- `Hub` managed a stack of `Scope` instances, which were pushed and popped. -- A `Hub` could be cloned, meaning there could be multiple stacks of scopes active, e.g. for two separate requests being handled in a server application. - -### Migrating to major version 8 of the SDK - -`IHub` has been replaced by `IScopes` -`HubAdapter` has been replaced by `ScopesAdapter` -`Hub.clone` should be replaced by using `pushScope` or `pushIsolationScope` -`Sentry.getCurrentHub` has been replaced by `Sentry.getCurrentScopes` -`Sentry.popScope` has been deprecated. Instead `close` should be called on the `ISentryLifecycleToken` returned e.g. by `pushScope`. This can also be done in a `try-with-resource` block. - -## `globalHubMode` -The SDK has a `globalHubMode` option which affects forking behaviour of the SDK. - -Android has `globalHubMode` enabled by default. -For JVM Desktop applications, `globalHubMode` can be used. -For JVM Backend applications (servers) we discourage enabling `globalHubMode` since it will cause scopes to bleed into each other. This can e.g. mean that state from request A leaks into request B and events sent to Sentry contain a mix of both request A and B potentially rendering the data useless. - -### Enabled - -If `globalHubMode` is enabled, the SDK avoids forking scopes. - -This means, retrieving current scopes on a thread where specific scopes do not exist yet for the thread, the root scopes are not forked but returned directly. -The SDK also doesn't fork scopes when `Sentry.pushScope`, `Sentry.pushIsolation`, `Sentry.withScope` or `Sentry.withIsolationScope` are executed. - -The suppression of forking via `globalHubMode` only applies when using `Sentry` static API or `ScopesAdapter`. -In case the `Scopes` instance is accessed directly, forking will happen as if `globalHubMode` is disabled. -However, while it's possible to use `Sentry.setCurrentScopes` it does not have any effect due to `Sentry.getCurrentScopes` directly returning `rootScopes` if `globalHubMode` is enabled. -This means the forked scopes have to be managed manually, e.g. by keeping a reference and accessing Sentry API via the reference instead of using static API. - -`ScopesAdapter` makes use of the static `Sentry` API internally. It allows us to access the correct scopes for the current context without passing it along explicitly. It also makes testing easier. - -### Disabled - -If `globalHubMode` is disabled, the SDK forks scopes freely, e.g. when: -- `Sentry.getCurrentScopes()` is executed on a Thread where no specific scopes for that thread have been stored yet. In this case the SDK will fork `rootScopes` (stored in a `Sentry` static property). -- `withScope` or `withIsolationScope` are executed -- `pushScope` or `pushIsolationScope` are executed - -## `defaultScopeType` - -The `defaultScopeType` controls which `Scope` instance is being used for writing to and reading from as a default value. -When using API like `Sentry.setTag` the SDK adds that tag to the default `Scope`. - -This also ensures, customers who migrate to the latest SDK version and already have `Sentry.configureScope` invocations in place, will now write to the default `Scope` instance that was chosen. - -The default value for `defaultScopeType` is `ISOLATION` scope for JVM and `CURRENT` scope for Android. - -Which fields are written/read from/to `defaultScopeType` is controlled in `CombinedScopeView`. diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 4aad35e29d3..00000000000 --- a/.editorconfig +++ /dev/null @@ -1,19 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -trim_trailing_whitespace = true -insert_final_newline = true -max_line_length = 140 -ij_java_names_count_to_use_import_on_demand = 9999 -ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL - -[*.md] -trim_trailing_whitespace = false - -[*.java] -charset = utf-8 - -[*.{kt,kts}] -charset = utf-8 diff --git a/.envrc b/.envrc deleted file mode 100644 index f58a7cee600..00000000000 --- a/.envrc +++ /dev/null @@ -1,3 +0,0 @@ -export VIRTUAL_ENV="${PWD}/.venv" -devenv sync -PATH_add "${PWD}/.venv/bin" diff --git a/.fossa.yml b/.fossa.yml deleted file mode 100644 index d4ee8ca10a1..00000000000 --- a/.fossa.yml +++ /dev/null @@ -1,4 +0,0 @@ -version: 3 -targets: - exclude: - - type: setuptools diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs deleted file mode 100644 index 2614e090e80..00000000000 --- a/.git-blame-ignore-revs +++ /dev/null @@ -1,2 +0,0 @@ -# Reformat codebase with Ktfmt and more accurate spotless configuration: #4499 -8b8369f06cbc5a9738de7810b1df5863b3ac6bcb diff --git a/.gitattributes b/.gitattributes index 92fa746b911..8383fff94e2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1 @@ -* text eol=lf -*.png binary -*.jpg binary -*.pb binary -*.gz binary - -# These are explicitly windows files and should use crlf -*.bat text eol=crlf +CHANGES merge=union diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index b01358a8742..00000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @adinauer @romtsn @markushi @lcian diff --git a/.github/ISSUE_TEMPLATE/bug_report_android.yml b/.github/ISSUE_TEMPLATE/bug_report_android.yml deleted file mode 100644 index e83e485450d..00000000000 --- a/.github/ISSUE_TEMPLATE/bug_report_android.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: 🐞 Bug Report - Android -description: Tell us about something that's not working the way we (probably) intend. -labels: ["Android", "Bug"] -body: - - type: dropdown - id: integration - attributes: - description: Integration - label: Integration - options: - - sentry-android - - sentry-android-ndk - - sentry-android-timber - - sentry-android-fragment - - sentry-android-sqlite - - sentry-apollo - - sentry-apollo-3 - - sentry-compose - - sentry-launchdarkly-android - - sentry-okhttp - - other - validations: - required: true - - - type: dropdown - id: build_system - attributes: - description: Build system - label: Build System - options: - - Gradle - - Buck - - Bazel - - Other - validations: - required: true - - - type: input - id: agp_version - attributes: - description: Android Gradle Plugin Version - placeholder: 7.0.0 ← should look like this - label: AGP Version - validations: - required: true - - - type: dropdown - id: proguard - attributes: - description: Proguard/R8 - label: Proguard - options: - - Enabled - - Disabled - validations: - required: true - - - type: input - id: version - attributes: - label: Version - description: Sentry SDK Version - placeholder: 6.0.0 ← should look like this - validations: - required: true - - - type: textarea - id: repro - attributes: - label: Steps to Reproduce - description: How can we see what you're seeing? Specific is terrific. - placeholder: |- - 1. foo - 2. bar - 3. baz - validations: - required: true - - - type: textarea - id: expected - attributes: - label: Expected Result - validations: - required: true - - - type: textarea - id: actual - attributes: - label: Actual Result - description: Logs? Screenshots? Yes, please. - validations: - required: true - - - type: markdown - attributes: - value: |- - ## Thanks 🙏 - Check our [triage docs](https://open.sentry.io/triage/) for what to expect next. diff --git a/.github/ISSUE_TEMPLATE/bug_report_java.yml b/.github/ISSUE_TEMPLATE/bug_report_java.yml deleted file mode 100644 index 3f2df40888b..00000000000 --- a/.github/ISSUE_TEMPLATE/bug_report_java.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: 🐞 Bug Report - Java -description: Tell us about something that's not working the way we (probably) intend. -labels: ["Java", "Bug"] -body: - - type: dropdown - id: integration - attributes: - description: Integration - label: Integration - options: - - sentry - - sentry-jul - - sentry-jdbc - - sentry-apollo - - sentry-apollo-3 - - sentry-kotlin-extensions - - sentry-opentelemetry-agent - - sentry-opentelemetry-agentless - - sentry-opentelemetry-agentless-spring - - sentry-opentelemetry-core - - sentry-servlet - - sentry-servlet-jakarta - - sentry-spring-boot - - sentry-spring-boot-jakarta - - sentry-spring-boot-starter - - sentry-spring-boot-starter-jakarta - - sentry-spring-boot-4 - - sentry-spring-boot-4-starter - - sentry-spring - - sentry-spring-jakarta - - sentry-spring-7 - - sentry-logback - - sentry-log4j2 - - sentry-graphql - - sentry-graphql-22 - - sentry-quartz - - sentry-openfeign - - sentry-openfeature - - sentry-launchdarkly-server - - sentry-apache-http-client-5 - - sentry-okhttp - - sentry-reactor - - other - validations: - required: true - - - type: input - id: java_version - attributes: - description: Java Version - placeholder: 8 ← should look like this - label: Java Version - validations: - required: true - - - type: input - id: version - attributes: - label: Version - description: Sentry SDK Version - placeholder: 6.0.0 ← should look like this - validations: - required: true - - - type: textarea - id: repro - attributes: - label: Steps to Reproduce - description: How can we see what you're seeing? Specific is terrific. - placeholder: |- - 1. foo - 2. bar - 3. baz - validations: - required: true - - - type: textarea - id: expected - attributes: - label: Expected Result - validations: - required: true - - - type: textarea - id: actual - attributes: - label: Actual Result - description: Logs? Screenshots? Yes, please. - validations: - required: true - - - type: markdown - attributes: - value: |- - ## Thanks 🙏 - Check our [triage docs](https://open.sentry.io/triage/) for what to expect next. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 819e8a1afbc..00000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Join Sentry Discord - url: https://discord.com/invite/sentry - about: A place to talk about SDK development and other Sentry related topics. It's not meant as a support channel. diff --git a/.github/ISSUE_TEMPLATE/feature_android.yml b/.github/ISSUE_TEMPLATE/feature_android.yml deleted file mode 100644 index d1f71024569..00000000000 --- a/.github/ISSUE_TEMPLATE/feature_android.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: 💡 Feature Request - Android -description: Tell us about a problem our SDK could solve but doesn't. -labels: ["Android", "Feature"] -body: - - type: textarea - id: problem - attributes: - label: Problem Statement - description: What problem could Sentry solve that it doesn't? - placeholder: |- - I want to make whirled peas, but Sentry doesn't blend. - validations: - required: true - - - type: textarea - id: expected - attributes: - label: Solution Brainstorm - description: We know you have bright ideas to share ... share away, friend. - placeholder: |- - Add a blender to Sentry. - validations: - required: false - - - type: markdown - attributes: - value: |- - ## Thanks 🙏 - Check our [triage docs](https://open.sentry.io/triage/) for what to expect next. diff --git a/.github/ISSUE_TEMPLATE/feature_java.yml b/.github/ISSUE_TEMPLATE/feature_java.yml deleted file mode 100644 index 686ad45e229..00000000000 --- a/.github/ISSUE_TEMPLATE/feature_java.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: 💡 Feature Request - Java -description: Tell us about a problem our SDK could solve but doesn't. -labels: ["Java", "Feature"] -body: - - type: textarea - id: problem - attributes: - label: Problem Statement - description: What problem could Sentry solve that it doesn't? - placeholder: |- - I want to make whirled peas, but Sentry doesn't blend. - validations: - required: true - - - type: textarea - id: expected - attributes: - label: Solution Brainstorm - description: We know you have bright ideas to share ... share away, friend. - placeholder: |- - Add a blender to Sentry. - validations: - required: false - - - type: markdown - attributes: - value: |- - ## Thanks 🙏 - Check our [triage docs](https://open.sentry.io/triage/) for what to expect next. diff --git a/.github/ISSUE_TEMPLATE/maintainer-blank.yml b/.github/ISSUE_TEMPLATE/maintainer-blank.yml deleted file mode 100644 index 150f35a1316..00000000000 --- a/.github/ISSUE_TEMPLATE/maintainer-blank.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Blank Issue -description: Blank Issue. Reserved for maintainers. -labels: ["Java"] -body: - - type: textarea - id: description - attributes: - label: Description - description: Please describe the issue. - validations: - required: true - - - type: markdown - attributes: - value: |- - ## Thanks 🙏 - Check our [triage docs](https://open.sentry.io/triage/) for what to expect next. diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index b88a67a7f0c..00000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: weekly diff --git a/.github/file-filters.yml b/.github/file-filters.yml deleted file mode 100644 index 2b81e2f0b6d..00000000000 --- a/.github/file-filters.yml +++ /dev/null @@ -1,12 +0,0 @@ -# This is used by the action https://github.com/dorny/paths-filter - -high_risk_code: &high_risk_code - # Transport classes - - "sentry/src/main/java/io/sentry/transport/AsyncHttpTransport.java" - - "sentry/src/main/java/io/sentry/transport/HttpConnection.java" - - "sentry/src/main/java/io/sentry/transport/QueuedThreadPoolExecutor.java" - - "sentry/src/main/java/io/sentry/transport/RateLimiter.java" - - "sentry-apache-http-client-5/src/main/java/io/sentry/transport/apache/ApacheHttpClientTransport.java" - - # Class used by hybrid SDKs - - "sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index b337ac9ea4e..00000000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,30 +0,0 @@ -## :scroll: Description - - - -## :bulb: Motivation and Context - - - - - -## :green_heart: How did you test it? - - -## :pencil: Checklist - - -- [ ] I added GH Issue ID _&_ Linear ID -- [ ] I added tests to verify the changes. -- [ ] No new PII added or SDK only sends newly added PII if `sendDefaultPII` is enabled. -- [ ] I updated the docs if needed. -- [ ] I updated the wizard if needed. -- [ ] Review from the native team if needed. -- [ ] No breaking change or entry added to the changelog. -- [ ] No breaking change for hybrid SDKs or communicated to hybrid SDKs. - - -## :crystal_ball: Next steps diff --git a/.github/workflows/add-platform-label.yml b/.github/workflows/add-platform-label.yml deleted file mode 100644 index 82d39cb8c76..00000000000 --- a/.github/workflows/add-platform-label.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Add Platform Label -on: - issues: - types: [opened] - pull_request: - types: [opened] - -jobs: - add_label: - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 # pin@1.0.4 - with: - add-labels: 'Platform: Java' - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/agp-matrix.yml b/.github/workflows/agp-matrix.yml deleted file mode 100644 index 7d2297ed33a..00000000000 --- a/.github/workflows/agp-matrix.yml +++ /dev/null @@ -1,121 +0,0 @@ -name: AGP Matrix Compatibility - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - agp-matrix-compatibility: - timeout-minutes: 30 - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - agp: [ '8.7.0','8.8.0','8.9.0' ] - integrations: [ true, false ] - - name: AGP Matrix Release - AGP ${{ matrix.agp }} - Integrations ${{ matrix.integrations }} - env: - VERSION_AGP: ${{ matrix.agp }} - APPLY_SENTRY_INTEGRATIONS: ${{ matrix.integrations }} - GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - steps: - - name: Checkout Repo - uses: actions/checkout@v6 - with: - submodules: 'recursive' - - - name: Setup Java Version - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - with: - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - - name: Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - - name: AVD cache - uses: actions/cache@v5 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-api-30-x86_64-aosp_atd - - - name: Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@b530d96654c385303d652368551fb075bc2f0b6b # pin@v2 - with: - api-level: 30 - target: aosp_atd - channel: canary # Necessary for ATDs - arch: x86_64 - force-avd-creation: false - disable-animations: true - disable-spellchecker: true - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disk-size: 4096M - script: echo "Generated AVD snapshot for caching." - - # Clean, build and release a test apk - - name: Make assembleUiTests - run: make assembleUiTests - - # We tried to use the cache action to cache gradle stuff, but it made tests slower and timeout - - name: Run instrumentation tests - uses: reactivecircus/android-emulator-runner@b530d96654c385303d652368551fb075bc2f0b6b # pin@v2 - with: - api-level: 30 - target: aosp_atd - channel: canary # Necessary for ATDs - arch: x86_64 - force-avd-creation: false - disable-animations: true - disable-spellchecker: true - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disk-size: 4096M - script: ./gradlew sentry-android-integration-tests:sentry-uitest-android:connectedReleaseAndroidTest -DtestBuildType=release -Denvironment=github --daemon - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v6 - with: - name: test-results-AGP${{ matrix.agp }}-Integrations${{ matrix.integrations }} - path: | - **/build/reports/* - **/build/outputs/*/connected/* - **/build/outputs/mapping/release/* - - - name: Test Report - uses: phoenix-actions/test-reporting@f957cd93fc2d848d556fa0d03c57bc79127b6b5e # pin@v15 - if: always() - with: - name: JUnit AGP ${{ matrix.agp }} - Integrations ${{ matrix.integrations }} - path: | - **/build/outputs/androidTest-results/**/*.xml - reporter: java-junit - output-to: step-summary - fail-on-error: false - - - name: Upload test results to Codecov - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: build/outputs/androidTest-results/**/*.xml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index ab7e6904c90..00000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: 'Build' -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - name: Build Job ubuntu-latest - Java 17 - runs-on: ubuntu-latest - - env: - GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - steps: - - name: Checkout Repo - uses: actions/checkout@v6 - with: - submodules: 'recursive' - - - name: Setup Java Version - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - # Workaround for https://github.com/gradle/actions/issues/21 to use config cache - - name: Cache buildSrc - uses: actions/cache@v5 - with: - path: buildSrc/build - key: build-logic-${{ hashFiles('buildSrc/src/**', 'buildSrc/build.gradle.kts','buildSrc/settings.gradle.kts') }} - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - with: - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - - name: Run Tests with coverage and Lint - run: make preMerge - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # pin@v4 - with: - name: sentry-java - fail_ci_if_error: false - token: ${{ secrets.CODECOV_TOKEN }} - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v6 - with: - name: test-results-build - path: | - **/build/reports/* - - - name: Test Report - uses: phoenix-actions/test-reporting@f957cd93fc2d848d556fa0d03c57bc79127b6b5e # pin@v15 - if: always() - with: - name: JUnit Build - list-suites: 'failed' - list-tests: 'failed' - path: | - **/build/test-results/**/*.xml - reporter: java-junit - output-to: step-summary - fail-on-error: false diff --git a/.github/workflows/changelog-preview.yml b/.github/workflows/changelog-preview.yml deleted file mode 100644 index af9087141e7..00000000000 --- a/.github/workflows/changelog-preview.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Changelog Preview -on: - pull_request: - types: - - opened - - synchronize - - reopened - - edited - - labeled - - unlabeled -permissions: - contents: write - pull-requests: write - statuses: write - -jobs: - changelog-preview: - uses: getsentry/craft/.github/workflows/changelog-preview.yml@v2 - secrets: inherit diff --git a/.github/workflows/changes-in-high-risk-code.yml b/.github/workflows/changes-in-high-risk-code.yml deleted file mode 100644 index ba1376ff513..00000000000 --- a/.github/workflows/changes-in-high-risk-code.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Changes In High Risk Code -on: - pull_request: - -# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - files-changed: - name: Detect changed files - runs-on: ubuntu-latest - # Map a step output to a job output - outputs: - high_risk_code: ${{ steps.changes.outputs.high_risk_code }} - high_risk_code_files: ${{ steps.changes.outputs.high_risk_code_files }} - steps: - - uses: actions/checkout@v6 - - name: Get changed files - id: changes - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 - with: - token: ${{ github.token }} - filters: .github/file-filters.yml - - # Enable listing of files matching each filter. - # Paths to files will be available in `${FILTER_NAME}_files` output variable. - list-files: csv - - validate-high-risk-code: - if: needs.files-changed.outputs.high_risk_code == 'true' - needs: files-changed - runs-on: ubuntu-latest - steps: - - name: Comment on PR to notify of changes in high risk files - uses: actions/github-script@v8 - env: - high_risk_code: ${{ needs.files-changed.outputs.high_risk_code_files }} - with: - script: | - const highRiskFiles = process.env.high_risk_code; - const fileList = highRiskFiles.split(',').map(file => `- [ ] ${file}`).join('\n'); - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `### 🚨 Detected changes in high risk code 🚨 \n High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:\n ${fileList}` - }) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index a49c5a1a0cc..00000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: 'CodeQL' - -on: - push: - branches: [main] - schedule: - - cron: '17 23 * * 3' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - analyze: - name: Analyze - runs-on: macos-15 - - env: - GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - steps: - - name: Checkout Repo - uses: actions/checkout@v6 - with: - submodules: 'recursive' - - - name: Setup Java Version - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - with: - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - - name: Initialize CodeQL - uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # pin@v2 - with: - languages: 'java' - - - name: Build Java - run: | - ./gradlew buildForCodeQL --no-build-cache - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # pin@v2 diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml deleted file mode 100644 index 7f2045ea980..00000000000 --- a/.github/workflows/danger.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: Danger - -on: - pull_request: - types: [opened, synchronize, reopened, edited, ready_for_review, labeled, unlabeled] - -jobs: - danger: - runs-on: ubuntu-latest - steps: - - uses: getsentry/github-workflows/danger@v3 diff --git a/.github/workflows/enforce-license-compliance.yml b/.github/workflows/enforce-license-compliance.yml deleted file mode 100644 index 9143270b5a1..00000000000 --- a/.github/workflows/enforce-license-compliance.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Enforce License Compliance - -on: - push: - branches: [master, main, release/*] - pull_request: - branches: [master, main] - -jobs: - enforce-license-compliance: - runs-on: ubuntu-latest - steps: - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - - - name: Set up Java - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - - name: Checkout - uses: actions/checkout@v6 - - # TODO: remove this when upstream is fixed - - name: Disable Gradle configuration cache (see https://github.com/fossas/fossa-cli/issues/872) - run: sed -i 's/^org.gradle.configuration-cache=.*/org.gradle.configuration-cache=false/' gradle.properties - - - name: 'Enforce License Compliance' - uses: getsentry/action-enforce-license-compliance@main - with: - skip_checkout: 'true' - fossa_test_timeout_seconds: 3600 - fossa_api_key: ${{ secrets.FOSSA_API_KEY }} diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml deleted file mode 100644 index 3662f1db3e8..00000000000 --- a/.github/workflows/format-code.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: format code -on: - pull_request: - -jobs: - format-code: - name: Format Code - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - submodules: 'recursive' - - - name: set up JDK 17 - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - with: - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - - name: Format with spotlessApply - run: ./gradlew spotlessApply - - # actions/checkout fetches only a single commit in a detached HEAD state. Therefore - # we need to pass the current branch, otherwise we can't commit the changes. - # GITHUB_HEAD_REF is the name of the head branch. GitHub Actions only sets this for PRs. - - name: Commit Formatted Code - run: ./scripts/commit-formatted-code.sh $GITHUB_HEAD_REF diff --git a/.github/workflows/generate-javadocs.yml b/.github/workflows/generate-javadocs.yml deleted file mode 100644 index 3ef5d3c52cf..00000000000 --- a/.github/workflows/generate-javadocs.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: 'Generate Javadocs' -on: - release: - types: [released] - -jobs: - build-and-deploy-javadocs: - name: Build and deploy Javadocs - runs-on: ubuntu-latest - steps: - - name: Checkout 🛎️ - uses: actions/checkout@v6 - with: - submodules: 'recursive' - - - name: set up JDK 17 - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - - - name: Generate Aggregate Javadocs - run: | - ./gradlew aggregateJavadocs - - name: Deploy - uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # pin@4.8.0 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: gh-pages - FOLDER: build/docs/javadoc - CLEAN: true diff --git a/.github/workflows/integration-tests-benchmarks.yml b/.github/workflows/integration-tests-benchmarks.yml deleted file mode 100644 index 4d6b403be65..00000000000 --- a/.github/workflows/integration-tests-benchmarks.yml +++ /dev/null @@ -1,114 +0,0 @@ -name: 'Integration Tests - Benchmarks' -on: - schedule: - - cron: '0 0 * * *' # every night at midnight UTC - pull_request: - paths: - - '**/sentry/**' - - '**/sentry-android/**' - - '**/sentry-android-core/**' - - '**/sentry-android-ndk/**' - - '**/sentry-android-integration-tests/**' - - '**/.github/**' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - test: - name: Benchmarks - runs-on: ubuntu-latest - - # we copy the secret to the env variable in order to access it in the workflow - env: - SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} - GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - steps: - - name: Git checkout - uses: actions/checkout@v6 - with: - submodules: 'recursive' - - - name: 'Set up Java: 17' - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - with: - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - # Clean, build and release a test apk, but only if we will run the benchmark - - name: Make assembleBenchmarks - if: env.SAUCE_USERNAME != null - run: make assembleBenchmarks - - - name: Run All Tests in SauceLab - uses: saucelabs/saucectl-run-action@39e4f0666ca8ecb4b60847213c6e0fbd6a0c2bd8 # pin@v3 - if: github.event_name != 'pull_request' && env.SAUCE_USERNAME != null - env: - GITHUB_TOKEN: ${{ github.token }} - with: - sauce-username: ${{ secrets.SAUCE_USERNAME }} - sauce-access-key: ${{ secrets.SAUCE_ACCESS_KEY }} - config-file: .sauce/sentry-uitest-android-benchmark.yml - - - name: Run one test in SauceLab - uses: saucelabs/saucectl-run-action@39e4f0666ca8ecb4b60847213c6e0fbd6a0c2bd8 # pin@v3 - if: github.event_name == 'pull_request' && env.SAUCE_USERNAME != null - env: - GITHUB_TOKEN: ${{ github.token }} - with: - sauce-username: ${{ secrets.SAUCE_USERNAME }} - sauce-access-key: ${{ secrets.SAUCE_ACCESS_KEY }} - config-file: .sauce/sentry-uitest-android-benchmark-lite.yml - - app-metrics: - runs-on: ubuntu-latest - - # we copy the secret to the env variable in order to access it in the workflow - env: - SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} - GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - steps: - - name: Git checkout - uses: actions/checkout@v6 - with: - submodules: 'recursive' - - - name: 'Set up Java: 17' - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - with: - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - - uses: actions/cache@v5 - id: app-plain-cache - with: - path: sentry-android-integration-tests/test-app-plain/build/outputs/apk/release/test-app-plain-release.apk - key: ${{ github.workflow }}-${{ github.job }}-appplain-${{ hashFiles('sentry-android-integration-tests/test-app-plain/**') }} - - - name: Build Android app plain - if: steps.app-plain-cache.outputs['cache-hit'] != 'true' - run: ./gradlew :sentry-android-integration-tests:test-app-plain:assembleRelease - - - name: Build Android app with Sentry - run: ./gradlew :sentry-android-integration-tests:test-app-sentry:assembleRelease - - - name: Collect app metrics - uses: getsentry/action-app-sdk-overhead-metrics@v1 - with: - config: sentry-android-integration-tests/metrics-test.yml - sauce-user: ${{ secrets.SAUCE_USERNAME }} - sauce-key: ${{ secrets.SAUCE_ACCESS_KEY }} - if: env.SAUCE_USERNAME != null diff --git a/.github/workflows/integration-tests-size.yml b/.github/workflows/integration-tests-size.yml deleted file mode 100644 index bb867681b59..00000000000 --- a/.github/workflows/integration-tests-size.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: SDK Size Analysis - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - name: Build and Analyze SDK Size - runs-on: ubuntu-latest - - env: - GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - steps: - - name: Checkout Repo - uses: actions/checkout@v6 - - - name: Setup Java Version - uses: actions/setup-java@v5 - with: - distribution: "temurin" - java-version: "17" - - # Workaround for https://github.com/gradle/actions/issues/21 to use config cache - - name: Cache buildSrc - uses: actions/cache@v5 - with: - path: buildSrc/build - key: build-logic-${{ hashFiles('buildSrc/src/**', 'buildSrc/build.gradle.kts','buildSrc/settings.gradle.kts') }} - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - with: - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - - name: Size Analysis - run: ./gradlew :sentry-android-integration-tests:test-app-size:bundleRelease - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} diff --git a/.github/workflows/integration-tests-ui-critical.yml b/.github/workflows/integration-tests-ui-critical.yml deleted file mode 100644 index 792583937ba..00000000000 --- a/.github/workflows/integration-tests-ui-critical.yml +++ /dev/null @@ -1,148 +0,0 @@ -name: UI Tests Critical - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - BASE_PATH: "sentry-android-integration-tests/sentry-uitest-android-critical" - BUILD_PATH: "build/outputs/apk/release" - APK_NAME: "sentry-uitest-android-critical-release.apk" - APK_ARTIFACT_NAME: "sentry-uitest-android-critical-release" - MAESTRO_VERSION: "2.1.0" - -jobs: - build: - name: Build - runs-on: ubuntu-latest - - env: - GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Set up Java 17 - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - with: - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - - name: Build debug APK - run: make assembleUiTestCriticalRelease - - - name: Upload APK artifact - uses: actions/upload-artifact@v6 - with: - name: ${{env.APK_ARTIFACT_NAME}} - path: "${{env.BASE_PATH}}/${{env.BUILD_PATH}}/${{env.APK_NAME}}" - retention-days: 1 - - run-maestro-tests: - name: Run Tests for API Level ${{ matrix.api-level }} - needs: build - runs-on: ubuntu-latest - strategy: - # we want that the matrix keeps running, default is to cancel them if it fails. - fail-fast: false - matrix: - include: - - api-level: 31 # Android 12 - target: google_apis - channel: canary # Necessary for ATDs - arch: x86_64 - - api-level: 33 # Android 13 - target: google_apis - channel: canary # Necessary for ATDs - arch: x86_64 - - api-level: 34 # Android 14 - target: google_apis - channel: canary # Necessary for ATDs - arch: x86_64 - - api-level: 35 # Android 15 - target: google_apis - channel: canary # Necessary for ATDs - arch: x86_64 - - api-level: 36 # Android 16 - target: google_apis - channel: canary # Necessary for ATDs - arch: x86_64 - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - - name: AVD cache - uses: actions/cache@v5 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-api-${{ matrix.api-level }}-${{ matrix.arch }}-${{ matrix.target }} - - - name: Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@b530d96654c385303d652368551fb075bc2f0b6b # pin@v2 - with: - api-level: ${{ matrix.api-level }} - target: ${{ matrix.target }} - channel: ${{ matrix.channel }} - arch: ${{ matrix.arch }} - force-avd-creation: false - disable-animations: true - disable-spellchecker: true - emulator-options: -memory 4096 -no-window -gpu auto -noaudio -no-boot-anim -camera-back none - disk-size: 4096M - script: echo "Generated AVD snapshot for caching." - - - name: Download APK artifact - uses: actions/download-artifact@v7 - with: - name: ${{env.APK_ARTIFACT_NAME}} - - - name: Install Maestro - uses: dniHze/maestro-test-action@bda8a93211c86d0a05b7a4597c5ad134566fbde4 # pin@v1.0.0 - with: - version: ${{env.MAESTRO_VERSION}} - - - name: Run tests - uses: reactivecircus/android-emulator-runner@b530d96654c385303d652368551fb075bc2f0b6b # pin@v2.35.0 - with: - api-level: ${{ matrix.api-level }} - target: ${{ matrix.target }} - channel: ${{ matrix.channel }} - arch: ${{ matrix.arch }} - force-avd-creation: false - disable-animations: true - disable-spellchecker: true - emulator-options: -memory 4096 -no-window -gpu auto -noaudio -no-boot-anim -camera-back none -no-snapshot-save - script: | - adb uninstall io.sentry.uitest.android.critical || echo "Already uninstalled (or not found)" - adb install -r -d "${{env.APK_NAME}}" - mkdir "${{env.BASE_PATH}}/maestro-logs/" || true; adb emu screenrecord start --time-limit 360 "${{env.BASE_PATH}}/maestro-logs/recording.webm" || true; maestro test "${{env.BASE_PATH}}/maestro" --test-output-dir="${{env.BASE_PATH}}/maestro-logs/test-output" || MAESTRO_EXIT_CODE=$?; adb emu screenrecord stop || true; adb logcat -d > "${{env.BASE_PATH}}/maestro-logs/logcat.txt" || true; exit ${MAESTRO_EXIT_CODE:-0} - - - name: Upload Maestro test results - if: ${{ always() }} - uses: actions/upload-artifact@v6 - with: - name: maestro-logs-${{ matrix.api-level }}-${{ matrix.arch }}-${{ matrix.target }} - path: "${{env.BASE_PATH}}/maestro-logs" - retention-days: 1 diff --git a/.github/workflows/integration-tests-ui.yml b/.github/workflows/integration-tests-ui.yml deleted file mode 100644 index 017a150155e..00000000000 --- a/.github/workflows/integration-tests-ui.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: 'Integration Tests' -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - test: - name: Ui tests - runs-on: ubuntu-latest - - # we copy the secret to the env variable in order to access it in the workflow - env: - SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} - SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} - GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - steps: - - name: Git checkout - uses: actions/checkout@v6 - with: - submodules: 'recursive' - - - name: 'Set up Java: 17' - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - with: - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - # Clean, build and release a test apk, but only if we will run the benchmark - - name: Make assembleUiTests - if: env.SAUCE_USERNAME != null - run: make assembleUiTests - - - name: Install SauceLabs CLI - uses: saucelabs/saucectl-run-action@39e4f0666ca8ecb4b60847213c6e0fbd6a0c2bd8 # pin@v4.3.0 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - skip-run: true - if: env.SAUCE_USERNAME != null - - - name: Run Tests - id: saucelabs - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - exec &> >(tee -a "test_logs.txt") - saucectl run -c .sauce/sentry-uitest-android-ui.yml - if: env.SAUCE_USERNAME != null - continue-on-error: true - - - name: Verify Test Results - run: | - processCrashed=$(cat test_logs.txt | grep "Instrumentation run failed due to 'Process crashed.'" | wc -l) - if [[ ${{ steps.saucelabs.outcome }} == 'success' ]]; then - exit 0 - elif [[ "$processCrashed" -ne 0 ]]; then - exit 0 - else - exit 1 - fi - if: env.SAUCE_USERNAME != null - - - - name: Upload test results to Codecov - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: ./artifacts/*.xml diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml deleted file mode 100644 index 8b41af5e846..00000000000 --- a/.github/workflows/release-build.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: 'Build Release Artifacts' -on: - push: - branches: - - release/** - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - release: - name: Build release artifacts - runs-on: ubuntu-latest - - steps: - - name: Checkout Repo - uses: actions/checkout@v6 - with: - submodules: 'recursive' - - - name: Setup Java Version - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - - - name: Build artifacts - run: make publish - - - name: Upload artifacts - uses: actions/upload-artifact@v6 - with: - name: ${{ github.sha }} - if-no-files-found: error - path: | - ./*/build/distributions/*.zip - ./sentry-opentelemetry/*/build/distributions/*.zip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 24a9dd81cbf..00000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: release -on: - workflow_dispatch: - inputs: - version: - description: Version to release (or "auto") - required: false - force: - description: Force a release even when there are release-blockers (optional) - required: false - merge_target: - description: Target branch to merge into. Uses the default branch as a fallback (optional) - required: false - -permissions: - contents: write - pull-requests: write - -jobs: - release: - runs-on: ubuntu-latest - name: "Release a new version" - steps: - - name: Get auth token - id: token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 - with: - app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} - private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} - - uses: actions/checkout@v6 - with: - token: ${{ steps.token.outputs.token }} - # Needs to be set, otherwise git describe --tags will fail with: No names found, cannot describe anything - fetch-depth: 0 - submodules: 'recursive' - - name: Prepare release - uses: getsentry/craft@63d1636bead951f6e034ed62c2a3610965fef010 # v2 - env: - GITHUB_TOKEN: ${{ steps.token.outputs.token }} - with: - version: ${{ github.event.inputs.version }} - force: ${{ github.event.inputs.force }} - merge_target: ${{ github.event.inputs.merge_target }} diff --git a/.github/workflows/spring-boot-2-matrix.yml b/.github/workflows/spring-boot-2-matrix.yml deleted file mode 100644 index 2cb073304a9..00000000000 --- a/.github/workflows/spring-boot-2-matrix.yml +++ /dev/null @@ -1,178 +0,0 @@ -name: Spring Boot 2.x Matrix - -on: - push: - branches: - - main - paths-ignore: - - '**/sentry-android/**' - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - spring-boot-2-matrix: - timeout-minutes: 45 - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - springboot-version: [ '2.1.0', '2.2.5', '2.4.13', '2.5.15', '2.6.15', '2.7.0', '2.7.18' ] - - name: Spring Boot ${{ matrix.springboot-version }} - env: - SENTRY_URL: http://127.0.0.1:8000 - GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - steps: - - name: Checkout Repo - uses: actions/checkout@v6 - with: - submodules: 'recursive' - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.10.5' - - - name: Install Python dependencies - run: | - python3 -m pip install --upgrade pip - python3 -m pip install -r requirements.txt - - - name: Set up Java - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - # Workaround for https://github.com/gradle/actions/issues/21 to use config cache - - name: Cache buildSrc - uses: actions/cache@v5 - with: - path: buildSrc/build - key: build-logic-${{ hashFiles('buildSrc/src/**', 'buildSrc/build.gradle.kts','buildSrc/settings.gradle.kts') }} - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - with: - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - - name: Update Spring Boot 2.x version - run: | - sed -i 's/^springboot2=.*/springboot2=${{ matrix.springboot-version }}/' gradle/libs.versions.toml - echo "Updated Spring Boot 2.x version to ${{ matrix.springboot-version }}" - - - name: Exclude android modules from build - run: | - sed -i \ - -e '/.*"sentry-android-ndk",/d' \ - -e '/.*"sentry-android",/d' \ - -e '/.*"sentry-compose",/d' \ - -e '/.*"sentry-android-core",/d' \ - -e '/.*"sentry-android-fragment",/d' \ - -e '/.*"sentry-android-navigation",/d' \ - -e '/.*"sentry-android-sqlite",/d' \ - -e '/.*"sentry-android-timber",/d' \ - -e '/.*"sentry-android-integration-tests:sentry-uitest-android-benchmark",/d' \ - -e '/.*"sentry-android-integration-tests:sentry-uitest-android",/d' \ - -e '/.*"sentry-android-integration-tests:sentry-uitest-android-critical",/d' \ - -e '/.*"sentry-android-integration-tests:test-app-sentry",/d' \ - -e '/.*"sentry-android-integration-tests:test-app-size",/d' \ - -e '/.*"sentry-samples:sentry-samples-android",/d' \ - -e '/.*"sentry-android-replay",/d' \ - settings.gradle.kts - - - name: Exclude android modules from ignore list - run: | - sed -i \ - -e '/.*"sentry-uitest-android",/d' \ - -e '/.*"sentry-uitest-android-benchmark",/d' \ - -e '/.*"sentry-uitest-android-critical",/d' \ - -e '/.*"test-app-sentry",/d' \ - -e '/.*"test-app-size",/d' \ - -e '/.*"sentry-samples-android",/d' \ - build.gradle.kts - - - name: Build SDK - run: | - ./gradlew assemble --parallel - - - name: Test sentry-samples-spring-boot - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-boot" \ - --agent false \ - --auto-init "true" \ - --build "true" - - - name: Test sentry-samples-spring-boot-webflux - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-boot-webflux" \ - --agent false \ - --auto-init "true" \ - --build "true" - - - name: Test sentry-samples-spring-boot-opentelemetry agent init true - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-boot-opentelemetry" \ - --agent true \ - --auto-init "true" \ - --build "true" - - - name: Test sentry-samples-spring-boot-opentelemetry agent init false - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-boot-opentelemetry" \ - --agent true \ - --auto-init "false" \ - --build "true" - - - name: Test sentry-samples-spring-boot-opentelemetry-noagent - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-boot-opentelemetry-noagent" \ - --agent false \ - --auto-init "true" \ - --build "true" - - - name: Test sentry-samples-spring - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring" \ - --agent false \ - --auto-init "true" \ - --build "true" - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v6 - with: - name: test-results-springboot-2-${{ matrix.springboot-version }} - path: | - **/build/reports/* - **/build/test-results/**/*.xml - sentry-mock-server.txt - spring-server.txt - - - name: Test Report - uses: phoenix-actions/test-reporting@f957cd93fc2d848d556fa0d03c57bc79127b6b5e # pin@v15 - if: always() - with: - name: JUnit Spring Boot 2.x ${{ matrix.springboot-version }} - path: | - **/build/test-results/**/*.xml - reporter: java-junit - output-to: step-summary - fail-on-error: false - - - name: Upload test results to Codecov - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: '**/build/test-results/**/*.xml' diff --git a/.github/workflows/spring-boot-3-matrix.yml b/.github/workflows/spring-boot-3-matrix.yml deleted file mode 100644 index 3b225ebe684..00000000000 --- a/.github/workflows/spring-boot-3-matrix.yml +++ /dev/null @@ -1,178 +0,0 @@ -name: Spring Boot 3.x Matrix - -on: - push: - branches: - - main - paths-ignore: - - '**/sentry-android/**' - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - spring-boot-3-matrix: - timeout-minutes: 45 - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - springboot-version: [ '3.0.0', '3.2.10', '3.3.5', '3.4.5', '3.5.6' ] - - name: Spring Boot ${{ matrix.springboot-version }} - env: - SENTRY_URL: http://127.0.0.1:8000 - GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - steps: - - name: Checkout Repo - uses: actions/checkout@v6 - with: - submodules: 'recursive' - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.10.5' - - - name: Install Python dependencies - run: | - python3 -m pip install --upgrade pip - python3 -m pip install -r requirements.txt - - - name: Set up Java - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - # Workaround for https://github.com/gradle/actions/issues/21 to use config cache - - name: Cache buildSrc - uses: actions/cache@v5 - with: - path: buildSrc/build - key: build-logic-${{ hashFiles('buildSrc/src/**', 'buildSrc/build.gradle.kts','buildSrc/settings.gradle.kts') }} - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - with: - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - - name: Update Spring Boot 3.x version - run: | - sed -i 's/^springboot3=.*/springboot3=${{ matrix.springboot-version }}/' gradle/libs.versions.toml - echo "Updated Spring Boot 3.x version to ${{ matrix.springboot-version }}" - - - name: Exclude android modules from build - run: | - sed -i \ - -e '/.*"sentry-android-ndk",/d' \ - -e '/.*"sentry-android",/d' \ - -e '/.*"sentry-compose",/d' \ - -e '/.*"sentry-android-core",/d' \ - -e '/.*"sentry-android-fragment",/d' \ - -e '/.*"sentry-android-navigation",/d' \ - -e '/.*"sentry-android-sqlite",/d' \ - -e '/.*"sentry-android-timber",/d' \ - -e '/.*"sentry-android-integration-tests:sentry-uitest-android-benchmark",/d' \ - -e '/.*"sentry-android-integration-tests:sentry-uitest-android",/d' \ - -e '/.*"sentry-android-integration-tests:sentry-uitest-android-critical",/d' \ - -e '/.*"sentry-android-integration-tests:test-app-sentry",/d' \ - -e '/.*"sentry-android-integration-tests:test-app-size",/d' \ - -e '/.*"sentry-samples:sentry-samples-android",/d' \ - -e '/.*"sentry-android-replay",/d' \ - settings.gradle.kts - - - name: Exclude android modules from ignore list - run: | - sed -i \ - -e '/.*"sentry-uitest-android",/d' \ - -e '/.*"sentry-uitest-android-benchmark",/d' \ - -e '/.*"sentry-uitest-android-critical",/d' \ - -e '/.*"test-app-sentry",/d' \ - -e '/.*"test-app-size",/d' \ - -e '/.*"sentry-samples-android",/d' \ - build.gradle.kts - - - name: Build SDK - run: | - ./gradlew assemble --parallel - - - name: Test sentry-samples-spring-boot-jakarta - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-boot-jakarta" \ - --agent false \ - --auto-init "true" \ - --build "true" - - - name: Test sentry-samples-spring-boot-webflux-jakarta - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-boot-webflux-jakarta" \ - --agent false \ - --auto-init "true" \ - --build "true" - - - name: Test sentry-samples-spring-boot-jakarta-opentelemetry agent init true - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-boot-jakarta-opentelemetry" \ - --agent true \ - --auto-init "true" \ - --build "true" - - - name: Test sentry-samples-spring-boot-jakarta-opentelemetry agent init false - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-boot-jakarta-opentelemetry" \ - --agent true \ - --auto-init "false" \ - --build "true" - - - name: Test sentry-samples-spring-boot-jakarta-opentelemetry-noagent - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-boot-jakarta-opentelemetry-noagent" \ - --agent false \ - --auto-init "true" \ - --build "true" - - - name: Test sentry-samples-spring-jakarta - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-jakarta" \ - --agent false \ - --auto-init "true" \ - --build "true" - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v6 - with: - name: test-results-springboot-3-${{ matrix.springboot-version }} - path: | - **/build/reports/* - **/build/test-results/**/*.xml - sentry-mock-server.txt - spring-server.txt - - - name: Test Report - uses: phoenix-actions/test-reporting@f957cd93fc2d848d556fa0d03c57bc79127b6b5e # pin@v15 - if: always() - with: - name: JUnit Spring Boot 3.x ${{ matrix.springboot-version }} - path: | - **/build/test-results/**/*.xml - reporter: java-junit - output-to: step-summary - fail-on-error: false - - - name: Upload test results to Codecov - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: '**/build/test-results/**/*.xml' diff --git a/.github/workflows/spring-boot-4-matrix.yml b/.github/workflows/spring-boot-4-matrix.yml deleted file mode 100644 index a7da8d3bf7e..00000000000 --- a/.github/workflows/spring-boot-4-matrix.yml +++ /dev/null @@ -1,179 +0,0 @@ -name: Spring Boot 4.x Matrix - -on: - push: - branches: - - main - paths-ignore: - - '**/sentry-android/**' - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - spring-boot-4-matrix: - timeout-minutes: 45 - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - springboot-version: [ '4.0.0' ] - - name: Spring Boot ${{ matrix.springboot-version }} - env: - SENTRY_URL: http://127.0.0.1:8000 - GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - steps: - - name: Checkout Repo - uses: actions/checkout@v6 - with: - submodules: 'recursive' - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.10.5' - - - name: Install Python dependencies - run: | - python3 -m pip install --upgrade pip - python3 -m pip install -r requirements.txt - - - name: Set up Java - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - # Workaround for https://github.com/gradle/actions/issues/21 to use config cache - - name: Cache buildSrc - uses: actions/cache@v5 - with: - path: buildSrc/build - key: build-logic-${{ hashFiles('buildSrc/src/**', 'buildSrc/build.gradle.kts','buildSrc/settings.gradle.kts') }} - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - with: - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - - name: Update Spring Boot 4.x version - run: | - sed -i 's/^springboot4=.*/springboot4=${{ matrix.springboot-version }}/' gradle/libs.versions.toml - echo "Updated Spring Boot 4.x version to ${{ matrix.springboot-version }}" - - - name: Exclude android modules from build - run: | - sed -i \ - -e '/.*"sentry-android-ndk",/d' \ - -e '/.*"sentry-android",/d' \ - -e '/.*"sentry-compose",/d' \ - -e '/.*"sentry-android-core",/d' \ - -e '/.*"sentry-android-fragment",/d' \ - -e '/.*"sentry-android-navigation",/d' \ - -e '/.*"sentry-android-sqlite",/d' \ - -e '/.*"sentry-android-timber",/d' \ - -e '/.*"sentry-android-integration-tests:sentry-uitest-android-benchmark",/d' \ - -e '/.*"sentry-android-integration-tests:sentry-uitest-android",/d' \ - -e '/.*"sentry-android-integration-tests:sentry-uitest-android-critical",/d' \ - -e '/.*"sentry-android-integration-tests:test-app-sentry",/d' \ - -e '/.*"sentry-android-integration-tests:test-app-size",/d' \ - -e '/.*"sentry-samples:sentry-samples-android",/d' \ - -e '/.*"sentry-android-replay",/d' \ - settings.gradle.kts - - - name: Exclude android modules from ignore list - run: | - sed -i \ - -e '/.*"sentry-uitest-android",/d' \ - -e '/.*"sentry-uitest-android-benchmark",/d' \ - -e '/.*"sentry-uitest-android-critical",/d' \ - -e '/.*"test-app-sentry",/d' \ - -e '/.*"test-app-size",/d' \ - -e '/.*"sentry-samples-android",/d' \ - build.gradle.kts - - - name: Build SDK - run: | - ./gradlew assemble --parallel - - - name: Run sentry-samples-spring-boot-4 - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-boot-4" \ - --agent false \ - --auto-init "true" \ - --build "true" - - - name: Run sentry-samples-spring-boot-4-webflux - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-boot-4-webflux" \ - --agent false \ - --auto-init "true" \ - --build "true" - - - name: Run sentry-samples-spring-boot-4-opentelemetry agent init true - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-boot-4-opentelemetry" \ - --agent true \ - --auto-init "true" \ - --build "true" - - - name: Run sentry-samples-spring-boot-4-opentelemetry agent init false - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-boot-4-opentelemetry" \ - --agent true \ - --auto-init "false" \ - --build "true" - -# needs a fix in opentelemetry-spring-boot-starter -# - name: Run sentry-samples-spring-boot-4-opentelemetry-noagent -# run: | -# python3 test/system-test-runner.py test \ -# --module "sentry-samples-spring-boot-4-opentelemetry-noagent" \ -# --agent false \ -# --auto-init "true" \ -# --build "true" - - - name: Run sentry-samples-spring-7 - run: | - python3 test/system-test-runner.py test \ - --module "sentry-samples-spring-7" \ - --agent false \ - --auto-init "true" \ - --build "true" - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v6 - with: - name: test-results-springboot-4-${{ matrix.springboot-version }} - path: | - **/build/reports/* - **/build/test-results/**/*.xml - sentry-mock-server.txt - spring-server.txt - - - name: Test Report - uses: phoenix-actions/test-reporting@f957cd93fc2d848d556fa0d03c57bc79127b6b5e # pin@v15 - if: always() - with: - name: JUnit Spring Boot 4.x ${{ matrix.springboot-version }} - path: | - **/build/test-results/**/*.xml - reporter: java-junit - output-to: step-summary - fail-on-error: false - - - name: Upload test results to Codecov - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: '**/build/test-results/**/*.xml' diff --git a/.github/workflows/system-tests-backend.yml b/.github/workflows/system-tests-backend.yml deleted file mode 100644 index 1e668577c93..00000000000 --- a/.github/workflows/system-tests-backend.yml +++ /dev/null @@ -1,167 +0,0 @@ -name: 'System Tests Backend' - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - system-test: - runs-on: ubuntu-latest - continue-on-error: true - env: - SENTRY_URL: http://127.0.0.1:8000 - GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - strategy: - fail-fast: false - matrix: - sample: [ "sentry-samples-spring-boot-jakarta" ] - agent: [ "false" ] - agent-auto-init: [ "true" ] - include: - - sample: "sentry-samples-spring-boot" - agent: "false" - agent-auto-init: "true" - - sample: "sentry-samples-spring-boot-opentelemetry-noagent" - agent: "false" - agent-auto-init: "true" - - sample: "sentry-samples-spring-boot-opentelemetry" - agent: "true" - agent-auto-init: "true" - - sample: "sentry-samples-spring-boot-opentelemetry" - agent: "true" - agent-auto-init: "false" - - sample: "sentry-samples-spring-boot-webflux-jakarta" - agent: "false" - agent-auto-init: "true" - - sample: "sentry-samples-spring-boot-webflux" - agent: "false" - agent-auto-init: "true" - - sample: "sentry-samples-spring-boot-jakarta-opentelemetry-noagent" - agent: "false" - agent-auto-init: "true" - - sample: "sentry-samples-spring-boot-jakarta-opentelemetry" - agent: "true" - agent-auto-init: "true" - - sample: "sentry-samples-spring-boot-jakarta-opentelemetry" - agent: "true" - agent-auto-init: "false" - - sample: "sentry-samples-console" - agent: "false" - agent-auto-init: "true" - - sample: "sentry-samples-logback" - agent: "false" - agent-auto-init: "true" - - sample: "sentry-samples-log4j2" - agent: "false" - agent-auto-init: "true" - - sample: "sentry-samples-jul" - agent: "false" - agent-auto-init: "true" - - sample: "sentry-samples-spring-boot-4" - agent: "false" - agent-auto-init: "true" - - sample: "sentry-samples-spring-boot-4-webflux" - agent: "false" - agent-auto-init: "true" -# - sample: "sentry-samples-spring-boot-4-opentelemetry-noagent" -# agent: "false" -# agent-auto-init: "true" - - sample: "sentry-samples-spring-boot-4-opentelemetry" - agent: "true" - agent-auto-init: "true" - - sample: "sentry-samples-spring-boot-4-opentelemetry" - agent: "true" - agent-auto-init: "false" - - sample: "sentry-samples-spring-7" - agent: "false" - agent-auto-init: "true" - - sample: "sentry-samples-spring-jakarta" - agent: "false" - agent-auto-init: "true" - - sample: "sentry-samples-spring" - agent: "false" - agent-auto-init: "true" - steps: - - uses: actions/checkout@v6 - with: - submodules: 'recursive' - - - uses: actions/setup-python@v6 - with: - python-version: '3.10.5' - - - name: Install Python dependencies - run: | - python3 -m pip install --upgrade pip - python3 -m pip install -r requirements.txt - - - name: Set up Java - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 - with: - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - - name: Exclude android modules from build - run: | - sed -i \ - -e '/.*"sentry-android-ndk",/d' \ - -e '/.*"sentry-android",/d' \ - -e '/.*"sentry-compose",/d' \ - -e '/.*"sentry-android-core",/d' \ - -e '/.*"sentry-android-fragment",/d' \ - -e '/.*"sentry-android-navigation",/d' \ - -e '/.*"sentry-android-sqlite",/d' \ - -e '/.*"sentry-android-timber",/d' \ - -e '/.*"sentry-android-integration-tests:sentry-uitest-android-benchmark",/d' \ - -e '/.*"sentry-android-integration-tests:sentry-uitest-android",/d' \ - -e '/.*"sentry-android-integration-tests:sentry-uitest-android-critical",/d' \ - -e '/.*"sentry-android-integration-tests:test-app-sentry",/d' \ - -e '/.*"sentry-samples:sentry-samples-android",/d' \ - -e '/.*"sentry-android-replay",/d' \ - settings.gradle.kts - - - name: Exclude android modules from ignore list - run: | - sed -i \ - -e '/.*"sentry-uitest-android",/d' \ - -e '/.*"sentry-uitest-android-benchmark",/d' \ - -e '/.*"sentry-uitest-android-critical",/d' \ - -e '/.*"test-app-sentry",/d' \ - -e '/.*"sentry-samples-android",/d' \ - build.gradle.kts - - - name: Build and run system tests - run: | - python3 test/system-test-runner.py test --module "${{ matrix.sample }}" --agent "${{ matrix.agent }}" --auto-init "${{ matrix.agent-auto-init }}" --build "true" - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v6 - with: - name: test-results-${{ matrix.sample }}-${{ matrix.agent }}-${{ matrix.agent-auto-init }}-system-test - path: | - **/build/reports/* - sentry-mock-server.txt - spring-server.txt - - - name: Test Report - uses: phoenix-actions/test-reporting@f957cd93fc2d848d556fa0d03c57bc79127b6b5e # pin@v15 - if: always() - with: - name: JUnit System Tests ${{ matrix.sample }} - path: | - **/build/test-results/**/*.xml - reporter: java-junit - output-to: step-summary - fail-on-error: false diff --git a/.github/workflows/update-deps.yml b/.github/workflows/update-deps.yml deleted file mode 100644 index e4e2d5433b7..00000000000 --- a/.github/workflows/update-deps.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Update Dependencies - -on: - # Run every day. - schedule: - - cron: '0 3 * * *' - # And on on every PR merge so we get the updated dependencies ASAP, and to make sure the changelog doesn't conflict. - push: - branches: - - main - -permissions: - contents: write - pull-requests: write - actions: write - -jobs: - native: - runs-on: ubuntu-latest - steps: - - uses: getsentry/github-workflows/updater@v3 - with: - path: scripts/update-sentry-native-ndk.sh - name: Native SDK - ssh-key: ${{ secrets.CI_DEPLOY_KEY }} - - gradle-wrapper: - runs-on: ubuntu-latest - steps: - - uses: getsentry/github-workflows/updater@v3 - with: - path: scripts/update-gradle.sh - name: Gradle - pattern: '^v[0-9.]+$' # only match non-preview versions - ssh-key: ${{ secrets.CI_DEPLOY_KEY }} diff --git a/.gitignore b/.gitignore index b8c2d7e9da2..bfd95355746 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,4 @@ -.DS_Store -.java-version -.idea/ -.gradle/ -.run/ -build/ -artifacts/ -out/ -local.properties -**.iml -*.hprof -.cxx -**/sentry-native-local target/ -.classpath -.project -.settings/ -bin/ -distributions/ -/Sentry/A1F16C4F5D23B2A1D281EE471D6F836BDEA23CB4/ -*.vscode/ -sentry-spring-boot-starter-jakarta/src/main/resources/META-INF/spring.factories -sentry-samples/sentry-samples-spring-boot-jakarta/spy.log -sentry-mock-server.txt -tomcat-server.txt -spring-server.txt -*.pid -spy.log -.kotlin -**/tomcat.8080/webapps/ -**/__pycache__ - -# Local Claude Code settings that should not be committed -.claude/settings.local.json +infer-out/ +agent/build/ +local.properties diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100755 index 9ac51e49730..00000000000 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import java.io.*; -import java.net.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - private static final String WRAPPER_VERSION = "0.5.6"; - /** Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. */ - private static final String DEFAULT_DOWNLOAD_URL = - "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION - + "/maven-wrapper-" - + WRAPPER_VERSION - + ".jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to use - * instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** Path where the maven-wrapper.jar will be saved to. */ - private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if (mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if (mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if (!outputFile.getParentFile().exists()) { - if (!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" - + outputFile.getParentFile().getAbsolutePath() - + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); - Authenticator.setDefault( - new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } -} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar deleted file mode 100755 index 2cc7d4a55c0..00000000000 Binary files a/.mvn/wrapper/maven-wrapper.jar and /dev/null differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties deleted file mode 100755 index 642d572ce90..00000000000 --- a/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,2 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/.python-version b/.python-version deleted file mode 100644 index 2c20ac9bea3..00000000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.13.3 diff --git a/.sauce/sentry-uitest-android-benchmark-lite.yml b/.sauce/sentry-uitest-android-benchmark-lite.yml deleted file mode 100644 index fec4a141def..00000000000 --- a/.sauce/sentry-uitest-android-benchmark-lite.yml +++ /dev/null @@ -1,34 +0,0 @@ -apiVersion: v1alpha -kind: espresso -sauce: - region: us-west-1 - concurrency: 1 - metadata: - build: sentry-uitest-android-benchmark-lite-$GITHUB_REF-$GITHUB_SHA - tags: - - benchmarks - - android - -defaults: - timeout: 40m - -espresso: - app: ./sentry-android-integration-tests/sentry-uitest-android-benchmark/build/outputs/apk/release/sentry-uitest-android-benchmark-release.apk - testApp: ./sentry-android-integration-tests/sentry-uitest-android-benchmark/build/outputs/apk/androidTest/release/sentry-uitest-android-benchmark-release-androidTest.apk - -suites: - - - name: "Android 15 Benchmark lite (api 35)" - testOptions: - clearPackageData: true - useTestOrchestrator: true - devices: - - name: ".*" - platformVersion: "15" - -artifacts: - download: - when: always - match: - - junit.xml - directory: ./artifacts/ diff --git a/.sauce/sentry-uitest-android-benchmark.yml b/.sauce/sentry-uitest-android-benchmark.yml deleted file mode 100644 index 12995ea5e07..00000000000 --- a/.sauce/sentry-uitest-android-benchmark.yml +++ /dev/null @@ -1,56 +0,0 @@ -apiVersion: v1alpha -kind: espresso -sauce: - region: us-west-1 - concurrency: 3 - metadata: - build: sentry-uitest-android-benchmark-$GITHUB_REF-$GITHUB_SHA - tags: - - benchmarks - - android - -defaults: - timeout: 90m - -espresso: - app: ./sentry-android-integration-tests/sentry-uitest-android-benchmark/build/outputs/apk/release/sentry-uitest-android-benchmark-release.apk - testApp: ./sentry-android-integration-tests/sentry-uitest-android-benchmark/build/outputs/apk/androidTest/release/sentry-uitest-android-benchmark-release-androidTest.apk - -suites: - - # Devices are chosen so that there is a high-end and a low-end device for each api level - - name: "Android 15 (api 35)" - testOptions: - clearPackageData: true - useTestOrchestrator: true - devices: - - id: Google_Pixel_9_Pro_XL_15_real_sjc1 # Google Pixel 9 Pro XL - api 35 (15) - high end - - id: Samsung_Galaxy_S23_15_real_sjc1 # Samsung Galaxy S23 - api 35 (15) - mid end - - id: Google_Pixel_6a_15_real_sjc1 # Google Pixel 6a - api 35 (15) - low end - - - name: "Android 14 (api 34)" - testOptions: - clearPackageData: true - useTestOrchestrator: true - devices: - - id: Google_Pixel_9_Pro_XL_real_sjc1 # Google Pixel 9 Pro XL - api 34 (14) - high end - - id: Samsung_Galaxy_A54_real_sjc1 # Samsung Galaxy A54 - api 34 (14) - low end - - - name: "Android 13 (api 33)" - testOptions: - clearPackageData: true - useTestOrchestrator: true - devices: - - id: Google_Pixel_7_Pro_real_us # Google Pixel 7 Pro - api 33 (13) - high end - - id: Samsung_Galaxy_A32_5G_real_sjc1 # Samsung Galaxy A32 5G - api 33 (13) - low end - -# At the time of writing (August, 13, 2025), the market share per android version is: -# 15.0 = 26.75%, 14.0 = 19.5%, 13 = 15.95% -# Using these 3 versions we cover 62.2% of all devices out there. Currently, this is enough for benchmarking scope - -artifacts: - download: - when: always - match: - - junit.xml - directory: ./artifacts/ diff --git a/.sauce/sentry-uitest-android-ui.yml b/.sauce/sentry-uitest-android-ui.yml deleted file mode 100644 index 8d84f865c95..00000000000 --- a/.sauce/sentry-uitest-android-ui.yml +++ /dev/null @@ -1,35 +0,0 @@ -apiVersion: v1alpha -kind: espresso -sauce: - region: us-west-1 - # Controls how many suites are executed at the same time (sauce test env only). - concurrency: 4 - metadata: - build: sentry-uitest-android-ui-$GITHUB_REF-$GITHUB_SHA - tags: - - e2e - - android - -defaults: - timeout: 45m - -espresso: - app: ./sentry-android-integration-tests/sentry-uitest-android/build/outputs/apk/release/sentry-uitest-android-release.apk - testApp: ./sentry-android-integration-tests/sentry-uitest-android/build/outputs/apk/androidTest/release/sentry-uitest-android-release-androidTest.apk -suites: - - - name: "Android 15 Ui test (api 35)" - testOptions: - clearPackageData: true - useTestOrchestrator: true - devices: - - name: ".*" - platformVersion: "15" - -# Controls what artifacts to fetch when the suite on Sauce Cloud has finished. -artifacts: - download: - when: always - match: - - junit.xml - directory: ./artifacts/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000000..6776a94fbf5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,49 @@ +env: + global: + - secure: "Xs3g5ZOMymPp0EvxF0T0tk5xMaV3htMPii5uDS17z9wDWYg7hj78mrP2cFea0ZjIqIjENG7YuxIjw34yb7507pvIxv8v+QvSkwlzVz4XJNzh2w1gbB2C+oVvbmbFd0A2Pmpf3jvkey6Tk5jhW7lxNVAZ7QzONuAf66iWdi6bwSg=" + - secure: "PBz5Jjoaf33a+Iqv5eJsFHbCYP1ZdeiQvrQBH7hJK5/Kdrbn4GCrDx+RkA58ZC8i8ZHfsW2MkMuD7RphcrgHuB3UwcvHwUgrEZaV1/ypIOe5O8CQ4Z0gkHCj2Ei/dsL3fpDU8WuPJSLa13tsMoQTdGaOhluCuSXpuArx3B3gX28=" + +after_success: + - "[[ $TRAVIS_BRANCH == 'master' ]] && { python .travis/addServer.py; mvn clean deploy --settings $HOME/.m2/mySettings.xml; };" + +matrix: + include: + - language: java + dist: precise + jdk: oraclejdk7 + install: make install + script: env | sort && make verify + cache: + directories: + - $HOME/.m2 + - language: java + dist: precise + jdk: oraclejdk8 + install: make install + script: env | sort && make verify + cache: + directories: + - $HOME/.m2 + - language: cpp + dist: trusty + jdk: oraclejdk7 + env: TARGET=x86_64 + before_install: .ci/agent-install.sh + script: .ci/agent-build.sh + cache: false + after_success: true + - language: cpp + dist: trusty + jdk: oraclejdk7 + env: TARGET=i686 + before_install: .ci/agent-install.sh + script: .ci/agent-build.sh + cache: false + after_success: true + +deploy: + provider: script + script: .ci/agent-deploy.sh + skip_cleanup: true + on: + tags: true diff --git a/.travis/addServer.py b/.travis/addServer.py new file mode 100644 index 00000000000..746e36f1a06 --- /dev/null +++ b/.travis/addServer.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +""" +This script adjusts the existing ~/.m2/settings.xml on Travis so that it includes +the Sonatype server, username and password for SNAPSHOT deploys. The user and password +come from secure environment variables in .travis.yml +""" + + +import sys +import os +import os.path +import xml.dom.minidom + +if os.environ["TRAVIS_SECURE_ENV_VARS"] == "false": + print "no secure env vars available, skipping deployment" + sys.exit() + +homedir = os.path.expanduser("~") + +m2 = xml.dom.minidom.parse(homedir + '/.m2/settings.xml') +settings = m2.getElementsByTagName("settings")[0] + +serversNodes = settings.getElementsByTagName("servers") +if not serversNodes: + serversNode = m2.createElement("servers") + settings.appendChild(serversNode) +else: + serversNode = serversNodes[0] + +sonatypeServerNode = m2.createElement("server") +sonatypeServerId = m2.createElement("id") +sonatypeServerUser = m2.createElement("username") +sonatypeServerPass = m2.createElement("password") + +idNode = m2.createTextNode("sonatype-nexus-snapshots") +userNode = m2.createTextNode(os.environ["SONATYPE_USERNAME"]) +passNode = m2.createTextNode(os.environ["SONATYPE_PASSWORD"]) + +sonatypeServerId.appendChild(idNode) +sonatypeServerUser.appendChild(userNode) +sonatypeServerPass.appendChild(passNode) + +sonatypeServerNode.appendChild(sonatypeServerId) +sonatypeServerNode.appendChild(sonatypeServerUser) +sonatypeServerNode.appendChild(sonatypeServerPass) + +serversNode.appendChild(sonatypeServerNode) + +m2Str = m2.toxml() +f = open(homedir + '/.m2/mySettings.xml', 'w') +f.write(m2Str) +f.close() diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index c34c5b48c73..00000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,4753 +0,0 @@ -# Changelog - -## Unreleased - -### Features - -- Add `installGroupsOverride` parameter to Build Distribution SDK for programmatic filtering, with support for configuration via properties file using `io.sentry.distribution.install-groups-override` ([#5066](https://github.com/getsentry/sentry-java/pull/5066)) - -### Fixes - -- When merging tombstones with Native SDK, use the tombstone message if the Native SDK didn't explicitly provide one. ([#5095](https://github.com/getsentry/sentry-java/pull/5095)) -- Fix thread leak caused by eager creation of `SentryExecutorService` in `SentryOptions` ([#5093](https://github.com/getsentry/sentry-java/pull/5093)) - - There were cases where we created options that ended up unused but we failed to clean those up. -- Attach user attributes to logs and metrics regardless of `sendDefaultPii` ([#5099](https://github.com/getsentry/sentry-java/pull/5099)) -- No longer log a warning if a logging integration cannot initialize Sentry due to missing DSN ([#5075](https://github.com/getsentry/sentry-java/pull/5075)) - - While this may have been useful to some, it caused lots of confusion. -- Session Replay: Add `androidx.camera.view.PreviewView` to default `maskedViewClasses` to mask camera previews by default. ([#5097](https://github.com/getsentry/sentry-java/pull/5097)) - -### Dependencies - -- Bump Native SDK from v0.12.4 to v0.12.7 ([#5071](https://github.com/getsentry/sentry-java/pull/5071), [#5098](https://github.com/getsentry/sentry-java/pull/5098)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0127) - - [diff](https://github.com/getsentry/sentry-native/compare/0.12.4...0.12.7) - -### Internal - -- Add integration to track session replay custom masking ([#5070](https://github.com/getsentry/sentry-java/pull/5070)) - -## 8.32.0 - -### Features - -- Add `installGroups` property to Build Distribution SDK ([#5062](https://github.com/getsentry/sentry-java/pull/5062)) -- Update Android targetSdk to API 36 (Android 16) ([#5016](https://github.com/getsentry/sentry-java/pull/5016)) -- Add AndroidManifest support for Spotlight configuration via `io.sentry.spotlight.enable` and `io.sentry.spotlight.url` ([#5064](https://github.com/getsentry/sentry-java/pull/5064)) -- Collect database transaction spans (`BEGIN`, `COMMIT`, `ROLLBACK`) ([#5072](https://github.com/getsentry/sentry-java/pull/5072)) - - To enable creation of these spans, set `options.enableDatabaseTransactionTracing` to `true` - - `enable-database-transaction-tracing=true` when using `sentry.properties` - - For Spring Boot, use `sentry.enable-database-transaction-tracing=true` in `application.properties` or in `application.yml`: - ```yaml - sentry: - enable-database-transaction-tracing: true - ``` -- Add support for collecting native crashes using Tombstones ([#4933](https://github.com/getsentry/sentry-java/pull/4933), [#5037](https://github.com/getsentry/sentry-java/pull/5037)) - - Added Tombstone integration that detects native crashes using `ApplicationExitInfo.REASON_CRASH_NATIVE` on Android 12+ - - Crashes enriched with Tombstones contain more crash details and detailed thread info - - Tombstone and NDK integrations are now automatically merged into a single crash event, eliminating duplicate reports - - To enable it, add the integration in your Sentry initialization: - ```kotlin - SentryAndroid.init(context, options -> { - options.isTombstoneEnabled = true - }) - ``` - or in the `AndroidManifest.xml` using: - ```xml - - ``` - -### Fixes - -- Extract `SpotlightIntegration` to separate `sentry-spotlight` module to prevent insecure HTTP URLs from appearing in release APKs ([#5064](https://github.com/getsentry/sentry-java/pull/5064)) - - **Breaking:** Users who enable Spotlight must now add the `io.sentry:sentry-spotlight` dependency: - ```kotlin - dependencies { - debugImplementation("io.sentry:sentry-spotlight:") - } - ``` -- Fix scroll target detection for Jetpack Compose ([#5017](https://github.com/getsentry/sentry-java/pull/5017)) -- No longer fork Sentry `Scopes` for `reactor-kafka` consumer poll `Runnable` ([#5080](https://github.com/getsentry/sentry-java/pull/5080)) - - This was causing a memory leak because `reactor-kafka`'s poll event reschedules itself infinitely, and each invocation of `SentryScheduleHook` created forked scopes with a parent reference, building an unbounded chain that couldn't be garbage collected. -- Fix cold/warm app start type detection for Android devices running API level 34+ ([#4999](https://github.com/getsentry/sentry-java/pull/4999)) - -### Internal - -- Establish new native exception mechanisms to differentiate events generated by `sentry-native` from `ApplicationExitInfo`. ([#5052](https://github.com/getsentry/sentry-java/pull/5052)) -- Set `write` permission for `statuses` in the changelog preview GHA workflow. ([#5053](https://github.com/getsentry/sentry-java/pull/5053)) - -### Dependencies - -- Bump Native SDK from v0.12.3 to v0.12.4 ([#5061](https://github.com/getsentry/sentry-java/pull/5061)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0124) - - [diff](https://github.com/getsentry/sentry-native/compare/0.12.3...0.12.4) - -## 8.31.0 - -### Features - -- Added `io.sentry.ndk.sdk-name` Android manifest option to configure the native SDK's name ([#5027](https://github.com/getsentry/sentry-java/pull/5027)) -- Replace `sentry.trace.parent_span_id` attribute with `spanId` property on `SentryLogEvent` ([#5040](https://github.com/getsentry/sentry-java/pull/5040)) - -### Fixes - -- Only attach user attributes to logs if `sendDefaultPii` is enabled ([#5036](https://github.com/getsentry/sentry-java/pull/5036)) -- Reject new logs if `LoggerBatchProcessor` is shutting down ([#5041](https://github.com/getsentry/sentry-java/pull/5041)) -- Downgrade protobuf-javalite dependency from 4.33.1 to 3.25.8 ([#5044](https://github.com/getsentry/sentry-java/pull/5044)) - -### Dependencies - -- Bump Native SDK from v0.12.2 to v0.12.3 ([#5012](https://github.com/getsentry/sentry-java/pull/5012)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0123) - - [diff](https://github.com/getsentry/sentry-native/compare/0.12.2...0.12.3) - -## 8.30.0 - -### Fixes - -- Fix ANRs when collecting device context ([#4970](https://github.com/getsentry/sentry-java/pull/4970)) - - **IMPORTANT:** This disables collecting external storage size (total/free) by default, to enable it back - use `options.isCollectExternalStorageContext = true` or `` -- Fix `NullPointerException` when reading ANR marker ([#4979](https://github.com/getsentry/sentry-java/pull/4979)) -- Report discarded log in batch processor as `log_byte` ([#4971](https://github.com/getsentry/sentry-java/pull/4971)) - -### Improvements - -- Expose `MAX_EVENT_SIZE_BYTES` constant in SentryOptions ([#4962](https://github.com/getsentry/sentry-java/pull/4962)) -- Discard envelopes on `4xx` and `5xx` response ([#4950](https://github.com/getsentry/sentry-java/pull/4950)) - - This aims to not overwhelm Sentry after an outage or load shedding (including HTTP 429) where too many events are sent at once - -### Feature - -- Add a Tombstone integration that detects native crashes without relying on the NDK integration, but instead using `ApplicationExitInfo.REASON_CRASH_NATIVE` on Android 12+. ([#4933](https://github.com/getsentry/sentry-java/pull/4933)) - - Currently exposed via options as an _internal_ API only. - - If enabled alongside the NDK integration, crashes will be reported as two separate events. Users should enable only one; deduplication between both integrations will be added in a future release. -- Add Sentry Metrics to Java SDK ([#5026](https://github.com/getsentry/sentry-java/pull/5026)) - - Metrics are enabled by default - - APIs are namespaced under `Sentry.metrics()` - - We offer the following APIs: - - `count`: A metric that increments counts - - `gauge`: A metric that tracks a value that can go up or down - - `distribution`: A metric that tracks the statistical distribution of values - - For more details, see the Metrics documentation: https://docs.sentry.io/product/explore/metrics/getting-started/ - -## 8.29.0 - -### Fixes - -- Support serialization of primitive arrays (boolean[], byte[], short[], char[], int[], long[], float[], double[]) ([#4968](https://github.com/getsentry/sentry-java/pull/4968)) -- Session Replay: Improve network body parsing and truncation handling ([#4958](https://github.com/getsentry/sentry-java/pull/4958)) - -### Internal - -- Support `metric` envelope item type ([#4956](https://github.com/getsentry/sentry-java/pull/4956)) - -## 8.28.0 - -### Features - -- Android: Flush logs when app enters background ([#4951](https://github.com/getsentry/sentry-java/pull/4951)) -- Add option to capture additional OkHttp network request/response details in session replays ([#4919](https://github.com/getsentry/sentry-java/pull/4919)) - - Depends on `SentryOkHttpInterceptor` to intercept the request and extract request/response bodies - - To enable, add url regexes via the `io.sentry.session-replay.network-detail-allow-urls` metadata tag in AndroidManifest ([code sample](https://github.com/getsentry/sentry-java/blob/b03edbb1b0d8b871c62a09bc02cbd8a4e1f6fea1/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml#L196-L205)) - - Or you can manually specify SentryReplayOptions via `SentryAndroid#init`: -_(Make sure you disable the auto init via manifest meta-data: io.sentry.auto-init=false)_ - -
- Kotlin - -```kotlin -SentryAndroid.init( - this, - options -> { - // options.dsn = "https://examplePublicKey@o0.ingest.sentry.io/0" - // options.sessionReplay.sessionSampleRate = 1.0 - // options.sessionReplay.onErrorSampleRate = 1.0 - // .. - - options.sessionReplay.networkDetailAllowUrls = listOf(".*") - options.sessionReplay.networkDetailDenyUrls = listOf(".*deny.*") - options.sessionReplay.networkRequestHeaders = listOf("Authorization", "X-Custom-Header", "X-Test-Request") - options.sessionReplay.networkResponseHeaders = listOf("X-Response-Time", "X-Cache-Status", "X-Test-Response") - }); -``` - -
- -
- Java - -```java -SentryAndroid.init( - this, - options -> { - options.getSessionReplay().setNetworkDetailAllowUrls(Arrays.asList(".*")); - options.getSessionReplay().setNetworkDetailDenyUrls(Arrays.asList(".*deny.*")); - options.getSessionReplay().setNetworkRequestHeaders( - Arrays.asList("Authorization", "X-Custom-Header", "X-Test-Request")); - options.getSessionReplay().setNetworkResponseHeaders( - Arrays.asList("X-Response-Time", "X-Cache-Status", "X-Test-Response")); - }); - -``` - -
- - -### Improvements - -- Avoid forking `rootScopes` for Reactor if current thread has `NoOpScopes` ([#4793](https://github.com/getsentry/sentry-java/pull/4793)) - - This reduces the SDKs overhead by avoiding unnecessary scope forks - -### Fixes - -- Fix missing thread stacks for ANRv1 events ([#4918](https://github.com/getsentry/sentry-java/pull/4918)) -- Fix handling of unparseable mime-type on request filter ([#4939](https://github.com/getsentry/sentry-java/pull/4939)) - -### Internal - -- Support `span` envelope item type ([#4935](https://github.com/getsentry/sentry-java/pull/4935)) - -### Dependencies - -- Bump Native SDK from v0.12.1 to v0.12.2 ([#4944](https://github.com/getsentry/sentry-java/pull/4944)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0122) - - [diff](https://github.com/getsentry/sentry-native/compare/0.12.1...0.12.2) - -## 8.27.1 - -### Fixes - -- Do not log if `sentry.properties` in rundir has not been found ([#4929](https://github.com/getsentry/sentry-java/pull/4929)) - -## 8.27.0 - -### Features - -- Implement OpenFeature Integration that tracks Feature Flag evaluations ([#4910](https://github.com/getsentry/sentry-java/pull/4910)) - - To make use of it, add the `sentry-openfeature` dependency and register the the hook using: `openFeatureApiInstance.addHooks(new SentryOpenFeatureHook());` -- Implement LaunchDarkly Integrations that track Feature Flag evaluations ([#4917](https://github.com/getsentry/sentry-java/pull/4917)) - - For Android, please add `sentry-launchdarkly-android` as a dependency and register the `SentryLaunchDarklyAndroidHook` - - For Server / JVM, please add `sentry-launchdarkly-server` as a dependency and register the `SentryLaunchDarklyServerHook` -- Detect oversized events and reduce their size ([#4903](https://github.com/getsentry/sentry-java/pull/4903)) - - You can opt into this new behaviour by setting `enableEventSizeLimiting` to `true` (`sentry.enable-event-size-limiting=true` for Spring Boot `application.properties`) - - You may optionally register an `onOversizedEvent` callback to implement custom logic that is executed in case an oversized event is detected - - This is executed first and if event size was reduced sufficiently, no further truncation is performed - - In case we detect an oversized event, we first drop breadcrumbs and if that isn't sufficient we also drop stack frames in order to get an events size down - -### Improvements - -- Do not send manual log origin ([#4897](https://github.com/getsentry/sentry-java/pull/4897)) - -### Dependencies - -- Bump Spring Boot 4 to GA ([#4923](https://github.com/getsentry/sentry-java/pull/4923)) - -## 8.26.0 - -### Features - -- Add feature flags API ([#4812](https://github.com/getsentry/sentry-java/pull/4812)) and ([#4831](https://github.com/getsentry/sentry-java/pull/4831)) - - You may now keep track of your feature flag evaluations and have them show up in Sentry. - - Top level API (`Sentry.addFeatureFlag("my-feature-flag", true);`) writes to scopes and the current span (if there is one) - - It is also possible to use API on `IScope`, `IScopes`, `ISpan` and `ITransaction` directly - - Feature flag evaluations tracked on scope(s) will be added to any errors reported to Sentry. - - The SDK keeps the latest 100 evaluations from scope(s), replacing old entries as new evaluations are added. - - For feature flag evaluations tracked on spans: - - Only 10 evaluations are tracked per span, existing flags are updated but new ones exceeding the limit are ignored - - Spans do not inherit evaluations from their parent -- Drop log events once buffer hits hard limit ([#4889](https://github.com/getsentry/sentry-java/pull/4889)) - - If we have 1000 log events queued up, we drop any new logs coming in to prevent OOM -- Remove vendored code and upgrade to async profiler 4.2 ([#4856](https://github.com/getsentry/sentry-java/pull/4856)) - - This adds support for JDK 23+ - -### Fixes - -- Removed SentryExecutorService limit for delayed scheduled tasks ([#4846](https://github.com/getsentry/sentry-java/pull/4846)) -- Fix visual artifacts for the Canvas strategy on some devices ([#4861](https://github.com/getsentry/sentry-java/pull/4861)) -- [Config] Trim whitespace on properties path ([#4880](https://github.com/getsentry/sentry-java/pull/4880)) -- Only set `DefaultReplayBreadcrumbConverter` if replay is available ([#4888](https://github.com/getsentry/sentry-java/pull/4888)) -- Session Replay: Cache connection status instead of using blocking calls ([#4891](https://github.com/getsentry/sentry-java/pull/4891)) -- Fix log count in client reports ([#4869](https://github.com/getsentry/sentry-java/pull/4869)) -- Fix profilerId propagation ([#4833](https://github.com/getsentry/sentry-java/pull/4833)) -- Fix profiling init for Spring and Spring Boot w Agent auto-init ([#4815](https://github.com/getsentry/sentry-java/pull/4815)) -- Copy active span on scope clone ([#4878](https://github.com/getsentry/sentry-java/pull/4878)) - -### Improvements - -- Fallback to distinct-id as user.id logging attribute when user is not set ([#4847](https://github.com/getsentry/sentry-java/pull/4847)) -- Report Timber.tag() as `timber.tag` log attribute ([#4845](https://github.com/getsentry/sentry-java/pull/4845)) -- Session Replay: Add screenshot strategy serialization to RRWeb events ([#4851](https://github.com/getsentry/sentry-java/pull/4851)) -- Report discarded log bytes ([#4871](https://github.com/getsentry/sentry-java/pull/4871)) -- Log why a properties file was not loaded ([#4879](https://github.com/getsentry/sentry-java/pull/4879)) - -### Dependencies - -- Bump Native SDK from v0.11.3 to v0.12.1 ([#4859](https://github.com/getsentry/sentry-java/pull/4859)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0121) - - [diff](https://github.com/getsentry/sentry-native/compare/0.11.3...0.12.1) -- Bump Spring Boot 4 to RC2 ([#4886](https://github.com/getsentry/sentry-java/pull/4886)) - -## 8.25.0 - -### Fixes - -- [ANR] Removed AndroidTransactionProfiler lock ([#4817](https://github.com/getsentry/sentry-java/pull/4817)) -- Avoid ExecutorService for DefaultCompositePerformanceCollector timeout ([#4841](https://github.com/getsentry/sentry-java/pull/4841)) - - This avoids infinite data collection for never stopped transactions, leading to OOMs -- Fix wrong .super() call in SentryTimberTree ([#4844](https://github.com/getsentry/sentry-java/pull/4844)) - -### Improvements - -- [ANR] Defer some class availability checks ([#4825](https://github.com/getsentry/sentry-java/pull/4825)) -- Collect PerformanceCollectionData only for sampled transactions ([#4834](https://github.com/getsentry/sentry-java/pull/4834)) - - **Breaking change**: Transactions with a deferred sampling decision (`sampled == null`) won't be collecting any performance data anymore (CPU, RAM, slow/frozen frames). - -### Dependencies - -- Bump Native SDK from v0.11.2 to v0.11.3 ([#4810](https://github.com/getsentry/sentry-java/pull/4810)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0113) - - [diff](https://github.com/getsentry/sentry-native/compare/0.11.2...0.11.3) - -## 8.24.0 - -### Features - -- Attach MDC properties to logs as attributes ([#4786](https://github.com/getsentry/sentry-java/pull/4786)) - - MDC properties set using supported logging frameworks (Logback, Log4j2, java.util.Logging) are now attached to structured logs as attributes. - - The attribute reflected on the log is `mdc.`, where `` is the original key in the MDC. - - This means that you will be able to filter/aggregate logs in the product based on these properties. - - Only properties with keys matching the configured `contextTags` are sent as log attributes. - - You can configure which properties are sent using `options.setContextTags` if initalizing manually, or by specifying a comma-separated list of keys with a `context-tags` entry in `sentry.properties` or `sentry.context-tags` in `application.properties`. - - Note that keys containing spaces are not supported. -- Add experimental Sentry Android Distribution module for integrating with Sentry Build Distribution to check for and install updates ([#4804](https://github.com/getsentry/sentry-java/pull/4804)) -- Allow passing a different `Handler` to `SystemEventsBreadcrumbsIntegration` and `AndroidConnectionStatusProvider` so their callbacks are deliver to that handler ([#4808](https://github.com/getsentry/sentry-java/pull/4808)) -- Session Replay: Add new _experimental_ Canvas Capture Strategy ([#4777](https://github.com/getsentry/sentry-java/pull/4777)) - - A new screenshot capture strategy that uses Android's Canvas API for more accurate and reliable text and image masking - - Any `.drawText()` or `.drawBitmap()` calls are replaced by rectangles, ensuring no text or images are present in the resulting output - - Note: If this strategy is used, all text and images will be masked, regardless of any masking configuration - - To enable this feature, set the `screenshotStrategy`, either via code: - ```kotlin - SentryAndroid.init(context) { options -> - options.sessionReplay.screenshotStrategy = ScreenshotStrategyType.CANVAS - } - ``` - or AndroidManifest.xml: - ```xml - - - - ``` - -### Fixes - -- Avoid StrictMode warnings ([#4724](https://github.com/getsentry/sentry-java/pull/4724)) -- Use logger from options for JVM profiler ([#4771](https://github.com/getsentry/sentry-java/pull/4771)) -- Session Replay: Avoid deadlock when pausing replay if no connection ([#4788](https://github.com/getsentry/sentry-java/pull/4788)) -- Session Replay: Fix capturing roots with no windows ([#4805](https://github.com/getsentry/sentry-java/pull/4805)) -- Session Replay: Fix `java.lang.IllegalArgumentException: width and height must be > 0` ([#4805](https://github.com/getsentry/sentry-java/pull/4805)) -- Handle `NoOpScopes` in `Context` when starting a span through OpenTelemetry ([#4823](https://github.com/getsentry/sentry-java/pull/4823)) - - This fixes "java.lang.IllegalArgumentException: The DSN is required" when combining WebFlux and OpenTelemetry -- Session Replay: Do not use recycled screenshots for masking ([#4790](https://github.com/getsentry/sentry-java/pull/4790)) - - This fixes native crashes seen in `Canvas.`/`ScreenshotRecorder.capture` -- Session Replay: Ensure bitmaps are recycled properly ([#4820](https://github.com/getsentry/sentry-java/pull/4820)) - -### Miscellaneous - -- Mark SentryClient(SentryOptions) constructor as not internal ([#4787](https://github.com/getsentry/sentry-java/pull/4787)) - -### Dependencies - -- Bump Native SDK from v0.10.1 to v0.11.2 ([#4775](https://github.com/getsentry/sentry-java/pull/4775)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0112) - - [diff](https://github.com/getsentry/sentry-native/compare/0.10.1...0.11.2) - -## 8.23.0 - -### Features - -- Add session replay id to Sentry Logs ([#4740](https://github.com/getsentry/sentry-java/pull/4740)) -- Add support for continuous profiling of JVM applications on macOS and Linux ([#4556](https://github.com/getsentry/sentry-java/pull/4556)) - - [Sentry continuous profiling](https://docs.sentry.io/product/explore/profiling/) on the JVM is using async-profiler under the hood. - - By default this feature is disabled. Set a profile sample rate and chose a lifecycle (see below) to enable it. - - Add the `sentry-async-profiler` dependency to your project - - Set a sample rate for profiles, e.g. `1.0` to send all of them. You may use `options.setProfileSessionSampleRate(1.0)` in code or `profile-session-sample-rate=1.0` in `sentry.properties` - - Set a profile lifecycle via `options.setProfileLifecycle(ProfileLifecycle.TRACE)` in code or `profile-lifecycle=TRACE` in `sentry.properties` - - By default the lifecycle is set to `MANUAL`, meaning you have to explicitly call `Sentry.startProfiler()` and `Sentry.stopProfiler()` - - You may change it to `TRACE` which will create a profile for each transaction - - To automatically upload Profiles for each transaction in a Spring Boot application - - set `sentry.profile-session-sample-rate=1.0` and `sentry.profile-lifecycle=TRACE` in `application.properties` - - or set `sentry.profile-session-sample-rate: 1.0` and `sentry.profile-lifecycle: TRACE` in `application.yml` - - Profiling can also be combined with our OpenTelemetry integration - -### Fixes - -- Start performance collection on AppStart continuous profiling ([#4752](https://github.com/getsentry/sentry-java/pull/4752)) -- Preserve modifiers in `SentryTraced` ([#4757](https://github.com/getsentry/sentry-java/pull/4757)) - -### Improvements - -- Handle `RejectedExecutionException` everywhere ([#4747](https://github.com/getsentry/sentry-java/pull/4747)) -- Mark `SentryEnvelope` as not internal ([#4748](https://github.com/getsentry/sentry-java/pull/4748)) - -## 8.22.0 - -### Features - -- Move SentryLogs out of experimental ([#4710](https://github.com/getsentry/sentry-java/pull/4710)) -- Add support for w3c traceparent header ([#4671](https://github.com/getsentry/sentry-java/pull/4671)) - - This feature is disabled by default. If enabled, outgoing requests will include the w3c `traceparent` header. - - See https://develop.sentry.dev/sdk/telemetry/traces/distributed-tracing/#w3c-trace-context-header for more details. - ```kotlin - Sentry(Android).init(context) { options -> - // ... - options.isPropagateTraceparent = true - } - ``` -- Sentry now supports Spring Boot 4 M3 pre-release ([#4739](https://github.com/getsentry/sentry-java/pull/4739)) - -### Improvements - -- Remove internal API status from get/setDistinctId ([#4708](https://github.com/getsentry/sentry-java/pull/4708)) -- Remove ApiStatus.Experimental annotation from check-in API ([#4721](https://github.com/getsentry/sentry-java/pull/4721)) - -### Fixes - -- Session Replay: Fix `NoSuchElementException` in `BufferCaptureStrategy` ([#4717](https://github.com/getsentry/sentry-java/pull/4717)) -- Session Replay: Fix continue recording in Session mode after Buffer is triggered ([#4719](https://github.com/getsentry/sentry-java/pull/4719)) - -### Dependencies - -- Bump Native SDK from v0.10.0 to v0.10.1 ([#4695](https://github.com/getsentry/sentry-java/pull/4695)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0101) - - [diff](https://github.com/getsentry/sentry-native/compare/0.10.0...0.10.1) - -## 8.21.1 - -### Fixes - -- Use Kotlin stdlib 1.9.24 dependency instead of 2.2.0 for all Android modules ([#4707](https://github.com/getsentry/sentry-java/pull/4707)) - - This fixes compile time issues if your app is using Kotlin < 2.x - -## 8.21.0 - -### Fixes - -- Only set log template for logging integrations if formatted message differs from template ([#4682](https://github.com/getsentry/sentry-java/pull/4682)) - -### Features - -- Add support for Spring Boot 4 and Spring 7 ([#4601](https://github.com/getsentry/sentry-java/pull/4601)) - - NOTE: Our `sentry-opentelemetry-agentless-spring` is not working yet for Spring Boot 4. Please use `sentry-opentelemetry-agent` until OpenTelemetry has support for Spring Boot 4. -- Replace `UUIDGenerator` implementation with Apache licensed code ([#4662](https://github.com/getsentry/sentry-java/pull/4662)) -- Replace `Random` implementation with MIT licensed code ([#4664](https://github.com/getsentry/sentry-java/pull/4664)) -- Add support for `vars` attribute in `SentryStackFrame` ([#4686](https://github.com/getsentry/sentry-java/pull/4686)) - - **Breaking change**: The type of the `vars` attribute has been changed from `Map` to `Map`. - -## 8.20.0 - -### Fixes - -- Do not use named capturing groups for regular expressions ([#4652](https://github.com/getsentry/sentry-java/pull/4652)) - - This fixes a crash on Android versions below 8.0 (API level 26) - -### Features - -- 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)) - - Stub for setting the callback on `Sentry.init`: - ```java - Sentry.init(options -> { - ... - options.setOnDiscard( - (reason, category, number) -> { - // Your logic to process discarded data - }); - }); - ``` - -## 8.19.1 - -> [!Warning] -> Android: This release is incompatible with API levels below 26. We recommend using SDK version 8.20.0 or higher instead. - -### Fixes - -- Do not store No-Op scopes onto OpenTelemetry Context when wrapping ([#4631](https://github.com/getsentry/sentry-java/pull/4631)) - - In 8.18.0 and 8.19.0 the SDK could break when initialized too late. - -## 8.19.0 - -> [!Warning] -> Android: This release is incompatible with API levels below 26. We recommend using SDK version 8.20.0 or higher instead. - -### Features - -- Add a `isEnableSystemEventBreadcrumbsExtras` option to disable reporting system events extras for breadcrumbs ([#4625](https://github.com/getsentry/sentry-java/pull/4625)) - -### Improvements - -- Session Replay: Use main thread looper to schedule replay capture ([#4542](https://github.com/getsentry/sentry-java/pull/4542)) -- Use single `LifecycleObserver` and multi-cast it to the integrations interested in lifecycle states ([#4567](https://github.com/getsentry/sentry-java/pull/4567)) -- Add `sentry.origin` attribute to logs ([#4618](https://github.com/getsentry/sentry-java/pull/4618)) - - This helps identify which integration captured a log event -- Prewarm `SentryExecutorService` for better performance at runtime ([#4606](https://github.com/getsentry/sentry-java/pull/4606)) - -### Fixes - -- Cache network capabilities and status to reduce IPC calls ([#4560](https://github.com/getsentry/sentry-java/pull/4560)) -- Deduplicate battery breadcrumbs ([#4561](https://github.com/getsentry/sentry-java/pull/4561)) -- Remove unused method in ManifestMetadataReader ([#4585](https://github.com/getsentry/sentry-java/pull/4585)) -- Have single `NetworkCallback` registered at a time to reduce IPC calls ([#4562](https://github.com/getsentry/sentry-java/pull/4562)) -- Do not register for SystemEvents and NetworkCallbacks immediately when launched with non-foreground importance ([#4579](https://github.com/getsentry/sentry-java/pull/4579)) -- Limit ProGuard keep rules for native methods within `sentry-android-ndk` to the `io.sentry.**` namespace. ([#4427](https://github.com/getsentry/sentry-java/pull/4427)) - - If you relied on the Sentry SDK to keep native method names for JNI compatibility within your namespace, please review your ProGuard rules and ensure the configuration still works. Especially when you're not consuming any of the default Android proguard rules (`proguard-android.txt` or `proguard-android-optimize.txt`) the following config should be present: - ``` - -keepclasseswithmembernames class * { - native ; - } - ``` -- Fix abstract method error in `SentrySupportSQLiteDatabase` ([#4597](https://github.com/getsentry/sentry-java/pull/4597)) -- Ensure frame metrics listeners are registered/unregistered on the main thread ([#4582](https://github.com/getsentry/sentry-java/pull/4582)) -- Do not report cached events as lost ([#4575](https://github.com/getsentry/sentry-java/pull/4575)) - - Previously events were recorded as lost early despite being retried later through the cache -- Move and flush unfinished previous session on init ([#4624](https://github.com/getsentry/sentry-java/pull/4624)) - - This removes the need for unnecessary blocking our background queue for 15 seconds in the case of a background app start -- Switch to compileOnly dependency for compose-ui-material ([#4630](https://github.com/getsentry/sentry-java/pull/4630)) - - This fixes `StackOverflowError` when using OSS Licenses plugin - -### Dependencies - -- Bump Native SDK from v0.8.4 to v0.10.0 ([#4623](https://github.com/getsentry/sentry-java/pull/4623)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0100) - - [diff](https://github.com/getsentry/sentry-native/compare/0.8.4...0.10.0) - -## 8.18.0 - -### Features - -- Add `SentryUserFeedbackButton` Composable ([#4559](https://github.com/getsentry/sentry-java/pull/4559)) - - Also added `Sentry.showUserFeedbackDialog` static method -- Add deadlineTimeout option ([#4555](https://github.com/getsentry/sentry-java/pull/4555)) -- Add Ktor client integration ([#4527](https://github.com/getsentry/sentry-java/pull/4527)) - - To use the integration, add a dependency on `io.sentry:sentry-ktor-client`, then install the `SentryKtorClientPlugin` on your `HttpClient`, - e.g.: - ```kotlin - val client = - HttpClient(Java) { - install(io.sentry.ktorClient.SentryKtorClientPlugin) { - captureFailedRequests = true - failedRequestTargets = listOf(".*") - failedRequestStatusCodes = listOf(HttpStatusCodeRange(500, 599)) - } - } - ``` - -### Fixes - -- Allow multiple UncaughtExceptionHandlerIntegrations to be active at the same time ([#4462](https://github.com/getsentry/sentry-java/pull/4462)) -- Prevent repeated scroll target determination during a single scroll gesture ([#4557](https://github.com/getsentry/sentry-java/pull/4557)) - - This should reduce the number of ANRs seen in `SentryGestureListener` -- Do not use Sentry logging API in JUL if logs are disabled ([#4574](https://github.com/getsentry/sentry-java/pull/4574)) - - This was causing Sentry SDK to log warnings: "Sentry Log is disabled and this 'logger' call is a no-op." -- Do not use Sentry logging API in Log4j2 if logs are disabled ([#4573](https://github.com/getsentry/sentry-java/pull/4573)) - - This was causing Sentry SDK to log warnings: "Sentry Log is disabled and this 'logger' call is a no-op." -- SDKs send queue is no longer shutdown immediately on re-init ([#4564](https://github.com/getsentry/sentry-java/pull/4564)) - - This means we're no longer losing events that have been enqueued right before SDK re-init. -- Reduce scope forking when using OpenTelemetry ([#4565](https://github.com/getsentry/sentry-java/pull/4565)) - - `Sentry.withScope` now has the correct current scope passed to the callback. Previously our OpenTelemetry integration forked scopes an additional. - - Overall the SDK is now forking scopes a bit less often. - -## 8.17.0 - -### Features - -- Send Timber logs through Sentry Logs ([#4490](https://github.com/getsentry/sentry-java/pull/4490)) - - Enable the Logs feature in your `SentryOptions` or with the `io.sentry.logs.enabled` manifest option and the SDK will automatically send Timber logs to Sentry, if the TimberIntegration is enabled. - - The SDK will automatically detect Timber and use it to send logs to Sentry. -- Send logcat through Sentry Logs ([#4487](https://github.com/getsentry/sentry-java/pull/4487)) - - Enable the Logs feature in your `SentryOptions` or with the `io.sentry.logs.enabled` manifest option and the SDK will automatically send logcat logs to Sentry, if the Sentry Android Gradle plugin is applied. - - To set the logcat level check the [Logcat integration documentation](https://docs.sentry.io/platforms/android/integrations/logcat/#configure). -- Read build tool info from `sentry-debug-meta.properties` and attach it to events ([#4314](https://github.com/getsentry/sentry-java/pull/4314)) - -### Dependencies - -- Bump OpenTelemetry ([#4532](https://github.com/getsentry/sentry-java/pull/4532)) - - `opentelemetry-sdk` to `1.51.0` - - `opentelemetry-instrumentation` to `2.17.0` - - `opentelemetry-javaagent` to `2.17.0` - - `opentelemetry-semconv` to `1.34.0` - - We are now configuring OpenTelemetry to still behave the same way it did before for span names it generates in GraphQL auto instrumentation ([#4537](https://github.com/getsentry/sentry-java/pull/4537)) -- Bump Gradle from v8.14.2 to v8.14.3 ([#4540](https://github.com/getsentry/sentry-java/pull/4540)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v8143) - - [diff](https://github.com/gradle/gradle/compare/v8.14.2...v8.14.3) - -### Fixes - -- Use Spring Boot Starter 3 in `sentry-spring-boot-starter-jakarta` ([#4545](https://github.com/getsentry/sentry-java/pull/4545)) - - While refactoring our dependency management, we accidentally added Spring Boot 2 and Spring Boot Starter 2 as dependencies of `sentry-spring-boot-starter-jakarta`, which is intended for Spring Boot 3. - - Now, the correct dependencies (Spring Boot 3 and Spring Boot Starter 3) are being added. - -## 8.16.1-alpha.2 - -### Fixes - -- Optimize scope when maxBreadcrumb is 0 ([#4504](https://github.com/getsentry/sentry-java/pull/4504)) -- Fix javadoc on TransportResult ([#4528](https://github.com/getsentry/sentry-java/pull/4528)) -- Session Replay: Fix `IllegalArgumentException` when `Bitmap` is initialized with non-positive values ([#4536](https://github.com/getsentry/sentry-java/pull/4536)) -- Set thread information on transaction from OpenTelemetry attributes ([#4478](https://github.com/getsentry/sentry-java/pull/4478)) - -### Internal - -- Flattened PerformanceCollectionData ([#4505](https://github.com/getsentry/sentry-java/pull/4505)) - -## 8.16.0 - -### Features - -- Send JUL logs to Sentry as logs ([#4518](https://github.com/getsentry/sentry-java/pull/4518)) - - You need to enable the logs feature, either in `sentry.properties`: - ```properties - logs.enabled=true - ``` - - Or, if you manually initialize Sentry, you may also enable logs on `Sentry.init`: - ```java - Sentry.init(options -> { - ... - options.getLogs().setEnabled(true); - }); - ``` - - It is also possible to set the `minimumLevel` in `logging.properties`, meaning any log message >= the configured level will be sent to Sentry and show up under Logs: - ```properties - io.sentry.jul.SentryHandler.minimumLevel=CONFIG - ``` -- Send Log4j2 logs to Sentry as logs ([#4517](https://github.com/getsentry/sentry-java/pull/4517)) - - You need to enable the logs feature either in `sentry.properties`: - ```properties - logs.enabled=true - ``` - - If you manually initialize Sentry, you may also enable logs on `Sentry.init`: - ```java - Sentry.init(options -> { - ... - options.getLogs().setEnabled(true); - }); - ``` - - It is also possible to set the `minimumLevel` in `log4j2.xml`, meaning any log message >= the configured level will be sent to Sentry and show up under Logs: - ```xml - - ``` - -## 8.15.1 - -### Fixes - -- Enabling Sentry Logs through Logback in Spring Boot config did not work in 3.15.0 ([#4523](https://github.com/getsentry/sentry-java/pull/4523)) - -## 8.15.0 - -### Features - -- Add chipset to device context ([#4512](https://github.com/getsentry/sentry-java/pull/4512)) - -### Fixes - -- No longer send out empty log envelopes ([#4497](https://github.com/getsentry/sentry-java/pull/4497)) -- Session Replay: Expand fix for crash on devices to all Unisoc/Spreadtrum chipsets ([#4510](https://github.com/getsentry/sentry-java/pull/4510)) -- Log parameter objects are now turned into `String` via `toString` ([#4515](https://github.com/getsentry/sentry-java/pull/4515)) - - One of the two `SentryLogEventAttributeValue` constructors did not convert the value previously. -- Logs are now flushed on shutdown ([#4503](https://github.com/getsentry/sentry-java/pull/4503)) -- User Feedback: Do not redefine system attributes for `SentryUserFeedbackButton`, but reference them instead ([#4519](https://github.com/getsentry/sentry-java/pull/4519)) - -### Features - -- Send Logback logs to Sentry as logs ([#4502](https://github.com/getsentry/sentry-java/pull/4502)) - - You need to enable the logs feature and can also set the `minimumLevel` for log events: - ```xml - - - - https://502f25099c204a2fbf4cb16edc5975d1@o447951.ingest.sentry.io/5428563 - - true - - - - - WARN - - DEBUG - - INFO - - ``` - - For Spring Boot you may also enable it in `application.properties` / `application.yml`: - ```properties - sentry.logs.enabled=true - sentry.logging.minimum-level=error - ``` - - If you manually initialize Sentry, you may also enable logs on `Sentry.init`: - ```java - Sentry.init(options -> { - ... - options.getLogs().setEnabled(true); - }); - ``` - - Enabling via `sentry.properties` is also possible: - ```properties - logs.enabled=true - ``` -- Automatically use `SentryOptions.Logs.BeforeSendLogCallback` Spring beans ([#4509](https://github.com/getsentry/sentry-java/pull/4509)) - -### Dependencies - -- Bump Gradle from v8.14.1 to v8.14.2 ([#4473](https://github.com/getsentry/sentry-java/pull/4473)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v8142) - - [diff](https://github.com/gradle/gradle/compare/v8.14.1...v8.14.2) - -## 8.14.0 - -### Fixes - -- Fix Session Replay masking for newer versions of Jetpack Compose (1.8+) ([#4485](https://github.com/getsentry/sentry-java/pull/4485)) - -### Features - -- Add New User Feedback Widget ([#4450](https://github.com/getsentry/sentry-java/pull/4450)) - - This widget is a custom button that can be used to show the user feedback form -- Add New User Feedback form ([#4384](https://github.com/getsentry/sentry-java/pull/4384)) - - We now introduce SentryUserFeedbackDialog, which extends AlertDialog, inheriting the show() and cancel() methods, among others. - To use it, just instantiate it and call show() on the instance (Sentry must be previously initialized). - For customization options, please check the [User Feedback documentation](https://docs.sentry.io/platforms/android/user-feedback/configuration/). - ```java - import io.sentry.android.core.SentryUserFeedbackDialog; - - new SentryUserFeedbackDialog.Builder(context).create().show(); - ``` - ```kotlin - import io.sentry.android.core.SentryUserFeedbackDialog - - SentryUserFeedbackDialog.Builder(context).create().show() - ``` -- Add `user.id`, `user.name` and `user.email` to log attributes ([#4486](https://github.com/getsentry/sentry-java/pull/4486)) -- User `name` attribute has been deprecated, please use `username` instead ([#4486](https://github.com/getsentry/sentry-java/pull/4486)) -- Add device (`device.brand`, `device.model` and `device.family`) and OS (`os.name` and `os.version`) attributes to logs ([#4493](https://github.com/getsentry/sentry-java/pull/4493)) -- Serialize `preContext` and `postContext` in `SentryStackFrame` ([#4482](https://github.com/getsentry/sentry-java/pull/4482)) - -### Internal - -- User Feedback now uses SentryUser.username instead of SentryUser.name ([#4494](https://github.com/getsentry/sentry-java/pull/4494)) - -## 8.13.3 - -### Fixes - -- Send UI Profiling app start chunk when it finishes ([#4423](https://github.com/getsentry/sentry-java/pull/4423)) -- Republish Javadoc [#4457](https://github.com/getsentry/sentry-java/pull/4457) -- Finalize `OkHttpEvent` even if no active span in `SentryOkHttpInterceptor` [#4469](https://github.com/getsentry/sentry-java/pull/4469) -- Session Replay: Do not capture current replay for cached events from the past ([#4474](https://github.com/getsentry/sentry-java/pull/4474)) -- Session Replay: Correctly capture Dialogs and non full-sized windows ([#4354](https://github.com/getsentry/sentry-java/pull/4354)) -- Session Replay: Fix inconsistent `segment_id` ([#4471](https://github.com/getsentry/sentry-java/pull/4471)) -- Session Replay: Fix crash on devices with the Unisoc/Spreadtrum T606 chipset ([#4477](https://github.com/getsentry/sentry-java/pull/4477)) - -## 8.13.2 - -### Fixes - -- Don't apply Spring Boot plugin in `sentry-spring-boot-jakarta` ([#4456](https://github.com/getsentry/sentry-java/pull/4456)) - - The jar for `io.sentry:sentry-spring-boot-jakarta` is now correctly being built and published to Maven Central. - -## 8.13.1 - -### Fixes - -- Fix `SentryAndroid.init` crash if SDK is initialized from a background thread while an `Activity` is in resumed state ([#4449](https://github.com/getsentry/sentry-java/pull/4449)) - -### Dependencies - -- Bump Gradle from v8.14 to v8.14.1 ([#4437](https://github.com/getsentry/sentry-java/pull/4437)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v8141) - - [diff](https://github.com/gradle/gradle/compare/v8.14...v8.14.1) - -## 8.13.0 - -### Features - -- Add debug mode for Session Replay masking ([#4357](https://github.com/getsentry/sentry-java/pull/4357)) - - Use `Sentry.replay().enableDebugMaskingOverlay()` to overlay the screen with the Session Replay masks. - - The masks will be invalidated at most once per `frameRate` (default 1 fps). -- Extend Logs API to allow passing in `attributes` ([#4402](https://github.com/getsentry/sentry-java/pull/4402)) - - `Sentry.logger.log` now takes a `SentryLogParameters` - - Use `SentryLogParameters.create(SentryAttributes.of(...))` to pass attributes - - Attribute values may be of type `string`, `boolean`, `integer` or `double`. - - Other types will be converted to `string`. Currently we simply call `toString()` but we might offer more in the future. - - You may manually flatten complex types into multiple separate attributes of simple types. - - e.g. intead of `SentryAttribute.named("point", Point(10, 20))` you may store it as `SentryAttribute.integerAttribute("point.x", point.x)` and `SentryAttribute.integerAttribute("point.y", point.y)` - - `SentryAttribute.named()` will automatically infer the type or fall back to `string`. - - `SentryAttribute.booleanAttribute()` takes a `Boolean` value - - `SentryAttribute.integerAttribute()` takes a `Integer` value - - `SentryAttribute.doubleAttribute()` takes a `Double` value - - `SentryAttribute.stringAttribute()` takes a `String` value - - We opted for handling parameters via `SentryLogParameters` to avoid creating tons of overloads that are ambiguous. - -### Fixes - -- Isolation scope is now forked in `OtelSentrySpanProcessor` instead of `OtelSentryPropagator` ([#4434](https://github.com/getsentry/sentry-java/pull/4434)) - - Since propagator may never be invoked we moved the location where isolation scope is forked. - - Not invoking `OtelSentryPropagator.extract` or having a `sentry-trace` header that failed to parse would cause isolation scope not to be forked. - - This in turn caused data to bleed between scopes, e.g. from one request into another - -### Dependencies - -- Bump Spring Boot to `3.5.0` ([#4111](https://github.com/getsentry/sentry-java/pull/4111)) - -## 8.12.0 - -### Features - -- Add new User Feedback API ([#4286](https://github.com/getsentry/sentry-java/pull/4286)) - - We now introduced Sentry.captureFeedback, which supersedes Sentry.captureUserFeedback -- Add Sentry Log Feature ([#4372](https://github.com/getsentry/sentry-java/pull/4372)) - - The feature is disabled by default and needs to be enabled by: - - `options.getLogs().setEnabled(true)` in `Sentry.init` / `SentryAndroid.init` - - `` in `AndroidManifest.xml` - - `logs.enabled=true` in `sentry.properties` - - `sentry.logs.enabled=true` in `application.properties` - - `sentry.logs.enabled: true` in `application.yml` - - Logs can be captured using `Sentry.logger().info()` and similar methods. - - Logs also take a format string and arguments which we then send through `String.format`. - - Please use `options.getLogs().setBeforeSend()` to filter outgoing logs - -### Fixes - -- Hook User Interaction integration into running Activity in case of deferred SDK init ([#4337](https://github.com/getsentry/sentry-java/pull/4337)) - -### Dependencies - -- Bump Gradle from v8.13 to v8.14.0 ([#4360](https://github.com/getsentry/sentry-java/pull/4360)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v8140) - - [diff](https://github.com/gradle/gradle/compare/v8.13...v8.14.0) - -## 8.11.1 - -### Fixes - -- Fix Android profile chunk envelope type for UI Profiling ([#4366](https://github.com/getsentry/sentry-java/pull/4366)) - -## 8.11.0 - -### Features - -- Make `RequestDetailsResolver` public ([#4326](https://github.com/getsentry/sentry-java/pull/4326)) - - `RequestDetailsResolver` is now public and has an additional constructor, making it easier to use a custom `TransportFactory` - -### Fixes - -- Session Replay: Fix masking of non-styled `Text` Composables ([#4361](https://github.com/getsentry/sentry-java/pull/4361)) -- Session Replay: Fix masking read-only `TextField` Composables ([#4362](https://github.com/getsentry/sentry-java/pull/4362)) - -## 8.10.0 - -### Features - -- Wrap configured OpenTelemetry `ContextStorageProvider` if available ([#4359](https://github.com/getsentry/sentry-java/pull/4359)) - - This is only relevant if you see `java.lang.IllegalStateException: Found multiple ContextStorageProvider. Set the io.opentelemetry.context.ContextStorageProvider property to the fully qualified class name of the provider to use. Falling back to default ContextStorage. Found providers: ...` - - Set `-Dio.opentelemetry.context.contextStorageProvider=io.sentry.opentelemetry.SentryContextStorageProvider` on your `java` command - - Sentry will then wrap the other `ContextStorageProvider` that has been configured by loading it through SPI - - If no other `ContextStorageProvider` is available or there are problems loading it, we fall back to using `SentryOtelThreadLocalStorage` - -### Fixes - -- Update profile chunk rate limit and client report ([#4353](https://github.com/getsentry/sentry-java/pull/4353)) - -### Dependencies - -- Bump Native SDK from v0.8.3 to v0.8.4 ([#4343](https://github.com/getsentry/sentry-java/pull/4343)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#084) - - [diff](https://github.com/getsentry/sentry-native/compare/0.8.3...0.8.4) - -## 8.9.0 - -### Features - -- Add `SentryWrapper.wrapRunnable` to wrap `Runnable` for use with Sentry ([#4332](https://github.com/getsentry/sentry-java/pull/4332)) - -### Fixes - -- Fix TTFD measurement when API called too early ([#4297](https://github.com/getsentry/sentry-java/pull/4297)) -- Tag sockets traffic originating from Sentry's HttpConnection ([#4340](https://github.com/getsentry/sentry-java/pull/4340)) - - This should suppress the StrictMode's `UntaggedSocketViolation` -- Reduce debug logs verbosity ([#4341](https://github.com/getsentry/sentry-java/pull/4341)) -- Fix unregister `SystemEventsBroadcastReceiver` when entering background ([#4338](https://github.com/getsentry/sentry-java/pull/4338)) - - This should reduce ANRs seen with this class in the stack trace for Android 14 and above - -### Improvements - -- Make user interaction tracing faster and do fewer allocations ([#4347](https://github.com/getsentry/sentry-java/pull/4347)) -- Pre-load modules on a background thread upon SDK init ([#4348](https://github.com/getsentry/sentry-java/pull/4348)) - -## 8.8.0 - -### Features - -- Add `CoroutineExceptionHandler` for reporting uncaught exceptions in coroutines to Sentry ([#4259](https://github.com/getsentry/sentry-java/pull/4259)) - - This is now part of `sentry-kotlin-extensions` and can be used together with `SentryContext` when launching a coroutine - - Any exceptions thrown in a coroutine when using the handler will be captured (not rethrown!) and reported to Sentry - - It's also possible to extend `CoroutineExceptionHandler` to implement custom behavior in addition to the one we provide by default - -### Fixes - -- Use thread context classloader when available ([#4320](https://github.com/getsentry/sentry-java/pull/4320)) - - This ensures correct resource loading in environments like Spring Boot where the thread context classloader is used for resource loading. -- Improve low memory breadcrumb capturing ([#4325](https://github.com/getsentry/sentry-java/pull/4325)) -- Fix do not initialize SDK for Jetpack Compose Preview builds ([#4324](https://github.com/getsentry/sentry-java/pull/4324)) -- Fix Synchronize Baggage values ([#4327](https://github.com/getsentry/sentry-java/pull/4327)) - -### Improvements - -- Make `SystemEventsBreadcrumbsIntegration` faster ([#4330](https://github.com/getsentry/sentry-java/pull/4330)) - -## 8.7.0 - -### Features - -- UI Profiling GA - - Continuous Profiling is now GA, named UI Profiling. To enable it you can use one of the following options. More info can be found at https://docs.sentry.io/platforms/android/profiling/. - Note: Both `options.profilesSampler` and `options.profilesSampleRate` must **not** be set to enable UI Profiling. - To keep the same transaction-based behaviour, without the 30 seconds limitation, you can use the `trace` lifecycle mode. - - ```xml - - - - - - - - - ``` - ```java - import io.sentry.ProfileLifecycle; - import io.sentry.android.core.SentryAndroid; - - SentryAndroid.init(context, options -> { - // Enable UI profiling, adjust in production env. This is evaluated only once per session - options.setProfileSessionSampleRate(1.0); - // Set profiling lifecycle, can be `manual` (controlled through `Sentry.startProfiler()` and `Sentry.stopProfiler()`) or `trace` (automatically starts and stop a profile whenever a sampled trace starts and finishes) - options.setProfileLifecycle(ProfileLifecycle.TRACE); - // Enable profiling on app start. The app start profile will be stopped automatically when the app start root span finishes - options.setStartProfilerOnAppStart(true); - }); - ``` - ```kotlin - import io.sentry.ProfileLifecycle - import io.sentry.android.core.SentryAndroid - - SentryAndroid.init(context, { options -> - // Enable UI profiling, adjust in production env. This is evaluated only once per session - options.profileSessionSampleRate = 1.0 - // Set profiling lifecycle, can be `manual` (controlled through `Sentry.startProfiler()` and `Sentry.stopProfiler()`) or `trace` (automatically starts and stop a profile whenever a sampled trace starts and finishes) - options.profileLifecycle = ProfileLifecycle.TRACE - // Enable profiling on app start. The app start profile will be stopped automatically when the app start root span finishes - options.isStartProfilerOnAppStart = true - }) - ``` - - - Continuous Profiling - Stop when app goes in background ([#4311](https://github.com/getsentry/sentry-java/pull/4311)) - - Continuous Profiling - Add delayed stop ([#4293](https://github.com/getsentry/sentry-java/pull/4293)) - - Continuous Profiling - Out of Experimental ([#4310](https://github.com/getsentry/sentry-java/pull/4310)) - -### Fixes - -- Compress Screenshots on a background thread ([#4295](https://github.com/getsentry/sentry-java/pull/4295)) - -## 8.6.0 - -### Behavioral Changes - -- The Sentry SDK will now crash on startup if mixed versions have been detected ([#4277](https://github.com/getsentry/sentry-java/pull/4277)) - - On `Sentry.init` / `SentryAndroid.init` the SDK now checks if all Sentry Java / Android SDK dependencies have the same version. - - While this may seem like a bad idea at first glance, mixing versions of dependencies has a very high chance of causing a crash later. We opted for a controlled crash that's hard to miss. - - Note: This detection only works for new versions of the SDK, so please take this as a reminder to check your SDK version alignment manually when upgrading the SDK to this version and then you should be good. - - The SDK will also print log messages if mixed versions have been detected at a later point. ([#4270](https://github.com/getsentry/sentry-java/pull/4270)) - - This takes care of cases missed by the startup check above due to older versions. - -### Features - -- Increase http timeouts from 5s to 30s to have a better chance of events being delivered without retry ([#4276](https://github.com/getsentry/sentry-java/pull/4276)) -- Add `MANIFEST.MF` to Sentry JARs ([#4272](https://github.com/getsentry/sentry-java/pull/4272)) -- Retain baggage sample rate/rand values as doubles ([#4279](https://github.com/getsentry/sentry-java/pull/4279)) -- Introduce fatal SDK logger ([#4288](https://github.com/getsentry/sentry-java/pull/4288)) - - We use this to print out messages when there is a problem that prevents the SDK from working correctly. - - One example for this is when the SDK has been configured with mixed dependency versions where we print out details, which module and version are affected. - -### Fixes - -- Do not override user-defined `SentryOptions` ([#4262](https://github.com/getsentry/sentry-java/pull/4262)) -- Session Replay: Change bitmap config to `ARGB_8888` for screenshots ([#4282](https://github.com/getsentry/sentry-java/pull/4282)) -- The `MANIFEST.MF` of `sentry-opentelemetry-agent` now has `Implementation-Version` set to the raw version ([#4291](https://github.com/getsentry/sentry-java/pull/4291)) - - An example value would be `8.6.0` - - The value of the `Sentry-Version-Name` attribute looks like `sentry-8.5.0-otel-2.10.0` -- Fix tags missing for compose view hierarchies ([#4275](https://github.com/getsentry/sentry-java/pull/4275)) -- Do not leak SentryFileInputStream/SentryFileOutputStream descriptors and channels ([#4296](https://github.com/getsentry/sentry-java/pull/4296)) -- Remove "not yet implemented" from `Sentry.flush` comment ([#4305](https://github.com/getsentry/sentry-java/pull/4305)) - -### Internal - -- Added `platform` to SentryEnvelopeItemHeader ([#4287](https://github.com/getsentry/sentry-java/pull/4287)) - - Set `android` platform to ProfileChunk envelope item header - -### Dependencies - -- Bump Native SDK from v0.8.1 to v0.8.3 ([#4267](https://github.com/getsentry/sentry-java/pull/4267), [#4298](https://github.com/getsentry/sentry-java/pull/4298)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#083) - - [diff](https://github.com/getsentry/sentry-native/compare/0.8.1...0.8.3) -- Bump Spring Boot from 2.7.5 to 2.7.18 ([#3496](https://github.com/getsentry/sentry-java/pull/3496)) - -## 8.5.0 - -### Features - -- Add native stack frame address information and debug image metadata to ANR events ([#4061](https://github.com/getsentry/sentry-java/pull/4061)) - - This enables symbolication for stripped native code in ANRs -- Add Continuous Profiling Support ([#3710](https://github.com/getsentry/sentry-java/pull/3710)) - - To enable Continuous Profiling use the `Sentry.startProfiler` and `Sentry.stopProfiler` experimental APIs. Sampling rate can be set through `options.profileSessionSampleRate`, which defaults to null (disabled). - Note: Both `options.profilesSampler` and `options.profilesSampleRate` must **not** be set to enable Continuous Profiling. - - ```java - import io.sentry.ProfileLifecycle; - import io.sentry.android.core.SentryAndroid; - - SentryAndroid.init(context) { options -> - - // Currently under experimental options: - options.getExperimental().setProfileSessionSampleRate(1.0); - // In manual mode, you need to start and stop the profiler manually using Sentry.startProfiler and Sentry.stopProfiler - // In trace mode, the profiler will start and stop automatically whenever a sampled trace starts and finishes - options.getExperimental().setProfileLifecycle(ProfileLifecycle.MANUAL); - } - // Start profiling - Sentry.startProfiler(); - - // After all profiling is done, stop the profiler. Profiles can last indefinitely if not stopped. - Sentry.stopProfiler(); - ``` - ```kotlin - import io.sentry.ProfileLifecycle - import io.sentry.android.core.SentryAndroid - - SentryAndroid.init(context) { options -> - - // Currently under experimental options: - options.experimental.profileSessionSampleRate = 1.0 - // In manual mode, you need to start and stop the profiler manually using Sentry.startProfiler and Sentry.stopProfiler - // In trace mode, the profiler will start and stop automatically whenever a sampled trace starts and finishes - options.experimental.profileLifecycle = ProfileLifecycle.MANUAL - } - // Start profiling - Sentry.startProfiler() - - // After all profiling is done, stop the profiler. Profiles can last indefinitely if not stopped. - Sentry.stopProfiler() - ``` - - To learn more visit [Sentry's Continuous Profiling](https://docs.sentry.io/product/explore/profiling/transaction-vs-continuous-profiling/#continuous-profiling-mode) documentation page. - -### Fixes - -- Reduce excessive CPU usage when serializing breadcrumbs to disk for ANRs ([#4181](https://github.com/getsentry/sentry-java/pull/4181)) -- Ensure app start type is set, even when ActivityLifecycleIntegration is not running ([#4250](https://github.com/getsentry/sentry-java/pull/4250)) -- Use `SpringServletTransactionNameProvider` as fallback for Spring WebMVC ([#4263](https://github.com/getsentry/sentry-java/pull/4263)) - - In certain cases the SDK was not able to provide a transaction name automatically and thus did not finish the transaction for the request. - - We now first try `SpringMvcTransactionNameProvider` which would provide the route as transaction name. - - If that does not return anything, we try `SpringServletTransactionNameProvider` next, which returns the URL of the request. - -### Behavioral Changes - -- The user's `device.name` is not reported anymore via the device context, even if `options.isSendDefaultPii` is enabled ([#4179](https://github.com/getsentry/sentry-java/pull/4179)) - -### Dependencies - -- Bump Gradle from v8.12.1 to v8.13.0 ([#4209](https://github.com/getsentry/sentry-java/pull/4209)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v8130) - - [diff](https://github.com/gradle/gradle/compare/v8.12.1...v8.13.0) - -## 8.4.0 - -### Fixes - -- The SDK now handles `null` on many APIs instead of expecting a non `null` value ([#4245](https://github.com/getsentry/sentry-java/pull/4245)) - - Certain APIs like `setTag`, `setData`, `setExtra`, `setContext` previously caused a `NullPointerException` when invoked with either `null` key or value. - - The SDK now tries to have a sane fallback when `null` is passed and no longer throws `NullPointerException` - - If `null` is passed, the SDK will - - do nothing if a `null` key is passed, returning `null` for non void methods - - remove any previous value if the new value is set to `null` -- Add support for setting in-app-includes/in-app-excludes via AndroidManifest.xml ([#4240](https://github.com/getsentry/sentry-java/pull/4240)) -- Modifications to OkHttp requests are now properly propagated to the affected span / breadcrumbs ([#4238](https://github.com/getsentry/sentry-java/pull/4238)) - - Please ensure the SentryOkHttpInterceptor is added last to your OkHttpClient, as otherwise changes to the `Request` by subsequent interceptors won't be considered -- Fix "class ch.qos.logback.classic.spi.ThrowableProxyVO cannot be cast to class ch.qos.logback.classic.spi.ThrowableProxy" ([#4206](https://github.com/getsentry/sentry-java/pull/4206)) - - In this case we cannot report the `Throwable` to Sentry as it's not available - - If you are using OpenTelemetry v1 `OpenTelemetryAppender`, please consider upgrading to v2 -- Pass OpenTelemetry span attributes into TracesSampler callback ([#4253](https://github.com/getsentry/sentry-java/pull/4253)) - - `SamplingContext` now has a `getAttribute` method that grants access to OpenTelemetry span attributes via their String key (e.g. `http.request.method`) -- Fix AbstractMethodError when using SentryTraced for Jetpack Compose ([#4255](https://github.com/getsentry/sentry-java/pull/4255)) -- Assume `http.client` for span `op` if not a root span ([#4257](https://github.com/getsentry/sentry-java/pull/4257)) -- Avoid unnecessary copies when using `CopyOnWriteArrayList` ([#4247](https://github.com/getsentry/sentry-java/pull/4247)) - - This affects in particular `SentryTracer.getLatestActiveSpan` which would have previously copied all child span references. This may have caused `OutOfMemoryError` on certain devices due to high frequency of calling the method. - -### Features - -- The SDK now automatically propagates the trace-context to the native layer. This allows to connect errors on different layers of the application. ([#4137](https://github.com/getsentry/sentry-java/pull/4137)) -- Capture OpenTelemetry span events ([#3564](https://github.com/getsentry/sentry-java/pull/3564)) - - OpenTelemetry spans may have exceptions attached to them (`openTelemetrySpan.recordException`). We can now send those to Sentry as errors. - - Set `capture-open-telemetry-events=true` in `sentry.properties` to enable it - - Set `sentry.capture-open-telemetry-events=true` in Springs `application.properties` to enable it - - Set `sentry.captureOpenTelemetryEvents: true` in Springs `application.yml` to enable it - -### Behavioural Changes - -- Use `java.net.URI` for parsing URLs in `UrlUtils` ([#4210](https://github.com/getsentry/sentry-java/pull/4210)) - - This could affect grouping for issues with messages containing URLs that fall in known corner cases that were handled incorrectly previously (e.g. email in URL path) - -### Internal - -- Also use port when checking if a request is made to Sentry DSN ([#4231](https://github.com/getsentry/sentry-java/pull/4231)) - - For our OpenTelemetry integration we check if a span is for a request to Sentry - - We now also consider the port when performing this check - -### Dependencies - -- Bump Native SDK from v0.7.20 to v0.8.1 ([#4137](https://github.com/getsentry/sentry-java/pull/4137)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0810) - - [diff](https://github.com/getsentry/sentry-native/compare/v0.7.20...0.8.1) - -## 8.3.0 - -### Features - -- Add HTTP server request headers from OpenTelemetry span attributes to sentry `request` in payload ([#4102](https://github.com/getsentry/sentry-java/pull/4102)) - - You have to explicitly enable each header by adding it to the [OpenTelemetry config](https://opentelemetry.io/docs/zero-code/java/agent/instrumentation/http/#capturing-http-request-and-response-headers) - - Please only enable headers you actually want to send to Sentry. Some may contain sensitive data like PII, cookies, tokens etc. - - We are no longer adding request/response headers to `contexts/otel/attributes` of the event. -- The `ignoredErrors` option is now configurable via the manifest property `io.sentry.traces.ignored-errors` ([#4178](https://github.com/getsentry/sentry-java/pull/4178)) -- A list of active Spring profiles is attached to payloads sent to Sentry (errors, traces, etc.) and displayed in the UI when using our Spring or Spring Boot integrations ([#4147](https://github.com/getsentry/sentry-java/pull/4147)) - - This consists of an empty list when only the default profile is active -- Added `enableTraceIdGeneration` to the AndroidOptions. This allows Hybrid SDKs to "freeze" and control the trace and connect errors on different layers of the application ([4188](https://github.com/getsentry/sentry-java/pull/4188)) -- Move to a single NetworkCallback listener to reduce number of IPC calls on Android ([#4164](https://github.com/getsentry/sentry-java/pull/4164)) -- Add GraphQL Apollo Kotlin 4 integration ([#4166](https://github.com/getsentry/sentry-java/pull/4166)) -- Add support for async dispatch requests to Spring Boot 2 and 3 ([#3983](https://github.com/getsentry/sentry-java/pull/3983)) - - To enable it, please set `sentry.keep-transactions-open-for-async-responses=true` in `application.properties` or `sentry.keepTransactionsOpenForAsyncResponses: true` in `application.yml` -- Add constructor to JUL `SentryHandler` for disabling external config ([#4208](https://github.com/getsentry/sentry-java/pull/4208)) - -### Fixes - -- Filter strings that cannot be parsed as Regex no longer cause an SDK crash ([#4213](https://github.com/getsentry/sentry-java/pull/4213)) - - This was the case e.g. for `ignoredErrors`, `ignoredTransactions` and `ignoredCheckIns` - - We now simply don't use such strings for Regex matching and only use them for String comparison -- `SentryOptions.setTracePropagationTargets` is no longer marked internal ([#4170](https://github.com/getsentry/sentry-java/pull/4170)) -- Session Replay: Fix crash when a navigation breadcrumb does not have "to" destination ([#4185](https://github.com/getsentry/sentry-java/pull/4185)) -- Session Replay: Cap video segment duration to maximum 5 minutes to prevent endless video encoding in background ([#4185](https://github.com/getsentry/sentry-java/pull/4185)) -- Check `tracePropagationTargets` in OpenTelemetry propagator ([#4191](https://github.com/getsentry/sentry-java/pull/4191)) - - If a URL can be retrieved from OpenTelemetry span attributes, we check it against `tracePropagationTargets` before attaching `sentry-trace` and `baggage` headers to outgoing requests - - If no URL can be retrieved we always attach the headers -- Fix `ignoredErrors`, `ignoredTransactions` and `ignoredCheckIns` being unset by external options like `sentry.properties` or ENV vars ([#4207](https://github.com/getsentry/sentry-java/pull/4207)) - - Whenever parsing of external options was enabled (`enableExternalConfiguration`), which is the default for many integrations, the values set on `SentryOptions` passed to `Sentry.init` would be lost - - Even if the value was not set in any external configuration it would still be set to an empty list - -### Behavioural Changes - -- The class `io.sentry.spring.jakarta.webflux.ReactorUtils` is now deprecated, please use `io.sentry.reactor.SentryReactorUtils` in the new `sentry-reactor` module instead ([#4155](https://github.com/getsentry/sentry-java/pull/4155)) - - The new module will be exposed as an `api` dependency when using `sentry-spring-boot-jakarta` (Spring Boot 3) or `sentry-spring-jakarta` (Spring 6). - Therefore, if you're using one of those modules, changing your imports will suffice. - -## 8.2.0 - -### Breaking Changes - -- The Kotlin Language version is now set to 1.6 ([#3936](https://github.com/getsentry/sentry-java/pull/3936)) - -### Features - -- Create onCreate and onStart spans for all Activities ([#4025](https://github.com/getsentry/sentry-java/pull/4025)) -- Add split apks info to the `App` context ([#3193](https://github.com/getsentry/sentry-java/pull/3193)) -- Expose new `withSentryObservableEffect` method overload that accepts `SentryNavigationListener` as a parameter ([#4143](https://github.com/getsentry/sentry-java/pull/4143)) - - This allows sharing the same `SentryNavigationListener` instance across fragments and composables to preserve the trace -- (Internal) Add API to filter native debug images based on stacktrace addresses ([#4089](https://github.com/getsentry/sentry-java/pull/4089)) -- Propagate sampling random value ([#4153](https://github.com/getsentry/sentry-java/pull/4153)) - - The random value used for sampling traces is now sent to Sentry and attached to the `baggage` header on outgoing requests -- Update `sampleRate` that is sent to Sentry and attached to the `baggage` header on outgoing requests ([#4158](https://github.com/getsentry/sentry-java/pull/4158)) - - If the SDK uses its `sampleRate` or `tracesSampler` callback, it now updates the `sampleRate` in Dynamic Sampling Context. - -### Fixes - -- Log a warning when envelope or items are dropped due to rate limiting ([#4148](https://github.com/getsentry/sentry-java/pull/4148)) -- Do not log if `OtelContextScopesStorage` cannot be found ([#4127](https://github.com/getsentry/sentry-java/pull/4127)) - - Previously `java.lang.ClassNotFoundException: io.sentry.opentelemetry.OtelContextScopesStorage` was shown in the log if the class could not be found. - - This is just a lookup the SDK performs to configure itself. The SDK also works without OpenTelemetry. -- Session Replay: Fix various crashes and issues ([#4135](https://github.com/getsentry/sentry-java/pull/4135)) - - Fix `FileNotFoundException` when trying to read/write `.ongoing_segment` file - - Fix `IllegalStateException` when registering `onDrawListener` - - Fix SIGABRT native crashes on Motorola devices when encoding a video -- Mention javadoc and sources for published artifacts in Gradle `.module` metadata ([#3936](https://github.com/getsentry/sentry-java/pull/3936)) -- (Jetpack Compose) Modifier.sentryTag now uses Modifier.Node ([#4029](https://github.com/getsentry/sentry-java/pull/4029)) - - This allows Composables that use this modifier to be skippable - -### Dependencies - -- Bump Native SDK from v0.7.19 to v0.7.20 ([#4128](https://github.com/getsentry/sentry-java/pull/4128)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0720) - - [diff](https://github.com/getsentry/sentry-native/compare/v0.7.19...0.7.20) -- Bump Gradle from v8.9.0 to v8.12.1 ([#4106](https://github.com/getsentry/sentry-java/pull/4106)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v8121) - - [diff](https://github.com/gradle/gradle/compare/v8.9.0...v8.12.1) - -## 8.1.0 - -### Features - -- Add `options.ignoredErrors` to filter out errors that match a certain String or Regex ([#4083](https://github.com/getsentry/sentry-java/pull/4083)) - - The matching is attempted on `event.message`, `event.formatted`, and `{event.throwable.class.name}: {event.throwable.message}` - - Can be set in `sentry.properties`, e.g. `ignored-errors=Some error,Another .*` - - Can be set in environment variables, e.g. `SENTRY_IGNORED_ERRORS=Some error,Another .*` - - For Spring Boot, it can be set in `application.properties`, e.g. `sentry.ignored-errors=Some error,Another .*` -- Log OpenTelemetry related Sentry config ([#4122](https://github.com/getsentry/sentry-java/pull/4122)) - -### Fixes - -- Avoid logging an error when a float is passed in the manifest ([#4031](https://github.com/getsentry/sentry-java/pull/4031)) -- Add `request` details to transactions created through OpenTelemetry ([#4098](https://github.com/getsentry/sentry-java/pull/4098)) - - We now add HTTP request method and URL where Sentry expects it to display it in Sentry UI -- Remove `java.lang.ClassNotFoundException` debug logs when searching for OpenTelemetry marker classes ([#4091](https://github.com/getsentry/sentry-java/pull/4091)) - - There was up to three of these, one for `io.sentry.opentelemetry.agent.AgentMarker`, `io.sentry.opentelemetry.agent.AgentlessMarker` and `io.sentry.opentelemetry.agent.AgentlessSpringMarker`. - - These were not indicators of something being wrong but rather the SDK looking at what is available at runtime to configure itself accordingly. -- Do not instrument File I/O operations if tracing is disabled ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) -- Do not instrument User Interaction multiple times ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) -- Speed up view traversal to find touched target in `UserInteractionIntegration` ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) -- Reduce IPC/Binder calls performed by the SDK ([#4058](https://github.com/getsentry/sentry-java/pull/4058)) - -### Behavioural Changes - -- Reduce the number of broadcasts the SDK is subscribed for ([#4052](https://github.com/getsentry/sentry-java/pull/4052)) - - Drop `TempSensorBreadcrumbsIntegration` - - Drop `PhoneStateBreadcrumbsIntegration` - - Reduce number of broadcasts in `SystemEventsBreadcrumbsIntegration` - -Current list of the broadcast events can be found [here](https://github.com/getsentry/sentry-java/blob/9b8dc0a844d10b55ddeddf55d278c0ab0f86421c/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java#L131-L153). If you'd like to subscribe for more events, consider overriding the `SystemEventsBreadcrumbsIntegration` as follows: - -```kotlin -SentryAndroid.init(context) { options -> - options.integrations.removeAll { it is SystemEventsBreadcrumbsIntegration } - options.integrations.add(SystemEventsBreadcrumbsIntegration(context, SystemEventsBreadcrumbsIntegration.getDefaultActions() + listOf(/* your custom actions */))) -} -``` - -If you would like to keep some of the default broadcast events as breadcrumbs, consider opening a [GitHub issue](https://github.com/getsentry/sentry-java/issues/new). -- Set mechanism `type` to `suppressed` for suppressed exceptions ([#4125](https://github.com/getsentry/sentry-java/pull/4125)) - - This helps to distinguish an exceptions cause from any suppressed exceptions in the Sentry UI - -### Dependencies - -- Bump Spring Boot to `3.4.2` ([#4081](https://github.com/getsentry/sentry-java/pull/4081)) -- Bump Native SDK from v0.7.14 to v0.7.19 ([#4076](https://github.com/getsentry/sentry-java/pull/4076)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0719) - - [diff](https://github.com/getsentry/sentry-native/compare/v0.7.14...0.7.19) - -## 8.0.0 - -### Summary - -Version 8 of the Sentry Android/Java SDK brings a variety of features and fixes. The most notable changes are: - -- `Hub` has been replaced by `Scopes` -- New `Scope` types have been introduced, see "Behavioural Changes" for more details. -- Lifecycle tokens have been introduced to manage `Scope` lifecycle, see "Behavioural Changes" for more details. -- Bumping `minSdk` level to 21 (Android 5.0) -- Our `sentry-opentelemetry-agent` has been improved and now works in combination with the rest of Sentry. You may now combine OpenTelemetry and Sentry for instrumenting your application. - - You may now use both OpenTelemetry SDK and Sentry SDK to capture transactions and spans. They can also be mixed and end up on the same transaction. - - OpenTelemetry extends the Sentry SDK by adding spans for numerous integrations, like Ktor, Vert.x and MongoDB. Please check [the OpenTelemetry GitHub repository](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation) for a full list. - - OpenTelemetry allows propagating trace information from and to additional libraries, that Sentry did not support before, for example gRPC. - - OpenTelemetry also has broader support for propagating the Sentry `Scopes` through reactive libraries like RxJava. -- The SDK is now compatible with Spring Boot 3.4 -- We now support GraphQL v22 (`sentry-graphql-22`) -- Metrics have been removed - -Please take a look at [our migration guide in docs](https://docs.sentry.io/platforms/java/migration/7.x-to-8.0). - -### Sentry Self-hosted Compatibility - -This SDK version is compatible with a self-hosted version of Sentry `22.12.0` or higher. If you are using an older version of [self-hosted Sentry](https://develop.sentry.dev/self-hosted/) (aka onpremise), you will need to [upgrade](https://develop.sentry.dev/self-hosted/releases/). If you're using `sentry.io` no action is required. - -### Breaking Changes - -- The Android minSdk level for all Android modules is now 21 ([#3852](https://github.com/getsentry/sentry-java/pull/3852)) -- The minSdk level for sentry-android-ndk changed from 19 to 21 ([#3851](https://github.com/getsentry/sentry-java/pull/3851)) -- Throw IllegalArgumentException when calling Sentry.init on Android ([#3596](https://github.com/getsentry/sentry-java/pull/3596)) -- Metrics have been removed from the SDK ([#3774](https://github.com/getsentry/sentry-java/pull/3774)) - - Metrics will return but we don't know in what exact form yet -- `enableTracing` option (a.k.a `enable-tracing`) has been removed from the SDK ([#3776](https://github.com/getsentry/sentry-java/pull/3776)) - - Please set `tracesSampleRate` to a value >= 0.0 for enabling performance instead. The default value is `null` which means performance is disabled. -- Replace `synchronized` methods and blocks with `ReentrantLock` (`AutoClosableReentrantLock`) ([#3715](https://github.com/getsentry/sentry-java/pull/3715)) - - If you are subclassing any Sentry classes, please check if the parent class used `synchronized` before. Please make sure to use the same lock object as the parent class in that case. -- `traceOrigins` option (`io.sentry.traces.tracing-origins` in manifest) has been removed, please use `tracePropagationTargets` (`io.sentry.traces.trace-propagation-targets` in manifest`) instead ([#3780](https://github.com/getsentry/sentry-java/pull/3780)) -- `profilingEnabled` option (`io.sentry.traces.profiling.enable` in manifest) has been removed, please use `profilesSampleRate` (`io.sentry.traces.profiling.sample-rate` instead) instead ([#3780](https://github.com/getsentry/sentry-java/pull/3780)) -- `shutdownTimeout` option has been removed, please use `shutdownTimeoutMillis` instead ([#3780](https://github.com/getsentry/sentry-java/pull/3780)) -- `profilingTracesIntervalMillis` option for Android has been removed ([#3780](https://github.com/getsentry/sentry-java/pull/3780)) -- `io.sentry.session-tracking.enable` manifest option has been removed ([#3780](https://github.com/getsentry/sentry-java/pull/3780)) -- `Sentry.traceHeaders()` method has been removed, please use `Sentry.getTraceparent()` instead ([#3718](https://github.com/getsentry/sentry-java/pull/3718)) -- `Sentry.reportFullDisplayed()` method has been removed, please use `Sentry.reportFullyDisplayed()` instead ([#3717](https://github.com/getsentry/sentry-java/pull/3717)) -- `User.other` has been removed, please use `data` instead ([#3780](https://github.com/getsentry/sentry-java/pull/3780)) -- `SdkVersion.getIntegrations()` has been removed, please use `getIntegrationSet` instead ([#3780](https://github.com/getsentry/sentry-java/pull/3780)) -- `SdkVersion.getPackages()` has been removed, please use `getPackageSet()` instead ([#3780](https://github.com/getsentry/sentry-java/pull/3780)) -- `Device.language` has been removed, please use `locale` instead ([#3780](https://github.com/getsentry/sentry-java/pull/3780)) -- `TraceContext.user` and `TraceContextUser` class have been removed, please use `userId` on `TraceContext` instead ([#3780](https://github.com/getsentry/sentry-java/pull/3780)) -- `TransactionContext.fromSentryTrace()` has been removed, please use `Sentry.continueTrace()` instead ([#3780](https://github.com/getsentry/sentry-java/pull/3780)) -- `SentryDataFetcherExceptionHandler` has been removed, please use `SentryGenericDataFetcherExceptionHandler` in combination with `SentryInstrumentation` instead ([#3780](https://github.com/getsentry/sentry-java/pull/3780)) -- `sentry-android-okhttp` has been removed in favor of `sentry-okhttp`, removing android dependency from the module ([#3510](https://github.com/getsentry/sentry-java/pull/3510)) -- `Contexts` no longer extends `ConcurrentHashMap`, instead we offer a selected set of methods. -- User segment has been removed ([#3512](https://github.com/getsentry/sentry-java/pull/3512)) -- One of the `AndroidTransactionProfiler` constructors has been removed, please use a different one ([#3780](https://github.com/getsentry/sentry-java/pull/3780)) -- Use String instead of UUID for SessionId ([#3834](https://github.com/getsentry/sentry-java/pull/3834)) - - The `Session` constructor now takes a `String` instead of a `UUID` for the `sessionId` parameter. - - `Session.getSessionId()` now returns a `String` instead of a `UUID`. -- All status codes below 400 are now mapped to `SpanStatus.OK` ([#3869](https://github.com/getsentry/sentry-java/pull/3869)) -- Change OkHttp sub-spans to span attributes ([#3556](https://github.com/getsentry/sentry-java/pull/3556)) - - This will reduce the number of spans created by the SDK -- `instrumenter` option should no longer be needed as our new OpenTelemetry integration now works in combination with the rest of Sentry - -### Behavioural Changes - -- We're introducing some new `Scope` types in the SDK, allowing for better control over what data is attached where. Previously there was a stack of scopes that was pushed and popped. Instead we now fork scopes for a given lifecycle and then restore the previous scopes. Since `Hub` is gone, it is also never cloned anymore. Separation of data now happens through the different scope types while making it easier to manipulate exactly what you need without having to attach data at the right time to have it apply where wanted. - - Global scope is attached to all events created by the SDK. It can also be modified before `Sentry.init` has been called. It can be manipulated using `Sentry.configureScope(ScopeType.GLOBAL, (scope) -> { ... })`. - - Isolation scope can be used e.g. to attach data to all events that come up while handling an incoming request. It can also be used for other isolation purposes. It can be manipulated using `Sentry.configureScope(ScopeType.ISOLATION, (scope) -> { ... })`. The SDK automatically forks isolation scope in certain cases like incoming requests, CRON jobs, Spring `@Async` and more. - - Current scope is forked often and data added to it is only added to events that are created while this scope is active. Data is also passed on to newly forked child scopes but not to parents. It can be manipulated using `Sentry.configureScope(ScopeType.CURRENT, (scope) -> { ... })`. -- `Sentry.popScope` has been deprecated, please call `.close()` on the token returned by `Sentry.pushScope` instead or use it in a way described in more detail in [our migration guide](https://docs.sentry.io/platforms/java/migration/7.x-to-8.0). -- We have chosen a default scope that is used for `Sentry.configureScope()` as well as API like `Sentry.setTag()` - - For Android the type defaults to `CURRENT` scope - - For Backend and other JVM applicatons it defaults to `ISOLATION` scope -- Event processors on `Scope` can now be ordered by overriding the `getOrder` method on implementations of `EventProcessor`. NOTE: This order only applies to event processors on `Scope` but not `SentryOptions` at the moment. Feel free to request this if you need it. -- `Hub` is deprecated in favor of `Scopes`, alongside some `Hub` relevant APIs. More details can be found in [our migration guide](https://docs.sentry.io/platforms/java/migration/7.x-to-8.0). -- Send file name and path only if `isSendDefaultPii` is `true` ([#3919](https://github.com/getsentry/sentry-java/pull/3919)) -- (Android) Enable Performance V2 by default ([#3824](https://github.com/getsentry/sentry-java/pull/3824)) - - With this change cold app start spans will include spans for ContentProviders, Application and Activity load. -- (Android) Replace thread id with kernel thread id in span data ([#3706](https://github.com/getsentry/sentry-java/pull/3706)) -- (Android) The JNI layer for sentry-native has now been moved from sentry-java to sentry-native ([#3189](https://github.com/getsentry/sentry-java/pull/3189)) - - This now includes prefab support for sentry-native, allowing you to link and access the sentry-native API within your native app code - - Checkout the `sentry-samples/sentry-samples-android` example on how to configure CMake and consume `sentry.h` -- The user ip-address is now only set to `"{{auto}}"` if `sendDefaultPii` is enabled ([#4072](https://github.com/getsentry/sentry-java/pull/4072)) - - This change gives you control over IP address collection directly on the client - -### Features - -- The SDK is now compatible with Spring Boot 3.4 ([#3939](https://github.com/getsentry/sentry-java/pull/3939)) -- Our `sentry-opentelemetry-agent` has been completely reworked and now plays nicely with the rest of the Java SDK - - You may also want to give this new agent a try even if you haven't used OpenTelemetry (with Sentry) before. It offers support for [many more libraries and frameworks](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/supported-libraries.md), improving on our trace propagation, `Scopes` (used to be `Hub`) propagation as well as performance instrumentation (i.e. more spans). - - If you are using a framework we did not support before and currently resort to manual instrumentation, please give the agent a try. See [here for a list of supported libraries, frameworks and application servers](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/supported-libraries.md). - - Please see [Java SDK docs](https://docs.sentry.io/platforms/java/tracing/instrumentation/opentelemetry/) for more details on how to set up the agent. Please make sure to select the correct SDK from the dropdown on the left side of the docs. - - What's new about the Agent - - When the OpenTelemetry Agent is used, Sentry API creates OpenTelemetry spans under the hood, handing back a wrapper object which bridges the gap between traditional Sentry API and OpenTelemetry. We might be replacing some of the Sentry performance API in the future. - - This is achieved by configuring the SDK to use `OtelSpanFactory` instead of `DefaultSpanFactory` which is done automatically by the auto init of the Java Agent. - - OpenTelemetry spans are now only turned into Sentry spans when they are finished so they can be sent to the Sentry server. - - Now registers an OpenTelemetry `Sampler` which uses Sentry sampling configuration - - Other Performance integrations automatically stop creating spans to avoid duplicate spans - - The Sentry SDK now makes use of OpenTelemetry `Context` for storing Sentry `Scopes` (which is similar to what used to be called `Hub`) and thus relies on OpenTelemetry for `Context` propagation. - - Classes used for the previous version of our OpenTelemetry support have been deprecated but can still be used manually. We're not planning to keep the old agent around in favor of less complexity in the SDK. -- Add `sentry-opentelemetry-agentless-spring` module ([#4000](https://github.com/getsentry/sentry-java/pull/4000)) - - This module can be added as a dependency when using Sentry with OpenTelemetry and Spring Boot but don't want to use our Agent. It takes care of configuring OpenTelemetry for use with Sentry. - - You may want to set `OTEL_LOGS_EXPORTER=none;OTEL_METRICS_EXPORTER=none;OTEL_TRACES_EXPORTER=none` env vars to not have the log flooded with error messages regarding OpenTelemetry features we don't use. -- Add `sentry-opentelemetry-agentless` module ([#3961](https://github.com/getsentry/sentry-java/pull/3961)) - - This module can be added as a dependency when using Sentry with OpenTelemetry but don't want to use our Agent. It takes care of configuring OpenTelemetry for use with Sentry. - - To enable the auto configuration of it, please set `-Dotel.java.global-autoconfigure.enabled=true` on the `java` command, when starting your application. - - You may also want to set `OTEL_LOGS_EXPORTER=none;OTEL_METRICS_EXPORTER=none;OTEL_TRACES_EXPORTER=none` env vars to not have the log flooded with error messages regarding OpenTelemetry features we don't use. -- `OpenTelemetryUtil.applyOpenTelemetryOptions` now takes an enum instead of a boolean for its mode -- Add `openTelemetryMode` option ([#3994](https://github.com/getsentry/sentry-java/pull/3994)) - - It defaults to `AUTO` meaning the SDK will figure out how to best configure itself for use with OpenTelemetry - - Use of OpenTelemetry can also be disabled completely by setting it to `OFF` ([#3995](https://github.com/getsentry/sentry-java/pull/3995)) - - In this case even if OpenTelemetry is present, the Sentry SDK will not use it - - Use `AGENT` when using `sentry-opentelemetry-agent` - - Use `AGENTLESS` when using `sentry-opentelemetry-agentless` - - Use `AGENTLESS_SPRING` when using `sentry-opentelemetry-agentless-spring` -- Add `ignoredTransactions` option to filter out transactions by name ([#3871](https://github.com/getsentry/sentry-java/pull/3871)) - - can be used via ENV vars, e.g. `SENTRY_IGNORED_TRANSACTIONS=POST /person/,GET /pers.*` - - can also be set in options directly, e.g. `options.setIgnoredTransactions(...)` - - can also be set in `sentry.properties`, e.g. `ignored-transactions=POST /person/,GET /pers.*` - - can also be set in Spring config `application.properties`, e.g. `sentry.ignored-transactions=POST /person/,GET /pers.*` -- Add `scopeBindingMode` to `SpanOptions` ([#4004](https://github.com/getsentry/sentry-java/pull/4004)) - - This setting only affects the SDK when used with OpenTelemetry. - - Defaults to `AUTO` meaning the SDK will decide whether the span should be bound to the current scope. It will not bind transactions to scope using `AUTO`, it will only bind spans where the parent span is on the current scope. - - `ON` sets the new span on the current scope. - - `OFF` does not set the new span on the scope. -- Add `ignoredSpanOrigins` option for ignoring spans coming from certain integrations - - We pre-configure this to ignore Performance instrumentation for Spring and other integrations when using our OpenTelemetry Agent to avoid duplicate spans -- Support `graphql-java` v22 via a new module `sentry-graphql-22` ([#3740](https://github.com/getsentry/sentry-java/pull/3740)) - - If you are using `graphql-java` v21 or earlier, you can use the `sentry-graphql` module - - For `graphql-java` v22 and newer please use the `sentry-graphql-22` module -- We now provide a `SentryInstrumenter` bean directly for Spring (Boot) if there is none yet instead of using `GraphQlSourceBuilderCustomizer` to add the instrumentation ([#3744](https://github.com/getsentry/sentry-java/pull/3744)) - - It is now also possible to provide a bean of type `SentryGraphqlInstrumentation.BeforeSpanCallback` which is then used by `SentryInstrumenter` -- Add data fetching environment hint to breadcrumb for GraphQL (#3413) ([#3431](https://github.com/getsentry/sentry-java/pull/3431)) -- Report exceptions returned by Throwable.getSuppressed() to Sentry as exception groups ([#3396] https://github.com/getsentry/sentry-java/pull/3396) - - Any suppressed exceptions are added to the issue details page in Sentry, the same way any cause is. - - We are planning to improve how we visualize suppressed exceptions. See https://github.com/getsentry/sentry-java/issues/4059 -- Enable `ThreadLocalAccessor` for Spring Boot 3 WebFlux by default ([#4023](https://github.com/getsentry/sentry-java/pull/4023)) -- Allow passing `environment` to `CheckinUtils.withCheckIn` ([3889](https://github.com/getsentry/sentry-java/pull/3889)) -- Add `globalHubMode` to options ([#3805](https://github.com/getsentry/sentry-java/pull/3805)) - - `globalHubMode` used to only be a param on `Sentry.init`. To make it easier to be used in e.g. Desktop environments, we now additionally added it as an option on SentryOptions that can also be set via `sentry.properties`. - - If both the param on `Sentry.init` and the option are set, the option will win. By default the option is set to `null` meaning whatever is passed to `Sentry.init` takes effect. -- Lazy uuid generation for SentryId and SpanId ([#3770](https://github.com/getsentry/sentry-java/pull/3770)) -- Faster generation of Sentry and Span IDs ([#3818](https://github.com/getsentry/sentry-java/pull/3818)) - - Uses faster implementation to convert UUID to SentryID String - - Uses faster Random implementation to generate UUIDs -- Android 15: Add support for 16KB page sizes ([#3851](https://github.com/getsentry/sentry-java/pull/3851)) - - See https://developer.android.com/guide/practices/page-sizes for more details -- Add init priority settings ([#3674](https://github.com/getsentry/sentry-java/pull/3674)) - - You may now set `forceInit=true` (`force-init` for `.properties` files) to ensure a call to Sentry.init / SentryAndroid.init takes effect -- Add force init option to Android Manifest ([#3675](https://github.com/getsentry/sentry-java/pull/3675)) - - Use `` to ensure Sentry Android auto init is not easily overwritten -- Attach request body for `application/x-www-form-urlencoded` requests in Spring ([#3731](https://github.com/getsentry/sentry-java/pull/3731)) - - Previously request body was only attached for `application/json` requests -- Set breadcrumb level based on http status ([#3771](https://github.com/getsentry/sentry-java/pull/3771)) -- Emit transaction.data inside contexts.trace.data ([#3735](https://github.com/getsentry/sentry-java/pull/3735)) - - Also does not emit `transaction.data` in `extras` anymore -- Add a sample for showcasing Sentry with OpenTelemetry for Spring Boot 3 with our Java agent (`sentry-samples-spring-boot-jakarta-opentelemetry`) ([#3856](https://github.com/getsentry/sentry-java/pull/3828)) -- Add a sample for showcasing Sentry with OpenTelemetry for Spring Boot 3 without our Java agent (`sentry-samples-spring-boot-jakarta-opentelemetry-noagent`) ([#3856](https://github.com/getsentry/sentry-java/pull/3856)) -- Add a sample for showcasing Sentry with OpenTelemetry (`sentry-samples-console-opentelemetry-noagent`) ([#3856](https://github.com/getsentry/sentry-java/pull/3862)) - -### Fixes - -- Fix incoming defer sampling decision `sentry-trace` header ([#3942](https://github.com/getsentry/sentry-java/pull/3942)) - - A `sentry-trace` header that only contains trace ID and span ID but no sampled flag (`-1`, `-0` suffix) means the receiving system can make its own sampling decision - - When generating `sentry-trace` header from `PropagationContext` we now copy the `sampled` flag. - - In `TransactionContext.fromPropagationContext` when there is no parent sampling decision, keep the decision `null` so a new sampling decision is made instead of defaulting to `false` -- Fix order of calling `close` on previous Sentry instance when re-initializing ([#3750](https://github.com/getsentry/sentry-java/pull/3750)) - - Previously some parts of Sentry were immediately closed after re-init that should have stayed open and some parts of the previous init were never closed -- All status codes below 400 are now mapped to `SpanStatus.OK` ([#3869](https://github.com/getsentry/sentry-java/pull/3869)) -- Improve ignored check performance ([#3992](https://github.com/getsentry/sentry-java/pull/3992)) - - Checking if a span origin, a transaction or a checkIn should be ignored is now faster -- Cache requests for Spring using Springs `ContentCachingRequestWrapper` instead of our own Wrapper to also cache parameters ([#3641](https://github.com/getsentry/sentry-java/pull/3641)) - - Previously only the body was cached which could lead to problems in the FilterChain as Request parameters were not available -- Close backpressure monitor on SDK shutdown ([#3998](https://github.com/getsentry/sentry-java/pull/3998)) - - Due to the backpressure monitor rescheduling a task to run every 10s, it very likely caused shutdown to wait the full `shutdownTimeoutMillis` (defaulting to 2s) instead of being able to terminate immediately -- Let OpenTelemetry auto instrumentation handle extracting and injecting tracing information if present ([#3953](https://github.com/getsentry/sentry-java/pull/3953)) - - Our integrations no longer call `.continueTrace` and also do not inject tracing headers if the integration has been added to `ignoredSpanOrigins` -- Fix testTag not working for Jetpack Compose user interaction tracking ([#3878](https://github.com/getsentry/sentry-java/pull/3878)) -- Mark `DiskFlushNotification` hint flushed when rate limited ([#3892](https://github.com/getsentry/sentry-java/pull/3892)) - - Our `UncaughtExceptionHandlerIntegration` waited for the full flush timeout duration (default 15s) when rate limited. -- Do not replace `op` with auto generated content for OpenTelemetry spans with span kind `INTERNAL` ([#3906](https://github.com/getsentry/sentry-java/pull/3906)) -- Add `enable-spotlight` and `spotlight-connection-url` to external options and check if spotlight is enabled when deciding whether to inspect an OpenTelemetry span for connecting to splotlight ([#3709](https://github.com/getsentry/sentry-java/pull/3709)) -- Trace context on `Contexts.setTrace` has been marked `@NotNull` ([#3721](https://github.com/getsentry/sentry-java/pull/3721)) - - Setting it to `null` would cause an exception. - - Transactions are dropped if trace context is missing -- Remove internal annotation on `SpanOptions` ([#3722](https://github.com/getsentry/sentry-java/pull/3722)) -- `SentryLogbackInitializer` is now public ([#3723](https://github.com/getsentry/sentry-java/pull/3723)) -- Parse and use `send-default-pii` and `max-request-body-size` from `sentry.properties` ([#3534](https://github.com/getsentry/sentry-java/pull/3534)) -- `TracesSampler` is now only created once in `SentryOptions` instead of creating a new one for every `Hub` (which is now `Scopes`). This means we're now creating fewer `SecureRandom` instances. - -### Internal - -- Make `SentryClient` constructor public ([#4045](https://github.com/getsentry/sentry-java/pull/4045)) -- Warm starts cleanup ([#3954](https://github.com/getsentry/sentry-java/pull/3954)) - -### Changes in pre-releases - -These changes have been made during development of `8.0.0`. You may skip this section. We just put it here for sake of completeness. - -- Extract OpenTelemetry `URL_PATH` span attribute into description ([#3933](https://github.com/getsentry/sentry-java/pull/3933)) -- Replace OpenTelemetry `ContextStorage` wrapper with `ContextStorageProvider` ([#3938](https://github.com/getsentry/sentry-java/pull/3938)) - - The wrapper had to be put in place before any call to `Context` whereas `ContextStorageProvider` is automatically invoked at the correct time. -- Send `otel.kind` to Sentry ([#3907](https://github.com/getsentry/sentry-java/pull/3907)) -- Spring Boot now automatically detects if OpenTelemetry is available and makes use of it ([#3846](https://github.com/getsentry/sentry-java/pull/3846)) - - This is only enabled if there is no OpenTelemetry agent available - - We prefer to use the OpenTelemetry agent as it offers more auto instrumentation - - In some cases the OpenTelemetry agent cannot be used, please see https://opentelemetry.io/docs/zero-code/java/spring-boot-starter/ for more details on when to prefer the Agent and when the Spring Boot starter makes more sense. - - In this mode the SDK makes use of the `OpenTelemetry` bean that is created by `opentelemetry-spring-boot-starter` instead of `GlobalOpenTelemetry` -- Spring Boot now automatically detects our OpenTelemetry agent if its auto init is disabled ([#3848](https://github.com/getsentry/sentry-java/pull/3848)) - - This means Spring Boot config mechanisms can now be combined with our OpenTelemetry agent - - The `sentry-opentelemetry-extra` module has been removed again, most classes have been moved to `sentry-opentelemetry-bootstrap` which is loaded into the bootstrap classloader (i.e. `null`) when our Java agent is used. The rest has been moved into `sentry-opentelemetry-agentcustomization` and is loaded into the agent classloader when our Java agent is used. - - The `sentry-opentelemetry-bootstrap` and `sentry-opentelemetry-agentcustomization` modules can be used without the agent as well, in which case all classes are loaded into the application classloader. Check out our `sentry-samples-spring-boot-jakarta-opentelemetry-noagent` sample. - - In this mode the SDK makes use of `GlobalOpenTelemetry` -- Automatically set span factory based on presence of OpenTelemetry ([#3858](https://github.com/getsentry/sentry-java/pull/3858)) - - `SentrySpanFactoryHolder` has been removed as it is no longer required. - -- Replace deprecated `SimpleInstrumentation` with `SimplePerformantInstrumentation` for graphql 22 ([#3974](https://github.com/getsentry/sentry-java/pull/3974)) -- We now hold a strong reference to the underlying OpenTelemetry span when it is created through Sentry API ([#3997](https://github.com/getsentry/sentry-java/pull/3997)) - - This keeps it from being garbage collected too early -- Defer sampling decision by setting `sampled` to `null` in `PropagationContext` when using OpenTelemetry in case of an incoming defer sampling `sentry-trace` header. ([#3945](https://github.com/getsentry/sentry-java/pull/3945)) -- Build `PropagationContext` from `SamplingDecision` made by `SentrySampler` instead of parsing headers and potentially ignoring a sampling decision in case a `sentry-trace` header comes in with deferred sampling decision. ([#3947](https://github.com/getsentry/sentry-java/pull/3947)) -- The Sentry OpenTelemetry Java agent now makes sure Sentry `Scopes` storage is initialized even if the agents auto init is disabled ([#3848](https://github.com/getsentry/sentry-java/pull/3848)) - - This is required for all integrations to work together with our OpenTelemetry Java agent if its auto init has been disabled and the SDKs init should be used instead. -- Fix `startChild` for span that is not in current OpenTelemetry `Context` ([#3862](https://github.com/getsentry/sentry-java/pull/3862)) - - Starting a child span from a transaction that wasn't in the current `Context` lead to multiple transactions being created (one for the transaction and another per span created). -- Add `auto.graphql.graphql22` to ignored span origins when using OpenTelemetry ([#3828](https://github.com/getsentry/sentry-java/pull/3828)) -- Use OpenTelemetry span name as fallback for transaction name ([#3557](https://github.com/getsentry/sentry-java/pull/3557)) - - In certain cases we were sending transactions as "" when using OpenTelemetry -- Add OpenTelemetry span data to Sentry span ([#3593](https://github.com/getsentry/sentry-java/pull/3593)) -- No longer selectively copy OpenTelemetry attributes to Sentry spans / transactions `data` ([#3663](https://github.com/getsentry/sentry-java/pull/3663)) -- Remove `PROCESS_COMMAND_ARGS` (`process.command_args`) OpenTelemetry span attribute as it can be very large ([#3664](https://github.com/getsentry/sentry-java/pull/3664)) -- Use RECORD_ONLY sampling decision if performance is disabled ([#3659](https://github.com/getsentry/sentry-java/pull/3659)) - - Also fix check whether Performance is enabled when making a sampling decision in the OpenTelemetry sampler -- Sentry OpenTelemetry Java Agent now sets Instrumenter to SENTRY (used to be OTEL) ([#3697](https://github.com/getsentry/sentry-java/pull/3697)) -- Set span origin in `ActivityLifecycleIntegration` on span options instead of after creating the span / transaction ([#3702](https://github.com/getsentry/sentry-java/pull/3702)) - - This allows spans to be filtered by span origin on creation -- Honor ignored span origins in `SentryTracer.startChild` ([#3704](https://github.com/getsentry/sentry-java/pull/3704)) -- Use span id of remote parent ([#3548](https://github.com/getsentry/sentry-java/pull/3548)) - - Traces were broken because on an incoming request, OtelSentrySpanProcessor did not set the parentSpanId on the span correctly. Traces were not referencing the actual parent span but some other (random) span ID which the server doesn't know. -- Attach active span to scope when using OpenTelemetry ([#3549](https://github.com/getsentry/sentry-java/pull/3549)) - - Errors weren't linked to traces correctly due to parts of the SDK not knowing the current span -- Record dropped spans in client report when sampling out OpenTelemetry spans ([#3552](https://github.com/getsentry/sentry-java/pull/3552)) -- Retrieve the correct current span from `Scope`/`Scopes` when using OpenTelemetry ([#3554](https://github.com/getsentry/sentry-java/pull/3554)) -- Support spans that are split into multiple batches ([#3539](https://github.com/getsentry/sentry-java/pull/3539)) - - When spans belonging to a single transaction were split into multiple batches for SpanExporter, we did not add all spans because the isSpanTooOld check wasn't inverted. -- Partially fix bootstrap class loading ([#3543](https://github.com/getsentry/sentry-java/pull/3543)) - - There was a problem with two separate Sentry `Scopes` being active inside each OpenTelemetry `Context` due to using context keys from more than one class loader. -- The Spring Boot 3 WebFlux sample now uses our GraphQL v22 integration ([#3828](https://github.com/getsentry/sentry-java/pull/3828)) -- Do not ignore certain span origins for OpenTelemetry without agent ([#3856](https://github.com/getsentry/sentry-java/pull/3856)) -- `span.startChild` now uses `.makeCurrent()` by default ([#3544](https://github.com/getsentry/sentry-java/pull/3544)) - - This caused an issue where the span tree wasn't correct because some spans were not added to their direct parent -- Do not set the exception group marker when there is a suppressed exception ([#4056](https://github.com/getsentry/sentry-java/pull/4056)) - - Due to how grouping works in Sentry currently sometimes the suppressed exception is treated as the main exception. This change ensures we keep using the main exception and not change how grouping works. - - As a consequence the list of exceptions in the group on top of an issue is no longer shown in Sentry UI. - - We are planning to improve this in the future but opted for this fix first. - -### Dependencies - -- Bump Native SDK from v0.7.0 to v0.7.17 ([#3441](https://github.com/getsentry/sentry-java/pull/3189)) ([#3851](https://github.com/getsentry/sentry-java/pull/3851)) ([#3914](https://github.com/getsentry/sentry-java/pull/3914)) ([#4003](https://github.com/getsentry/sentry-java/pull/4003)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0717) - - [diff](https://github.com/getsentry/sentry-native/compare/0.7.0...0.7.17) -- Bump OpenTelemetry to 1.44.1, OpenTelemetry Java Agent to 2.10.0 and Semantic Conventions to 1.28.0 ([#3668](https://github.com/getsentry/sentry-java/pull/3668)) ([#3935](https://github.com/getsentry/sentry-java/pull/3935)) - -### Migration Guide / Deprecations - -Please take a look at [our migration guide in docs](https://docs.sentry.io/platforms/java/migration/7.x-to-8.0). - -- `Hub` has been deprecated, we're replacing the following: - - `IHub` has been replaced by `IScopes`, however you should be able to simply pass `IHub` instances to code expecting `IScopes`, allowing for an easier migration. - - `HubAdapter.getInstance()` has been replaced by `ScopesAdapter.getInstance()` - - The `.clone()` method on `IHub`/`IScopes` has been deprecated, please use `.pushScope()` or `.pushIsolationScope()` instead - - Some internal methods like `.getCurrentHub()` and `.setCurrentHub()` have also been replaced. -- `Sentry.popScope` has been replaced by calling `.close()` on the token returned by `Sentry.pushScope()` and `Sentry.pushIsolationScope()`. The token can also be used in a `try` block like this: - -``` -try (final @NotNull ISentryLifecycleToken ignored = Sentry.pushScope()) { - // this block has its separate current scope -} -``` - -as well as: - - -``` -try (final @NotNull ISentryLifecycleToken ignored = Sentry.pushIsolationScope()) { - // this block has its separate isolation scope -} -``` -- Classes used by our previous OpenTelemetry integration have been deprecated (`SentrySpanProcessor`, `SentryPropagator`, `OpenTelemetryLinkErrorEventProcessor`). Please take a look at [docs](https://docs.sentry.io/platforms/java/tracing/instrumentation/opentelemetry/) on how to setup OpenTelemetry in v8. - -You may also use `LifecycleHelper.close(token)`, e.g. in case you need to pass the token around for closing later. - - -### Changes from `rc.4` - -If you have been using `8.0.0-rc.4` of the Java SDK, here's the new changes that have been included in the `8.0.0` release: - -- Make `SentryClient` constructor public ([#4045](https://github.com/getsentry/sentry-java/pull/4045)) -- The user ip-address is now only set to `"{{auto}}"` if sendDefaultPii is enabled ([#4072](https://github.com/getsentry/sentry-java/pull/4072)) - - This change gives you control over IP address collection directly on the client -- Do not set the exception group marker when there is a suppressed exception ([#4056](https://github.com/getsentry/sentry-java/pull/4056)) - - Due to how grouping works in Sentry currently sometimes the suppressed exception is treated as the main exception. This change ensures we keep using the main exception and not change how grouping works. - - As a consequence the list of exceptions in the group on top of an issue is no longer shown in Sentry UI. - - We are planning to improve this in the future but opted for this fix first. -- Fix swallow NDK loadLibrary errors ([#4082](https://github.com/getsentry/sentry-java/pull/4082)) - -## 7.22.6 - -### Fixes - -- Compress Screenshots on a background thread ([#4295](https://github.com/getsentry/sentry-java/pull/4295)) -- Improve low memory breadcrumb capturing ([#4325](https://github.com/getsentry/sentry-java/pull/4325)) -- Make `SystemEventsBreadcrumbsIntegration` faster ([#4330](https://github.com/getsentry/sentry-java/pull/4330)) -- Fix unregister `SystemEventsBroadcastReceiver` when entering background ([#4338](https://github.com/getsentry/sentry-java/pull/4338)) - - This should reduce ANRs seen with this class in the stack trace for Android 14 and above -- Pre-load modules on a background thread upon SDK init ([#4348](https://github.com/getsentry/sentry-java/pull/4348)) -- Session Replay: Fix inconsistent `segment_id` ([#4471](https://github.com/getsentry/sentry-java/pull/4471)) -- Session Replay: Do not capture current replay for cached events from the past ([#4474](https://github.com/getsentry/sentry-java/pull/4474)) -- Session Replay: Fix crash on devices with the Unisoc/Spreadtrum T606 chipset ([#4477](https://github.com/getsentry/sentry-java/pull/4477)) -- Session Replay: Fix masking of non-styled `Text` Composables ([#4361](https://github.com/getsentry/sentry-java/pull/4361)) -- Session Replay: Fix masking read-only `TextField` Composables ([#4362](https://github.com/getsentry/sentry-java/pull/4362)) -- Fix Session Replay masking for newer versions of Jetpack Compose (1.8+) ([#4485](https://github.com/getsentry/sentry-java/pull/4485)) -- Session Replay: Expand fix for crash on devices to all Unisoc/Spreadtrum chipsets ([#4510](https://github.com/getsentry/sentry-java/pull/4510)) - -## 7.22.5 - -### Fixes - -- Session Replay: Change bitmap config to `ARGB_8888` for screenshots ([#4282](https://github.com/getsentry/sentry-java/pull/4282)) - -## 7.22.4 - -### Fixes - -- Session Replay: Fix crash when a navigation breadcrumb does not have "to" destination ([#4185](https://github.com/getsentry/sentry-java/pull/4185)) -- Session Replay: Cap video segment duration to maximum 5 minutes to prevent endless video encoding in background ([#4185](https://github.com/getsentry/sentry-java/pull/4185)) -- Avoid logging an error when a float is passed in the manifest ([#4266](https://github.com/getsentry/sentry-java/pull/4266)) - -## 7.22.3 - -### Fixes - -- Reduce excessive CPU usage when serializing breadcrumbs to disk for ANRs ([#4181](https://github.com/getsentry/sentry-java/pull/4181)) - -## 7.22.2 - -### Fixes - -- Fix AbstractMethodError when using SentryTraced for Jetpack Compose ([#4256](https://github.com/getsentry/sentry-java/pull/4256)) - -## 7.22.1 - -### Fixes - -- Fix Ensure app start type is set, even when ActivityLifecycleIntegration is not running ([#4216](https://github.com/getsentry/sentry-java/pull/4216)) -- Fix properly reset application/content-provider timespans for warm app starts ([#4244](https://github.com/getsentry/sentry-java/pull/4244)) - -## 7.22.0 - -### Fixes - -- Session Replay: Fix various crashes and issues ([#4135](https://github.com/getsentry/sentry-java/pull/4135)) - - Fix `FileNotFoundException` when trying to read/write `.ongoing_segment` file - - Fix `IllegalStateException` when registering `onDrawListener` - - Fix SIGABRT native crashes on Motorola devices when encoding a video -- (Jetpack Compose) Modifier.sentryTag now uses Modifier.Node ([#4029](https://github.com/getsentry/sentry-java/pull/4029)) - - This allows Composables that use this modifier to be skippable - -## 7.21.0 - -### Fixes - -- Do not instrument File I/O operations if tracing is disabled ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) -- Do not instrument User Interaction multiple times ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) -- Speed up view traversal to find touched target in `UserInteractionIntegration` ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) -- Reduce IPC/Binder calls performed by the SDK ([#4058](https://github.com/getsentry/sentry-java/pull/4058)) - -### Behavioural Changes - -- (changed in [7.20.1](https://github.com/getsentry/sentry-java/releases/tag/7.20.1)) The user ip-address is now only set to `"{{auto}}"` if sendDefaultPii is enabled ([#4071](https://github.com/getsentry/sentry-java/pull/4071)) - - This change gives you control over IP address collection directly on the client -- Reduce the number of broadcasts the SDK is subscribed for ([#4052](https://github.com/getsentry/sentry-java/pull/4052)) - - Drop `TempSensorBreadcrumbsIntegration` - - Drop `PhoneStateBreadcrumbsIntegration` - - Reduce number of broadcasts in `SystemEventsBreadcrumbsIntegration` - -Current list of the broadcast events can be found [here](https://github.com/getsentry/sentry-java/blob/9b8dc0a844d10b55ddeddf55d278c0ab0f86421c/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java#L131-L153). If you'd like to subscribe for more events, consider overriding the `SystemEventsBreadcrumbsIntegration` as follows: - -```kotlin -SentryAndroid.init(context) { options -> - options.integrations.removeAll { it is SystemEventsBreadcrumbsIntegration } - options.integrations.add(SystemEventsBreadcrumbsIntegration(context, SystemEventsBreadcrumbsIntegration.getDefaultActions() + listOf(/* your custom actions */))) -} -``` - -If you would like to keep some of the default broadcast events as breadcrumbs, consider opening a [GitHub issue](https://github.com/getsentry/sentry-java/issues/new). - -## 7.21.0-beta.1 - -### Fixes - -- Do not instrument File I/O operations if tracing is disabled ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) -- Do not instrument User Interaction multiple times ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) -- Speed up view traversal to find touched target in `UserInteractionIntegration` ([#4051](https://github.com/getsentry/sentry-java/pull/4051)) -- Reduce IPC/Binder calls performed by the SDK ([#4058](https://github.com/getsentry/sentry-java/pull/4058)) - -### Behavioural Changes - -- Reduce the number of broadcasts the SDK is subscribed for ([#4052](https://github.com/getsentry/sentry-java/pull/4052)) - - Drop `TempSensorBreadcrumbsIntegration` - - Drop `PhoneStateBreadcrumbsIntegration` - - Reduce number of broadcasts in `SystemEventsBreadcrumbsIntegration` - -Current list of the broadcast events can be found [here](https://github.com/getsentry/sentry-java/blob/9b8dc0a844d10b55ddeddf55d278c0ab0f86421c/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java#L131-L153). If you'd like to subscribe for more events, consider overriding the `SystemEventsBreadcrumbsIntegration` as follows: - -```kotlin -SentryAndroid.init(context) { options -> - options.integrations.removeAll { it is SystemEventsBreadcrumbsIntegration } - options.integrations.add(SystemEventsBreadcrumbsIntegration(context, SystemEventsBreadcrumbsIntegration.getDefaultActions() + listOf(/* your custom actions */))) -} -``` - -If you would like to keep some of the default broadcast events as breadcrumbs, consider opening a [GitHub issue](https://github.com/getsentry/sentry-java/issues/new). - -## 7.20.1 - -### Behavioural Changes - -- The user ip-address is now only set to `"{{auto}}"` if sendDefaultPii is enabled ([#4071](https://github.com/getsentry/sentry-java/pull/4071)) - - This change gives you control over IP address collection directly on the client - -## 7.20.0 - -### Features - -- Session Replay GA ([#4017](https://github.com/getsentry/sentry-java/pull/4017)) - -To enable Replay use the `sessionReplay.sessionSampleRate` or `sessionReplay.onErrorSampleRate` options. - - ```kotlin - import io.sentry.SentryReplayOptions - import io.sentry.android.core.SentryAndroid - - SentryAndroid.init(context) { options -> - - options.sessionReplay.sessionSampleRate = 1.0 - options.sessionReplay.onErrorSampleRate = 1.0 - - // To change default redaction behavior (defaults to true) - options.sessionReplay.redactAllImages = true - options.sessionReplay.redactAllText = true - - // To change quality of the recording (defaults to MEDIUM) - options.sessionReplay.quality = SentryReplayOptions.SentryReplayQuality.MEDIUM // (LOW|MEDIUM|HIGH) - } - ``` - -### Fixes - -- Fix warm start detection ([#3937](https://github.com/getsentry/sentry-java/pull/3937)) -- Session Replay: Reduce memory allocations, disk space consumption, and payload size ([#4016](https://github.com/getsentry/sentry-java/pull/4016)) -- Session Replay: Do not try to encode corrupted frames multiple times ([#4016](https://github.com/getsentry/sentry-java/pull/4016)) - -### Internal - -- Session Replay: Allow overriding `SdkVersion` for replay events ([#4014](https://github.com/getsentry/sentry-java/pull/4014)) -- Session Replay: Send replay options as tags ([#4015](https://github.com/getsentry/sentry-java/pull/4015)) - -### Breaking changes - -- Session Replay options were moved from under `experimental` to the main `options` object ([#4017](https://github.com/getsentry/sentry-java/pull/4017)) - -## 7.19.1 - -### Fixes - -- Change TTFD timeout to 25 seconds ([#3984](https://github.com/getsentry/sentry-java/pull/3984)) -- Session Replay: Fix memory leak when masking Compose screens ([#3985](https://github.com/getsentry/sentry-java/pull/3985)) -- Session Replay: Fix potential ANRs in `GestureRecorder` ([#4001](https://github.com/getsentry/sentry-java/pull/4001)) - -### Internal - -- Session Replay: Flutter improvements ([#4007](https://github.com/getsentry/sentry-java/pull/4007)) - -## 7.19.0 - -### Fixes - -- Session Replay: fix various crashes and issues ([#3970](https://github.com/getsentry/sentry-java/pull/3970)) - - Fix `IndexOutOfBoundsException` when tracking window changes - - Fix `IllegalStateException` when adding/removing draw listener for a dead view - - Fix `ConcurrentModificationException` when registering window listeners and stopping `WindowRecorder`/`GestureRecorder` -- Add support for setting sentry-native handler_strategy ([#3671](https://github.com/getsentry/sentry-java/pull/3671)) - -### Dependencies - -- Bump Native SDK from v0.7.8 to v0.7.16 ([#3671](https://github.com/getsentry/sentry-java/pull/3671)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0716) - - [diff](https://github.com/getsentry/sentry-native/compare/0.7.8...0.7.16) - -## 7.18.1 - -### Fixes - -- Fix testTag not working for Jetpack Compose user interaction tracking ([#3878](https://github.com/getsentry/sentry-java/pull/3878)) - -## 7.18.0 - -### Features - -- Android 15: Add support for 16KB page sizes ([#3620](https://github.com/getsentry/sentry-java/pull/3620)) - - See https://developer.android.com/guide/practices/page-sizes for more details -- Session Replay: Add `beforeSendReplay` callback ([#3855](https://github.com/getsentry/sentry-java/pull/3855)) -- Session Replay: Add support for masking/unmasking view containers ([#3881](https://github.com/getsentry/sentry-java/pull/3881)) - -### Fixes - -- Avoid collecting normal frames ([#3782](https://github.com/getsentry/sentry-java/pull/3782)) -- Ensure android initialization process continues even if options configuration block throws an exception ([#3887](https://github.com/getsentry/sentry-java/pull/3887)) -- Do not report parsing ANR error when there are no threads ([#3888](https://github.com/getsentry/sentry-java/pull/3888)) - - This should significantly reduce the number of events with message "Sentry Android SDK failed to parse system thread dump..." reported -- Session Replay: Disable replay in session mode when rate limit is active ([#3854](https://github.com/getsentry/sentry-java/pull/3854)) - -### Dependencies - -- Bump Native SDK from v0.7.2 to v0.7.8 ([#3620](https://github.com/getsentry/sentry-java/pull/3620)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#078) - - [diff](https://github.com/getsentry/sentry-native/compare/0.7.2...0.7.8) - -## 7.17.0 - -### Features - -- Add meta option to set the maximum amount of breadcrumbs to be logged. ([#3836](https://github.com/getsentry/sentry-java/pull/3836)) -- Use a separate `Random` instance per thread to improve SDK performance ([#3835](https://github.com/getsentry/sentry-java/pull/3835)) - -### Fixes - -- Using MaxBreadcrumb with value 0 no longer crashes. ([#3836](https://github.com/getsentry/sentry-java/pull/3836)) -- Accept manifest integer values when requiring floating values ([#3823](https://github.com/getsentry/sentry-java/pull/3823)) -- Fix standalone tomcat jndi issue ([#3873](https://github.com/getsentry/sentry-java/pull/3873)) - - Using Sentry Spring Boot on a standalone tomcat caused the following error: - - Failed to bind properties under 'sentry.parsed-dsn' to io.sentry.Dsn - -## 7.16.0 - -### Features - -- Add meta option to attach ANR thread dumps ([#3791](https://github.com/getsentry/sentry-java/pull/3791)) - -### Fixes - -- Cache parsed Dsn ([#3796](https://github.com/getsentry/sentry-java/pull/3796)) -- fix invalid profiles when the transaction name is empty ([#3747](https://github.com/getsentry/sentry-java/pull/3747)) -- Deprecate `enableTracing` option ([#3777](https://github.com/getsentry/sentry-java/pull/3777)) -- Vendor `java.util.Random` and replace `java.security.SecureRandom` usages ([#3783](https://github.com/getsentry/sentry-java/pull/3783)) -- Fix potential ANRs due to NDK scope sync ([#3754](https://github.com/getsentry/sentry-java/pull/3754)) -- Fix potential ANRs due to NDK System.loadLibrary calls ([#3670](https://github.com/getsentry/sentry-java/pull/3670)) -- Fix slow `Log` calls on app startup ([#3793](https://github.com/getsentry/sentry-java/pull/3793)) -- Fix slow Integration name parsing ([#3794](https://github.com/getsentry/sentry-java/pull/3794)) -- Session Replay: Reduce startup and capture overhead ([#3799](https://github.com/getsentry/sentry-java/pull/3799)) -- Load lazy fields on init in the background ([#3803](https://github.com/getsentry/sentry-java/pull/3803)) -- Replace setOf with HashSet.add ([#3801](https://github.com/getsentry/sentry-java/pull/3801)) - -### Breaking changes - -- The method `addIntegrationToSdkVersion(Ljava/lang/Class;)V` has been removed from the core (`io.sentry:sentry`) package. Please make sure all of the packages (e.g. `io.sentry:sentry-android-core`, `io.sentry:sentry-android-fragment`, `io.sentry:sentry-okhttp` and others) are all aligned and using the same version to prevent the `NoSuchMethodError` exception. - -## 7.16.0-alpha.1 - -### Features - -- Add meta option to attach ANR thread dumps ([#3791](https://github.com/getsentry/sentry-java/pull/3791)) - -### Fixes - -- Cache parsed Dsn ([#3796](https://github.com/getsentry/sentry-java/pull/3796)) -- fix invalid profiles when the transaction name is empty ([#3747](https://github.com/getsentry/sentry-java/pull/3747)) -- Deprecate `enableTracing` option ([#3777](https://github.com/getsentry/sentry-java/pull/3777)) -- Vendor `java.util.Random` and replace `java.security.SecureRandom` usages ([#3783](https://github.com/getsentry/sentry-java/pull/3783)) -- Fix potential ANRs due to NDK scope sync ([#3754](https://github.com/getsentry/sentry-java/pull/3754)) -- Fix potential ANRs due to NDK System.loadLibrary calls ([#3670](https://github.com/getsentry/sentry-java/pull/3670)) -- Fix slow `Log` calls on app startup ([#3793](https://github.com/getsentry/sentry-java/pull/3793)) -- Fix slow Integration name parsing ([#3794](https://github.com/getsentry/sentry-java/pull/3794)) -- Session Replay: Reduce startup and capture overhead ([#3799](https://github.com/getsentry/sentry-java/pull/3799)) - -## 7.15.0 - -### Features - -- Add support for `feedback` envelope header item type ([#3687](https://github.com/getsentry/sentry-java/pull/3687)) -- Add breadcrumb.origin field ([#3727](https://github.com/getsentry/sentry-java/pull/3727)) -- Session Replay: Add options to selectively mask/unmask views captured in replay. The following options are available: ([#3689](https://github.com/getsentry/sentry-java/pull/3689)) - - `android:tag="sentry-mask|sentry-unmask"` in XML or `view.setTag("sentry-mask|sentry-unmask")` in code tags - - if you already have a tag set for a view, you can set a tag by id: `` in XML or `view.setTag(io.sentry.android.replay.R.id.sentry_privacy, "mask|unmask")` in code - - `view.sentryReplayMask()` or `view.sentryReplayUnmask()` extension functions - - mask/unmask `View`s of a certain type by adding fully-qualified classname to one of the lists `options.experimental.sessionReplay.addMaskViewClass()` or `options.experimental.sessionReplay.addUnmaskViewClass()`. Note, that all of the view subclasses/subtypes will be masked/unmasked as well - - For example, (this is already a default behavior) to mask all `TextView`s and their subclasses (`RadioButton`, `EditText`, etc.): `options.experimental.sessionReplay.addMaskViewClass("android.widget.TextView")` - - If you're using code obfuscation, adjust your proguard-rules accordingly, so your custom view class name is not minified -- Session Replay: Support Jetpack Compose masking ([#3739](https://github.com/getsentry/sentry-java/pull/3739)) - - To selectively mask/unmask @Composables, use `Modifier.sentryReplayMask()` and `Modifier.sentryReplayUnmask()` modifiers -- Session Replay: Mask `WebView`, `VideoView` and `androidx.media3.ui.PlayerView` by default ([#3775](https://github.com/getsentry/sentry-java/pull/3775)) - -### Fixes - -- Avoid stopping appStartProfiler after application creation ([#3630](https://github.com/getsentry/sentry-java/pull/3630)) -- Session Replay: Correctly detect dominant color for `TextView`s with Spans ([#3682](https://github.com/getsentry/sentry-java/pull/3682)) -- Fix ensure Application Context is used even when SDK is initialized via Activity Context ([#3669](https://github.com/getsentry/sentry-java/pull/3669)) -- Fix potential ANRs due to `Calendar.getInstance` usage in Breadcrumbs constructor ([#3736](https://github.com/getsentry/sentry-java/pull/3736)) -- Fix potential ANRs due to default integrations ([#3778](https://github.com/getsentry/sentry-java/pull/3778)) -- Lazily initialize heavy `SentryOptions` members to avoid ANRs on app start ([#3749](https://github.com/getsentry/sentry-java/pull/3749)) - -*Breaking changes*: - -- `options.experimental.sessionReplay.errorSampleRate` was renamed to `options.experimental.sessionReplay.onErrorSampleRate` ([#3637](https://github.com/getsentry/sentry-java/pull/3637)) -- Manifest option `io.sentry.session-replay.error-sample-rate` was renamed to `io.sentry.session-replay.on-error-sample-rate` ([#3637](https://github.com/getsentry/sentry-java/pull/3637)) -- Change `redactAllText` and `redactAllImages` to `maskAllText` and `maskAllImages` ([#3741](https://github.com/getsentry/sentry-java/pull/3741)) - -## 7.14.0 - -### Features - -- Session Replay: Gesture/touch support for Flutter ([#3623](https://github.com/getsentry/sentry-java/pull/3623)) - -### Fixes - -- Fix app start spans missing from Pixel devices ([#3634](https://github.com/getsentry/sentry-java/pull/3634)) -- Avoid ArrayIndexOutOfBoundsException on Android cpu data collection ([#3598](https://github.com/getsentry/sentry-java/pull/3598)) -- Fix lazy select queries instrumentation ([#3604](https://github.com/getsentry/sentry-java/pull/3604)) -- Session Replay: buffer mode improvements ([#3622](https://github.com/getsentry/sentry-java/pull/3622)) - - Align next segment timestamp with the end of the buffered segment when converting from buffer mode to session mode - - Persist `buffer` replay type for the entire replay when converting from buffer mode to session mode - - Properly store screen names for `buffer` mode -- Session Replay: fix various crashes and issues ([#3628](https://github.com/getsentry/sentry-java/pull/3628)) - - Fix video not being encoded on Pixel devices - - Fix SIGABRT native crashes on Xiaomi devices when encoding a video - - Fix `RejectedExecutionException` when redacting a screenshot - - Fix `FileNotFoundException` when persisting segment values - -### Chores - -- Introduce `ReplayShadowMediaCodec` and refactor tests using custom encoder ([#3612](https://github.com/getsentry/sentry-java/pull/3612)) - -## 7.13.0 - -### Features - -- Session Replay: ([#3565](https://github.com/getsentry/sentry-java/pull/3565)) ([#3609](https://github.com/getsentry/sentry-java/pull/3609)) - - Capture remaining replay segment for ANRs on next app launch - - Capture remaining replay segment for unhandled crashes on next app launch - -### Fixes - -- Session Replay: ([#3565](https://github.com/getsentry/sentry-java/pull/3565)) ([#3609](https://github.com/getsentry/sentry-java/pull/3609)) - - Fix stopping replay in `session` mode at 1 hour deadline - - Never encode full frames for a video segment, only do partial updates. This further reduces size of the replay segment - - Use propagation context when no active transaction for ANRs - -### Dependencies - -- Bump Spring Boot to 3.3.2 ([#3541](https://github.com/getsentry/sentry-java/pull/3541)) - -## 7.12.1 - -### Fixes - -- Check app start spans time and ignore background app starts ([#3550](https://github.com/getsentry/sentry-java/pull/3550)) - - This should eliminate long-lasting App Start transactions - -## 7.12.0 - -### Features - -- Session Replay Public Beta ([#3339](https://github.com/getsentry/sentry-java/pull/3339)) - - To enable Replay use the `sessionReplay.sessionSampleRate` or `sessionReplay.errorSampleRate` experimental options. - - ```kotlin - import io.sentry.SentryReplayOptions - import io.sentry.android.core.SentryAndroid - - SentryAndroid.init(context) { options -> - - // Currently under experimental options: - options.experimental.sessionReplay.sessionSampleRate = 1.0 - options.experimental.sessionReplay.errorSampleRate = 1.0 - - // To change default redaction behavior (defaults to true) - options.experimental.sessionReplay.redactAllImages = true - options.experimental.sessionReplay.redactAllText = true - - // To change quality of the recording (defaults to MEDIUM) - options.experimental.sessionReplay.quality = SentryReplayOptions.SentryReplayQuality.MEDIUM // (LOW|MEDIUM|HIGH) - } - ``` - - To learn more visit [Sentry's Mobile Session Replay](https://docs.sentry.io/product/explore/session-replay/mobile/) documentation page. - -## 7.11.0 - -### Features - -- Report dropped spans ([#3528](https://github.com/getsentry/sentry-java/pull/3528)) - -### Fixes - -- Fix duplicate session start for React Native ([#3504](https://github.com/getsentry/sentry-java/pull/3504)) -- Move onFinishCallback before span or transaction is finished ([#3459](https://github.com/getsentry/sentry-java/pull/3459)) -- Add timestamp when a profile starts ([#3442](https://github.com/getsentry/sentry-java/pull/3442)) -- Move fragment auto span finish to onFragmentStarted ([#3424](https://github.com/getsentry/sentry-java/pull/3424)) -- Remove profiling timeout logic and disable profiling on API 21 ([#3478](https://github.com/getsentry/sentry-java/pull/3478)) -- Properly reset metric flush flag on metric emission ([#3493](https://github.com/getsentry/sentry-java/pull/3493)) -- Use SecureRandom in favor of Random for Metrics ([#3495](https://github.com/getsentry/sentry-java/pull/3495)) -- Fix UncaughtExceptionHandlerIntegration Memory Leak ([#3398](https://github.com/getsentry/sentry-java/pull/3398)) -- Deprecated `User.segment`. Use a custom tag or context instead. ([#3511](https://github.com/getsentry/sentry-java/pull/3511)) -- Fix duplicated http spans ([#3526](https://github.com/getsentry/sentry-java/pull/3526)) -- When capturing unhandled hybrid exception session should be ended and new start if need ([#3480](https://github.com/getsentry/sentry-java/pull/3480)) - -### Dependencies - -- Bump Native SDK from v0.7.0 to v0.7.2 ([#3314](https://github.com/getsentry/sentry-java/pull/3314)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#072) - - [diff](https://github.com/getsentry/sentry-native/compare/0.7.0...0.7.2) - -## 7.10.0 - -### Features - -- Publish Gradle module metadata ([#3422](https://github.com/getsentry/sentry-java/pull/3422)) - -### Fixes - -- Fix faulty `span.frame_delay` calculation for early app start spans ([#3427](https://github.com/getsentry/sentry-java/pull/3427)) -- Fix crash when installing `ShutdownHookIntegration` and the VM is shutting down ([#3456](https://github.com/getsentry/sentry-java/pull/3456)) - -## 7.9.0 - -### Features - -- Add start_type to app context ([#3379](https://github.com/getsentry/sentry-java/pull/3379)) -- Add ttid/ttfd contribution flags ([#3386](https://github.com/getsentry/sentry-java/pull/3386)) - -### Fixes - -- (Internal) Metrics code cleanup ([#3403](https://github.com/getsentry/sentry-java/pull/3403)) -- Fix Frame measurements in app start transactions ([#3382](https://github.com/getsentry/sentry-java/pull/3382)) -- Fix timing metric value different from span duration ([#3368](https://github.com/getsentry/sentry-java/pull/3368)) -- Do not always write startup crash marker ([#3409](https://github.com/getsentry/sentry-java/pull/3409)) - - This may have been causing the SDK init logic to block the main thread - -## 7.8.0 - -### Features - -- Add description to OkHttp spans ([#3320](https://github.com/getsentry/sentry-java/pull/3320)) -- Enable backpressure management by default ([#3284](https://github.com/getsentry/sentry-java/pull/3284)) - -### Fixes - -- Add rate limit to Metrics ([#3334](https://github.com/getsentry/sentry-java/pull/3334)) -- Fix java.lang.ClassNotFoundException: org.springframework.web.servlet.HandlerMapping in Spring Boot Servlet mode without WebMVC ([#3336](https://github.com/getsentry/sentry-java/pull/3336)) -- Fix normalization of metrics keys, tags and values ([#3332](https://github.com/getsentry/sentry-java/pull/3332)) - -## 7.7.0 - -### Features - -- Add support for Spring Rest Client ([#3199](https://github.com/getsentry/sentry-java/pull/3199)) -- Extend Proxy options with proxy type ([#3326](https://github.com/getsentry/sentry-java/pull/3326)) - -### Fixes - -- Fixed default deadline timeout to 30s instead of 300s ([#3322](https://github.com/getsentry/sentry-java/pull/3322)) -- Fixed `Fix java.lang.ClassNotFoundException: org.springframework.web.servlet.HandlerExceptionResolver` in Spring Boot Servlet mode without WebMVC ([#3333](https://github.com/getsentry/sentry-java/pull/3333)) - -## 7.6.0 - -### Features - -- Experimental: Add support for Sentry Developer Metrics ([#3205](https://github.com/getsentry/sentry-java/pull/3205), [#3238](https://github.com/getsentry/sentry-java/pull/3238), [#3248](https://github.com/getsentry/sentry-java/pull/3248), [#3250](https://github.com/getsentry/sentry-java/pull/3250)) - Use the Metrics API to track processing time, download sizes, user signups, and conversion rates and correlate them back to tracing data in order to get deeper insights and solve issues faster. Our API supports counters, distributions, sets, gauges and timers, and it's easy to get started: - ```kotlin - Sentry.metrics() - .increment( - "button_login_click", // key - 1.0, // value - null, // unit - mapOf( // tags - "provider" to "e-mail" - ) - ) - ``` - To learn more about Sentry Developer Metrics, head over to our [Java](https://docs.sentry.io/platforms/java/metrics/) and [Android](https://docs.sentry.io//platforms/android/metrics/) docs page. - -## 7.5.0 - -### Features - -- Add support for measurements at span level ([#3219](https://github.com/getsentry/sentry-java/pull/3219)) -- Add `enableScopePersistence` option to disable `PersistingScopeObserver` used for ANR reporting which may increase performance overhead. Defaults to `true` ([#3218](https://github.com/getsentry/sentry-java/pull/3218)) - - When disabled, the SDK will not enrich ANRv2 events with scope data (e.g. breadcrumbs, user, tags, etc.) -- Configurable defaults for Cron - MonitorConfig ([#3195](https://github.com/getsentry/sentry-java/pull/3195)) -- We now display a warning on startup if an incompatible version of Spring Boot is detected ([#3233](https://github.com/getsentry/sentry-java/pull/3233)) - - This should help notice a mismatching Sentry dependency, especially when upgrading a Spring Boot application -- Experimental: Add Metrics API ([#3205](https://github.com/getsentry/sentry-java/pull/3205)) - -### Fixes - -- Ensure performance measurement collection is not taken too frequently ([#3221](https://github.com/getsentry/sentry-java/pull/3221)) -- Fix old profiles deletion on SDK init ([#3216](https://github.com/getsentry/sentry-java/pull/3216)) -- Fix hub restore point in wrappers: SentryWrapper, SentryTaskDecorator and SentryScheduleHook ([#3225](https://github.com/getsentry/sentry-java/pull/3225)) - - We now reset the hub to its previous value on the thread where the `Runnable`/`Callable`/`Supplier` is executed instead of setting it to the hub that was used on the thread where the `Runnable`/`Callable`/`Supplier` was created. -- Fix add missing thread name/id to app start spans ([#3226](https://github.com/getsentry/sentry-java/pull/3226)) - -## 7.4.0 - -### Features - -- Add new threshold parameters to monitor config ([#3181](https://github.com/getsentry/sentry-java/pull/3181)) -- Report process init time as a span for app start performance ([#3159](https://github.com/getsentry/sentry-java/pull/3159)) -- (perf-v2): Calculate frame delay on a span level ([#3197](https://github.com/getsentry/sentry-java/pull/3197)) -- Resolve spring properties in @SentryCheckIn annotation ([#3194](https://github.com/getsentry/sentry-java/pull/3194)) -- Experimental: Add Spotlight integration ([#3166](https://github.com/getsentry/sentry-java/pull/3166)) - - For more details about Spotlight head over to https://spotlightjs.com/ - - Set `options.isEnableSpotlight = true` to enable Spotlight - -### Fixes - -- Don't wait on main thread when SDK restarts ([#3200](https://github.com/getsentry/sentry-java/pull/3200)) -- Fix Jetpack Compose widgets are not being correctly identified for user interaction tracing ([#3209](https://github.com/getsentry/sentry-java/pull/3209)) -- Fix issue title on Android when a wrapping `RuntimeException` is thrown by the system ([#3212](https://github.com/getsentry/sentry-java/pull/3212)) - - This will change grouping of the issues that were previously titled `RuntimeInit$MethodAndArgsCaller` to have them split up properly by the original root cause exception - -## 7.3.0 - -### Features - -- Added App Start profiling - - This depends on the new option `io.sentry.profiling.enable-app-start`, other than the already existing `io.sentry.traces.profiling.sample-rate`. - - Sampler functions can check the new `isForNextAppStart` flag, to adjust startup profiling sampling programmatically. - Relevant PRs: - - Decouple Profiler from Transaction ([#3101](https://github.com/getsentry/sentry-java/pull/3101)) - - Add options and sampling logic ([#3121](https://github.com/getsentry/sentry-java/pull/3121)) - - Add ContentProvider and start profile ([#3128](https://github.com/getsentry/sentry-java/pull/3128)) -- Extend internal performance collector APIs ([#3102](https://github.com/getsentry/sentry-java/pull/3102)) -- Collect slow and frozen frames for spans using `OnFrameMetricsAvailableListener` ([#3111](https://github.com/getsentry/sentry-java/pull/3111)) -- Interpolate total frame count to match span duration ([#3158](https://github.com/getsentry/sentry-java/pull/3158)) - -### Fixes - -- Avoid multiple breadcrumbs from OkHttpEventListener ([#3175](https://github.com/getsentry/sentry-java/pull/3175)) -- Apply OkHttp listener auto finish timestamp to all running spans ([#3167](https://github.com/getsentry/sentry-java/pull/3167)) -- Fix not eligible for auto proxying warnings ([#3154](https://github.com/getsentry/sentry-java/pull/3154)) -- Set default fingerprint for ANRv2 events to correctly group background and foreground ANRs ([#3164](https://github.com/getsentry/sentry-java/pull/3164)) - - This will improve grouping of ANRs that have similar stacktraces but differ in background vs foreground state. Only affects newly-ingested ANR events with `mechanism:AppExitInfo` -- Fix UserFeedback disk cache name conflicts with linked events ([#3116](https://github.com/getsentry/sentry-java/pull/3116)) - -### Breaking changes - -- Remove `HostnameVerifier` option as it's flagged by security tools of some app stores ([#3150](https://github.com/getsentry/sentry-java/pull/3150)) - - If you were using this option, you have 3 possible paths going forward: - - Provide a custom `ITransportFactory` through `SentryOptions.setTransportFactory()`, where you can copy over most of the parts like `HttpConnection` and `AsyncHttpTransport` from the SDK with necessary modifications - - Get a certificate for your server through e.g. [Let's Encrypt](https://letsencrypt.org/) - - Fork the SDK and add the hostname verifier back - -### Dependencies - -- Bump Native SDK from v0.6.7 to v0.7.0 ([#3133](https://github.com/getsentry/sentry-java/pull/3133)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#070) - - [diff](https://github.com/getsentry/sentry-native/compare/0.6.7...0.7.0) - -## 7.2.0 - -### Features - -- Handle `monitor`/`check_in` in client reports and rate limiter ([#3096](https://github.com/getsentry/sentry-java/pull/3096)) -- Add support for `graphql-java` version 21 ([#3090](https://github.com/getsentry/sentry-java/pull/3090)) - -### Fixes - -- Avoid concurrency in AndroidProfiler performance data collection ([#3130](https://github.com/getsentry/sentry-java/pull/3130)) -- Improve thresholds for network changes breadcrumbs ([#3083](https://github.com/getsentry/sentry-java/pull/3083)) -- SchedulerFactoryBeanCustomizer now runs first so user customization is not overridden ([#3095](https://github.com/getsentry/sentry-java/pull/3095)) - - If you are setting global job listeners please also add `SentryJobListener` -- Ensure serialVersionUID of Exception classes are unique ([#3115](https://github.com/getsentry/sentry-java/pull/3115)) -- Get rid of "is not eligible for getting processed by all BeanPostProcessors" warnings in Spring Boot ([#3108](https://github.com/getsentry/sentry-java/pull/3108)) -- Fix missing `release` and other fields for ANRs reported with `mechanism:AppExitInfo` ([#3074](https://github.com/getsentry/sentry-java/pull/3074)) - -### Dependencies - -- Bump `opentelemetry-sdk` to `1.33.0` and `opentelemetry-javaagent` to `1.32.0` ([#3112](https://github.com/getsentry/sentry-java/pull/3112)) - -## 7.1.0 - -### Features - -- Support multiple debug-metadata.properties ([#3024](https://github.com/getsentry/sentry-java/pull/3024)) -- Automatically downsample transactions when the system is under load ([#3072](https://github.com/getsentry/sentry-java/pull/3072)) - - You can opt into this behaviour by setting `enable-backpressure-handling=true`. - - We're happy to receive feedback, e.g. [in this GitHub issue](https://github.com/getsentry/sentry-java/issues/2829) - - When the system is under load we start reducing the `tracesSampleRate` automatically. - - Once the system goes back to healthy, we reset the `tracesSampleRate` to its original value. -- (Android) Experimental: Provide more detailed cold app start information ([#3057](https://github.com/getsentry/sentry-java/pull/3057)) - - Attaches spans for Application, ContentProvider, and Activities to app-start timings - - Application and ContentProvider timings are added using bytecode instrumentation, which requires sentry-android-gradle-plugin version `4.1.0` or newer - - Uses Process.startUptimeMillis to calculate app-start timings - - To enable this feature set `options.isEnablePerformanceV2 = true` -- Move slow+frozen frame calculation, as well as frame delay inside SentryFrameMetricsCollector ([#3100](https://github.com/getsentry/sentry-java/pull/3100)) -- Extract Activity Breadcrumbs generation into own Integration ([#3064](https://github.com/getsentry/sentry-java/pull/3064)) - -### Fixes - -- Send breadcrumbs and client error in `SentryOkHttpEventListener` even without transactions ([#3087](https://github.com/getsentry/sentry-java/pull/3087)) -- Keep `io.sentry.exception.SentryHttpClientException` from obfuscation to display proper issue title on Sentry ([#3093](https://github.com/getsentry/sentry-java/pull/3093)) -- (Android) Fix wrong activity transaction duration in case SDK init is deferred ([#3092](https://github.com/getsentry/sentry-java/pull/3092)) - -### Dependencies - -- Bump Gradle from v8.4.0 to v8.5.0 ([#3070](https://github.com/getsentry/sentry-java/pull/3070)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v850) - - [diff](https://github.com/gradle/gradle/compare/v8.4.0...v8.5.0) - -## 7.0.0 - -Version 7 of the Sentry Android/Java SDK brings a variety of features and fixes. The most notable changes are: -- Bumping `minSdk` level to 19 (Android 4.4) -- The SDK will now listen to connectivity changes and try to re-upload cached events when internet connection is re-established additionally to uploading events on app restart -- `Sentry.getSpan` now returns the root transaction, which should improve the span hierarchy and make it leaner -- Multiple improvements to reduce probability of the SDK causing ANRs -- New `sentry-okhttp` artifact is unbundled from Android and can be used in pure JVM-only apps - -## Sentry Self-hosted Compatibility - -This SDK version is compatible with a self-hosted version of Sentry `22.12.0` or higher. If you are using an older version of [self-hosted Sentry](https://develop.sentry.dev/self-hosted/) (aka onpremise), you will need to [upgrade](https://develop.sentry.dev/self-hosted/releases/). If you're using `sentry.io` no action is required. - -## Sentry Integrations Version Compatibility (Android) - -Make sure to align _all_ Sentry dependencies to the same version when bumping the SDK to 7.+, otherwise it will crash at runtime due to binary incompatibility. (E.g. if you're using `-timber`, `-okhttp` or other packages) - -For example, if you're using the [Sentry Android Gradle plugin](https://github.com/getsentry/sentry-android-gradle-plugin) with the `autoInstallation` [feature](https://docs.sentry.io/platforms/android/configuration/gradle/#auto-installation) (enabled by default), make sure to use version 4.+ of the gradle plugin together with version 7.+ of the SDK. If you can't do that for some reason, you can specify sentry version via the plugin config block: - -```kotlin -sentry { - autoInstallation { - sentryVersion.set("7.0.0") - } -} -``` - -Similarly, if you have a Sentry SDK (e.g. `sentry-android-core`) dependency on one of your Gradle modules and you're updating it to 7.+, make sure the Gradle plugin is at 4.+ or specify the SDK version as shown in the snippet above. - -## Breaking Changes - -- Bump min API to 19 ([#2883](https://github.com/getsentry/sentry-java/pull/2883)) -- If you're using `sentry-kotlin-extensions`, it requires `kotlinx-coroutines-core` version `1.6.1` or higher now ([#2838](https://github.com/getsentry/sentry-java/pull/2838)) -- Move enableNdk from SentryOptions to SentryAndroidOptions ([#2793](https://github.com/getsentry/sentry-java/pull/2793)) -- Apollo v2 BeforeSpanCallback now allows returning null ([#2890](https://github.com/getsentry/sentry-java/pull/2890)) -- `SentryOkHttpUtils` was removed from public API as it's been exposed by mistake ([#3005](https://github.com/getsentry/sentry-java/pull/3005)) -- `Scope` now implements the `IScope` interface, therefore some methods like `ScopeCallback.run` accept `IScope` now ([#3066](https://github.com/getsentry/sentry-java/pull/3066)) -- Cleanup `startTransaction` overloads ([#2964](https://github.com/getsentry/sentry-java/pull/2964)) - - We have reduced the number of overloads by allowing to pass in a `TransactionOptions` object instead of having separate parameters for certain options - - `TransactionOptions` has defaults set and can be customized, for example: - -```kotlin -// old -val transaction = Sentry.startTransaction("name", "op", bindToScope = true) -// new -val transaction = Sentry.startTransaction("name", "op", TransactionOptions().apply { isBindToScope = true }) -``` - -## Behavioural Changes - -- Android only: `Sentry.getSpan()` returns the root span/transaction instead of the latest span ([#2855](https://github.com/getsentry/sentry-java/pull/2855)) -- Capture failed HTTP and GraphQL (Apollo) requests by default ([#2794](https://github.com/getsentry/sentry-java/pull/2794)) - - This can increase your event consumption and may affect your quota, because we will report failed network requests as Sentry events by default, if you're using the `sentry-android-okhttp` or `sentry-apollo-3` integrations. You can customize what errors you want/don't want to have reported for [OkHttp](https://docs.sentry.io/platforms/android/integrations/okhttp#http-client-errors) and [Apollo3](https://docs.sentry.io/platforms/android/integrations/apollo3#graphql-client-errors) respectively. -- Measure AppStart time till First Draw instead of `onResume` ([#2851](https://github.com/getsentry/sentry-java/pull/2851)) -- Automatic user interaction tracking: every click now starts a new automatic transaction ([#2891](https://github.com/getsentry/sentry-java/pull/2891)) - - Previously performing a click on the same UI widget twice would keep the existing transaction running, the new behavior now better aligns with other SDKs -- Add deadline timeout for automatic transactions ([#2865](https://github.com/getsentry/sentry-java/pull/2865)) - - This affects all automatically generated transactions on Android (UI, clicks), the default timeout is 30s, meaning the automatic transaction will be force-finished with status `deadline_exceeded` when reaching the deadline -- Set ip_address to {{auto}} by default, even if sendDefaultPII is disabled ([#2860](https://github.com/getsentry/sentry-java/pull/2860)) - - Instead use the "Prevent Storing of IP Addresses" option in the "Security & Privacy" project settings on sentry.io -- Raw logback message and parameters are now guarded by `sendDefaultPii` if an `encoder` has been configured ([#2976](https://github.com/getsentry/sentry-java/pull/2976)) -- The `maxSpans` setting (defaults to 1000) is enforced for nested child spans which means a single transaction can have `maxSpans` number of children (nested or not) at most ([#3065](https://github.com/getsentry/sentry-java/pull/3065)) -- The `ScopeCallback` in `withScope` is now always executed ([#3066](https://github.com/getsentry/sentry-java/pull/3066)) - -## Deprecations - -- `sentry-android-okhttp` was deprecated in favour of the new `sentry-okhttp` module. Make sure to replace `io.sentry.android.okhttp` package name with `io.sentry.okhttp` before the next major, where the classes will be removed ([#3005](https://github.com/getsentry/sentry-java/pull/3005)) - -## Other Changes - -### Features - -- Observe network state to upload any unsent envelopes ([#2910](https://github.com/getsentry/sentry-java/pull/2910)) - - Android: it works out-of-the-box as part of the default `SendCachedEnvelopeIntegration` - - JVM: you'd have to install `SendCachedEnvelopeFireAndForgetIntegration` as mentioned in https://docs.sentry.io/platforms/java/configuration/#configuring-offline-caching and provide your own implementation of `IConnectionStatusProvider` via `SentryOptions` -- Add `sentry-okhttp` module to support instrumenting OkHttp in non-Android projects ([#3005](https://github.com/getsentry/sentry-java/pull/3005)) -- Do not filter out Sentry SDK frames in case of uncaught exceptions ([#3021](https://github.com/getsentry/sentry-java/pull/3021)) -- Do not try to send and drop cached envelopes when rate-limiting is active ([#2937](https://github.com/getsentry/sentry-java/pull/2937)) - -### Fixes - -- Use `getMyMemoryState()` instead of `getRunningAppProcesses()` to retrieve process importance ([#3004](https://github.com/getsentry/sentry-java/pull/3004)) - - This should prevent some app stores from flagging apps as violating their privacy -- Reduce flush timeout to 4s on Android to avoid ANRs ([#2858](https://github.com/getsentry/sentry-java/pull/2858)) -- Reduce timeout of AsyncHttpTransport to avoid ANR ([#2879](https://github.com/getsentry/sentry-java/pull/2879)) -- Do not overwrite UI transaction status if set by the user ([#2852](https://github.com/getsentry/sentry-java/pull/2852)) -- Capture unfinished transaction on Scope with status `aborted` in case a crash happens ([#2938](https://github.com/getsentry/sentry-java/pull/2938)) - - This will fix the link between transactions and corresponding crashes, you'll be able to see them in a single trace -- Fix Coroutine Context Propagation using CopyableThreadContextElement ([#2838](https://github.com/getsentry/sentry-java/pull/2838)) -- Fix don't overwrite the span status of unfinished spans ([#2859](https://github.com/getsentry/sentry-java/pull/2859)) -- Migrate from `default` interface methods to proper implementations in each interface implementor ([#2847](https://github.com/getsentry/sentry-java/pull/2847)) - - This prevents issues when using the SDK on older AGP versions (< 4.x.x) -- Reduce main thread work on init ([#3036](https://github.com/getsentry/sentry-java/pull/3036)) -- Move Integrations registration to background on init ([#3043](https://github.com/getsentry/sentry-java/pull/3043)) -- Fix `SentryOkHttpInterceptor.BeforeSpanCallback` was not finishing span when it was dropped ([#2958](https://github.com/getsentry/sentry-java/pull/2958)) - -## 6.34.0 - -### Features - -- Add current activity name to app context ([#2999](https://github.com/getsentry/sentry-java/pull/2999)) -- Add `MonitorConfig` param to `CheckInUtils.withCheckIn` ([#3038](https://github.com/getsentry/sentry-java/pull/3038)) - - This makes it easier to automatically create or update (upsert) monitors. -- (Internal) Extract Android Profiler and Measurements for Hybrid SDKs ([#3016](https://github.com/getsentry/sentry-java/pull/3016)) -- (Internal) Remove SentryOptions dependency from AndroidProfiler ([#3051](https://github.com/getsentry/sentry-java/pull/3051)) -- (Internal) Add `readBytesFromFile` for use in Hybrid SDKs ([#3052](https://github.com/getsentry/sentry-java/pull/3052)) -- (Internal) Add `getProguardUuid` for use in Hybrid SDKs ([#3054](https://github.com/getsentry/sentry-java/pull/3054)) - -### Fixes - -- Fix SIGSEV, SIGABRT and SIGBUS crashes happening after/around the August Google Play System update, see [#2955](https://github.com/getsentry/sentry-java/issues/2955) for more details (fix provided by Native SDK bump) -- Ensure DSN uses http/https protocol ([#3044](https://github.com/getsentry/sentry-java/pull/3044)) - -### Dependencies - -- Bump Native SDK from v0.6.6 to v0.6.7 ([#3048](https://github.com/getsentry/sentry-java/pull/3048)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#067) - - [diff](https://github.com/getsentry/sentry-native/compare/0.6.6...0.6.7) - -## 6.33.2-beta.1 - -### Fixes - -- Fix SIGSEV, SIGABRT and SIGBUS crashes happening after/around the August Google Play System update, see [#2955](https://github.com/getsentry/sentry-java/issues/2955) for more details (fix provided by Native SDK bump) - -### Dependencies - -- Bump Native SDK from v0.6.6 to v0.6.7 ([#3048](https://github.com/getsentry/sentry-java/pull/3048)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#067) - - [diff](https://github.com/getsentry/sentry-native/compare/0.6.6...0.6.7) - -## 6.33.1 - -### Fixes - -- Do not register `sentrySpringFilter` in ServletContext for Spring Boot ([#3027](https://github.com/getsentry/sentry-java/pull/3027)) - -## 6.33.0 - -### Features - -- Add thread information to spans ([#2998](https://github.com/getsentry/sentry-java/pull/2998)) -- Use PixelCopy API for capturing screenshots on API level 24+ ([#3008](https://github.com/getsentry/sentry-java/pull/3008)) - -### Fixes - -- Fix crash when HTTP connection error message contains formatting symbols ([#3002](https://github.com/getsentry/sentry-java/pull/3002)) -- Cap max number of stack frames to 100 to not exceed payload size limit ([#3009](https://github.com/getsentry/sentry-java/pull/3009)) - - This will ensure we report errors with a big number of frames such as `StackOverflowError` -- Fix user interaction tracking not working for Jetpack Compose 1.5+ ([#3010](https://github.com/getsentry/sentry-java/pull/3010)) -- Make sure to close all Closeable resources ([#3000](https://github.com/getsentry/sentry-java/pull/3000)) - -## 6.32.0 - -### Features - -- Make `DebugImagesLoader` public ([#2993](https://github.com/getsentry/sentry-java/pull/2993)) - -### Fixes - -- Make `SystemEventsBroadcastReceiver` exported on API 33+ ([#2990](https://github.com/getsentry/sentry-java/pull/2990)) - - This will fix the `SystemEventsBreadcrumbsIntegration` crashes that you might have encountered on Play Console - -## 6.31.0 - -### Features - -- Improve default debouncing mechanism ([#2945](https://github.com/getsentry/sentry-java/pull/2945)) -- Add `CheckInUtils.withCheckIn` which abstracts away some of the manual check-ins complexity ([#2959](https://github.com/getsentry/sentry-java/pull/2959)) -- Add `@SentryCaptureExceptionParameter` annotation which captures exceptions passed into an annotated method ([#2764](https://github.com/getsentry/sentry-java/pull/2764)) - - This can be used to replace `Sentry.captureException` calls in `@ExceptionHandler` of a `@ControllerAdvice` -- Add `ServerWebExchange` to `Hint` for WebFlux as `WEBFLUX_EXCEPTION_HANDLER_EXCHANGE` ([#2977](https://github.com/getsentry/sentry-java/pull/2977)) -- Allow filtering GraphQL errors ([#2967](https://github.com/getsentry/sentry-java/pull/2967)) - - This list can be set directly when calling the constructor of `SentryInstrumentation` - - For Spring Boot it can also be set in `application.properties` as `sentry.graphql.ignored-error-types=SOME_ERROR,ANOTHER_ERROR` - -### Fixes - -- Add OkHttp span auto-close when response body is not read ([#2923](https://github.com/getsentry/sentry-java/pull/2923)) -- Fix json parsing of nullable/empty fields for Hybrid SDKs ([#2968](https://github.com/getsentry/sentry-java/pull/2968)) - - (Internal) Rename `nextList` to `nextListOrNull` to actually match what the method does - - (Hybrid) Check if there's any object in a collection before trying to parse it (which prevents the "Failed to deserilize object in list" log message) - - (Hybrid) If a date can't be parsed as an ISO timestamp, attempts to parse it as millis silently, without printing a log message - - (Hybrid) If `op` is not defined as part of `SpanContext`, fallback to an empty string, because the filed is optional in the spec -- Always attach OkHttp errors and Http Client Errors only to call root span ([#2961](https://github.com/getsentry/sentry-java/pull/2961)) -- Fixed crash accessing Choreographer instance ([#2970](https://github.com/getsentry/sentry-java/pull/2970)) - -### Dependencies - -- Bump Native SDK from v0.6.5 to v0.6.6 ([#2975](https://github.com/getsentry/sentry-java/pull/2975)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#066) - - [diff](https://github.com/getsentry/sentry-native/compare/0.6.5...0.6.6) -- Bump Gradle from v8.3.0 to v8.4.0 ([#2966](https://github.com/getsentry/sentry-java/pull/2966)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v840) - - [diff](https://github.com/gradle/gradle/compare/v8.3.0...v8.4.0) - -## 6.30.0 - -### Features - -- Add `sendModules` option for disable sending modules ([#2926](https://github.com/getsentry/sentry-java/pull/2926)) -- Send `db.system` and `db.name` in span data for androidx.sqlite spans ([#2928](https://github.com/getsentry/sentry-java/pull/2928)) -- Check-ins (CRONS) support ([#2952](https://github.com/getsentry/sentry-java/pull/2952)) - - Add API for sending check-ins (CRONS) manually ([#2935](https://github.com/getsentry/sentry-java/pull/2935)) - - Support check-ins (CRONS) for Quartz ([#2940](https://github.com/getsentry/sentry-java/pull/2940)) - - `@SentryCheckIn` annotation and advice config for Spring ([#2946](https://github.com/getsentry/sentry-java/pull/2946)) - - Add option for ignoring certain monitor slugs ([#2943](https://github.com/getsentry/sentry-java/pull/2943)) - -### Fixes - -- Always send memory stats for transactions ([#2936](https://github.com/getsentry/sentry-java/pull/2936)) - - This makes it possible to query transactions by the `device.class` tag on Sentry -- Add `sentry.enable-aot-compatibility` property to SpringBoot Jakarta `SentryAutoConfiguration` to enable building for GraalVM ([#2915](https://github.com/getsentry/sentry-java/pull/2915)) - -### Dependencies - -- Bump Gradle from v8.2.1 to v8.3.0 ([#2900](https://github.com/getsentry/sentry-java/pull/2900)) - - [changelog](https://github.com/gradle/gradle/blob/master release-test/CHANGELOG.md#v830) - - [diff](https://github.com/gradle/gradle/compare/v8.2.1...v8.3.0) - -## 6.29.0 - -### Features - -- Send `db.system` and `db.name` in span data ([#2894](https://github.com/getsentry/sentry-java/pull/2894)) -- Send `http.request.method` in span data ([#2896](https://github.com/getsentry/sentry-java/pull/2896)) -- Add `enablePrettySerializationOutput` option for opting out of pretty print ([#2871](https://github.com/getsentry/sentry-java/pull/2871)) - -## 6.28.0 - -### Features - -- Add HTTP response code to Spring WebFlux transactions ([#2870](https://github.com/getsentry/sentry-java/pull/2870)) -- Add `sampled` to Dynamic Sampling Context ([#2869](https://github.com/getsentry/sentry-java/pull/2869)) -- Improve server side GraphQL support for spring-graphql and Nextflix DGS ([#2856](https://github.com/getsentry/sentry-java/pull/2856)) - - If you have already been using `SentryDataFetcherExceptionHandler` that still works but has been deprecated. Please use `SentryGenericDataFetcherExceptionHandler` combined with `SentryInstrumentation` instead for better error reporting. - - More exceptions and errors caught and reported to Sentry by also looking at the `ExecutionResult` (more specifically its `errors`) - - You may want to filter out certain errors, please see [docs on filtering](https://docs.sentry.io/platforms/java/configuration/filtering/) - - More details for Sentry events: query, variables and response (where possible) - - Breadcrumbs for operation (query, mutation, subscription), data fetchers and data loaders (Spring only) - - Better hub propagation by using `GraphQLContext` -- Add autoconfigure modules for Spring Boot called `sentry-spring-boot` and `sentry-spring-boot-jakarta` ([#2880](https://github.com/getsentry/sentry-java/pull/2880)) - - The autoconfigure modules `sentry-spring-boot` and `sentry-spring-boot-jakarta` have a `compileOnly` dependency on `spring-boot-starter` which is needed for our auto installation in [sentry-android-gradle-plugin](https://github.com/getsentry/sentry-android-gradle-plugin) - - The starter modules `sentry-spring-boot-starter` and `sentry-spring-boot-starter-jakarta` now bring `spring-boot-starter` as a dependency -- You can now disable Sentry by setting the `enabled` option to `false` ([#2840](https://github.com/getsentry/sentry-java/pull/2840)) - -### Fixes - -- Propagate OkHttp status to parent spans ([#2872](https://github.com/getsentry/sentry-java/pull/2872)) - -## 6.27.0 - -### Features - -- Add TraceOrigin to Transactions and Spans ([#2803](https://github.com/getsentry/sentry-java/pull/2803)) - -### Fixes - -- Deduplicate events happening in multiple threads simultaneously (e.g. `OutOfMemoryError`) ([#2845](https://github.com/getsentry/sentry-java/pull/2845)) - - This will improve Crash-Free Session Rate as we no longer will send multiple Session updates with `Crashed` status, but only the one that is relevant -- Ensure no Java 8 method reference sugar is used for Android ([#2857](https://github.com/getsentry/sentry-java/pull/2857)) -- Do not send session updates for terminated sessions ([#2849](https://github.com/getsentry/sentry-java/pull/2849)) - -## 6.26.0 - -### Features -- (Internal) Extend APIs for hybrid SDKs ([#2814](https://github.com/getsentry/sentry-java/pull/2814), [#2846](https://github.com/getsentry/sentry-java/pull/2846)) - -### Fixes - -- Fix ANRv2 thread dump parsing for native-only threads ([#2839](https://github.com/getsentry/sentry-java/pull/2839)) -- Derive `TracingContext` values from event for ANRv2 events ([#2839](https://github.com/getsentry/sentry-java/pull/2839)) - -## 6.25.2 - -### Fixes - -- Change Spring Boot, Apollo, Apollo 3, JUL, Logback, Log4j2, OpenFeign, GraphQL and Kotlin coroutines core dependencies to compileOnly ([#2837](https://github.com/getsentry/sentry-java/pull/2837)) - -## 6.25.1 - -### Fixes - -- Allow removing integrations in SentryAndroid.init ([#2826](https://github.com/getsentry/sentry-java/pull/2826)) -- Fix concurrent access to frameMetrics listener ([#2823](https://github.com/getsentry/sentry-java/pull/2823)) - -### Dependencies - -- Bump Native SDK from v0.6.4 to v0.6.5 ([#2822](https://github.com/getsentry/sentry-java/pull/2822)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#065) - - [diff](https://github.com/getsentry/sentry-native/compare/0.6.4...0.6.5) -- Bump Gradle from v8.2.0 to v8.2.1 ([#2830](https://github.com/getsentry/sentry-java/pull/2830)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v821) - - [diff](https://github.com/gradle/gradle/compare/v8.2.0...v8.2.1) - -## 6.25.0 - -### Features - -- Add manifest `AutoInit` to integrations list ([#2795](https://github.com/getsentry/sentry-java/pull/2795)) -- Tracing headers (`sentry-trace` and `baggage`) are now attached and passed through even if performance is disabled ([#2788](https://github.com/getsentry/sentry-java/pull/2788)) - -### Fixes - -- Set `environment` from `SentryOptions` if none persisted in ANRv2 ([#2809](https://github.com/getsentry/sentry-java/pull/2809)) -- Remove code that set `tracesSampleRate` to `0.0` for Spring Boot if not set ([#2800](https://github.com/getsentry/sentry-java/pull/2800)) - - This used to enable performance but not send any transactions by default. - - Performance is now disabled by default. -- Fix slow/frozen frames were not reported with transactions ([#2811](https://github.com/getsentry/sentry-java/pull/2811)) - -### Dependencies - -- Bump Native SDK from v0.6.3 to v0.6.4 ([#2796](https://github.com/getsentry/sentry-java/pull/2796)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#064) - - [diff](https://github.com/getsentry/sentry-native/compare/0.6.3...0.6.4) -- Bump Gradle from v8.1.1 to v8.2.0 ([#2810](https://github.com/getsentry/sentry-java/pull/2810)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v820) - - [diff](https://github.com/gradle/gradle/compare/v8.1.1...v8.2.0) - -## 6.24.0 - -### Features - -- Add debouncing mechanism and before-capture callbacks for screenshots and view hierarchies ([#2773](https://github.com/getsentry/sentry-java/pull/2773)) -- Improve ANRv2 implementation ([#2792](https://github.com/getsentry/sentry-java/pull/2792)) - - Add a proguard rule to keep `ApplicationNotResponding` class from obfuscation - - Add a new option `setReportHistoricalAnrs`; when enabled, it will report all of the ANRs from the [getHistoricalExitReasons](https://developer.android.com/reference/android/app/ActivityManager?hl=en#getHistoricalProcessExitReasons(java.lang.String,%20int,%20int)) list. - By default, the SDK only reports and enriches the latest ANR and only this one counts towards ANR rate. - Worth noting that this option is mainly useful when updating the SDK to the version where ANRv2 has been introduced, to report all ANRs happened prior to the SDK update. After that, the SDK will always pick up the latest ANR from the historical exit reasons list on next app restart, so there should be no historical ANRs to report. - These ANRs are reported with the `HistoricalAppExitInfo` mechanism. - - Add a new option `setAttachAnrThreadDump` to send ANR thread dump from the system as an attachment. - This is only useful as additional information, because the SDK attempts to parse the thread dump into proper threads with stacktraces by default. - - If [ApplicationExitInfo#getTraceInputStream](https://developer.android.com/reference/android/app/ApplicationExitInfo#getTraceInputStream()) returns null, the SDK no longer reports an ANR event, as these events are not very useful without it. - - Enhance regex patterns for native stackframes - -## 6.23.0 - -### Features - -- Add profile rate limiting ([#2782](https://github.com/getsentry/sentry-java/pull/2782)) -- Support for automatically capturing Failed GraphQL (Apollo 3) Client errors ([#2781](https://github.com/getsentry/sentry-java/pull/2781)) - -```kotlin -import com.apollographql.apollo3.ApolloClient -import io.sentry.apollo3.sentryTracing - -val apolloClient = ApolloClient.Builder() - .serverUrl("https://example.com/graphql") - .sentryTracing(captureFailedRequests = true) - .build() -``` - -### Dependencies - -- Bump Native SDK from v0.6.2 to v0.6.3 ([#2746](https://github.com/getsentry/sentry-java/pull/2746)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#063) - - [diff](https://github.com/getsentry/sentry-native/compare/0.6.2...0.6.3) - -### Fixes - -- Align http.status with [span data conventions](https://develop.sentry.dev/sdk/performance/span-data-conventions/) ([#2786](https://github.com/getsentry/sentry-java/pull/2786)) - -## 6.22.0 - -### Features - -- Add `lock` attribute to the `SentryStackFrame` protocol to better highlight offending frames in the UI ([#2761](https://github.com/getsentry/sentry-java/pull/2761)) -- Enrich database spans with blocked main thread info ([#2760](https://github.com/getsentry/sentry-java/pull/2760)) -- Add `api_target` to `Request` and `data` to `Response` Protocols ([#2775](https://github.com/getsentry/sentry-java/pull/2775)) - -### Fixes - -- No longer use `String.join` in `Baggage` as it requires API level 26 ([#2778](https://github.com/getsentry/sentry-java/pull/2778)) - -## 6.21.0 - -### Features - -- Introduce new `sentry-android-sqlite` integration ([#2722](https://github.com/getsentry/sentry-java/pull/2722)) - - This integration replaces the old `androidx.sqlite` database instrumentation in the Sentry Android Gradle plugin - - A new capability to manually instrument your `androidx.sqlite` databases. - - You can wrap your custom `SupportSQLiteOpenHelper` instance into `SentrySupportSQLiteOpenHelper(myHelper)` if you're not using the Sentry Android Gradle plugin and still benefit from performance auto-instrumentation. -- Add SentryWrapper for Callable and Supplier Interface ([#2720](https://github.com/getsentry/sentry-java/pull/2720)) -- Load sentry-debug-meta.properties ([#2734](https://github.com/getsentry/sentry-java/pull/2734)) - - This enables source context for Java - - For more information on how to enable source context, please refer to [#633](https://github.com/getsentry/sentry-java/issues/633#issuecomment-1465599120) - -### Fixes - -- Finish WebFlux transaction before popping scope ([#2724](https://github.com/getsentry/sentry-java/pull/2724)) -- Use daemon threads for SentryExecutorService ([#2747](https://github.com/getsentry/sentry-java/pull/2747)) - - We started using `SentryExecutorService` in `6.19.0` which caused the application to hang on shutdown unless `Sentry.close()` was called. By using daemon threads we no longer block shutdown. -- Use Base64.NO_WRAP to avoid unexpected char errors in Apollo ([#2745](https://github.com/getsentry/sentry-java/pull/2745)) -- Don't warn R8 on missing `ComposeViewHierarchyExporter` class ([#2743](https://github.com/getsentry/sentry-java/pull/2743)) - -## 6.20.0 - -### Features - -- Add support for Sentry Kotlin Compiler Plugin ([#2695](https://github.com/getsentry/sentry-java/pull/2695)) - - In conjunction with our sentry-kotlin-compiler-plugin we improved Jetpack Compose support for - - [View Hierarchy](https://docs.sentry.io/platforms/android/enriching-events/viewhierarchy/) support for Jetpack Compose screens - - Automatic breadcrumbs for [user interactions](https://docs.sentry.io/platforms/android/performance/instrumentation/automatic-instrumentation/#user-interaction-instrumentation) -- More granular http requests instrumentation with a new SentryOkHttpEventListener ([#2659](https://github.com/getsentry/sentry-java/pull/2659)) - - Create spans for time spent on: - - Proxy selection - - DNS resolution - - HTTPS setup - - Connection - - Requesting headers - - Receiving response - - You can attach the event listener to your OkHttpClient through `client.eventListener(new SentryOkHttpEventListener()).addInterceptor(new SentryOkHttpInterceptor()).build();` - - In case you already have an event listener you can use the SentryOkHttpEventListener as well through `client.eventListener(new SentryOkHttpEventListener(myListener)).addInterceptor(new SentryOkHttpInterceptor()).build();` -- Add a new option to disable `RootChecker` ([#2735](https://github.com/getsentry/sentry-java/pull/2735)) - -### Fixes - -- Base64 encode internal Apollo3 Headers ([#2707](https://github.com/getsentry/sentry-java/pull/2707)) -- Fix `SentryTracer` crash when scheduling auto-finish of a transaction, but the timer has already been cancelled ([#2731](https://github.com/getsentry/sentry-java/pull/2731)) -- Fix `AndroidTransactionProfiler` crash when finishing a profile that happened due to race condition ([#2731](https://github.com/getsentry/sentry-java/pull/2731)) - -## 6.19.1 - -### Fixes - -- Ensure screenshots and view hierarchies are captured on the main thread ([#2712](https://github.com/getsentry/sentry-java/pull/2712)) - -## 6.19.0 - -### Features - -- Add Screenshot and ViewHierarchy to integrations list ([#2698](https://github.com/getsentry/sentry-java/pull/2698)) -- New ANR detection based on [ApplicationExitInfo API](https://developer.android.com/reference/android/app/ApplicationExitInfo) ([#2697](https://github.com/getsentry/sentry-java/pull/2697)) - - This implementation completely replaces the old one (based on a watchdog) on devices running Android 11 and above: - - New implementation provides more precise ANR events/ANR rate detection as well as system thread dump information. The new implementation reports ANRs exactly as Google Play Console, without producing false positives or missing important background ANR events. - - New implementation reports ANR events with a new mechanism `mechanism:AppExitInfo`. - - However, despite producing many false positives, the old implementation is capable of better enriching ANR errors (which is not available with the new implementation), for example: - - Capturing screenshots at the time of ANR event; - - Capturing transactions and profiling data corresponding to the ANR event; - - Auxiliary information (such as current memory load) at the time of ANR event. - - If you would like us to provide support for the old approach working alongside the new one on Android 11 and above (e.g. for raising events for slow code on main thread), consider upvoting [this issue](https://github.com/getsentry/sentry-java/issues/2693). - - The old watchdog implementation will continue working for older API versions (Android < 11): - - The old implementation reports ANR events with the existing mechanism `mechanism:ANR`. -- Open up `TransactionOptions`, `ITransaction` and `IHub` methods allowing consumers modify start/end timestamp of transactions and spans ([#2701](https://github.com/getsentry/sentry-java/pull/2701)) -- Send source bundle IDs to Sentry to enable source context ([#2663](https://github.com/getsentry/sentry-java/pull/2663)) - - For more information on how to enable source context, please refer to [#633](https://github.com/getsentry/sentry-java/issues/633#issuecomment-1465599120) - -### Fixes - -- Android Profiler on calling thread ([#2691](https://github.com/getsentry/sentry-java/pull/2691)) -- Use `configureScope` instead of `withScope` in `Hub.close()`. This ensures that the main scope releases the in-memory data when closing a hub instance. ([#2688](https://github.com/getsentry/sentry-java/pull/2688)) -- Remove null keys/values before creating concurrent hashmap in order to avoid NPE ([#2708](https://github.com/getsentry/sentry-java/pull/2708)) -- Exclude SentryOptions from R8/ProGuard obfuscation ([#2699](https://github.com/getsentry/sentry-java/pull/2699)) - - This fixes AGP 8.+ incompatibility, where full R8 mode is enforced - -### Dependencies - -- Bump Gradle from v8.1.0 to v8.1.1 ([#2666](https://github.com/getsentry/sentry-java/pull/2666)) - - [changelog](https://github.com/gradle/gradle/blob/master release-test/CHANGELOG.md#v811) - - [diff](https://github.com/gradle/gradle/compare/v8.1.0...v8.1.1) -- Bump Native SDK from v0.6.1 to v0.6.2 ([#2689](https://github.com/getsentry/sentry-java/pull/2689)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#062) - - [diff](https://github.com/getsentry/sentry-native/compare/0.6.1...0.6.2) - -## 6.18.1 - -### Fixes - -- Fix crash when Sentry SDK is initialized more than once ([#2679](https://github.com/getsentry/sentry-java/pull/2679)) -- Track a ttfd span per Activity ([#2673](https://github.com/getsentry/sentry-java/pull/2673)) - -## 6.18.0 - -### Features - -- Attach Trace Context when an ANR is detected (ANRv1) ([#2583](https://github.com/getsentry/sentry-java/pull/2583)) -- Make log4j2 integration compatible with log4j 3.0 ([#2634](https://github.com/getsentry/sentry-java/pull/2634)) - - Instead of relying on package scanning, we now use an annotation processor to generate `Log4j2Plugins.dat` -- Create `User` and `Breadcrumb` from map ([#2614](https://github.com/getsentry/sentry-java/pull/2614)) -- Add `sent_at` to envelope header item ([#2638](https://github.com/getsentry/sentry-java/pull/2638)) - -### Fixes - -- Fix timestamp intervals of PerformanceCollectionData in profiles ([#2648](https://github.com/getsentry/sentry-java/pull/2648)) -- Fix timestamps of PerformanceCollectionData in profiles ([#2632](https://github.com/getsentry/sentry-java/pull/2632)) -- Fix missing propagateMinConstraints flag for SentryTraced ([#2637](https://github.com/getsentry/sentry-java/pull/2637)) -- Fix potential SecurityException thrown by ConnectivityManager on Android 11 ([#2653](https://github.com/getsentry/sentry-java/pull/2653)) -- Fix aar artifacts publishing for Maven ([#2641](https://github.com/getsentry/sentry-java/pull/2641)) - -### Dependencies -- Bump Kotlin compile version from v1.6.10 to 1.8.0 ([#2563](https://github.com/getsentry/sentry-java/pull/2563)) -- Bump Compose compile version from v1.1.1 to v1.3.0 ([#2563](https://github.com/getsentry/sentry-java/pull/2563)) -- Bump AGP version from v7.3.0 to v7.4.2 ([#2574](https://github.com/getsentry/sentry-java/pull/2574)) -- Bump Gradle from v7.6.0 to v8.0.2 ([#2563](https://github.com/getsentry/sentry-java/pull/2563)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v802) - - [diff](https://github.com/gradle/gradle/compare/v7.6.0...v8.0.2) -- Bump Gradle from v8.0.2 to v8.1.0 ([#2650](https://github.com/getsentry/sentry-java/pull/2650)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v810) - - [diff](https://github.com/gradle/gradle/compare/v8.0.2...v8.1.0) - -## 6.17.0 - -### Features - -- Add `name` and `geo` to `User` ([#2556](https://github.com/getsentry/sentry-java/pull/2556)) -- Add breadcrumbs on network changes ([#2608](https://github.com/getsentry/sentry-java/pull/2608)) -- Add time-to-initial-display and time-to-full-display measurements to Activity transactions ([#2611](https://github.com/getsentry/sentry-java/pull/2611)) -- Read integration list written by sentry gradle plugin from manifest ([#2598](https://github.com/getsentry/sentry-java/pull/2598)) -- Add Logcat adapter ([#2620](https://github.com/getsentry/sentry-java/pull/2620)) -- Provide CPU count/frequency data as device context ([#2622](https://github.com/getsentry/sentry-java/pull/2622)) - -### Fixes - -- Trim time-to-full-display span if reportFullyDisplayed API is never called ([#2631](https://github.com/getsentry/sentry-java/pull/2631)) -- Fix Automatic UI transactions having wrong durations ([#2623](https://github.com/getsentry/sentry-java/pull/2623)) -- Fix wrong default environment in Session ([#2610](https://github.com/getsentry/sentry-java/pull/2610)) -- Pass through unknown sentry baggage keys into SentryEnvelopeHeader ([#2618](https://github.com/getsentry/sentry-java/pull/2618)) -- Fix missing null check when removing lifecycle observer ([#2625](https://github.com/getsentry/sentry-java/pull/2625)) - -### Dependencies - -- Bump Native SDK from v0.6.0 to v0.6.1 ([#2629](https://github.com/getsentry/sentry-java/pull/2629)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#061) - - [diff](https://github.com/getsentry/sentry-native/compare/0.6.0...0.6.1) - -## 6.16.0 - -### Features - -- Improve versatility of exception resolver component for Spring with more flexible API for consumers. ([#2577](https://github.com/getsentry/sentry-java/pull/2577)) -- Automatic performance instrumentation for WebFlux ([#2597](https://github.com/getsentry/sentry-java/pull/2597)) - - You can enable it by adding `sentry.enable-tracing=true` to your `application.properties` -- The Spring Boot integration can now be configured to add the `SentryAppender` to specific loggers instead of the `ROOT` logger ([#2173](https://github.com/getsentry/sentry-java/pull/2173)) - - You can specify the loggers using `"sentry.logging.loggers[0]=foo.bar` and `"sentry.logging.loggers[1]=baz` in your `application.properties` -- Add capabilities to track Jetpack Compose composition/rendering time ([#2507](https://github.com/getsentry/sentry-java/pull/2507)) -- Adapt span op and description for graphql to fit spec ([#2607](https://github.com/getsentry/sentry-java/pull/2607)) - -### Fixes - -- Fix timestamps of slow and frozen frames for profiles ([#2584](https://github.com/getsentry/sentry-java/pull/2584)) -- Deprecate reportFullDisplayed in favor of reportFullyDisplayed ([#2585](https://github.com/getsentry/sentry-java/pull/2585)) -- Add mechanism for logging integrations and update spring mechanism types ([#2595](https://github.com/getsentry/sentry-java/pull/2595)) - - NOTE: If you're using these mechanism types (`HandlerExceptionResolver`, `SentryWebExceptionHandler`) in your dashboards please update them to use the new types. -- Filter out session cookies sent by Spring and Spring Boot integrations ([#2593](https://github.com/getsentry/sentry-java/pull/2593)) - - We filter out some common cookies like JSESSIONID - - We also read the value from `server.servlet.session.cookie.name` and filter it out -- No longer send event / transaction to Sentry if `beforeSend` / `beforeSendTransaction` throws ([#2591](https://github.com/getsentry/sentry-java/pull/2591)) -- Add version to sentryClientName used in auth header ([#2596](https://github.com/getsentry/sentry-java/pull/2596)) -- Keep integration names from being obfuscated ([#2599](https://github.com/getsentry/sentry-java/pull/2599)) -- Change log level from INFO to WARN for error message indicating a failed Log4j2 Sentry.init ([#2606](https://github.com/getsentry/sentry-java/pull/2606)) - - The log message was often not visible as our docs suggest a minimum log level of WARN -- Fix session tracking on Android ([#2609](https://github.com/getsentry/sentry-java/pull/2609)) - - Incorrect number of session has been sent. In addition, some of the sessions were not properly ended, messing up Session Health Metrics. - -### Dependencies - -- Bump `opentelemetry-sdk` to `1.23.1` and `opentelemetry-javaagent` to `1.23.0` ([#2590](https://github.com/getsentry/sentry-java/pull/2590)) -- Bump Native SDK from v0.5.4 to v0.6.0 ([#2545](https://github.com/getsentry/sentry-java/pull/2545)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#060) - - [diff](https://github.com/getsentry/sentry-native/compare/0.5.4...0.6.0) - -## 6.15.0 - -### Features - -- Adjust time-to-full-display span if reportFullDisplayed is called too early ([#2550](https://github.com/getsentry/sentry-java/pull/2550)) -- Add `enableTracing` option ([#2530](https://github.com/getsentry/sentry-java/pull/2530)) - - This change is backwards compatible. The default is `null` meaning existing behaviour remains unchanged (setting either `tracesSampleRate` or `tracesSampler` enables performance). - - If set to `true`, performance is enabled, even if no `tracesSampleRate` or `tracesSampler` have been configured. - - If set to `false` performance is disabled, regardless of `tracesSampleRate` and `tracesSampler` options. -- Detect dependencies by listing MANIFEST.MF files at runtime ([#2538](https://github.com/getsentry/sentry-java/pull/2538)) -- Report integrations in use, report packages in use more consistently ([#2179](https://github.com/getsentry/sentry-java/pull/2179)) -- Implement `ThreadLocalAccessor` for propagating Sentry hub with reactor / WebFlux ([#2570](https://github.com/getsentry/sentry-java/pull/2570)) - - Requires `io.micrometer:context-propagation:1.0.2+` as well as Spring Boot 3.0.3+ - - Enable the feature by setting `sentry.reactive.thread-local-accessor-enabled=true` - - This is still considered experimental. Once we have enough feedback we may turn this on by default. - - Checkout the sample here: https://github.com/getsentry/sentry-java/tree/main/sentry-samples/sentry-samples-spring-boot-webflux-jakarta - - A new hub is now cloned from the main hub for every request - -### Fixes - -- Leave `inApp` flag for stack frames undecided in SDK if unsure and let ingestion decide instead ([#2547](https://github.com/getsentry/sentry-java/pull/2547)) -- Allow `0.0` error sample rate ([#2573](https://github.com/getsentry/sentry-java/pull/2573)) -- Fix memory leak in WebFlux related to an ever growing stack ([#2580](https://github.com/getsentry/sentry-java/pull/2580)) -- Use the same hub in WebFlux exception handler as we do in WebFilter ([#2566](https://github.com/getsentry/sentry-java/pull/2566)) -- Switch upstream Jetpack Compose dependencies to `compileOnly` in `sentry-compose-android` ([#2578](https://github.com/getsentry/sentry-java/pull/2578)) - - NOTE: If you're using Compose Navigation/User Interaction integrations, make sure to have the following dependencies on the classpath as we do not bring them in transitively anymore: - - `androidx.navigation:navigation-compose:` - - `androidx.compose.runtime:runtime:` - - `androidx.compose.ui:ui:` - -## 6.14.0 - -### Features - -- Add time-to-full-display span to Activity auto-instrumentation ([#2432](https://github.com/getsentry/sentry-java/pull/2432)) -- Add `main` flag to threads and `in_foreground` flag for app contexts ([#2516](https://github.com/getsentry/sentry-java/pull/2516)) - -### Fixes - -- Ignore Shutdown in progress when closing ShutdownHookIntegration ([#2521](https://github.com/getsentry/sentry-java/pull/2521)) -- Fix app start span end-time is wrong if SDK init is deferred ([#2519](https://github.com/getsentry/sentry-java/pull/2519)) -- Fix invalid session creation when app is launched in background ([#2543](https://github.com/getsentry/sentry-java/pull/2543)) - -## 6.13.1 - -### Fixes - -- Fix transaction performance collector oom ([#2505](https://github.com/getsentry/sentry-java/pull/2505)) -- Remove authority from URLs sent to Sentry ([#2366](https://github.com/getsentry/sentry-java/pull/2366)) -- Fix `sentry-bom` containing incorrect artifacts ([#2504](https://github.com/getsentry/sentry-java/pull/2504)) - -### Dependencies - -- Bump Native SDK from v0.5.3 to v0.5.4 ([#2500](https://github.com/getsentry/sentry-java/pull/2500)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#054) - - [diff](https://github.com/getsentry/sentry-native/compare/0.5.3...0.5.4) - -## 6.13.0 - -### Features - -- Send cpu usage percentage in profile payload ([#2469](https://github.com/getsentry/sentry-java/pull/2469)) -- Send transaction memory stats in profile payload ([#2447](https://github.com/getsentry/sentry-java/pull/2447)) -- Add cpu usage collection ([#2462](https://github.com/getsentry/sentry-java/pull/2462)) -- Improve ANR implementation: ([#2475](https://github.com/getsentry/sentry-java/pull/2475)) - - Add `abnormal_mechanism` to sessions for ANR rate calculation - - Always attach thread dump to ANR events - - Distinguish between foreground and background ANRs -- Improve possible date precision to 10 μs ([#2451](https://github.com/getsentry/sentry-java/pull/2451)) - -### Fixes - -- Fix performance collector setup called in main thread ([#2499](https://github.com/getsentry/sentry-java/pull/2499)) -- Expand guard against CVE-2018-9492 "Privilege Escalation via Content Provider" ([#2482](https://github.com/getsentry/sentry-java/pull/2482)) -- Prevent OOM by disabling TransactionPerformanceCollector for now ([#2498](https://github.com/getsentry/sentry-java/pull/2498)) - -## 6.12.1 - -### Fixes - -- Create timer in `TransactionPerformanceCollector` lazily ([#2478](https://github.com/getsentry/sentry-java/pull/2478)) - -## 6.12.0 - -### Features - -- Attach View Hierarchy to the errored/crashed events ([#2440](https://github.com/getsentry/sentry-java/pull/2440)) -- Collect memory usage in transactions ([#2445](https://github.com/getsentry/sentry-java/pull/2445)) -- Add `traceOptionsRequests` option to disable tracing of OPTIONS requests ([#2453](https://github.com/getsentry/sentry-java/pull/2453)) -- Extend list of HTTP headers considered sensitive ([#2455](https://github.com/getsentry/sentry-java/pull/2455)) - -### Fixes - -- Use a single TransactionPerfomanceCollector ([#2464](https://github.com/getsentry/sentry-java/pull/2464)) -- Don't override sdk name with Timber ([#2450](https://github.com/getsentry/sentry-java/pull/2450)) -- Set transactionNameSource to CUSTOM when setting transaction name ([#2405](https://github.com/getsentry/sentry-java/pull/2405)) -- Guard against CVE-2018-9492 "Privilege Escalation via Content Provider" ([#2466](https://github.com/getsentry/sentry-java/pull/2466)) - -## 6.11.0 - -### Features - -- Disable Android concurrent profiling ([#2434](https://github.com/getsentry/sentry-java/pull/2434)) -- Add logging for OpenTelemetry integration ([#2425](https://github.com/getsentry/sentry-java/pull/2425)) -- Auto add `OpenTelemetryLinkErrorEventProcessor` for Spring Boot ([#2429](https://github.com/getsentry/sentry-java/pull/2429)) - -### Fixes - -- Use minSdk compatible `Objects` class ([#2436](https://github.com/getsentry/sentry-java/pull/2436)) -- Prevent R8 from warning on missing classes, as we check for their presence at runtime ([#2439](https://github.com/getsentry/sentry-java/pull/2439)) - -### Dependencies - -- Bump Gradle from v7.5.1 to v7.6.0 ([#2438](https://github.com/getsentry/sentry-java/pull/2438)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v760) - - [diff](https://github.com/gradle/gradle/compare/v7.5.1...v7.6.0) - -## 6.10.0 - -### Features - -- Add time-to-initial-display span to Activity transactions ([#2369](https://github.com/getsentry/sentry-java/pull/2369)) -- Start a session after init if AutoSessionTracking is enabled ([#2356](https://github.com/getsentry/sentry-java/pull/2356)) -- Provide automatic breadcrumbs and transactions for click/scroll events for Compose ([#2390](https://github.com/getsentry/sentry-java/pull/2390)) -- Add `blocked_main_thread` and `call_stack` to File I/O spans to detect performance issues ([#2382](https://github.com/getsentry/sentry-java/pull/2382)) - -### Dependencies - -- Bump Native SDK from v0.5.2 to v0.5.3 ([#2423](https://github.com/getsentry/sentry-java/pull/2423)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#053) - - [diff](https://github.com/getsentry/sentry-native/compare/0.5.2...0.5.3) - -## 6.9.2 - -### Fixes - -- Updated ProfileMeasurementValue types ([#2412](https://github.com/getsentry/sentry-java/pull/2412)) -- Clear window reference only on activity stop in profileMeasurements collector ([#2407](https://github.com/getsentry/sentry-java/pull/2407)) -- No longer disable OpenTelemetry exporters in default Java Agent config ([#2408](https://github.com/getsentry/sentry-java/pull/2408)) -- Fix `ClassNotFoundException` for `io.sentry.spring.SentrySpringServletContainerInitializer` in `sentry-spring-jakarta` ([#2411](https://github.com/getsentry/sentry-java/issues/2411)) -- Fix `sentry-samples-spring-jakarta` ([#2411](https://github.com/getsentry/sentry-java/issues/2411)) - -### Features - -- Add SENTRY_AUTO_INIT environment variable to control OpenTelemetry Agent init ([#2410](https://github.com/getsentry/sentry-java/pull/2410)) -- Add OpenTelemetryLinkErrorEventProcessor for linking errors to traces created via OpenTelemetry ([#2418](https://github.com/getsentry/sentry-java/pull/2418)) - -### Dependencies - -- Bump OpenTelemetry to 1.20.1 and OpenTelemetry Java Agent to 1.20.2 ([#2420](https://github.com/getsentry/sentry-java/pull/2420)) - -## 6.9.1 - -### Fixes - -- OpenTelemetry modules were missing in `6.9.0` so we released the same code again as `6.9.1` including OpenTelemetry modules - -## 6.9.0 - -### Fixes - -- Use `canonicalName` in Fragment Integration for better de-obfuscation ([#2379](https://github.com/getsentry/sentry-java/pull/2379)) -- Fix Timber and Fragment integrations auto-installation for obfuscated builds ([#2379](https://github.com/getsentry/sentry-java/pull/2379)) -- Don't attach screenshots to events from Hybrid SDKs ([#2360](https://github.com/getsentry/sentry-java/pull/2360)) -- Ensure Hints do not cause memory leaks ([#2387](https://github.com/getsentry/sentry-java/pull/2387)) -- Do not attach empty `sentry-trace` and `baggage` headers ([#2385](https://github.com/getsentry/sentry-java/pull/2385)) - -### Features - -- Add beforeSendTransaction which allows users to filter and change transactions ([#2388](https://github.com/getsentry/sentry-java/pull/2388)) -- Add experimental support for OpenTelemetry ([README](sentry-opentelemetry/README.md))([#2344](https://github.com/getsentry/sentry-java/pull/2344)) - -### Dependencies - -- Update Spring Boot Jakarta to Spring Boot 3.0.0 ([#2389](https://github.com/getsentry/sentry-java/pull/2389)) -- Bump Spring Boot to 2.7.5 ([#2383](https://github.com/getsentry/sentry-java/pull/2383)) - -## 6.8.0 - -### Features - -- Add FrameMetrics to Android profiling data ([#2342](https://github.com/getsentry/sentry-java/pull/2342)) - -### Fixes - -- Remove profiler main thread io ([#2348](https://github.com/getsentry/sentry-java/pull/2348)) -- Fix ensure all options are processed before integrations are loaded ([#2377](https://github.com/getsentry/sentry-java/pull/2377)) - -## 6.7.1 - -### Fixes - -- Fix `Gpu.vendorId` should be a String ([#2343](https://github.com/getsentry/sentry-java/pull/2343)) -- Don't set device name on Android if `sendDefaultPii` is disabled ([#2354](https://github.com/getsentry/sentry-java/pull/2354)) -- Fix corrupted UUID on Motorola devices ([#2363](https://github.com/getsentry/sentry-java/pull/2363)) -- Fix ANR on dropped uncaught exception events ([#2368](https://github.com/getsentry/sentry-java/pull/2368)) - -### Features - -- Update Spring Boot Jakarta to Spring Boot 3.0.0-RC2 ([#2347](https://github.com/getsentry/sentry-java/pull/2347)) - -## 6.7.0 - -### Fixes - -- Use correct set-cookie for the HTTP Client response object ([#2326](https://github.com/getsentry/sentry-java/pull/2326)) -- Fix NoSuchElementException in CircularFifoQueue when cloning a Scope ([#2328](https://github.com/getsentry/sentry-java/pull/2328)) - -### Features - -- Customizable fragment lifecycle breadcrumbs ([#2299](https://github.com/getsentry/sentry-java/pull/2299)) -- Provide hook for Jetpack Compose navigation instrumentation ([#2320](https://github.com/getsentry/sentry-java/pull/2320)) -- Populate `event.modules` with dependencies metadata ([#2324](https://github.com/getsentry/sentry-java/pull/2324)) -- Support Spring 6 and Spring Boot 3 ([#2289](https://github.com/getsentry/sentry-java/pull/2289)) - -### Dependencies - -- Bump Native SDK from v0.5.1 to v0.5.2 ([#2315](https://github.com/getsentry/sentry-java/pull/2315)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#052) - - [diff](https://github.com/getsentry/sentry-native/compare/0.5.1...0.5.2) - -## 6.6.0 - -### Fixes - -- Ensure potential callback exceptions are caught #2123 ([#2291](https://github.com/getsentry/sentry-java/pull/2291)) -- Remove verbose FrameMetricsAggregator failure logging ([#2293](https://github.com/getsentry/sentry-java/pull/2293)) -- Ignore broken regex for tracePropagationTarget ([#2288](https://github.com/getsentry/sentry-java/pull/2288)) -- No longer serialize static fields; use toString as fallback ([#2309](https://github.com/getsentry/sentry-java/pull/2309)) -- Fix `SentryFileWriter`/`SentryFileOutputStream` append overwrites file contents ([#2304](https://github.com/getsentry/sentry-java/pull/2304)) -- Respect incoming parent sampled decision when continuing a trace ([#2311](https://github.com/getsentry/sentry-java/pull/2311)) - -### Features - -- Profile envelopes are sent directly from profiler ([#2298](https://github.com/getsentry/sentry-java/pull/2298)) -- Add support for using Encoder with logback.SentryAppender ([#2246](https://github.com/getsentry/sentry-java/pull/2246)) -- Report Startup Crashes ([#2277](https://github.com/getsentry/sentry-java/pull/2277)) -- HTTP Client errors for OkHttp ([#2287](https://github.com/getsentry/sentry-java/pull/2287)) -- Add option to enable or disable Frame Tracking ([#2314](https://github.com/getsentry/sentry-java/pull/2314)) - -### Dependencies - -- Bump Native SDK from v0.5.0 to v0.5.1 ([#2306](https://github.com/getsentry/sentry-java/pull/2306)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#051) - - [diff](https://github.com/getsentry/sentry-native/compare/0.5.0...0.5.1) - -## 6.5.0 - -### Fixes - -- Improve public facing API for creating Baggage from header ([#2284](https://github.com/getsentry/sentry-java/pull/2284)) - -## 6.5.0-beta.3 - -### Features - -- Provide API for attaching custom measurements to transactions ([#2260](https://github.com/getsentry/sentry-java/pull/2260)) -- Bump spring to 2.7.4 ([#2279](https://github.com/getsentry/sentry-java/pull/2279)) - -## 6.5.0-beta.2 - -### Features - -- Make user segment a top level property ([#2257](https://github.com/getsentry/sentry-java/pull/2257)) -- Replace user `other` with `data` ([#2258](https://github.com/getsentry/sentry-java/pull/2258)) -- `isTraceSampling` is now on by default. `tracingOrigins` has been replaced by `tracePropagationTargets` ([#2255](https://github.com/getsentry/sentry-java/pull/2255)) - -## 6.5.0-beta.1 - -### Features - -- Server-Side Dynamic Sampling Context support ([#2226](https://github.com/getsentry/sentry-java/pull/2226)) - -## 6.4.4 - -### Fixes - -- Fix ConcurrentModificationException due to FrameMetricsAggregator manipulation ([#2282](https://github.com/getsentry/sentry-java/pull/2282)) - -## 6.4.3 - -- Fix slow and frozen frames tracking ([#2271](https://github.com/getsentry/sentry-java/pull/2271)) - -## 6.4.2 - -### Fixes - -- Fixed AbstractMethodError when getting Lifecycle ([#2228](https://github.com/getsentry/sentry-java/pull/2228)) -- Missing unit fields for Android measurements ([#2204](https://github.com/getsentry/sentry-java/pull/2204)) -- Avoid sending empty profiles ([#2232](https://github.com/getsentry/sentry-java/pull/2232)) -- Fix file descriptor leak in FileIO instrumentation ([#2248](https://github.com/getsentry/sentry-java/pull/2248)) - -## 6.4.1 - -### Fixes - -- Fix memory leak caused by throwableToSpan ([#2227](https://github.com/getsentry/sentry-java/pull/2227)) - -## 6.4.0 - -### Fixes - -- make profiling rate defaults to 101 hz ([#2211](https://github.com/getsentry/sentry-java/pull/2211)) -- SentryOptions.setProfilingTracesIntervalMillis has been deprecated -- Added cpu architecture and default environment in profiles envelope ([#2207](https://github.com/getsentry/sentry-java/pull/2207)) -- SentryOptions.setProfilingEnabled has been deprecated in favor of setProfilesSampleRate -- Use toString for enum serialization ([#2220](https://github.com/getsentry/sentry-java/pull/2220)) - -### Features - -- Concurrent profiling 3 - added truncation reason ([#2247](https://github.com/getsentry/sentry-java/pull/2247)) -- Concurrent profiling 2 - added list of transactions ([#2218](https://github.com/getsentry/sentry-java/pull/2218)) -- Concurrent profiling 1 - added envelope payload data format ([#2216](https://github.com/getsentry/sentry-java/pull/2216)) -- Send source for transactions ([#2180](https://github.com/getsentry/sentry-java/pull/2180)) -- Add profilesSampleRate and profileSampler options for Android sdk ([#2184](https://github.com/getsentry/sentry-java/pull/2184)) -- Add baggage header to RestTemplate ([#2206](https://github.com/getsentry/sentry-java/pull/2206)) -- Bump Native SDK from v0.4.18 to v0.5.0 ([#2199](https://github.com/getsentry/sentry-java/pull/2199)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#050) - - [diff](https://github.com/getsentry/sentry-native/compare/0.4.18...0.5.0) -- Bump Gradle from v7.5.0 to v7.5.1 ([#2212](https://github.com/getsentry/sentry-java/pull/2212)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v751) - - [diff](https://github.com/gradle/gradle/compare/v7.5.0...v7.5.1) - -## 6.3.1 - -### Fixes - -- Prevent NPE by checking SentryTracer.timer for null again inside synchronized ([#2200](https://github.com/getsentry/sentry-java/pull/2200)) -- Weakly reference Activity for transaction finished callback ([#2203](https://github.com/getsentry/sentry-java/pull/2203)) -- `attach-screenshot` set on Manual init. didn't work ([#2186](https://github.com/getsentry/sentry-java/pull/2186)) -- Remove extra space from `spring.factories` causing issues in old versions of Spring Boot ([#2181](https://github.com/getsentry/sentry-java/pull/2181)) - - -### Features - -- Bump Native SDK to v0.4.18 ([#2154](https://github.com/getsentry/sentry-java/pull/2154)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0418) - - [diff](https://github.com/getsentry/sentry-native/compare/0.4.17...0.4.18) -- Bump Gradle to v7.5.0 ([#2174](https://github.com/getsentry/sentry-java/pull/2174), [#2191](https://github.com/getsentry/sentry-java/pull/2191)) - - [changelog](https://github.com/gradle/gradle/blob/master/CHANGELOG.md#v750) - - [diff](https://github.com/gradle/gradle/compare/v7.4.2...v7.5.0) - -## 6.3.0 - -### Features - -- Switch upstream dependencies to `compileOnly` in integrations ([#2175](https://github.com/getsentry/sentry-java/pull/2175)) - -### Fixes - -- Lazily retrieve HostnameCache in MainEventProcessor ([#2170](https://github.com/getsentry/sentry-java/pull/2170)) - -## 6.2.1 - -### Fixes - -- Only send userid in Dynamic Sampling Context if sendDefaultPii is true ([#2147](https://github.com/getsentry/sentry-java/pull/2147)) -- Remove userId from baggage due to PII ([#2157](https://github.com/getsentry/sentry-java/pull/2157)) - -### Features - -- Add integration for Apollo-Kotlin 3 ([#2109](https://github.com/getsentry/sentry-java/pull/2109)) -- New package `sentry-android-navigation` for AndroidX Navigation support ([#2136](https://github.com/getsentry/sentry-java/pull/2136)) -- New package `sentry-compose` for Jetpack Compose support (Navigation) ([#2136](https://github.com/getsentry/sentry-java/pull/2136)) -- Add sample rate to baggage as well as trace in envelope header and flatten user ([#2135](https://github.com/getsentry/sentry-java/pull/2135)) - -Breaking Changes: -- The boolean parameter `samplingDecision` in the `TransactionContext` constructor has been replaced with a `TracesSamplingDecision` object. Feel free to ignore the `@ApiStatus.Internal` in this case. - -## 6.1.4 - -### Fixes - -- Filter out app starts with more than 60s ([#2127](https://github.com/getsentry/sentry-java/pull/2127)) - -## 6.1.3 - -### Fixes - -- Fix thread leak due to Timer being created and never cancelled ([#2131](https://github.com/getsentry/sentry-java/pull/2131)) - -## 6.1.2 - -### Fixes - -- Swallow error when reading ActivityManager#getProcessesInErrorState instead of crashing ([#2114](https://github.com/getsentry/sentry-java/pull/2114)) -- Use charset string directly as StandardCharsets is not available on earlier Android versions ([#2111](https://github.com/getsentry/sentry-java/pull/2111)) - -## 6.1.1 - -### Features - -- Replace `tracestate` header with `baggage` header ([#2078](https://github.com/getsentry/sentry-java/pull/2078)) -- Allow opting out of device info collection that requires Inter-Process Communication (IPC) ([#2100](https://github.com/getsentry/sentry-java/pull/2100)) - -## 6.1.0 - -### Features - -- Implement local scope by adding overloads to the capture methods that accept a ScopeCallback ([#2084](https://github.com/getsentry/sentry-java/pull/2084)) -- SentryOptions#merge is now public and can be used to load ExternalOptions ([#2088](https://github.com/getsentry/sentry-java/pull/2088)) - -### Fixes - -- Fix proguard rules to work R8 [issue](https://issuetracker.google.com/issues/235733922) around on AGP 7.3.0-betaX and 7.4.0-alphaX ([#2094](https://github.com/getsentry/sentry-java/pull/2094)) -- Fix GraalVM Native Image compatibility ([#2172](https://github.com/getsentry/sentry-java/pull/2172)) - -## 6.0.0 - -### Sentry Self-hosted Compatibility - -- Starting with version `6.0.0` of the `sentry` package, [Sentry's self hosted version >= v21.9.0](https://github.com/getsentry/self-hosted/releases) is required or you have to manually disable sending client reports via the `sendClientReports` option. This only applies to self-hosted Sentry. If you are using [sentry.io](https://sentry.io), no action is needed. - -### Features - -- Allow optimization and obfuscation of the SDK by reducing proguard rules ([#2031](https://github.com/getsentry/sentry-java/pull/2031)) -- Relax TransactionNameProvider ([#1861](https://github.com/getsentry/sentry-java/pull/1861)) -- Use float instead of Date for protocol types for higher precision ([#1737](https://github.com/getsentry/sentry-java/pull/1737)) -- Allow setting SDK info (name & version) in manifest ([#2016](https://github.com/getsentry/sentry-java/pull/2016)) -- Allow setting native Android SDK name during build ([#2035](https://github.com/getsentry/sentry-java/pull/2035)) -- Include application permissions in Android events ([#2018](https://github.com/getsentry/sentry-java/pull/2018)) -- Automatically create transactions for UI events ([#1975](https://github.com/getsentry/sentry-java/pull/1975)) -- Hints are now used via a Hint object and passed into beforeSend and EventProcessor as @NotNull Hint object ([#2045](https://github.com/getsentry/sentry-java/pull/2045)) -- Attachments can be manipulated via hint ([#2046](https://github.com/getsentry/sentry-java/pull/2046)) -- Add sentry-servlet-jakarta module ([#1987](https://github.com/getsentry/sentry-java/pull/1987)) -- Add client reports ([#1982](https://github.com/getsentry/sentry-java/pull/1982)) -- Screenshot is taken when there is an error ([#1967](https://github.com/getsentry/sentry-java/pull/1967)) -- Add Android profiling traces ([#1897](https://github.com/getsentry/sentry-java/pull/1897)) ([#1959](https://github.com/getsentry/sentry-java/pull/1959)) and its tests ([#1949](https://github.com/getsentry/sentry-java/pull/1949)) -- Enable enableScopeSync by default for Android ([#1928](https://github.com/getsentry/sentry-java/pull/1928)) -- Feat: Vendor JSON ([#1554](https://github.com/getsentry/sentry-java/pull/1554)) - - Introduce `JsonSerializable` and `JsonDeserializer` interfaces for manual json - serialization/deserialization. - - Introduce `JsonUnknwon` interface to preserve unknown properties when deserializing/serializing - SDK classes. - - When passing custom objects, for example in `Contexts`, these are supported for serialization: - - `JsonSerializable` - - `Map`, `Collection`, `Array`, `String` and all primitive types. - - Objects with the help of refection. - - `Map`, `Collection`, `Array`, `String` and all primitive types. - - Call `toString()` on objects that have a cyclic reference to a ancestor object. - - Call `toString()` where object graphs exceed max depth. - - Remove `gson` dependency. - - Remove `IUnknownPropertiesConsumer` -- Pass MDC tags as Sentry tags ([#1954](https://github.com/getsentry/sentry-java/pull/1954)) - -### Fixes - -- Calling Sentry.init and specifying contextTags now has an effect on the Logback SentryAppender ([#2052](https://github.com/getsentry/sentry-java/pull/2052)) -- Calling Sentry.init and specifying contextTags now has an effect on the Log4j SentryAppender ([#2054](https://github.com/getsentry/sentry-java/pull/2054)) -- Calling Sentry.init and specifying contextTags now has an effect on the jul SentryAppender ([#2057](https://github.com/getsentry/sentry-java/pull/2057)) -- Update Spring Boot dependency to 2.6.8 and fix the CVE-2022-22970 ([#2068](https://github.com/getsentry/sentry-java/pull/2068)) -- Sentry can now self heal after a Thread had its currentHub set to a NoOpHub ([#2076](https://github.com/getsentry/sentry-java/pull/2076)) -- No longer close OutputStream that is passed into JsonSerializer ([#2029](https://github.com/getsentry/sentry-java/pull/2029)) -- Fix setting context tags on events captured by Spring ([#2060](https://github.com/getsentry/sentry-java/pull/2060)) -- Isolate cached events with hashed DSN subfolder ([#2038](https://github.com/getsentry/sentry-java/pull/2038)) -- SentryThread.current flag will not be overridden by DefaultAndroidEventProcessor if already set ([#2050](https://github.com/getsentry/sentry-java/pull/2050)) -- Fix serialization of Long inside of Request.data ([#2051](https://github.com/getsentry/sentry-java/pull/2051)) -- Update sentry-native to 0.4.17 ([#2033](https://github.com/getsentry/sentry-java/pull/2033)) -- Update Gradle to 7.4.2 and AGP to 7.2 ([#2042](https://github.com/getsentry/sentry-java/pull/2042)) -- Change order of event filtering mechanisms ([#2001](https://github.com/getsentry/sentry-java/pull/2001)) -- Only send session update for dropped events if state changed ([#2002](https://github.com/getsentry/sentry-java/pull/2002)) -- Android profiling initializes on first profile start ([#2009](https://github.com/getsentry/sentry-java/pull/2009)) -- Profiling rate decreased from 300hz to 100hz ([#1997](https://github.com/getsentry/sentry-java/pull/1997)) -- Allow disabling sending of client reports via Android Manifest and external options ([#2007](https://github.com/getsentry/sentry-java/pull/2007)) -- Ref: Upgrade Spring Boot dependency to 2.5.13 ([#2011](https://github.com/getsentry/sentry-java/pull/2011)) -- Ref: Make options.printUncaughtStackTrace primitive type ([#1995](https://github.com/getsentry/sentry-java/pull/1995)) -- Ref: Remove not needed interface abstractions on Android ([#1953](https://github.com/getsentry/sentry-java/pull/1953)) -- Ref: Make hints Map instead of only Object ([#1929](https://github.com/getsentry/sentry-java/pull/1929)) -- Ref: Simplify DateUtils with ISO8601Utils ([#1837](https://github.com/getsentry/sentry-java/pull/1837)) -- Ref: Remove deprecated and scheduled fields ([#1875](https://github.com/getsentry/sentry-java/pull/1875)) -- Ref: Add shutdownTimeoutMillis in favor of shutdownTimeout ([#1873](https://github.com/getsentry/sentry-java/pull/1873)) -- Ref: Remove Attachment ContentType since the Server infers it ([#1874](https://github.com/getsentry/sentry-java/pull/1874)) -- Ref: Bind external properties to a dedicated class. ([#1750](https://github.com/getsentry/sentry-java/pull/1750)) -- Ref: Debug log serializable objects ([#1795](https://github.com/getsentry/sentry-java/pull/1795)) -- Ref: catch Throwable instead of Exception to suppress internal SDK errors ([#1812](https://github.com/getsentry/sentry-java/pull/1812)) -- `SentryOptions` can merge properties from `ExternalOptions` instead of another instance of `SentryOptions` -- Following boolean properties from `SentryOptions` that allowed `null` values are now not nullable - `debug`, `enableUncaughtExceptionHandler`, `enableDeduplication` -- `SentryOptions` cannot be created anymore using `PropertiesProvider` with `SentryOptions#from` method. Use `ExternalOptions#from` instead and merge created object with `SentryOptions#merge` -- Bump: Kotlin to 1.5 and compatibility to 1.4 for sentry-android-timber ([#1815](https://github.com/getsentry/sentry-java/pull/1815)) - -## 5.7.4 - -### Fixes - -* Change order of event filtering mechanisms and only send session update for dropped events if session state changed (#2028) - -## 5.7.3 - -### Fixes - -- Sentry Timber integration throws an exception when using args ([#1986](https://github.com/getsentry/sentry-java/pull/1986)) - -## 5.7.2 - -### Fixes - -- Bring back support for `Timber.tag` ([#1974](https://github.com/getsentry/sentry-java/pull/1974)) - -## 5.7.1 - -### Fixes - -- Sentry Timber integration does not submit msg.formatted breadcrumbs ([#1957](https://github.com/getsentry/sentry-java/pull/1957)) -- ANR WatchDog won't crash on SecurityException ([#1962](https://github.com/getsentry/sentry-java/pull/1962)) - -## 5.7.0 - -### Features - -- Automatically enable `Timber` and `Fragment` integrations if they are present on the classpath ([#1936](https://github.com/getsentry/sentry-java/pull/1936)) - -## 5.6.3 - -### Fixes - -- If transaction or span is finished, do not allow to mutate ([#1940](https://github.com/getsentry/sentry-java/pull/1940)) -- Keep used AndroidX classes from obfuscation (Fixes UI breadcrumbs and Slow/Frozen frames) ([#1942](https://github.com/getsentry/sentry-java/pull/1942)) - -## 5.6.2 - -### Fixes - -- Ref: Make ActivityFramesTracker public to be used by Hybrid SDKs ([#1931](https://github.com/getsentry/sentry-java/pull/1931)) -- Bump: AGP to 7.1.2 ([#1930](https://github.com/getsentry/sentry-java/pull/1930)) -- NPE while adding "response_body_size" breadcrumb, when response body length is unknown ([#1908](https://github.com/getsentry/sentry-java/pull/1908)) -- Do not include stacktrace frames into Timber message ([#1898](https://github.com/getsentry/sentry-java/pull/1898)) -- Potential memory leaks ([#1909](https://github.com/getsentry/sentry-java/pull/1909)) - -Breaking changes: -`Timber.tag` is no longer supported by our [Timber integration](https://docs.sentry.io/platforms/android/configuration/integrations/timber/) and will not appear on Sentry for error events. -Please vote on this [issue](https://github.com/getsentry/sentry-java/issues/1900), if you'd like us to provide support for that. - -## 5.6.2-beta.3 - -### Fixes - -- Ref: Make ActivityFramesTracker public to be used by Hybrid SDKs ([#1931](https://github.com/getsentry/sentry-java/pull/1931)) -- Bump: AGP to 7.1.2 ([#1930](https://github.com/getsentry/sentry-java/pull/1930)) - -## 5.6.2-beta.2 - -### Fixes - -- NPE while adding "response_body_size" breadcrumb, when response body length is unknown ([#1908](https://github.com/getsentry/sentry-java/pull/1908)) - -## 5.6.2-beta.1 - -### Fixes - -- Do not include stacktrace frames into Timber message ([#1898](https://github.com/getsentry/sentry-java/pull/1898)) -- Potential memory leaks ([#1909](https://github.com/getsentry/sentry-java/pull/1909)) - -Breaking changes: -`Timber.tag` is no longer supported by our [Timber integration](https://docs.sentry.io/platforms/android/configuration/integrations/timber/) and will not appear on Sentry for error events. -Please vote on this [issue](https://github.com/getsentry/sentry-java/issues/1900), if you'd like us to provide support for that. - -## 5.6.1 - -### Features - -- Add options.printUncaughtStackTrace to print uncaught exceptions ([#1890](https://github.com/getsentry/sentry-java/pull/1890)) - -### Fixes - -- NPE while adding "response_body_size" breadcrumb, when response body is null ([#1884](https://github.com/getsentry/sentry-java/pull/1884)) -- Bump: AGP to 7.1.0 ([#1892](https://github.com/getsentry/sentry-java/pull/1892)) - -## 5.6.0 - -### Features - -- Add breadcrumbs support for UI events (automatically captured) ([#1876](https://github.com/getsentry/sentry-java/pull/1876)) - -### Fixes - -- Change scope of servlet-api to compileOnly ([#1880](https://github.com/getsentry/sentry-java/pull/1880)) - -## 5.5.3 - -### Fixes - -- Do not create SentryExceptionResolver bean when Spring MVC is not on the classpath ([#1865](https://github.com/getsentry/sentry-java/pull/1865)) - -## 5.5.2 - -### Fixes - -- Detect App Cold start correctly for Hybrid SDKs ([#1855](https://github.com/getsentry/sentry-java/pull/1855)) -- Bump: log4j to 2.17.0 ([#1852](https://github.com/getsentry/sentry-java/pull/1852)) -- Bump: logback to 1.2.9 ([#1853](https://github.com/getsentry/sentry-java/pull/1853)) - -## 5.5.1 - -### Fixes - -- Bump: log4j to 2.16.0 ([#1845](https://github.com/getsentry/sentry-java/pull/1845)) -- Make App start cold/warm visible to Hybrid SDKs ([#1848](https://github.com/getsentry/sentry-java/pull/1848)) - -## 5.5.0 - -### Features - -- Add locale to device context and deprecate language ([#1832](https://github.com/getsentry/sentry-java/pull/1832)) -- Add `SentryFileInputStream` and `SentryFileOutputStream` for File I/O performance instrumentation ([#1826](https://github.com/getsentry/sentry-java/pull/1826)) -- Add `SentryFileReader` and `SentryFileWriter` for File I/O instrumentation ([#1843](https://github.com/getsentry/sentry-java/pull/1843)) - -### Fixes - -- Bump: log4j to 2.15.0 ([#1839](https://github.com/getsentry/sentry-java/pull/1839)) -- Ref: Rename Fragment span operation from `ui.fragment.load` to `ui.load` ([#1824](https://github.com/getsentry/sentry-java/pull/1824)) -- Ref: change `java.util.Random` to `java.security.SecureRandom` for possible security reasons ([#1831](https://github.com/getsentry/sentry-java/pull/1831)) - -## 5.4.3 - -### Fixes - -- Only report App start measurement for full launch on Android ([#1821](https://github.com/getsentry/sentry-java/pull/1821)) - -## 5.4.2 - -### Fixes - -- Ref: catch Throwable instead of Exception to suppress internal SDK errors ([#1812](https://github.com/getsentry/sentry-java/pull/1812)) - -## 5.4.1 - -### Features - -- Refactor OkHttp and Apollo to Kotlin functional interfaces ([#1797](https://github.com/getsentry/sentry-java/pull/1797)) -- Add secondary constructor to SentryInstrumentation ([#1804](https://github.com/getsentry/sentry-java/pull/1804)) - -### Fixes - -- Do not start fragment span if not added to the Activity ([#1813](https://github.com/getsentry/sentry-java/pull/1813)) - -## 5.4.0 - -### Features - -- Add `graphql-java` instrumentation ([#1777](https://github.com/getsentry/sentry-java/pull/1777)) - -### Fixes - -- Do not crash when event processors throw a lower level Throwable class ([#1800](https://github.com/getsentry/sentry-java/pull/1800)) -- ActivityFramesTracker does not throw if Activity has no observers ([#1799](https://github.com/getsentry/sentry-java/pull/1799)) - -## 5.3.0 - -### Features - -- Add datasource tracing with P6Spy ([#1784](https://github.com/getsentry/sentry-java/pull/1784)) - -### Fixes - -- ActivityFramesTracker does not throw if Activity has not been added ([#1782](https://github.com/getsentry/sentry-java/pull/1782)) -- PerformanceAndroidEventProcessor uses up to date isTracingEnabled set on Configuration callback ([#1786](https://github.com/getsentry/sentry-java/pull/1786)) - -## 5.2.4 - -### Fixes - -- Window.FEATURE_NO_TITLE does not work when using activity traces ([#1769](https://github.com/getsentry/sentry-java/pull/1769)) -- unregister UncaughtExceptionHandler on close ([#1770](https://github.com/getsentry/sentry-java/pull/1770)) - -## 5.2.3 - -### Fixes - -- Make ActivityFramesTracker operations thread-safe ([#1762](https://github.com/getsentry/sentry-java/pull/1762)) -- Clone Scope Contexts ([#1763](https://github.com/getsentry/sentry-java/pull/1763)) -- Bump: AGP to 7.0.3 ([#1765](https://github.com/getsentry/sentry-java/pull/1765)) - -## 5.2.2 - -### Fixes - -- Close HostnameCache#executorService on SentryClient#close ([#1757](https://github.com/getsentry/sentry-java/pull/1757)) - -## 5.2.1 - -### Features - -- Add isCrashedLastRun support ([#1739](https://github.com/getsentry/sentry-java/pull/1739)) -- Attach Java vendor and version to events and transactions ([#1703](https://github.com/getsentry/sentry-java/pull/1703)) - -### Fixes - -- Handle exception if Context.registerReceiver throws ([#1747](https://github.com/getsentry/sentry-java/pull/1747)) - -## 5.2.0 - -### Features - -- Allow setting proguard via Options and/or external resources ([#1728](https://github.com/getsentry/sentry-java/pull/1728)) -- Add breadcrumbs for the Apollo integration ([#1726](https://github.com/getsentry/sentry-java/pull/1726)) - -### Fixes - -- Don't set lastEventId for transactions ([#1727](https://github.com/getsentry/sentry-java/pull/1727)) -- ActivityLifecycleIntegration#appStartSpan memory leak ([#1732](https://github.com/getsentry/sentry-java/pull/1732)) - -## 5.2.0-beta.3 - -### Features - -- Add "data" to spans ([#1717](https://github.com/getsentry/sentry-java/pull/1717)) - -### Fixes - -- Check at runtime if AndroidX.Core is available ([#1718](https://github.com/getsentry/sentry-java/pull/1718)) -- Should not capture unfinished transaction ([#1719](https://github.com/getsentry/sentry-java/pull/1719)) - -## 5.2.0-beta.2 - -### Fixes - -- Bump AGP to 7.0.2 ([#1650](https://github.com/getsentry/sentry-java/pull/1650)) -- Drop spans in BeforeSpanCallback. ([#1713](https://github.com/getsentry/sentry-java/pull/1713)) - -## 5.2.0-beta.1 - -### Features - -- Add tracestate HTTP header support ([#1683](https://github.com/getsentry/sentry-java/pull/1683)) -- Add option to filter which origins receive tracing headers ([#1698](https://github.com/getsentry/sentry-java/pull/1698)) -- Include unfinished spans in transaction ([#1699](https://github.com/getsentry/sentry-java/pull/1699)) -- Add static helpers for creating breadcrumbs ([#1702](https://github.com/getsentry/sentry-java/pull/1702)) -- Performance support for Android Apollo ([#1705](https://github.com/getsentry/sentry-java/pull/1705)) - -### Fixes - -- Move tags from transaction.contexts.trace.tags to transaction.tags ([#1700](https://github.com/getsentry/sentry-java/pull/1700)) - -Breaking changes: - -- Updated proguard keep rule for enums, which affects consumer application code ([#1694](https://github.com/getsentry/sentry-java/pull/1694)) - -## 5.1.2 - -### Fixes - -- Servlet 3.1 compatibility issue ([#1681](https://github.com/getsentry/sentry-java/pull/1681)) -- Do not drop Contexts key if Collection, Array or Char ([#1680](https://github.com/getsentry/sentry-java/pull/1680)) - -## 5.1.1 - -### Features - -- Add support for async methods in Spring MVC ([#1652](https://github.com/getsentry/sentry-java/pull/1652)) -- Add secondary constructor taking IHub to SentryOkHttpInterceptor ([#1657](https://github.com/getsentry/sentry-java/pull/1657)) -- Merge external map properties ([#1656](https://github.com/getsentry/sentry-java/pull/1656)) - -### Fixes - -- Remove onActivityPreCreated call in favor of onActivityCreated ([#1661](https://github.com/getsentry/sentry-java/pull/1661)) -- Do not crash if SENSOR_SERVICE throws ([#1655](https://github.com/getsentry/sentry-java/pull/1655)) -- Make sure scope is popped when processing request results in exception ([#1665](https://github.com/getsentry/sentry-java/pull/1665)) - -## 5.1.0 - -### Features - -- Spring WebClient integration ([#1621](https://github.com/getsentry/sentry-java/pull/1621)) -- OpenFeign integration ([#1632](https://github.com/getsentry/sentry-java/pull/1632)) -- Add more convenient way to pass BeforeSpanCallback in OpenFeign integration ([#1637](https://github.com/getsentry/sentry-java/pull/1637)) - -### Fixes - -- Bump: sentry-native to 0.4.12 ([#1651](https://github.com/getsentry/sentry-java/pull/1651)) - -## 5.1.0-beta.9 - -- No documented changes. - -## 5.1.0-beta.8 - -### Features - -- Generate Sentry BOM ([#1486](https://github.com/getsentry/sentry-java/pull/1486)) - -## 5.1.0-beta.7 - -### Features - -- Slow/Frozen frames metrics ([#1609](https://github.com/getsentry/sentry-java/pull/1609)) - -## 5.1.0-beta.6 - -### Features - -- Add request body extraction for Spring MVC integration ([#1595](https://github.com/getsentry/sentry-java/pull/1595)) - -### Fixes - -- set min sdk version of sentry-android-fragment to API 14 ([#1608](https://github.com/getsentry/sentry-java/pull/1608)) -- Ser/Deser of the UserFeedback from cached envelope ([#1611](https://github.com/getsentry/sentry-java/pull/1611)) - -## 5.1.0-beta.5 - -### Fixes - -- Make SentryAppender non-final for Log4j2 and Logback ([#1603](https://github.com/getsentry/sentry-java/pull/1603)) -- Do not throw IAE when tracing header contain invalid trace id ([#1605](https://github.com/getsentry/sentry-java/pull/1605)) - -## 5.1.0-beta.4 - -### Fixes - -- Update sentry-native to 0.4.11 ([#1591](https://github.com/getsentry/sentry-java/pull/1591)) - -## 5.1.0-beta.3 - -### Features - -- Spring Webflux integration ([#1529](https://github.com/getsentry/sentry-java/pull/1529)) - -## 5.1.0-beta.2 - -### Features - -- Support transaction waiting for children to finish. ([#1535](https://github.com/getsentry/sentry-java/pull/1535)) -- Capture logged marker in log4j2 and logback appenders ([#1551](https://github.com/getsentry/sentry-java/pull/1551)) -- Allow clearing of attachments in the scope ([#1562](https://github.com/getsentry/sentry-java/pull/1562)) -- Set mechanism type in SentryExceptionResolver ([#1556](https://github.com/getsentry/sentry-java/pull/1556)) -- Perf. for fragments ([#1528](https://github.com/getsentry/sentry-java/pull/1528)) - -### Fixes - -- Handling missing Spring Security on classpath on Java 8 ([#1552](https://github.com/getsentry/sentry-java/pull/1552)) -- Use a different method to get strings from JNI, and avoid excessive Stack Space usage. ([#1214](https://github.com/getsentry/sentry-java/pull/1214)) -- Add data field to SentrySpan ([#1555](https://github.com/getsentry/sentry-java/pull/1555)) -- Clock drift issue when calling DateUtils#getDateTimeWithMillisPrecision ([#1557](https://github.com/getsentry/sentry-java/pull/1557)) -- Prefer snake case for HTTP integration data keys ([#1559](https://github.com/getsentry/sentry-java/pull/1559)) -- Assign lastEventId only if event was queued for submission ([#1565](https://github.com/getsentry/sentry-java/pull/1565)) - -## 5.1.0-beta.1 - -### Features - -- Measure app start time ([#1487](https://github.com/getsentry/sentry-java/pull/1487)) -- Automatic breadcrumbs logging for fragment lifecycle ([#1522](https://github.com/getsentry/sentry-java/pull/1522)) - -## 5.0.1 - -### Fixes - -- Sources and Javadoc artifacts were mixed up ([#1515](https://github.com/getsentry/sentry-java/pull/1515)) - -## 5.0.0 - -This release brings many improvements but also new features: - -- OkHttp Interceptor for Android ([#1330](https://github.com/getsentry/sentry-java/pull/1330)) -- GraalVM Native Image Compatibility ([#1329](https://github.com/getsentry/sentry-java/pull/1329)) -- Add option to ignore exceptions by type ([#1352](https://github.com/getsentry/sentry-java/pull/1352)) -- Enrich transactions with device contexts ([#1430](https://github.com/getsentry/sentry-java/pull/1430)) ([#1469](https://github.com/getsentry/sentry-java/pull/1469)) -- Better interoperability with Kotlin null-safety ([#1439](https://github.com/getsentry/sentry-java/pull/1439)) and ([#1462](https://github.com/getsentry/sentry-java/pull/1462)) -- Add coroutines support ([#1479](https://github.com/getsentry/sentry-java/pull/1479)) -- OkHttp callback for Customising the Span ([#1478](https://github.com/getsentry/sentry-java/pull/1478)) -- Add breadcrumb in Spring RestTemplate integration ([#1481](https://github.com/getsentry/sentry-java/pull/1481)) - -Breaking changes: - -- Migration Guide for [Java](https://docs.sentry.io/platforms/java/migration/) -- Migration Guide for [Android](https://docs.sentry.io/platforms/android/migration/) - -Other fixes: - -- Fix: Add attachmentType to envelope ser/deser. ([#1504](https://github.com/getsentry/sentry-java/pull/1504)) - -Thank you: - -- @maciejwalkowiak for coding most of it. - -## 5.0.0-beta.7 - -### Fixes - - -- Ref: Deprecate SentryBaseEvent#getOriginThrowable and add SentryBaseEvent#getThrowableMechanism ([#1502](https://github.com/getsentry/sentry-java/pull/1502)) -- Graceful Shutdown flushes event instead of Closing SDK ([#1500](https://github.com/getsentry/sentry-java/pull/1500)) -- Do not append threads that come from the EnvelopeFileObserver ([#1501](https://github.com/getsentry/sentry-java/pull/1501)) -- Ref: Deprecate cacheDirSize and add maxCacheItems ([#1499](https://github.com/getsentry/sentry-java/pull/1499)) -- Append all threads if Hint is Cached but attachThreads is enabled ([#1503](https://github.com/getsentry/sentry-java/pull/1503)) - -## 5.0.0-beta.6 - -### Features - -- Add secondary constructor to SentryOkHttpInterceptor ([#1491](https://github.com/getsentry/sentry-java/pull/1491)) -- Add option to enable debug mode in Log4j2 integration ([#1492](https://github.com/getsentry/sentry-java/pull/1492)) - -### Fixes - -- Ref: Replace clone() with copy constructor ([#1496](https://github.com/getsentry/sentry-java/pull/1496)) - -## 5.0.0-beta.5 - -### Features - -- OkHttp callback for Customising the Span ([#1478](https://github.com/getsentry/sentry-java/pull/1478)) -- Add breadcrumb in Spring RestTemplate integration ([#1481](https://github.com/getsentry/sentry-java/pull/1481)) -- Add coroutines support ([#1479](https://github.com/getsentry/sentry-java/pull/1479)) - -### Fixes - -- Cloning Stack ([#1483](https://github.com/getsentry/sentry-java/pull/1483)) - -## 5.0.0-beta.4 - -### Fixes - -- Enrich Transactions with Context Data ([#1469](https://github.com/getsentry/sentry-java/pull/1469)) -- Bump: Apache HttpClient to 5.0.4 ([#1476](https://github.com/getsentry/sentry-java/pull/1476)) - -## 5.0.0-beta.3 - -### Fixes - -- Handling immutable collections on SentryEvent and protocol objects ([#1468](https://github.com/getsentry/sentry-java/pull/1468)) -- Associate event with transaction when thrown exception is not a direct cause ([#1463](https://github.com/getsentry/sentry-java/pull/1463)) -- Ref: nullability annotations to Sentry module ([#1439](https://github.com/getsentry/sentry-java/pull/1439)) and ([#1462](https://github.com/getsentry/sentry-java/pull/1462)) -- NPE when adding Context Data with null values for log4j2 ([#1465](https://github.com/getsentry/sentry-java/pull/1465)) - -## 5.0.0-beta.2 - -### Fixes - -- sentry-android-timber package sets sentry.java.android.timber as SDK name ([#1456](https://github.com/getsentry/sentry-java/pull/1456)) -- When AppLifecycleIntegration is closed, it should remove observer using UI thread ([#1459](https://github.com/getsentry/sentry-java/pull/1459)) -- Bump: AGP to 4.2.0 ([#1460](https://github.com/getsentry/sentry-java/pull/1460)) - -Breaking Changes: - -- Remove: Settings.Secure.ANDROID_ID in favor of generated installationId ([#1455](https://github.com/getsentry/sentry-java/pull/1455)) -- Rename: enableSessionTracking to enableAutoSessionTracking ([#1457](https://github.com/getsentry/sentry-java/pull/1457)) - -## 5.0.0-beta.1 - -### Fixes - -- Ref: Refactor converting HttpServletRequest to Sentry Request in Spring integration ([#1387](https://github.com/getsentry/sentry-java/pull/1387)) -- Bump: sentry-native to 0.4.9 ([#1431](https://github.com/getsentry/sentry-java/pull/1431)) -- Activity tracing auto instrumentation for Android API < 29 ([#1402](https://github.com/getsentry/sentry-java/pull/1402)) -- use connection and read timeouts in ApacheHttpClient based transport ([#1397](https://github.com/getsentry/sentry-java/pull/1397)) -- set correct transaction status for unhandled exceptions in SentryTracingFilter ([#1406](https://github.com/getsentry/sentry-java/pull/1406)) -- handle network errors in SentrySpanClientHttpRequestInterceptor ([#1407](https://github.com/getsentry/sentry-java/pull/1407)) -- set scope on transaction ([#1409](https://github.com/getsentry/sentry-java/pull/1409)) -- set status and associate events with transactions ([#1426](https://github.com/getsentry/sentry-java/pull/1426)) -- Do not set free memory and is low memory fields when it's a NDK hard crash ([#1399](https://github.com/getsentry/sentry-java/pull/1399)) -- Apply user from the scope to transaction ([#1424](https://github.com/getsentry/sentry-java/pull/1424)) -- Pass maxBreadcrumbs config. to sentry-native ([#1425](https://github.com/getsentry/sentry-java/pull/1425)) -- Run event processors and enrich transactions with contexts ([#1430](https://github.com/getsentry/sentry-java/pull/1430)) -- Set Span status for OkHttp integration ([#1447](https://github.com/getsentry/sentry-java/pull/1447)) -- Set user on transaction in Spring & Spring Boot integrations ([#1443](https://github.com/getsentry/sentry-java/pull/1443)) - -## 4.4.0-alpha.2 - -### Features - -- Add option to ignore exceptions by type ([#1352](https://github.com/getsentry/sentry-java/pull/1352)) -- Sentry closes Android NDK and ShutdownHook integrations ([#1358](https://github.com/getsentry/sentry-java/pull/1358)) -- Allow inheritance of SentryHandler class in sentry-jul package([#1367](https://github.com/getsentry/sentry-java/pull/1367)) -- Make NoOpHub public ([#1379](https://github.com/getsentry/sentry-java/pull/1379)) -- Configure max spans per transaction ([#1394](https://github.com/getsentry/sentry-java/pull/1394)) - -### Fixes - -- Bump: Upgrade Apache HttpComponents Core to 5.0.3 ([#1375](https://github.com/getsentry/sentry-java/pull/1375)) -- NPE when MDC contains null values (sentry-logback) ([#1364](https://github.com/getsentry/sentry-java/pull/1364)) -- Avoid NPE when MDC contains null values (sentry-jul) ([#1385](https://github.com/getsentry/sentry-java/pull/1385)) -- Accept only non null value maps ([#1368](https://github.com/getsentry/sentry-java/pull/1368)) -- Do not bind transactions to scope by default. ([#1376](https://github.com/getsentry/sentry-java/pull/1376)) -- Hub thread safety ([#1388](https://github.com/getsentry/sentry-java/pull/1388)) -- SentryTransactionAdvice should operate on the new scope ([#1389](https://github.com/getsentry/sentry-java/pull/1389)) - -## 4.4.0-alpha.1 - -### Features - -- Add an overload for `startTransaction` that sets the created transaction to the Scope ([#1313](https://github.com/getsentry/sentry-java/pull/1313)) -- Set SDK version on Transactions ([#1307](https://github.com/getsentry/sentry-java/pull/1307)) -- GraalVM Native Image Compatibility ([#1329](https://github.com/getsentry/sentry-java/pull/1329)) -- Add OkHttp client application interceptor ([#1330](https://github.com/getsentry/sentry-java/pull/1330)) - -### Fixes - -- Bump: sentry-native to 0.4.8 -- Ref: Separate user facing and protocol classes in the Performance feature ([#1304](https://github.com/getsentry/sentry-java/pull/1304)) -- Use logger set on SentryOptions in GsonSerializer ([#1308](https://github.com/getsentry/sentry-java/pull/1308)) -- Use the bindToScope correctly -- Allow 0.0 to be set on tracesSampleRate ([#1328](https://github.com/getsentry/sentry-java/pull/1328)) -- set "java" platform to transactions ([#1332](https://github.com/getsentry/sentry-java/pull/1332)) -- Allow disabling tracing through SentryOptions ([#1337](https://github.com/getsentry/sentry-java/pull/1337)) - -## 4.3.0 - -### Features - -- Activity tracing auto instrumentation - -### Fixes - -- Aetting in-app-includes from external properties ([#1291](https://github.com/getsentry/sentry-java/pull/1291)) -- Initialize Sentry in Logback appender when DSN is not set in XML config ([#1296](https://github.com/getsentry/sentry-java/pull/1296)) -- JUL integration SDK name ([#1293](https://github.com/getsentry/sentry-java/pull/1293)) - -## 4.2.0 - -### Features - -- Improve EventProcessor nullability annotations ([#1229](https://github.com/getsentry/sentry-java/pull/1229)). -- Add ability to flush events synchronously. -- Support @SentrySpan and @SentryTransaction on classes and interfaces. ([#1243](https://github.com/getsentry/sentry-java/pull/1243)) -- Do not serialize empty collections and maps ([#1245](https://github.com/getsentry/sentry-java/pull/1245)) -- Integration interface better compatibility with Kotlin null-safety -- Simplify Sentry configuration in Spring integration ([#1259](https://github.com/getsentry/sentry-java/pull/1259)) -- Simplify configuring Logback integration when environment variable with the DSN is not set ([#1271](https://github.com/getsentry/sentry-java/pull/1271)) -- Add Request to the Scope. [#1270](https://github.com/getsentry/sentry-java/pull/1270)) -- Optimize SentryTracingFilter when hub is disabled. - -### Fixes - -- Bump: sentry-native to 0.4.7 -- Optimize DuplicateEventDetectionEventProcessor performance ([#1247](https://github.com/getsentry/sentry-java/pull/1247)). -- Prefix sdk.package names with io.sentry ([#1249](https://github.com/getsentry/sentry-java/pull/1249)) -- Remove experimental annotation for Attachment ([#1257](https://github.com/getsentry/sentry-java/pull/1257)) -- Mark stacktrace as snapshot if captured at arbitrary moment ([#1231](https://github.com/getsentry/sentry-java/pull/1231)) -- Disable Gson HTML escaping -- Make the ANR Atomic flags immutable -- Prevent NoOpHub from creating heavy SentryOptions objects ([#1272](https://github.com/getsentry/sentry-java/pull/1272)) -- SentryTransaction#getStatus NPE ([#1273](https://github.com/getsentry/sentry-java/pull/1273)) -- Discard unfinished Spans before sending them over to Sentry ([#1279](https://github.com/getsentry/sentry-java/pull/1279)) -- Interrupt the thread in QueuedThreadPoolExecutor ([#1276](https://github.com/getsentry/sentry-java/pull/1276)) -- SentryTransaction#finish should not clear another transaction from the scope ([#1278](https://github.com/getsentry/sentry-java/pull/1278)) - -Breaking Changes: -- Enchancement: SentryExceptionResolver should not send handled errors by default ([#1248](https://github.com/getsentry/sentry-java/pull/1248)). -- Ref: Simplify RestTemplate instrumentation ([#1246](https://github.com/getsentry/sentry-java/pull/1246)) -- Enchancement: Add overloads for startTransaction taking op and description ([#1244](https://github.com/getsentry/sentry-java/pull/1244)) - -## 4.1.0 - -### Features - -- Improve Kotlin compatibility for SdkVersion ([#1213](https://github.com/getsentry/sentry-java/pull/1213)) -- Support logging via JUL ([#1211](https://github.com/getsentry/sentry-java/pull/1211)) - -### Fixes - -- Returning Sentry trace header from Span ([#1217](https://github.com/getsentry/sentry-java/pull/1217)) -- Remove misleading error logs ([#1222](https://github.com/getsentry/sentry-java/pull/1222)) - -## 4.0.0 - -This release brings the Sentry Performance feature to Java SDK, Spring, Spring Boot, and Android integrations. Read more in the reference documentation: - -- [Performance for Java](https://docs.sentry.io/platforms/java/performance/) -- [Performance for Spring](https://docs.sentry.io/platforms/java/guides/spring/) -- [Performance for Spring Boot](https://docs.sentry.io/platforms/java/guides/spring-boot/) -- [Performance for Android](https://docs.sentry.io/platforms/android/performance/) - -### Other improvements: - -#### Core: - -- Improved loading external configuration: - - Load `sentry.properties` from the application's current working directory ([#1046](https://github.com/getsentry/sentry-java/pull/1046)) - - Resolve `in-app-includes`, `in-app-excludes`, `tags`, `debug`, `uncaught.handler.enabled` parameters from the external configuration -- Set global tags on SentryOptions and load them from external configuration ([#1066](https://github.com/getsentry/sentry-java/pull/1066)) -- Add support for attachments ([#1082](https://github.com/getsentry/sentry-java/pull/1082)) -- Resolve `servername` from the localhost address -- Simplified transport configuration through setting `TransportFactory` instead of `ITransport` on SentryOptions ([#1124](https://github.com/getsentry/sentry-java/pull/1124)) - -#### Spring Boot: - -- Add the ability to register multiple `OptionsConfiguration` beans ([#1093](https://github.com/getsentry/sentry-java/pull/1093)) -- Initialize Logback after context refreshes ([#1129](https://github.com/getsentry/sentry-java/pull/1129)) - -#### Android: - -- Add `isSideLoaded` and `installerStore` tags automatically (Where your App. was installed from eg Google Play, Amazon Store, downloaded APK, etc...) -- Bump: sentry-native to 0.4.6 -- Bump: Gradle to 6.8.1 and AGP to 4.1.2 - -## 4.0.0-beta.1 - -### Features - -- Add addToTransactions to Attachment ([#1191](https://github.com/getsentry/sentry-java/pull/1191)) -- Support SENTRY_TRACES_SAMPLE_RATE conf. via env variables ([#1171](https://github.com/getsentry/sentry-java/pull/1171)) -- Pass request to CustomSamplingContext in Spring integration ([#1172](https://github.com/getsentry/sentry-java/pull/1172)) -- Move `SentrySpanClientHttpRequestInterceptor` to Spring module ([#1181](https://github.com/getsentry/sentry-java/pull/1181)) -- Add overload for `transaction/span.finish(SpanStatus)` ([#1182](https://github.com/getsentry/sentry-java/pull/1182)) -- Simplify registering traces sample callback in Spring integration ([#1184](https://github.com/getsentry/sentry-java/pull/1184)) -- Polish Performance API ([#1165](https://github.com/getsentry/sentry-java/pull/1165)) -- Set "debug" through external properties ([#1186](https://github.com/getsentry/sentry-java/pull/1186)) -- Simplify Spring integration ([#1188](https://github.com/getsentry/sentry-java/pull/1188)) -- Init overload with dsn ([#1195](https://github.com/getsentry/sentry-java/pull/1195)) -- Enable Kotlin map-like access on CustomSamplingContext ([#1192](https://github.com/getsentry/sentry-java/pull/1192)) -- Auto register custom ITransportFactory in Spring integration ([#1194](https://github.com/getsentry/sentry-java/pull/1194)) -- Improve Kotlin property access in Performance API ([#1193](https://github.com/getsentry/sentry-java/pull/1193)) -- Copy options tags to transactions ([#1198](https://github.com/getsentry/sentry-java/pull/1198)) -- Add convenient method for accessing event's throwable ([#1202](https://github.com/getsentry/sentry-java/pull/1202)) - -### Fixes - -- Ref: Set SpanContext on SentryTransaction to avoid potential NPE ([#1173](https://github.com/getsentry/sentry-java/pull/1173)) -- Free Local Refs manually due to Android local ref. count limits -- Bring back support for setting transaction name without ongoing transaction ([#1183](https://github.com/getsentry/sentry-java/pull/1183)) - -## 4.0.0-alpha.3 - -### Features - -- Improve ITransaction and ISpan null-safety compatibility ([#1161](https://github.com/getsentry/sentry-java/pull/1161)) -- Automatically assign span context to captured events ([#1156](https://github.com/getsentry/sentry-java/pull/1156)) -- Autoconfigure Apache HttpClient 5 based Transport in Spring Boot integration ([#1143](https://github.com/getsentry/sentry-java/pull/1143)) -- Send user.ip_address = {{auto}} when sendDefaultPii is true ([#1015](https://github.com/getsentry/sentry-java/pull/1015)) -- Read tracesSampleRate from AndroidManifest -- OutboxSender supports all envelope item types ([#1158](https://github.com/getsentry/sentry-java/pull/1158)) -- Read `uncaught.handler.enabled` property from the external configuration -- Resolve servername from the localhost address -- Add maxAttachmentSize to SentryOptions ([#1138](https://github.com/getsentry/sentry-java/pull/1138)) -- Drop invalid attachments ([#1134](https://github.com/getsentry/sentry-java/pull/1134)) -- Set isSideLoaded info tags -- Add non blocking Apache HttpClient 5 based Transport ([#1136](https://github.com/getsentry/sentry-java/pull/1136)) - -### Fixes - -- Ref: Make Attachment immutable ([#1120](https://github.com/getsentry/sentry-java/pull/1120)) -- Ref: using Calendar to generate Dates -- Ref: Return NoOpTransaction instead of null ([#1126](https://github.com/getsentry/sentry-java/pull/1126)) -- Ref: `ITransport` implementations are now responsible for executing request in asynchronous or synchronous way ([#1118](https://github.com/getsentry/sentry-java/pull/1118)) -- Ref: Add option to set `TransportFactory` instead of `ITransport` on `SentryOptions` ([#1124](https://github.com/getsentry/sentry-java/pull/1124)) -- Ref: Simplify ITransport creation in ITransportFactory ([#1135](https://github.com/getsentry/sentry-java/pull/1135)) -- Fixes and Tests: Session serialization and deserialization -- Inheriting sampling decision from parent ([#1100](https://github.com/getsentry/sentry-java/pull/1100)) -- Exception only sets a stack trace if there are frames -- Initialize Logback after context refreshes ([#1129](https://github.com/getsentry/sentry-java/pull/1129)) -- Do not crash when passing null values to @Nullable methods, eg User and Scope -- Resolving dashed properties from external configuration -- Consider {{ auto }} as a default ip address ([#1015](https://github.com/getsentry/sentry-java/pull/1015)) -- Set release and environment on Transactions ([#1152](https://github.com/getsentry/sentry-java/pull/1152)) -- Do not set transaction on the scope automatically - -## 4.0.0-alpha.2 - -### Features - -- Add basic support for attachments ([#1082](https://github.com/getsentry/sentry-java/pull/1082)) -- Set transaction name on events and transactions sent using Spring integration ([#1067](https://github.com/getsentry/sentry-java/pull/1067)) -- Set global tags on SentryOptions and load them from external configuration ([#1066](https://github.com/getsentry/sentry-java/pull/1066)) -- Add API validator and remove deprecated methods -- Add more convenient method to start a child span ([#1073](https://github.com/getsentry/sentry-java/pull/1073)) -- Autoconfigure traces callback in Spring Boot integration ([#1074](https://github.com/getsentry/sentry-java/pull/1074)) -- Resolve in-app-includes and in-app-excludes parameters from the external configuration -- Make InAppIncludesResolver public ([#1084](https://github.com/getsentry/sentry-java/pull/1084)) -- Add the ability to register multiple OptionsConfiguration beans ([#1093](https://github.com/getsentry/sentry-java/pull/1093)) -- Database query tracing with datasource-proxy ([#1095](https://github.com/getsentry/sentry-java/pull/1095)) - -### Fixes - -- Ref: Refactor resolving SpanContext for Throwable ([#1068](https://github.com/getsentry/sentry-java/pull/1068)) -- Ref: Change "op" to "operation" in @SentrySpan and @SentryTransaction -- Remove method reference in SentryEnvelopeItem ([#1091](https://github.com/getsentry/sentry-java/pull/1091)) -- Set current thread only if there are no exceptions -- SentryOptions creates GsonSerializer by default -- Append DebugImage list if event already has it -- Sort breadcrumbs by Date if there are breadcrumbs already in the event - -## 4.0.0-alpha.1 - -### Features - -- Load `sentry.properties` from the application's current working directory ([#1046](https://github.com/getsentry/sentry-java/pull/1046)) -- Performance monitoring ([#971](https://github.com/getsentry/sentry-java/pull/971)) -- Performance monitoring for Spring Boot applications ([#971](https://github.com/getsentry/sentry-java/pull/971)) - -### Fixes - -- Ref: Refactor JSON deserialization ([#1047](https://github.com/getsentry/sentry-java/pull/1047)) - -## 3.2.1 - -### Fixes - -- Set current thread only if theres no exceptions ([#1064](https://github.com/getsentry/sentry-java/pull/1064)) -- Append DebugImage list if event already has it ([#1092](https://github.com/getsentry/sentry-java/pull/1092)) -- Sort breadcrumbs by Date if there are breadcrumbs already in the event ([#1094](https://github.com/getsentry/sentry-java/pull/1094)) -- Free Local Refs manually due to Android local ref. count limits ([#1179](https://github.com/getsentry/sentry-java/pull/1179)) - -## 3.2.0 - -### Features - -- Expose a Module (Debug images) Loader for Android thru sentry-native ([#1043](https://github.com/getsentry/sentry-java/pull/1043)) -- Added java doc to protocol classes based on sentry-data-schemes project ([#1045](https://github.com/getsentry/sentry-java/pull/1045)) -- Make SentryExceptionResolver Order configurable to not send handled web exceptions ([#1008](https://github.com/getsentry/sentry-java/pull/1008)) -- Resolve HTTP Proxy parameters from the external configuration ([#1028](https://github.com/getsentry/sentry-java/pull/1028)) -- Sentry NDK integration is compiled against default NDK version based on AGP's version ([#1048](https://github.com/getsentry/sentry-java/pull/1048)) - -### Fixes - -- Bump: AGP 4.1.1 ([#1040](https://github.com/getsentry/sentry-java/pull/1040)) -- Update to sentry-native 0.4.4 and fix shared library builds ([#1039](https://github.com/getsentry/sentry-java/pull/1039)) -- use neutral Locale for String operations ([#1033](https://github.com/getsentry/sentry-java/pull/1033)) -- Clean up JNI code and properly free strings ([#1050](https://github.com/getsentry/sentry-java/pull/1050)) -- set userId for hard-crashes if no user is set ([#1049](https://github.com/getsentry/sentry-java/pull/1049)) - -## 3.1.3 - -### Fixes - -- Fix broken NDK integration on 3.1.2 (release failed on packaging a .so file) -- Increase max cached events to 30 ([#1029](https://github.com/getsentry/sentry-java/pull/1029)) -- Normalize DSN URI ([#1030](https://github.com/getsentry/sentry-java/pull/1030)) - -## 3.1.2 - -### Features - -- Manually capturing User Feedback -- Set environment to "production" by default. -- Make public the Breadcrumb constructor that accepts a Date ([#1012](https://github.com/getsentry/sentry-java/pull/1012)) - -### Fixes - -- ref: Validate event id on user feedback submission - -## 3.1.1 - -### Features - -- Bind logging related SentryProperties to Slf4j Level instead of Logback to improve Log4j2 compatibility - -### Fixes - -- Prevent Logback and Log4j2 integrations from re-initializing Sentry when Sentry is already initialized -- Make sure HttpServletRequestSentryUserProvider runs by default before custom SentryUserProvider beans -- Fix setting up Sentry in Spring Webflux annotation by changing the scope of Spring WebMvc related dependencies - -## 3.1.0 - -### Features - -- Make getThrowable public and improve set contexts ([#967](https://github.com/getsentry/sentry-java/pull/967)) -- Accepted quoted values in properties from external configuration ([#972](https://github.com/getsentry/sentry-java/pull/972)) - -### Fixes - -- Auto-Configure `inAppIncludes` in Spring Boot integration ([#966](https://github.com/getsentry/sentry-java/pull/966)) -- Bump: Android Gradle Plugin 4.0.2 ([#968](https://github.com/getsentry/sentry-java/pull/968)) -- Don't require `sentry.dsn` to be set when using `io.sentry:sentry-spring-boot-starter` and `io.sentry:sentry-logback` together ([#965](https://github.com/getsentry/sentry-java/pull/965)) -- Remove chunked streaming mode ([#974](https://github.com/getsentry/sentry-java/pull/974)) -- Android 11 + targetSdkVersion 30 crashes Sentry on start ([#977](https://github.com/getsentry/sentry-java/pull/977)) - -## 3.0.0 - -## Java + Android - -This release marks the re-unification of Java and Android SDK code bases. -It's based on the Android 2.0 SDK, which implements [Sentry's unified API](https://develop.sentry.dev/sdk/unified-api/). - -Considerable changes were done, which include a lot of improvements. More are covered below, but the highlights are: - -- Improved `log4j2` integration - - Capture breadcrumbs for level INFO and higher - - Raises event for ERROR and higher. - - Minimum levels are configurable. - - Optionally initializes the SDK via appender.xml -- Dropped support to `log4j`. -- Improved `logback` integration - - Capture breadcrumbs for level INFO and higher - - Raises event for ERROR and higher. - - Minimum levels are configurable. - - Optionally initializes the SDK via appender.xml - - Configurable via Spring integration if both are enabled -- Spring - - No more duplicate events with Spring and logback - - Auto initalizes if DSN is available - - Configuration options available with auto complete -- Google App Engine support dropped - -## What’s Changed - -- Callback to validate SSL certificate ([#944](https://github.com/getsentry/sentry-java/pull/944)) -- Attach stack traces enabled by default - -### Android specific - -- Release health enabled by default for Android -- Sync of Scopes for Java -> Native (NDK) -- Bump Sentry-Native v0.4.2 -- Android 11 Support - -[Android migration docs](https://docs.sentry.io/platforms/android/migration/#migrating-from-sentry-android-2x-to-sentry-android-3x) - -### Java specific - -- Unified API for Java SDK and integrations (Spring, Spring boot starter, Servlet, Logback, Log4j2) - -New Java [docs](https://docs.sentry.io/platforms/java/) are live and being improved. - -## Acquisition - -Packages were released on [`bintray sentry-java`](https://dl.bintray.com/getsentry/sentry-java/io/sentry/), [`bintray sentry-android`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/) - -## Where is the Java 1.7 code base? - -The previous Java releases, are all available in this repository through the tagged releases. -## 3.0.0-beta.1 - -## What’s Changed - -- feat: ssl support ([#944](https://github.com/getsentry/sentry-java/pull/944)) @ninekaw9 @marandaneto -- feat: sync Java to C ([#937](https://github.com/getsentry/sentry-java/pull/937)) @bruno-garcia @marandaneto -- feat: Auto-configure Logback appender in Spring Boot integration. ([#938](https://github.com/getsentry/sentry-java/pull/938)) @maciejwalkowiak -- feat: Add Servlet integration. ([#935](https://github.com/getsentry/sentry-java/pull/935)) @maciejwalkowiak -- fix: Pop scope at the end of the request in Spring integration. ([#936](https://github.com/getsentry/sentry-java/pull/936)) @maciejwalkowiak -- bump: Upgrade Spring Boot to 2.3.4. ([#932](https://github.com/getsentry/sentry-java/pull/932)) @maciejwalkowiak -- fix: Do not set cookies when send pii is set to false. ([#931](https://github.com/getsentry/sentry-java/pull/931)) @maciejwalkowiak - -Packages were released on [`bintray sentry-java`](https://dl.bintray.com/getsentry/sentry-java/io/sentry/), [`bintray sentry-android`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/) - -We'd love to get feedback. - -## 3.0.0-alpha.3 - -### Features - -- Enable attach stack traces and disable attach threads by default ([#921](https://github.com/getsentry/sentry-java/pull/921)) @marandaneto - -### Fixes - -- Bump sentry-native to 0.4.2 ([#926](https://github.com/getsentry/sentry-java/pull/926)) @marandaneto -- ref: remove log level as RN do not use it anymore ([#924](https://github.com/getsentry/sentry-java/pull/924)) @marandaneto -- Read sample rate correctly from manifest meta data ([#923](https://github.com/getsentry/sentry-java/pull/923)) @marandaneto - -Packages were released on [`bintray sentry-android`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/) and [`bintray sentry-java`](https://dl.bintray.com/getsentry/sentry-java/io/sentry/) - -We'd love to get feedback. - -## 3.0.0-alpha.2 - -TBD - -Packages were released on [bintray](https://dl.bintray.com/getsentry/maven/io/sentry/) - -> Note: This release marks the unification of the Java and Android Sentry codebases based on the core of the Android SDK (version 2.x). -Previous releases for the Android SDK (version 2.x) can be found on the now archived: https://github.com/getsentry/sentry-android/ - -## 3.0.0-alpha.1 - -### Features - -### Fixes - - -## New releases will happen on a different repository: - -https://github.com/getsentry/sentry-java - -## What’s Changed - -### Features - -### Fixes - - -- feat: enable release health by default - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.3.1 - -### Fixes - -- Add main thread checker for the app lifecycle integration ([#525](https://github.com/getsentry/sentry-android/pull/525)) @marandaneto -- Set correct migration link ([#523](https://github.com/getsentry/sentry-android/pull/523)) @fupduck -- Warn about Sentry re-initialization. ([#521](https://github.com/getsentry/sentry-android/pull/521)) @maciejwalkowiak -- Set SDK version in `MainEventProcessor`. ([#513](https://github.com/getsentry/sentry-android/pull/513)) @maciejwalkowiak -- Bump sentry-native to 0.4.0 ([#512](https://github.com/getsentry/sentry-android/pull/512)) @marandaneto -- Bump Gradle to 6.6 and fix linting issues ([#510](https://github.com/getsentry/sentry-android/pull/510)) @marandaneto -- fix(sentry-java): Contexts belong on the Scope ([#504](https://github.com/getsentry/sentry-android/pull/504)) @maciejwalkowiak -- Add tests for verifying scope changes thread isolation ([#508](https://github.com/getsentry/sentry-android/pull/508)) @maciejwalkowiak -- Set `SdkVersion` in default `SentryOptions` created in sentry-core module ([#506](https://github.com/getsentry/sentry-android/pull/506)) @maciejwalkowiak - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.3.0 - -### Features - -- Add console application sample. ([#502](https://github.com/getsentry/sentry-android/pull/502)) @maciejwalkowiak -- Log stacktraces in SystemOutLogger ([#498](https://github.com/getsentry/sentry-android/pull/498)) @maciejwalkowiak -- Add method to add breadcrumb with string parameter. ([#501](https://github.com/getsentry/sentry-android/pull/501)) @maciejwalkowiak - -### Fixes - -- Converting UTC and ISO timestamp when missing Locale/TimeZone do not error ([#505](https://github.com/getsentry/sentry-android/pull/505)) @marandaneto -- Call `Sentry#close` on JVM shutdown. ([#497](https://github.com/getsentry/sentry-android/pull/497)) @maciejwalkowiak -- ref: sentry-core changes for console app ([#473](https://github.com/getsentry/sentry-android/pull/473)) @marandaneto - -Obs: If you are using its own instance of `Hub`/`SentryClient` and reflection to set up the SDK to be usable within Libraries, this change may break your code, please fix the renamed classes. - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.2.2 - -### Features - -- Add sdk to envelope header ([#488](https://github.com/getsentry/sentry-android/pull/488)) @marandaneto -- Log request if response code is not 200 ([#484](https://github.com/getsentry/sentry-android/pull/484)) @marandaneto - -### Fixes - -- Bump plugin versions ([#487](https://github.com/getsentry/sentry-android/pull/487)) @marandaneto -- Bump: AGP 4.0.1 ([#486](https://github.com/getsentry/sentry-android/pull/486)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.2.1 - -### Fixes - -- Timber adds breadcrumb even if event level is < minEventLevel ([#480](https://github.com/getsentry/sentry-android/pull/480)) @marandaneto -- Contexts serializer avoids reflection and fixes desugaring issue ([#478](https://github.com/getsentry/sentry-android/pull/478)) @marandaneto -- clone session before sending to the transport ([#474](https://github.com/getsentry/sentry-android/pull/474)) @marandaneto -- Bump Gradle 6.5.1 ([#479](https://github.com/getsentry/sentry-android/pull/479)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.2.0 - -### Fixes - -- Negative session sequence if the date is before java date epoch ([#471](https://github.com/getsentry/sentry-android/pull/471)) @marandaneto -- Deserialise unmapped contexts values from envelope ([#470](https://github.com/getsentry/sentry-android/pull/470)) @marandaneto -- Bump: sentry-native 0.3.4 ([#468](https://github.com/getsentry/sentry-android/pull/468)) @marandaneto - -- feat: timber integration ([#464](https://github.com/getsentry/sentry-android/pull/464)) @marandaneto - -1) To add integrations it requires a [manual initialization](https://docs.sentry.io/platforms/android/#manual-initialization) of the Android SDK. - -2) Add the `sentry-android-timber` dependency: - -```groovy -implementation 'io.sentry:sentry-android-timber:{version}' // version >= 2.2.0 -``` - -3) Initialize and add the `SentryTimberIntegration`: - -```java -SentryAndroid.init(this, options -> { - // default values: - // minEventLevel = ERROR - // minBreadcrumbLevel = INFO - options.addIntegration(new SentryTimberIntegration()); - - // custom values for minEventLevel and minBreadcrumbLevel - // options.addIntegration(new SentryTimberIntegration(SentryLevel.WARNING, SentryLevel.ERROR)); -}); -``` - -4) Use the Timber integration: - -```java -try { - int x = 1 / 0; -} catch (Exception e) { - Timber.e(e); -} -``` - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.1.7 - -### Fixes - -- Init native libs if available on SDK init ([#461](https://github.com/getsentry/sentry-android/pull/461)) @marandaneto -- Make JVM target explicit in sentry-core ([#462](https://github.com/getsentry/sentry-android/pull/462)) @dilbernd -- Timestamp with millis from react-native should be in UTC format ([#456](https://github.com/getsentry/sentry-android/pull/456)) @marandaneto -- Bump Gradle to 6.5 ([#454](https://github.com/getsentry/sentry-android/pull/454)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.1.6 - -### Fixes - -- Do not lookup sentry-debug-meta but instead load it directly ([#445](https://github.com/getsentry/sentry-android/pull/445)) @marandaneto -- Regression on v2.1.5 which can cause a crash on SDK init - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.1.5 - -### Fixes - -This version has a severe bug and can cause a crash on SDK init - -Please upgrade to https://github.com/getsentry/sentry-android/releases/tag/2.1.6 - -## 2.1.4 - -### Features - -- Make gzip as default content encoding type ([#433](https://github.com/getsentry/sentry-android/pull/433)) @marandaneto -- Use AGP 4 features ([#366](https://github.com/getsentry/sentry-android/pull/366)) @marandaneto -- Create GH Actions CI for Ubuntu/macOS ([#403](https://github.com/getsentry/sentry-android/pull/403)) @marandaneto -- Make root checker better and minimize false positive ([#417](https://github.com/getsentry/sentry-android/pull/417)) @marandaneto - -### Fixes - -- bump: sentry-native to 0.3.1 ([#440](https://github.com/getsentry/sentry-android/pull/440)) @marandaneto -- Update last session timestamp ([#437](https://github.com/getsentry/sentry-android/pull/437)) @marandaneto -- Filter trim memory breadcrumbs ([#431](https://github.com/getsentry/sentry-android/pull/431)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.1.3 - -### Fixes - -This fixes several critical bugs in sentry-android 2.0 and 2.1 - -- Sentry.init register integrations after creating the main Hub instead of doing it in the main Hub ctor ([#427](https://github.com/getsentry/sentry-android/pull/427)) @marandaneto -- make NoOpLogger public ([#425](https://github.com/getsentry/sentry-android/pull/425)) @marandaneto -- ConnectivityChecker returns connection status and events are not trying to be sent if no connection. ([#420](https://github.com/getsentry/sentry-android/pull/420)) @marandaneto -- thread pool executor is a single thread executor instead of scheduled thread executor ([#422](https://github.com/getsentry/sentry-android/pull/422)) @marandaneto -- Add Abnormal to the Session.State enum as its part of the protocol ([#424](https://github.com/getsentry/sentry-android/pull/424)) @marandaneto -- Bump: Gradle to 6.4.1 ([#419](https://github.com/getsentry/sentry-android/pull/419)) @marandaneto - -We recommend that you use sentry-android 2.1.3 over the initial release of sentry-android 2.0 and 2.1. - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.1.2 - -### Features - -- Added options to configure http transport ([#411](https://github.com/getsentry/sentry-android/pull/411)) @marandaneto - -### Fixes - -- Phone state breadcrumbs require read_phone_state on older OS versions ([#415](https://github.com/getsentry/sentry-android/pull/415)) @marandaneto @bsergean -- before raising ANR events, we check ProcessErrorStateInfo if available ([#412](https://github.com/getsentry/sentry-android/pull/412)) @marandaneto -- send cached events to use a single thread executor ([#405](https://github.com/getsentry/sentry-android/pull/405)) @marandaneto -- initing SDK on AttachBaseContext ([#409](https://github.com/getsentry/sentry-android/pull/409)) @marandaneto -- sessions can't be abnormal, but exited if not ended properly ([#410](https://github.com/getsentry/sentry-android/pull/410)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.1.1 - -### Features - -- Added missing getters on Breadcrumb and SentryEvent ([#397](https://github.com/getsentry/sentry-android/pull/397)) @marandaneto -- Add trim memory breadcrumbs ([#395](https://github.com/getsentry/sentry-android/pull/395)) @marandaneto -- Only set breadcrumb extras if not empty ([#394](https://github.com/getsentry/sentry-android/pull/394)) @marandaneto -- Added samples of how to disable automatic breadcrumbs ([#389](https://github.com/getsentry/sentry-android/pull/389)) @marandaneto - -### Fixes - -- Set missing release, environment and dist to sentry-native options ([#404](https://github.com/getsentry/sentry-android/pull/404)) @marandaneto -- Do not add automatic and empty sensor breadcrumbs ([#401](https://github.com/getsentry/sentry-android/pull/401)) @marandaneto -- ref: removed Thread.sleep from LifecycleWatcher tests, using awaitility and DateProvider ([#392](https://github.com/getsentry/sentry-android/pull/392)) @marandaneto -- ref: added a DateTimeProvider for making retry after testable ([#391](https://github.com/getsentry/sentry-android/pull/391)) @marandaneto -- Bump Gradle to 6.4 ([#390](https://github.com/getsentry/sentry-android/pull/390)) @marandaneto -- Bump sentry-native to 0.2.6 ([#396](https://github.com/getsentry/sentry-android/pull/396)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.1.0 - -### Features - -- Includes all the changes of 2.1.0 alpha, beta and RC - -### Fixes - -- fix when PhoneStateListener is not ready for use ([#387](https://github.com/getsentry/sentry-android/pull/387)) @marandaneto -- make ANR 5s by default ([#388](https://github.com/getsentry/sentry-android/pull/388)) @marandaneto -- rate limiting by categories ([#381](https://github.com/getsentry/sentry-android/pull/381)) @marandaneto -- Bump NDK to latest stable version 21.1.6352462 ([#386](https://github.com/getsentry/sentry-android/pull/386)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.0.3 - -### Fixes - -- patch from 2.1.0-alpha.2 - avoid crash if NDK throws UnsatisfiedLinkError ([#344](https://github.com/getsentry/sentry-android/pull/344)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.1.0-RC.1 - -### Features - -- Options for uncaught exception and make SentryOptions list Thread-Safe ([#384](https://github.com/getsentry/sentry-android/pull/384)) @marandaneto -- Automatic breadcrumbs for app, activity and sessions lifecycles and system events ([#348](https://github.com/getsentry/sentry-android/pull/348)) @marandaneto -- Make capture session and envelope internal ([#372](https://github.com/getsentry/sentry-android/pull/372)) @marandaneto - -### Fixes - -- If retry after header has empty categories, apply retry after to all of them ([#377](https://github.com/getsentry/sentry-android/pull/377)) @marandaneto -- Discard events and envelopes if cached and retry after ([#378](https://github.com/getsentry/sentry-android/pull/378)) @marandaneto -- Merge loadLibrary calls for sentry-native and clean up CMake files ([#373](https://github.com/getsentry/sentry-android/pull/373)) @Swatinem -- Exceptions should be sorted oldest to newest ([#370](https://github.com/getsentry/sentry-android/pull/370)) @marandaneto -- Check external storage size even if its read only ([#368](https://github.com/getsentry/sentry-android/pull/368)) @marandaneto -- Wrong check for cellular network capability ([#369](https://github.com/getsentry/sentry-android/pull/369)) @marandaneto -- add ScheduledForRemoval annotation to deprecated methods ([#375](https://github.com/getsentry/sentry-android/pull/375)) @marandaneto -- Bump NDK to 21.0.6113669 ([#367](https://github.com/getsentry/sentry-android/pull/367)) @marandaneto -- Bump AGP and add new make cmd to check for updates ([#365](https://github.com/getsentry/sentry-android/pull/365)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.1.0-beta.2 - -### Fixes - -- Bump sentry-native to 0.2.4 ([#364](https://github.com/getsentry/sentry-android/pull/364)) @marandaneto -- Update current session on session start after deleting previous session ([#362](https://github.com/getsentry/sentry-android/pull/362)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.1.0-beta.1 - -### Fixes - -- Bump sentry-native to 0.2.3 ([#357](https://github.com/getsentry/sentry-android/pull/357)) @marandaneto -- Check for androidx availability on runtime ([#356](https://github.com/getsentry/sentry-android/pull/356)) @marandaneto -- If theres a left over session file and its crashed, we should not overwrite its state ([#354](https://github.com/getsentry/sentry-android/pull/354)) @marandaneto -- Session should be exited state if state was ok ([#352](https://github.com/getsentry/sentry-android/pull/352)) @marandaneto -- Envelope has dedicated endpoint ([#353](https://github.com/getsentry/sentry-android/pull/353)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.1.0-alpha.2 - -### Fixes - -- Change integration order for cached outbox events ([#347](https://github.com/getsentry/sentry-android/pull/347)) @marandaneto -- Avoid crash if NDK throws UnsatisfiedLinkError ([#344](https://github.com/getsentry/sentry-android/pull/344)) @marandaneto -- Avoid getting a threadlocal twice. ([#339](https://github.com/getsentry/sentry-android/pull/339)) @metlos -- Removing session tracking guard on hub and client ([#338](https://github.com/getsentry/sentry-android/pull/338)) @marandaneto -- Bump agp to 3.6.2 ([#336](https://github.com/getsentry/sentry-android/pull/336)) @marandaneto -- Fix racey ANR integration ([#332](https://github.com/getsentry/sentry-android/pull/332)) @marandaneto -- Logging envelopes path when possible instead of nullable id ([#331](https://github.com/getsentry/sentry-android/pull/331)) @marandaneto -- Renaming transport gate method ([#330](https://github.com/getsentry/sentry-android/pull/330)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.1.0-alpha.1 - -Release of Sentry's new SDK for Android. - -## What’s Changed - -### Features - -- Release health @marandaneto @bruno-garcia -- ANR report should have 'was active=yes' on the dashboard ([#299](https://github.com/getsentry/sentry-android/pull/299)) @marandaneto -- NDK events apply scoped data ([#322](https://github.com/getsentry/sentry-android/pull/322)) @marandaneto -- Add a StdoutTransport ([#310](https://github.com/getsentry/sentry-android/pull/310)) @mike-burns -- Implementing new retry after protocol ([#306](https://github.com/getsentry/sentry-android/pull/306)) @marandaneto - -### Fixes - -- Bump sentry-native to 0.2.2 ([#305](https://github.com/getsentry/sentry-android/pull/305)) @Swatinem -- Missing App's info ([#315](https://github.com/getsentry/sentry-android/pull/315)) @marandaneto -- Buffered writers/readers - otimizations ([#311](https://github.com/getsentry/sentry-android/pull/311)) @marandaneto -- Boot time should be UTC ([#309](https://github.com/getsentry/sentry-android/pull/309)) @marandaneto -- Make transport result public ([#300](https://github.com/getsentry/sentry-android/pull/300)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.0.2 - -Release of Sentry's new SDK for Android. - -### Features - -- MavenCentral support ([#284](https://github.com/getsentry/sentry-android/pull/284)) @marandaneto - -### Fixes - -- Bump AGP to 3.6.1 ([#285](https://github.com/getsentry/sentry-android/pull/285)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/sentry-android/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) and [`mavenCentral`](https://repo.maven.apache.org/maven2/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.0.1 - -Release of Sentry's new SDK for Android. - -## What’s Changed - -### Features - -- Attach threads/stacktraces ([#267](https://github.com/getsentry/sentry-android/pull/267)) @marandaneto -- Add the default serverName to SentryOptions and use it in MainEventProcessor ([#279](https://github.com/getsentry/sentry-android/pull/279)) @metlos - -### Fixes - -- set current threadId when there's no mechanism set ([#277](https://github.com/getsentry/sentry-android/pull/277)) @marandaneto -- Preview package manager ([#269](https://github.com/getsentry/sentry-android/pull/269)) @bruno-garcia - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.0.0 - -Release of Sentry's new SDK for Android. - -New features not offered by (1.7.x): - -- NDK support - - Captures crashes caused by native code - - Access to the [`sentry-native` SDK](https://github.com/getsentry/sentry-native/) API by your native (C/C++/Rust code/..). -- Automatic init (just add your `DSN` to the manifest) - - Proguard rules are added automatically - - Permission (Internet) is added automatically -- Uncaught Exceptions might be captured even before the app restarts -- Sentry's Unified API. -- More context/device information -- Packaged as `aar` -- Frames from the app automatically marked as `InApp=true` (stack traces in Sentry highlights them by default). -- Complete Sentry Protocol available. -- All threads and their stack traces are captured. -- Sample project in this repo to test many features (segfault, uncaught exception, ANR...) - -Features from the current SDK like `ANR` are also available (by default triggered after 4 seconds). - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) - -We'd love to get feedback. - -## 2.0.0-rc04 - -Release of Sentry's new SDK for Android. - -### Features - -- Take sampleRate from metadata ([#262](https://github.com/getsentry/sentry-android/pull/262)) @bruno-garcia -- Support mills timestamp format ([#263](https://github.com/getsentry/sentry-android/pull/263)) @marandaneto -- Adding logs to installed integrations ([#265](https://github.com/getsentry/sentry-android/pull/265)) @marandaneto - -### Fixes - -- Breacrumb.data to string,object, Add LOG level ([#264](https://github.com/getsentry/sentry-android/pull/264)) @HazAT -- Read release conf. on manifest ([#266](https://github.com/getsentry/sentry-android/pull/266)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) - -We'd love to get feedback and we'll work in getting the GA `2.0.0` out soon. -Until then, the [stable SDK offered by Sentry is at version 1.7.30](https://github.com/getsentry/sentry-java/releases/tag/v1.7.30) - -## 2.0.0-rc03 - -Release of Sentry's new SDK for Android. - -### Fixes - -- fixes ([#259](https://github.com/getsentry/sentry-android/issues/259)) - NPE check on getExternalFilesDirs items. ([#260](https://github.com/getsentry/sentry-android/pull/260)) @marandaneto -- strictMode typo ([#258](https://github.com/getsentry/sentry-android/pull/258)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) - -We'd love to get feedback and we'll work in getting the GA `2.0.0` out soon. -Until then, the [stable SDK offered by Sentry is at version 1.7.30](https://github.com/getsentry/sentry-java/releases/tag/v1.7.30) - -## 2.0.0-rc02 - -Release of Sentry's new SDK for Android. - -### Features - -- Hub mode configurable ([#247](https://github.com/getsentry/sentry-android/pull/247)) @bruno-garcia -- Added remove methods (tags/extras) to the sentry static class ([#243](https://github.com/getsentry/sentry-android/pull/243)) @marandaneto - -### Fixes - - -- Update ndk for new sentry-native version ([#235](https://github.com/getsentry/sentry-android/pull/235)) @Swatinem @marandaneto -- Make integrations public ([#256](https://github.com/getsentry/sentry-android/pull/256)) @marandaneto -- Bump build-tools ([#255](https://github.com/getsentry/sentry-android/pull/255)) @marandaneto -- Added javadocs to scope and its dependencies ([#253](https://github.com/getsentry/sentry-android/pull/253)) @marandaneto -- Build all ABIs ([#254](https://github.com/getsentry/sentry-android/pull/254)) @marandaneto -- Moving back ANR timeout from long to int param. ([#252](https://github.com/getsentry/sentry-android/pull/252)) @marandaneto -- Added HubAdapter to call Sentry static methods from Integrations ([#250](https://github.com/getsentry/sentry-android/pull/250)) @marandaneto -- New Release format ([#242](https://github.com/getsentry/sentry-android/pull/242)) @marandaneto -- Javadocs for SentryOptions ([#246](https://github.com/getsentry/sentry-android/pull/246)) @marandaneto -- non-app is already inApp excluded by default. ([#244](https://github.com/getsentry/sentry-android/pull/244)) @marandaneto -- Fix if symlink exists for sentry-native ([#241](https://github.com/getsentry/sentry-android/pull/241)) @marandaneto -- Clone method - race condition free ([#226](https://github.com/getsentry/sentry-android/pull/226)) @marandaneto -- Refactoring breadcrumbs callback ([#239](https://github.com/getsentry/sentry-android/pull/239)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) - -We'd love to get feedback and we'll work in getting the GA `2.0.0` out soon. -Until then, the [stable SDK offered by Sentry is at version 1.7.30](https://github.com/getsentry/sentry-java/releases/tag/v1.7.30) - -## 2.0.0-rc01 - -Release of Sentry's new SDK for Android. - -## What’s Changed - -### Features - -- Added remove methods for Scope data ([#237](https://github.com/getsentry/sentry-android/pull/237)) @marandaneto -- More device context (deviceId, connectionType and language) ([#229](https://github.com/getsentry/sentry-android/pull/229)) @marandaneto -- Added a few java docs (Sentry, Hub and SentryClient) ([#223](https://github.com/getsentry/sentry-android/pull/223)) @marandaneto -- Implemented diagnostic logger ([#218](https://github.com/getsentry/sentry-android/pull/218)) @marandaneto -- Added event processors to scope ([#209](https://github.com/getsentry/sentry-android/pull/209)) @marandaneto -- Added android transport gate ([#206](https://github.com/getsentry/sentry-android/pull/206)) @marandaneto -- Added executor for caching values out of the main thread ([#201](https://github.com/getsentry/sentry-android/pull/201)) @marandaneto - -### Fixes - - -- Honor RetryAfter ([#236](https://github.com/getsentry/sentry-android/pull/236)) @marandaneto -- Add tests for SentryValues ([#238](https://github.com/getsentry/sentry-android/pull/238)) @philipphofmann -- Do not set frames if there's none ([#234](https://github.com/getsentry/sentry-android/pull/234)) @marandaneto -- Always call interrupt after InterruptedException ([#232](https://github.com/getsentry/sentry-android/pull/232)) @marandaneto -- Mark as current thread if its the main thread ([#228](https://github.com/getsentry/sentry-android/pull/228)) @marandaneto -- Fix lgtm alerts ([#219](https://github.com/getsentry/sentry-android/pull/219)) @marandaneto -- Written unit tests to ANR integration ([#215](https://github.com/getsentry/sentry-android/pull/215)) @marandaneto -- Added blog posts to README ([#214](https://github.com/getsentry/sentry-android/pull/214)) @marandaneto -- Raise code coverage for Dsn to 100% ([#212](https://github.com/getsentry/sentry-android/pull/212)) @philipphofmann -- Remove redundant times(1) for Mockito.verify ([#211](https://github.com/getsentry/sentry-android/pull/211)) @philipphofmann -- Transport may be set on options ([#203](https://github.com/getsentry/sentry-android/pull/203)) @marandaneto -- dist may be set on options ([#204](https://github.com/getsentry/sentry-android/pull/204)) @marandaneto -- Throw an exception if DSN is not set ([#200](https://github.com/getsentry/sentry-android/pull/200)) @marandaneto -- Migration guide markdown ([#197](https://github.com/getsentry/sentry-android/pull/197)) @marandaneto - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) - -We'd love to get feedback and we'll work in getting the GA `2.0.0` out soon. -Until then, the [stable SDK offered by Sentry is at version 1.7.29](https://github.com/getsentry/sentry-java/releases/tag/v1.7.29) - -## 2.0.0-beta02 - -Release of Sentry's new SDK for Android. - -### Features - -- addBreadcrumb overloads ([#196](https://github.com/getsentry/sentry-android/pull/196)) and ([#198](https://github.com/getsentry/sentry-android/pull/198)) - -### Fixes - -- fix Android bug on API 24 and 25 about getting current threads and stack traces ([#194](https://github.com/getsentry/sentry-android/pull/194)) - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) - -We'd love to get feedback and we'll work in getting the GA `2.0.0` out soon. -Until then, the [stable SDK offered by Sentry is at version 1.7.28](https://github.com/getsentry/sentry-java/releases/tag/v1.7.28) - -## 2.0.0-beta01 - -Release of Sentry's new SDK for Android. - -### Fixes - -- ref: ANR doesn't set handled flag ([#186](https://github.com/getsentry/sentry-android/pull/186)) -- SDK final review ([#183](https://github.com/getsentry/sentry-android/pull/183)) -- ref: Drop errored in favor of crashed ([#187](https://github.com/getsentry/sentry-android/pull/187)) -- Workaround android_id ([#185](https://github.com/getsentry/sentry-android/pull/185)) -- Renamed sampleRate ([#191](https://github.com/getsentry/sentry-android/pull/191)) -- Making timestamp package-private or test-only ([#190](https://github.com/getsentry/sentry-android/pull/190)) -- Split event processor in Device/App data ([#180](https://github.com/getsentry/sentry-android/pull/180)) - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) - -We'd love to get feedback and we'll work in getting the GA `2.0.0` out soon. -Until then, the [stable SDK offered by Sentry is at version 1.7.28](https://github.com/getsentry/sentry-java/releases/tag/v1.7.28) - -## 2.0.0-alpha09 - -Release of Sentry's new SDK for Android. - -### Features - -- Adding nativeBundle plugin ([#161](https://github.com/getsentry/sentry-android/pull/161)) -- Adding scope methods to sentry static class ([#179](https://github.com/getsentry/sentry-android/pull/179)) - -### Fixes - -- fix: DSN parsing ([#165](https://github.com/getsentry/sentry-android/pull/165)) -- Don't avoid exception type minification ([#166](https://github.com/getsentry/sentry-android/pull/166)) -- make Gson retro compatible with older versions of AGP ([#177](https://github.com/getsentry/sentry-android/pull/177)) -- Bump sentry-native with message object instead of a string ([#172](https://github.com/getsentry/sentry-android/pull/172)) - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) - -We'd love to get feedback and we'll work in getting the GA `2.0.0` out soon. -Until then, the [stable SDK offered by Sentry is at version 1.7.28](https://github.com/getsentry/sentry-java/releases/tag/v1.7.28) - -## 2.0.0-alpha08 - -Release of Sentry's new SDK for Android. - -### Fixes - -- DebugId endianness ([#162](https://github.com/getsentry/sentry-android/pull/162)) -- Executed beforeBreadcrumb also for scope ([#160](https://github.com/getsentry/sentry-android/pull/160)) -- Benefit of manifest merging when minSdk ([#159](https://github.com/getsentry/sentry-android/pull/159)) -- Add method to captureMessage with level ([#157](https://github.com/getsentry/sentry-android/pull/157)) -- Listing assets file on the wrong dir ([#156](https://github.com/getsentry/sentry-android/pull/156)) - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) - -We'd love to get feedback and we'll work in getting the GA `2.0.0` out soon. -Until then, the [stable SDK offered by Sentry is at version 1.7.28](https://github.com/getsentry/sentry-java/releases/tag/v1.7.28) - -## 2.0.0-alpha07 - -Third release of Sentry's new SDK for Android. - -### Fixes - -- Fixed release for jcenter and bintray - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) - -We'd love to get feedback and we'll work in getting the GA `2.0.0` out soon. -Until then, the [stable SDK offered by Sentry is at version 1.7.28](https://github.com/getsentry/sentry-java/releases/tag/v1.7.28) - -## 2.0.0-alpha06 - -Second release of Sentry's new SDK for Android. - -### Fixes - -- Fixed a typo on pom generation. - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) - -We'd love to get feedback and we'll work in getting the GA `2.0.0` out soon. -Until then, the [stable SDK offered by Sentry is at version 1.7.28](https://github.com/getsentry/sentry-java/releases/tag/v1.7.28) - -## 2.0.0-alpha05 - -First release of Sentry's new SDK for Android. - -New features not offered by our current (1.7.x), stable SDK are: - -- NDK support - - Captures crashes caused by native code - - Access to the [`sentry-native` SDK](https://github.com/getsentry/sentry-native/) API by your native (C/C++/Rust code/..). -- Automatic init (just add your `DSN` to the manifest) - - Proguard rules are added automatically - - Permission (Internet) is added automatically -- Uncaught Exceptions might be captured even before the app restarts -- Unified API which include scopes etc. -- More context/device information -- Packaged as `aar` -- Frames from the app automatically marked as `InApp=true` (stack traces in Sentry highlights them by default). -- Complete Sentry Protocol available. -- All threads and their stack traces are captured. -- Sample project in this repo to test many features (segfault, uncaught exception, scope) - -Features from the current SDK like `ANR` are also available (by default triggered after 4 seconds). - -Packages were released on [`bintray`](https://dl.bintray.com/getsentry/sentry-android/io/sentry/), [`jcenter`](https://jcenter.bintray.com/io/sentry/sentry-android/) - -We'd love to get feedback and we'll work in getting the GA `2.0.0` out soon. -Until then, the [stable SDK offered by Sentry is at version 1.7.28](https://github.com/getsentry/sentry-java/releases/tag/v1.7.28) diff --git a/CHANGES b/CHANGES new file mode 100644 index 00000000000..f3d16cf2bdb --- /dev/null +++ b/CHANGES @@ -0,0 +1,387 @@ +Version 1.7.11 +-------------- + +- + +Version 1.7.10 +-------------- + +- Android Plugin: Look for properties file directly under flavor directory. (thanks biggestT) +- Drop events when the server returns an HTTP 429. + +Version 1.7.9 +------------- + +- Encode SDK_VERSION in a Java class instead of looking up a ResouceBundle. + +Version 1.7.8 +------------- + +- Fix Android Gradle Plugin for >=3.3.0. + +Version 1.7.7 +------------- + +- Handle exceptions in `toString` calls when marshalling arbitrary objects to JSON. + +Version 1.7.6 +------------- + +*Built with the incorrect version of Java, please skip this release* + +Version 1.7.5 +------------- + +- Add `BreadcrumbBuilder.withData` (thanks kohenkatz) + +Version 1.7.4 +------------- + +- Allow new-style DSNs without the secret key. + +Version 1.7.3 +------------- + +- Handle Event `extra` deserialization failures when using Proguard. (thanks mcomella) +- Fix support for JUL handler level. (thanks nsitbon) + +Version 1.7.2 +------------- + +- Set HTTPConnection read timeout to 5 seconds. (thanks kalaspuffar) + +Version 1.7.1 +------------- + +- Android Gradle Plugin: Improve various directory structure support. (thanks ruZZil) + +Version 1.7.0 +------------- + +- Warn (once) when a client lockdown is first initiated and why. +- Log events skipped by a lockdown at the DEBUG level. +- Don't log a full stacktrace when an HTTP 429 (Too Many Requests) is returned. +- Fix parsing of Retry-After header which apparently changed to a floating point number. +- Pass MDC context down to AsyncConnection worker threads. +- Changed `buffer.size` default from 50 to 10. +- When buffering is enabled, only retry sending events when the network is down or the project + is being throttled (HTTP 429). + +Version 1.6.8 +------------- + +- Create a noop sentry client when the user provides bad configuration. (thanks dmclain) +- Use project.name instead of hardcoding 'app' in Android Gradle Plugin. + +Version 1.6.7 +------------- + +- Add Automatic-Module-Name to manifest so we have basic Java 9 support. +- Print unhandled exceptions to stderr by default (like ThreadGroup does). + +Version 1.6.6 +------------- + +- Attempt to unregister async/buffer shutdown hooks when connection is closed. (thanks obourgain) + +Version 1.6.5 +------------- + +- Implemented Serializable for DebugImage, fixes buffering with Proguard symbols. +- Use default DSN when empty string is provided. (thanks afking) + +Version 1.6.4 +------------- + +- Add web-fragment.xml to prevent classpath scanning. +- Update Android Gradle plugin to use new NPM package if available. + +Version 1.6.3 +------------- + +- Fix issue where Gradle tasks were not created. (thanks szymanskip) + +Version 1.6.2 +------------- + +- Handle multi-APK projects with different version codes. + +Version 1.6.1 +------------- + +- Don't throw a ConnectionException if an event is filtered by the Sentry server (HTTP 403). + +Version 1.6.0 +------------- + +- Add transaction field to Event and EventBuilder. +- Deprecate culprits in favor of transactions. +- Culprit is no longer set automatically in logging integrations. + +Version 1.5.6 +------------- + +- Add ``in_app`` frame blacklist regex list to skip CGLIB generated classes in Spring. + +Version 1.5.5 +------------- + +- Fix crash on Android API < 16. (thanks Syhids) + +Version 1.5.4 +------------- + +- Change ``EventBuilder$HostnameCache`` timeout to only log at debug level. + +Version 1.5.3 +------------- + +- Cleanup SentryEnvironment state to avoid memory leak warnings on Tomcat. +- Fix for buffering Events to disk that have null values in their extras map. + +Version 1.5.2 +------------- + +- Add ability to remove individual tags or extra data from the context, or clear them entirely. + +Version 1.5.1 +------------- + +- Don't attempt to serialize Iterables and don't special case serialization for Path objects, fixes broken Android release. + +Version 1.5.0 +------------- + +- Reduce logging noise if JNDI is missing. +- Add OSGi support. (thanks markwoon) +- Local variable agent beta. + +Version 1.4.0 +------------- + +- Automatically set ``stacktrace.app.packages`` on Android if it is not provided by the user. +- Enable UncaughtExceptionHandler by default, add ``uncaught.handler.enabled`` option. +- Add Spring exception reporter and Spring Boot servlet initializer. + +Version 1.3.1 +------------- + +- Reduce startup and shutdown logging noise by moving a lot of INFO level logs to DEBUG. +- Android: fallback to `os.version` property for kernel version is `/proc/version` is unreadable. + +Version 1.3.0 +------------- + +- Deprecate ``extratags`` in favor of the more clear ``mdctags``. +- Add ability to set ``extra`` data via options and on ``SentryClient`` instances. +- Add ability to set ``extra`` data and ``tags`` in the current context. + +Version 1.2.2 +------------- + +- Make operations on Context threadsafe. + +Version 1.2.1 +------------- + +- Fix ``SentryStackTraceElement`` to implement ``Serializable``. + +Version 1.2.0 +------------- + +- Allow overriding the location of the properties file. +- Add support for handling and uploading Proguard files to Sentry (for Android applications). +- Cleanup thread local context in ``ThreadLocalContextManager`` after each servlet request finishes. +- Change the ``EventBuilder`` hostname lookup code to only run one thread at a time. +- Add ``ShouldSendEventCallback``, which can be added to each ``SentryClient`` instance. +- Add ability to set extra data on the ``UserInterface``. + +Version 1.1.0 +------------- + +- Add ability to set the culprit from a SentryStackTraceElement. +- (alpha) Add agent to collect local variable informatino when an exception occurs. + +Version 1.0.0 +------------- + +1.0.0 is a major refactor, please see the official documentation to see how configuration and usage +has changed: https://docs.sentry.io/clients/java/ + +- Move from ``com.getsentry.raven`` package to ``io.sentry``. +- Rename from ``raven-java`` to ``sentry-java``. +- Rename the ``Sentry`` class to ``SentryClient``. +- Rename the ``SentryFactory`` class to ``SentryClientFactory``, and renamed all subclasses + to now end in ``ClientFactory``. +- Remove ``Breadcrumbs`` class. +- Add new ``Sentry`` class for static client initialization, access and usage. +- Remove ``android.Sentry`` in favor of using the single static ``Sentry`` class everywhere. +- Rename ``sentryFactory`` option to ``factory`` so that the environment variable configuration is + now ``SENTRY_FACTORY`` and the Java System Property is ``sentry.factory``. +- Add ``dist`` field to Event. +- Compressed JSON payloads are no longer base64 encoded. +- Rename ``EventSendFailureCallback`` to ``EventSendCallback``, add ``onSuccess`` callback. +- Add way to set release, dist, environment, serverName, tags, and extraTags on a ``SentryClient``. +- Add way to set release, dist, environment, serverName, tags, and extraTags via the DSN. +- Logging integrations now use the new ``Sentry`` static API. +- Configuration can now be provided by a ``sentry.properties`` file provided in resources. + +raven-java changelog (before rename to sentry-java) +=================================================== + +Version 8.0.2 +------------- + +- Add user breadcrumb type. (thanks NLthijs48) +- Add getter for underlying ``Event`` in ``EventBuilder`` so that helpers can read fields. (thanks anjo-swe) + +Version 8.0.1 +------------- + +- Fix setting the ``RavenFactory`` via Java System Properties or environment variables. +- Fix message interface errors in log4j2 integration. + +Version 8.0.0 +------------- + +- Remove ``DefaultRavenFactory.getNotInAppFrames()`` blacklist in favor of a new whitelist method, + ``DefaultRavenFactory.getInAppFrames(Dsn dsn)``. +- Add ``raven.stacktrace.app.packages`` DSN option for configuring your application's package prefixes. +- Fix so that system properties and environment variables always override hardcoded logger configuration for settings + such as release, environment, etc. +- Fix Raven initialization so that slf4j doesn't emit log replay warning on startup. +- Changed ``Breadcrumb`` and `BreadcrumbBuilder`` to use enums for some fields. + +Version 7.8.6 +------------- + +- Automatically collect device information in ``contexts`` field on Android. +- Updated Jackson, slf4j, log4j2 and logback dependencies. + +Version 7.8.5 +------------- + +- Add optional ``body`` field to HttpInterface. +- Fix name of the lockdown logger. + +Version 7.8.4 +------------- + +- Add ``contexts`` field support to Events. +- Add ``com.getsentry.raven.Raven.lockdown`` logger that can be disabled to ignore warning messages + on each attempted Event send when Raven is in lockdown mode. + +Version 7.8.3 +------------- + +- Add User to RavenContext and ContextBuilderHelper. (thanks mpecan) +- Drop Events when the connection is locked down, users must enable buffering to attempt to resend later. +- Respect the Sentry server's ``Retry-After`` header. + +Version 7.8.2 +------------- + +- Fix race condition, ensuring Raven initialization is synchronized in appenders. (thanks OutOfBrain) +- Allow use of custom AndroidRavenFactory. (thanks kassim) +- Add ``sdk`` field to payload, identify subprojects in SDK name. +- Add ``raven.sample.rate`` DSN option to optionally reduce the number of events sent to the server. + +Version 7.8.1 +------------- + +- Make Breadcrumbs serializable. +- Don't log errors in RavenServletRequestListener if Raven hasn't been initialized. + +Version 7.8.0 +------------- + +- Add Android support under the ``raven-android`` subproject. +- Add Filters to drop all Raven logs before they go to the Sentry server. +- Loosen permission on some Appender/Handler methods to allow for easier overriding. (thanks briprowe) + +Version 7.7.1 +------------- + +- Add ``raven.maxmessagelength`` DSN option. (thanks vektory79) +- Add ``Buffer`` interface, used to store events that fail to be sent to the Sentry server. + Includes a DiskBuffer implementation and related ``raven.buffer.*`` options. +- Broke out many helper methods inside of ``DefaultRavenFactory`` to allow for easier + overrides. +- Add a way to retrieve the thread's last sent Event ID, if any. Useful for integrating + with the user feedback feature. +- Add ``raven.async.queue.overflow`` option for controlling what to do when the async + executor queue is full. (thanks barogi) + +Version 7.7.0 +------------- + +- Add static ``Raven.capture`` methods that send to the most recently constructed Raven instance. +- Add support for setting ``ravenFactory``, ``release``, ``environment`` and ``serverName`` + via JNDI, System Environment or Java System Properties (like the DSN). +- Send headers as an array of arrays, no longer wrapped in added apostrophes. + +Version 7.6.0 +------------- + +- Add ``environment`` property to events and appenders. + +Version 7.5.0 +------------- + +- Add support for configuring an HTTP proxy in the DSN. +- Add more debugging information to ``RavenFactory.ravenInstance``. +- Add ``formatted`` field to JSON payload if possible. + +Version 7.4.0 +------------- + +- Changed default ``raven.async.queuesize`` from unlimited to 50. +- Add callback system for exceptions raised while attempting to send events. + +Version 7.3.0 +------------- + +- Add (manual) support for breadcrumbs to event objects. +- Add ``sendEvent(EventBuilder)`` method which calls builder helpers before building and sending the ``Event``. +- Add ``RavenContext`` which tracks thread-local state. +- Add ``Breadcrumbs`` helper to log breadcrumbs from anywhere without manually passing context around. + +Version 7.2.3 +------------- + +- Accept ``Throwable`` instances as parameter to ``Raven.sendException``. +- Add ``raven.async.shutdowntimeout`` option. +- Remove default ``WARNING`` filter level for the Logback appender. + +Version 7.2.2 +------------- + +- Fix ServerName configuration in ``raven-log4j``. + +Version 7.2.1 +------------- + +- Drop dependency on Guava. + +Version 7.2.0 +------------- + +- Add printfStyle option to JUL integration. (thanks giilby) +- Updated Log4j2 documentation to refer to the Thread Context rather than the MDC. (thanks grobmeier) +- Fix duplicate ShutdownHook warnings in ``raven-log4j2``. +- Add way to override how remote addresses are resolved (RemoteAddressResolver interface). (thanks j-fernandes) +- Add way to set ``serverName`` statically in configuration. (thanks mattbillenstein) + +Version 7.1.0 +------------- + +- Use RavenFactory's class loader when creating the service loader, fixing some issues in containers. (thanks exell-christopher) +- Add ``release`` property to ``raven-log4j``. (thanks molaschi) +- Add ``release`` property to JUL integration and ``raven-log4j2``. +- Add setters for ``dsn`` and ``tags`` in JUL integration, fixes integration with WildFly. (thanks giilby) + + +Version 7.0.0 +------------- + +- Changed Maven groupId to ``com.getsentry`` diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 9de4130c1a7..00000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,156 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -This is the Sentry Java/Android SDK - a comprehensive error monitoring and performance tracking SDK for Java and Android applications. The repository contains multiple modules for different integrations and platforms. - -## Build System - -The project uses **Gradle** with Kotlin DSL. Key build files: -- `build.gradle.kts` - Root build configuration -- `settings.gradle.kts` - Multi-module project structure -- `buildSrc/` and `build-logic/` - Custom build logic and plugins -- `Makefile` - High-level build commands - -## Essential Commands - -### Development Workflow -```bash -# Format code and regenerate .api files (REQUIRED before committing) -./gradlew spotlessApply apiDump - -# Run all tests and linter -./gradlew check - -# Build entire project -./gradlew build - -# Create coverage reports -./gradlew jacocoTestReport koverXmlReportRelease - -# Generate documentation -./gradlew aggregateJavadocs -``` - -### Testing -```bash -# Run unit tests for a specific file -./gradlew '::testDebugUnitTest' --tests="**" --info - -# Run system tests (requires Python virtual env) -make systemTest - -# Run specific test suites -./gradlew :sentry-android-core:testDebugUnitTest -./gradlew :sentry:test -``` - -### Code Quality -```bash -# Check code formatting -./gradlew spotlessJavaCheck spotlessKotlinCheck - -# Apply code formatting -./gradlew spotlessApply - -# Update API dump files (after API changes) -./gradlew apiDump - -# Dependency updates check -./gradlew dependencyUpdates -Drevision=release -``` - -### Android-Specific Commands -```bash -# Assemble Android test APKs -./gradlew :sentry-android-integration-tests:sentry-uitest-android:assembleRelease -./gradlew :sentry-android-integration-tests:sentry-uitest-android:assembleAndroidTest -DtestBuildType=release - -# Run critical UI tests -./scripts/test-ui-critical.sh -``` - -## Development Workflow Rules - -### Planning and Implementation Process -1. **First think through the problem**: Read the codebase for relevant files and propose a plan -2. **Check in before beginning**: Verify the plan before starting implementation -3. **Use todo tracking**: Work through todo items, marking them as complete as you go -4. **High-level communication**: Give high-level explanations of changes made, not step-by-step descriptions -5. **Simplicity first**: Make every task and code change as simple as possible. Avoid massive or complex changes. Impact as little code as possible. -6. **Format and regenerate**: Once done, format code and regenerate .api files: `./gradlew spotlessApply apiDump` -7. **Propose commit**: As final step, git stage relevant files and propose (but not execute) a single git commit command - -## Module Architecture - -The repository is organized into multiple modules: - -### Core Modules -- **`sentry`** - Core Java SDK implementation -- **`sentry-android-core`** - Core Android SDK implementation -- **`sentry-android`** - High-level Android SDK - -### Integration Modules -- **Spring Framework**: `sentry-spring*`, `sentry-spring-boot*` -- **Logging**: `sentry-logback`, `sentry-log4j2`, `sentry-jul` -- **Web**: `sentry-servlet*`, `sentry-okhttp`, `sentry-apache-http-client-5` -- **GraphQL**: `sentry-graphql*`, `sentry-apollo*` -- **Android UI**: `sentry-android-fragment`, `sentry-android-navigation`, `sentry-compose` -- **Reactive**: `sentry-reactor`, `sentry-ktor-client` -- **Monitoring**: `sentry-opentelemetry*`, `sentry-quartz` - -### Utility Modules -- **`sentry-test-support`** - Shared test utilities -- **`sentry-system-test-support`** - System testing infrastructure -- **`sentry-samples`** - Example applications -- **`sentry-bom`** - Bill of Materials for dependency management - -### Key Architectural Patterns -- **Multi-platform**: Supports JVM, Android, and Kotlin Multiplatform (Compose modules) -- **Modular Design**: Each integration is a separate module with minimal dependencies -- **Options Pattern**: Features are opt-in via `SentryOptions` and similar configuration classes -- **Transport Layer**: Pluggable transport implementations for different environments -- **Scope Management**: Thread-safe scope/context management for error tracking - -## Development Guidelines - -### Code Style -- **Languages**: Java 8+ and Kotlin -- **Formatting**: Enforced via Spotless - always run `./gradlew spotlessApply` before committing -- **API Compatibility**: Binary compatibility is enforced - run `./gradlew apiDump` after API changes - -### Testing Requirements -- Write comprehensive unit tests for new features -- Android modules require both unit tests and instrumented tests where applicable -- System tests validate end-to-end functionality with sample applications -- Coverage reports are generated for both JaCoCo (Java/Android) and Kover (KMP modules) - -### Contributing Guidelines -1. Follow existing code style and language -2. Do not modify API files (e.g. sentry.api) manually - run `./gradlew apiDump` to regenerate them -3. Write comprehensive tests -4. New features must be **opt-in by default** - extend `SentryOptions` or similar Option classes with getters/setters -5. Consider backwards compatibility - -## Domain-Specific Knowledge Areas - -For complex SDK functionality, refer to the detailed cursor rules in `.cursor/rules/`: - -- **Scopes and Hub Management**: See `.cursor/rules/scopes.mdc` for details on `IScopes`, scope types (global/isolation/current), thread-local storage, forking behavior, and v7→v8 migration patterns -- **Event Deduplication**: See `.cursor/rules/deduplication.mdc` for `DuplicateEventDetectionEventProcessor` and `enableDeduplication` option -- **Offline Behavior and Caching**: See `.cursor/rules/offline.mdc` for envelope caching, retry logic, transport behavior, and Android vs JVM differences -- **OpenTelemetry Integration**: See `.cursor/rules/opentelemetry.mdc` for agent vs agentless modes, span processing, context propagation, and configuration -- **System Testing (E2E)**: See `.cursor/rules/e2e_tests.mdc` for system test framework, mock server setup, and CI workflows - -### Usage Pattern -When working on these specific areas, read the corresponding cursor rule file first to understand the detailed architecture, then proceed with implementation. - -## Useful Resources - -- Main SDK documentation: https://develop.sentry.dev/sdk/overview/ -- Internal contributing guide: https://docs.sentry.io/internal/contributing/ -- Git commit message conventions: https://develop.sentry.dev/engineering-practices/commit-messages/ - -This SDK is production-ready and used by thousands of applications. Changes should be thoroughly tested and maintain backwards compatibility. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e2c8b78bf1..bf76825124e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,63 +1,17 @@ # Contributing to sentry-java We love pull requests from everyone. -We suggest opening an issue to discuss bigger changes before investing on a big PR. -# Requirements +The test suite currently requires you run JDK version `1.7.0_80`. +See [#487](https://github.com/getsentry/sentry-java/issues/478) +for more information. -The project requires you to run JDK 17. - -## Android - -This repository is a monorepo which includes Java and Android libraries. -If you'd like to contribute to Java and don't have an Android SDK with NDK installed, -you can remove the Android libraries from `settings.gradle.kts` to make sure you can build the project. - -# Git commit hook: - -Optionally, you can install spotlessCheck pre-commit hook: - -```shell -git config core.hooksPath hooks/ -``` - -To run the build and tests: - -```shell -make compile -``` - -# Format - -To format the changed code and make CI happy you can run: - -```shell -make format -``` - -or - -```shell -./gradlew spotlessApply -``` - -# Binary compatibility validation - -To prevent breaking ABI changes and exposing things we should not, we make use of https://github.com/Kotlin/binary-compatibility-validator. If your change intended to introduce a new public method/property or modify the existing one you can overwrite the API declarations to make CI happy as follows (overwrites them from scratch): +To run the tests (and checkstyle): ```shell -make api +make test ``` -or - -```shell -./gradlew apiDump -``` - -However, if your change did not intend to modify the public API, consider changing the method/property visibility or removing the change altogether. - -# CI - -Build and tests are automatically run against branches and pull requests -via GH Actions. +Tests are automatically run against branches and pull requests +via TravisCI, so you can also depend on that if you'd rather not +deal with installing an older JDK. diff --git a/LICENSE b/LICENSE index 6b8b8d58af0..02802804714 100644 --- a/LICENSE +++ b/LICENSE @@ -1,22 +1,13 @@ -MIT License +Copyright (c) 2016 Functional Software, Inc. +Copyright (c) 2012 Ken Cochrane and individual contributors. +All rights reserved. -Copyright (c) 2019 Sentry -Copyright (c) 2015 Salomon BRYS for Android ANRWatchDog +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MIGRATION.md b/MIGRATION.md deleted file mode 100644 index 5513d143ddb..00000000000 --- a/MIGRATION.md +++ /dev/null @@ -1,155 +0,0 @@ -# Migrating from `sentry-java` to `sentry-android` - -#### Docs - -Migration page from [sentry-android 1.x to sentry-android 2.x](https://docs.sentry.io/platforms/android/migrate). - -#### Installation - -_Old_: - -``` -Sentry.init("___PUBLIC_DSN___", new AndroidSentryClientFactory(context)); -``` - -_New_: - -The SDK is able to initialize automatically. The only thing required is to make the DSN available. -*`sentry.properties` has been discontinued and configurations on this SDK version is over `AndroidManifest.xml` or code.* - -```xml - -``` -_Or:_ - -*If you want to call `SentryAndroid.init(...)` by yourself, first of all you need to disable the `auto-init` feature.* - -```xml - -``` - -``` -SentryAndroid.init(context, options -> { - options.setDsn("___PUBLIC_DSN___"); -}); -``` - -#### Set tag - -_Old_: - -``` -Sentry.getContext().addTag("tagName", "tagValue"); -``` - -_New_: - -``` -Sentry.setTag("tagName", "tagValue"); -``` - -#### Capture custom exception - -_Old_: - -``` -try { - int x = 1 / 0; -} catch (Exception e) { - Sentry.capture(e); -} -``` - -_New_: - -``` -try { - int x = 1 / 0; -} catch (Exception e) { - Sentry.captureException(e); -} -``` - -#### Capture a custom event - -_Old_: - -``` -Exception exception = new Exception("custom error"); -EventBuilder eventBuilder = new EventBuilder() - .withLevel(Event.Level.ERROR) - .withSentryInterface(new ExceptionInterface(exception)); -Sentry.capture(eventBuilder); -``` - -_New_: - -``` -Exception exception = new Exception("custom error"); -SentryEvent event = new SentryEvent(exception); -event.setLevel(SentryLevel.ERROR); -Sentry.captureEvent(event); -``` - -#### Capture a message - -_Old_: - -``` -Sentry.capture("This is a test"); -``` - -_New_: - -``` -Sentry.captureMessage("This is a test"); // SentryLevel.INFO by default -Sentry.captureMessage("This is a test", SentryLevel.WARNING); // or specific level -``` - -#### Breadcrumbs - -_Old_: - -``` -Sentry.getContext().recordBreadcrumb( - new BreadcrumbBuilder().setMessage("User made an action").build() -); -``` - -_New_: - -``` -Sentry.addBreadcrumb("User made an action"); -``` - -#### User - -_Old_: - -``` -Sentry.getContext().setUser( - new UserBuilder().setEmail("hello@sentry.io").build() -); -``` - -_New_: - -``` -User user = new User(); -user.setEmail("hello@sentry.io"); -Sentry.setUser(user); -``` - -#### Set extra - -_Old_: - -``` -Sentry.getContext().addExtra("extra", "thing"); -``` - -_New_: - -``` -Sentry.setExtra("extra", "thing"); -``` diff --git a/Makefile b/Makefile index 55f465a9663..917e2905803 100644 --- a/Makefile +++ b/Makefile @@ -1,79 +1,60 @@ -.PHONY: all clean compile javadocs dryRelease update checkFormat api assembleBenchmarkTestRelease assembleUiTestRelease assembleUiTestCriticalRelease createCoverageReports runUiTestCritical setupPython systemTest systemTestInteractive check preMerge publish +# Temporary: Required for macOS release +# export GPG_TTY=`tty` +# eval $(gpg-agent --daemon) -all: stop clean javadocs compile createCoverageReports -assembleBenchmarks: assembleBenchmarkTestRelease -assembleUiTests: assembleUiTestRelease -preMerge: check createCoverageReports -publish: clean dryRelease +# the test suite currently only works with 1.7.0_80 +# https://github.com/getsentry/sentry-java/issues/478 -# deep clean -clean: - ./gradlew clean --no-configuration-cache - rm -rf distributions - rm -rf .venv +.PHONY: checkstyle compile test install clean prepare prepareMvn prepareChanges perform verify + +# TODO: Fix to work between macOS and Linux +MVN=mvn -e -Dhttps.protocols=TLSv1.2 +ECHO=echo +SED=sed + +all: checkstyle test install -# build and run tests compile: - ./gradlew build - -javadocs: - ./gradlew aggregateJavadocs - -# do a dry release (like a local deploy) -dryRelease: - ./gradlew aggregateJavadocs distZip --no-build-cache --no-configuration-cache - -# check for dependencies update -update: - ./gradlew dependencyUpdates -Drevision=release - -# Spotless check's code -checkFormat: - ./gradlew spotlessJavaCheck spotlessKotlinCheck - -# Binary compatibility validator -api: - ./gradlew apiDump - -# Assemble release and Android test apk of the uitest-android-benchmark module -assembleBenchmarkTestRelease: - ./gradlew :sentry-android-integration-tests:sentry-uitest-android-benchmark:assembleRelease - ./gradlew :sentry-android-integration-tests:sentry-uitest-android-benchmark:assembleAndroidTest -DtestBuildType=release - -# Assemble release and Android test apk of the uitest-android module -assembleUiTestRelease: - ./gradlew :sentry-android-integration-tests:sentry-uitest-android:assembleRelease - ./gradlew :sentry-android-integration-tests:sentry-uitest-android:assembleAndroidTest -DtestBuildType=release - -# Assemble release of the uitest-android-critical module -assembleUiTestCriticalRelease: - ./gradlew :sentry-android-integration-tests:sentry-uitest-android-critical:assembleRelease - -# Run Maestro tests for the uitest-android-critical module -runUiTestCritical: - ./scripts/test-ui-critical.sh - -# Create coverage reports -# - Jacoco for Java & Android modules -# - Kover for KMP modules e.g sentry-compose -createCoverageReports: - ./gradlew jacocoTestReport - ./gradlew koverXmlReportRelease - -# Create the Python virtual environment for system tests, and install the necessary dependencies -setupPython: - @test -d .venv || python3 -m venv .venv - .venv/bin/pip install --upgrade pip - .venv/bin/pip install -r requirements.txt - -# Run system tests for sample applications -systemTest: setupPython - .venv/bin/python test/system-test-runner.py test --all - -# Run system tests with interactive module selection -systemTestInteractive: setupPython - .venv/bin/python test/system-test-runner.py test --interactive - -# Run tests and lint -check: - ./gradlew check + $(MVN) compile + +checkstyle: + $(MVN) checkstyle:check + +verify: + $(MVN) verify + +test: verify + +install: + $(MVN) source:jar install -Dcheckstyle.skip=true -DskipTests -Dmaven.javadoc.skip=true -B -V + +clean: + $(MVN) clean + +prepareMvn: +# Prepare release (interactive) + $(MVN) release:prepare + +prepareChanges: +# Store new project version + $(eval DEV_VERSION=$(shell mvn help:evaluate -Dexpression=project.version | grep -Ev '^\[' | $(SED) -e 's/-SNAPSHOT//')) + @echo Development version: $(DEV_VERSION) +# Store enough dashes to go under "Version X.Y.Z", accounting for changes in the $VERSION length + $(eval DASHES=$(shell python -c 'print("-" * (8 + len("$(DEV_VERSION)")))')) +# Add new Version section to the top of the CHANGES file + @echo Updating CHANGES file + $(ECHO) -e "Version $(DEV_VERSION)\n$(DASHES)\n\n-\n" > CHANGES.new && cat CHANGES >> CHANGES.new && mv CHANGES.new CHANGES + git add CHANGES + git commit -m "Bump CHANGES to $(DEV_VERSION)" + +change-version: + $(MVN) release:update-versions + +# Prepare is broken into stages because otherwise `make` will run things out of order +prepare: prepareMvn prepareChanges + +perform: + $(MVN) release:perform + +rollback: + $(MVN) release:rollback diff --git a/README.md b/README.md index ee32d485f42..15a9246060b 100644 --- a/README.md +++ b/README.md @@ -1,129 +1,25 @@

- - - - - Sentry - - + + + +
+

sentry-java - Sentry SDK for Java

-_Bad software is everywhere, and we're tired of it. Sentry is on a mission to help developers write better software faster, so we can get back to enjoying technology. If you want to join us [**Check out our open positions**](https://sentry.io/careers/)_ +This is the Java SDK for [Sentry](https://sentry.io/). It provides out-of-the-box support for +many popular JVM based frameworks and libraries, including Android, Log4j, Logback, and more. -Sentry SDK for Java and Android -=========== -[![GH Workflow](https://img.shields.io/github/actions/workflow/status/getsentry/sentry-java/build.yml?branch=main)](https://github.com/getsentry/sentry-java/actions) -[![codecov](https://codecov.io/gh/getsentry/sentry-java/branch/main/graph/badge.svg)](https://codecov.io/gh/getsentry/sentry-java) -[![X Follow](https://img.shields.io/twitter/follow/sentry?label=sentry&style=social)](https://x.com/intent/follow?screen_name=sentry) -[![Discord Chat](https://img.shields.io/discord/621778831602221064?logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/PXa5Apfe7K) +In most cases using one of the existing integrations is preferred, but Sentry additionally provides +a low level client for manually building and sending events to Sentry that can be used in any JVM +based application. -| Packages | Maven Central | Minimum Android API Version | -|-----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| ------- | -| sentry-android | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-android-core | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android-core?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-android-distribution | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android-distribution?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-android-ndk | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android-ndk?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-android-timber | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android-timber?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-android-fragment | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android-fragment?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-android-navigation | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android-navigation?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-android-sqlite | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android-sqlite?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-android-replay | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android-replay?style=for-the-badge&logo=sentry&color=green) | 26 | -| sentry-compose-android | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-compose-android?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-compose-desktop | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-compose-desktop?style=for-the-badge&logo=sentry&color=green) | -| sentry-compose | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-compose?style=for-the-badge&logo=sentry&color=green) | -| sentry-apache-http-client-5 | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-apache-http-client-5?style=for-the-badge&logo=sentry&color=green) | -| sentry | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-jul | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-jul?style=for-the-badge&logo=sentry&color=green) | -| sentry-jdbc | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-jdbc?style=for-the-badge&logo=sentry&color=green) | -| sentry-apollo | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-apollo?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-apollo-3 | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-apollo-3?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-apollo-4 | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-apollo-4?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-kotlin-extensions | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-kotlin-extensions?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-ktor-client | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-ktor-client?style=for-the-badge&logo=sentry&color=green) | 21 | -| sentry-servlet | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-servlet?style=for-the-badge&logo=sentry&color=green) | | -| sentry-servlet-jakarta | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-servlet-jakarta?style=for-the-badge&logo=sentry&color=green) | | -| sentry-spring-boot | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-spring-boot?style=for-the-badge&logo=sentry&color=green) | -| sentry-spring-boot-jakarta | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-spring-boot-jakarta?style=for-the-badge&logo=sentry&color=green) | -| sentry-spring-boot-4 | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-spring-boot-4?style=for-the-badge&logo=sentry&color=green) | -| sentry-spring-boot-4-starter | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-spring-boot-4-starter?style=for-the-badge&logo=sentry&color=green) | -| sentry-spring-boot-starter | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-spring-boot-starter?style=for-the-badge&logo=sentry&color=green) | -| sentry-spring-boot-starter-jakarta | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-spring-boot-starter-jakarta?style=for-the-badge&logo=sentry&color=green) | -| sentry-spring | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-spring?style=for-the-badge&logo=sentry&color=green) | -| sentry-spring-jakarta | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-spring-jakarta?style=for-the-badge&logo=sentry&color=green) | -| sentry-spring-7 | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-spring-7?style=for-the-badge&logo=sentry&color=green) | -| sentry-logback | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-logback?style=for-the-badge&logo=sentry&color=green) | -| sentry-log4j2 | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-log4j2?style=for-the-badge&logo=sentry&color=green) | -| sentry-bom | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-bom?style=for-the-badge&logo=sentry&color=green) | -| sentry-graphql | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-graphql?style=for-the-badge&logo=sentry&color=green) | -| sentry-graphql-core | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-graphql-core?style=for-the-badge&logo=sentry&color=green) | -| sentry-graphql-22 | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-graphql-22?style=for-the-badge&logo=sentry&color=green) | -| sentry-quartz | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-quartz?style=for-the-badge&logo=sentry&color=green) | -| sentry-openfeign | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-openfeign?style=for-the-badge&logo=sentry&color=green) | -| sentry-openfeature | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-openfeature?style=for-the-badge&logo=sentry&color=green) | -| sentry-launchdarkly-android | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-launchdarkly-android?style=for-the-badge&logo=sentry&color=green) | -| sentry-launchdarkly-server | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-launchdarkly-server?style=for-the-badge&logo=sentry&color=green) | -| sentry-opentelemetry-agent | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-opentelemetry-agent?style=for-the-badge&logo=sentry&color=green) | -| sentry-opentelemetry-agentcustomization | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-opentelemetry-agentcustomization?style=for-the-badge&logo=sentry&color=green) | -| sentry-opentelemetry-core | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-opentelemetry-core?style=for-the-badge&logo=sentry&color=green) | -| sentry-okhttp | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-okhttp?style=for-the-badge&logo=sentry&color=green) | -| sentry-reactor | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-reactor?style=for-the-badge&logo=sentry&color=green) | -| sentry-spotlight | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-spotlight?style=for-the-badge&logo=sentry&color=green) | +## Resources -# Releases +* [Documentation](https://docs.sentry.io/clients/java/) +* [Examples](https://github.com/getsentry/examples) +* [Bug Tracker](http://github.com/getsentry/sentry-java/issues) +* [Code](http://github.com/getsentry/sentry-java) +* [Mailing List](https://groups.google.com/group/getsentry) +* [IRC](irc://irc.freenode.net/sentry) (irc.freenode.net, #sentry) +* [Travis CI](http://travis-ci.org/getsentry/sentry-java) -This repo uses the following ways to release SDK updates: - -- `Pre-release`: We create pre-releases (alpha, beta, RC,…) for larger and potentially more impactful changes, such as new features or major versions. -- `Latest`: We continuously release major/minor/hotfix versions from the `main` branch. These releases go through all our internal quality gates and are very safe to use and intended to be the default for most teams. -- `Stable`: We promote releases from `Latest` when they have been used in the field for some time and in scale, considering time since release, adoption, and other quality and stability metrics. These releases will be indicated on the releases page (https://github.com/getsentry/sentry-java/releases/) with the `Stable` suffix. - -# Useful links and docs - -* A deep dive into how we built [Session Replay for Android](https://www.droidcon.com/2024/11/22/rewind-and-resolve-a-deep-dive-into-building-session-replay-for-android/) at Droidcon London 2024. -* Current Javadocs [generated from source code](https://getsentry.github.io/sentry-java/). -* Java SDK version 1.x [can still be found here](https://docs.sentry.io/clients/java/). -* Migration page from [sentry-android 1.x and 2.x to sentry-android 4.x](https://docs.sentry.io/platforms/android/migration/). -* Migration page from [sentry 1.x to sentry 4.x](https://docs.sentry.io/platforms/java/migration/). -* Releases from sentry-android [2.x and its changelogs](https://github.com/getsentry/sentry-android/releases). -* Sentry Android Gradle Plugin repo [sits on another repo](https://github.com/getsentry/sentry-android-gradle-plugin) - -# Blog posts - -* [Sentry’s Android Gradle Plugin Updated with Room Support and More](https://blog.sentry.io/2022/04/20/sentrys-android-gradle-plugin-updated-with-room-support-and-more/) -* [Troubleshooting Spring Boot applications with Sentry](https://blog.sentry.io/2022/04/18/troubleshooting-spring-boot-applications-with-sentry) -* [Android Manifest Placeholders](https://blog.sentry.io/2022/03/30/android-manifest-placeholders/) -* [UI Breadcrumbs for Android Error Events](https://blog.sentry.io/2022/02/08/ui-breadcrumbs-for-android-error-events) -* [Bytecode transformations: The Android Gradle Plugin](https://blog.sentry.io/2021/12/14/bytecode-transformations-the-android-gradle-plugin) -* [Sentry's response to Log4j vulnerability CVE-2021-44228](https://blog.sentry.io/2021/12/15/sentrys-response-to-log4j-vulnerability-cve-2021-44228) -* [Mobile Vitals - Four Metrics Every Mobile Developer Should Care About](https://blog.sentry.io/2021/08/23/mobile-vitals-four-metrics-every-mobile-developer-should-care-about/). -* [Supporting Native Android Libraries Loaded From APKs](https://blog.sentry.io/2021/05/13/supporting-native-android-libraries-loaded-from-apks). -* [A Sanity Listicle for Mobile Developers](https://blog.sentry.io/2021/03/30/a-sanity-listicle-for-mobile-developers/). -* [Performance Monitoring for Android Applications](https://blog.sentry.io/2021/03/18/performance-monitoring-for-android-applications). -* [Close the Loop with User Feedback](https://blog.sentry.io/2021/02/16/close-the-loop-with-user-feedback). -* [How to use Sentry Attachments with Mobile Applications](https://blog.sentry.io/2021/02/03/how-to-use-sentry-attachments-with-mobile-applications). -* [Adding Native support to our Android SDK](https://blog.sentry.io/2019/11/25/adding-native-support-to-our-android-sdk). -* [New Android SDK How-to](https://blog.sentry.io/2019/12/10/new-android-sdk-how-to). - -# Samples - -* [Sample App. with Sentry Android SDK and Sentry Gradle Plugin](https://github.com/getsentry/examples/tree/master/android). -* [Sample App. with Sentry Java SDK](https://github.com/getsentry/examples/tree/master/java). -* [Sample for Development](https://github.com/getsentry/sentry-java/tree/main/sentry-samples). - -# Sentry Self Hosted Compatibility - -Since version 3.0.0 of this SDK, Sentry version >= v20.6.0 is required. This only applies to self-hosted Sentry, if you are using [sentry.io](http://sentry.io/) no action is needed. - -Since version 6.0.0 of this SDK, Sentry version >= v21.9.0 is required or you have to manually disable sending client reports via the `sendClientReports` option. This only applies to self-hosted Sentry, if you are using [sentry.io](http://sentry.io/) no action is needed. - -Since version 7.0.0 of this SDK, Sentry version >= 22.12.0 is required to properly ingest transactions with unfinished spans. This only applies to self-hosted Sentry, if you are using [sentry.io](http://sentry.io/) no action is needed. - -# Resources - -* [![Java Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg?label=java%20docs)](https://docs.sentry.io/platforms/java/) -* [![Android Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg?label=android%20docs)](https://docs.sentry.io/platforms/android/) -* [![Discussions](https://img.shields.io/github/discussions/getsentry/sentry-java.svg)](https://github.com/getsentry/sentry-java/discussions) -* [![Discord Chat](https://img.shields.io/discord/621778831602221064?logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/PXa5Apfe7K) -* [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-sentry-green.svg)](http://stackoverflow.com/questions/tagged/sentry) -* [![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-sentry-green.svg)](https://github.com/getsentry/.github/blob/master/CODE_OF_CONDUCT.md) -* [![Twitter Follow](https://img.shields.io/twitter/follow/getsentry?label=getsentry&style=social)](https://twitter.com/intent/follow?screen_name=getsentry) diff --git a/agent/.gitignore b/agent/.gitignore new file mode 100644 index 00000000000..110456abe75 --- /dev/null +++ b/agent/.gitignore @@ -0,0 +1,40 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Custom +CMakeCache.txt +CMakeFiles/ +Makefile +cmake-build-debug/ +cmake_install.cmake +sentry_java_agent.cbp diff --git a/agent/CMakeLists.txt b/agent/CMakeLists.txt new file mode 100644 index 00000000000..0b8268a4011 --- /dev/null +++ b/agent/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.2) +project(sentry_java_agent) + +set(CMAKE_CXX_STANDARD 14) + +set(SOURCE_FILES agent.cpp lib.cpp lib.h) +add_library(sentry_agent SHARED ${SOURCE_FILES}) + +if($ENV{TARGET} MATCHES "i686") + message("Setting target to 32-bit.") + set_target_properties(sentry_agent PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32") +endif() + +include_directories($ENV{JAVA_HOME}/include) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + include_directories($ENV{JAVA_HOME}/include/darwin) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + include_directories($ENV{JAVA_HOME}/include/linux) +endif() diff --git a/agent/LICENSE b/agent/LICENSE new file mode 100644 index 00000000000..0eaccd3e00e --- /dev/null +++ b/agent/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2017 Sentry (https://sentry.io) +Copyright (c) 2017 Mike Clarke +Copyright (c) 2008 Tobias Ivarsson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/agent/README.md b/agent/README.md new file mode 100644 index 00000000000..df658a1db02 --- /dev/null +++ b/agent/README.md @@ -0,0 +1,9 @@ +# sentry-java-agent + +The Sentry Java Agent collects and stores local variable information for each stack frame +when an exception is created. If local variable information is available the Sentry Java +SDK will send the variable information along with events. + +Build: `cmake CMakeLists.txt && make` + +Run: `java -agentpath:libsentry_agent{.dylib,.so} ...` diff --git a/agent/agent.cpp b/agent/agent.cpp new file mode 100644 index 00000000000..b6a9b8d0cb5 --- /dev/null +++ b/agent/agent.cpp @@ -0,0 +1,131 @@ +// TODO: cache all FindClass/Find* calls globally +// TODO: better error messages with (void) jvmti->GetErrorName(errnum, &errnum_str); +// TODO: do we need any locking? jrawMonitorID lock; jvmti->RawMonitorEnter(lock); jvmti->RawMonitorExit(lock); +// TODO: use *options instead of env for log level + +#include "jvmti.h" +#include +#include +#include "lib.h" + +extern Level LOG_LEVEL; + +static bool INIT_SUCCESS = false; + +static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, + jmethodID method, jlocation location, jobject exception, + jmethodID catch_method, jlocation catch_location) { + log(TRACE, "ExceptionCallback called."); + + if (!INIT_SUCCESS) { + return; + } + + jclass framecache_class = env->FindClass("io/sentry/jvmti/FrameCache"); + if (framecache_class == nullptr) { + env->ExceptionClear(); + log(TRACE, "Unable to locate FrameCache class."); + return; + } + + jmethodID should_cache_method = env->GetStaticMethodID(framecache_class, + "shouldCacheThrowable", + "(Ljava/lang/Throwable;I)Z"); + if (should_cache_method == nullptr) { + log(TRACE, "Unable to locate static FrameCache.shouldCacheThrowable method."); + return; + } + + jint num_frames; + jvmtiError jvmti_error = jvmti->GetFrameCount(thread, &num_frames); + if (jvmti_error != JVMTI_ERROR_NONE) { + log(ERROR, "Could not get the frame count."); + return; + } + + jboolean shouldCache = env->CallStaticBooleanMethod(framecache_class, + should_cache_method, + exception, + num_frames); + if (!((bool) shouldCache)) { + return; + } + + jmethodID framecache_add_method = env->GetStaticMethodID(framecache_class, + "add", + "(Ljava/lang/Throwable;[Lio/sentry/jvmti/Frame;)V"); + if (framecache_add_method == nullptr) { + log(TRACE, "Unable to locate static FrameCache.add method."); + return; + } + + jint start_depth = 0; + jobjectArray frames = buildStackTraceFrames(jvmti, env, thread, start_depth, num_frames); + + env->CallStaticVoidMethod(framecache_class, framecache_add_method, exception, frames); + + log(TRACE, "ExceptionCallback exit."); +} + +JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { + char *env_log_level = std::getenv("SENTRY_AGENT_LOG_LEVEL"); + if (env_log_level != nullptr) { + std::string env_log_level_str(env_log_level); + for (auto &c: env_log_level_str) c = (char) toupper(c); + if (env_log_level_str.compare("TRACE") == 0) { + LOG_LEVEL = TRACE; + } else if (env_log_level_str.compare("DEBUG") == 0) { + LOG_LEVEL = DEBUG; + } else if (env_log_level_str.compare("INFO") == 0) { + LOG_LEVEL = INFO; + } else if (env_log_level_str.compare("WARN") == 0) { + LOG_LEVEL = WARN; + } else if (env_log_level_str.compare("ERROR") == 0) { + LOG_LEVEL = ERROR; + } else { + log(ERROR, "Unknown log level: " + env_log_level_str); + return JNI_ABORT; + } + } + + log(TRACE, "OnLoad called."); + + jvmtiEnv *jvmti; + jint jvmti_error; + + jvmti_error = vm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_0); + if (jvmti_error != JVMTI_ERROR_NONE || jvmti == nullptr) { + log(ERROR, "Unable to access JVMTI Version 1."); + return JNI_ABORT; + } + + jvmtiCapabilities capabilities; + memset(&capabilities, 0, sizeof(capabilities)); + capabilities.can_access_local_variables = 1; + capabilities.can_generate_exception_events = 1; + capabilities.can_get_line_numbers = 1; + jint capabilities_error = jvmti->AddCapabilities(&capabilities); + if (capabilities_error != JVMTI_ERROR_NONE) { + log(ERROR, "Unable to get the necessary JVMTI capabilities."); + return JNI_ABORT; + } + + jvmtiEventCallbacks callbacks; + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.Exception = &ExceptionCallback; + jvmti_error = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); + if (jvmti_error != JVMTI_ERROR_NONE) { + log(ERROR, "Unable to the necessary JVMTI callbacks."); + return JNI_ABORT; + } + + jvmti_error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, nullptr); + if (jvmti_error != JVMTI_ERROR_NONE) { + log(ERROR, "Unable to register the exception callback."); + return JNI_ABORT; + } + + INIT_SUCCESS = true; + log(TRACE, "OnLoad exit."); + return JNI_OK; +} diff --git a/agent/lib.cpp b/agent/lib.cpp new file mode 100644 index 00000000000..967af13739c --- /dev/null +++ b/agent/lib.cpp @@ -0,0 +1,270 @@ +#include +#include "lib.h" + +const std::string LEVEL_STRINGS[] = { + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR" +}; + +Level LOG_LEVEL = WARN; + +void log(Level level, std::string message) { + if (level >= LOG_LEVEL) { + std::cerr << LEVEL_STRINGS[level] << " [Sentry Agent]: " << message << std::endl; + } +} + +static jint throwException(JNIEnv *env, const char *name, const char *message) { + jclass clazz; + clazz = env->FindClass(name); + return env->ThrowNew(clazz, message); +} + +static jobject getLocalValue(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, + jvmtiLocalVariableEntry *table, int index) { + jobject result; + jint i_val; + jfloat f_val; + jdouble d_val; + jlong j_val; + jvmtiError jvmti_error; + jclass reflect_class; + jmethodID value_of; + + switch (table[index].signature[0]) { + case '[': // Array + case 'L': // Object + jvmti_error = jvmti->GetLocalObject(thread, depth, table[index].slot, &result); + if (jvmti_error != JVMTI_ERROR_NONE || result == nullptr) { + return nullptr; + } + + break; + case 'J': // long + jvmti_error = jvmti->GetLocalLong(thread, depth, table[index].slot, &j_val); + break; + case 'F': // float + jvmti_error = jvmti->GetLocalFloat(thread, depth, table[index].slot, &f_val); + break; + case 'D': // double + jvmti_error = jvmti->GetLocalDouble(thread, depth, table[index].slot, &d_val); + break; + case 'I': // int + case 'S': // short + case 'C': // char + case 'B': // byte + case 'Z': // boolean + jvmti_error = jvmti->GetLocalInt(thread, depth, table[index].slot, &i_val); + break; + // error type + default: + return nullptr; + } + + if (jvmti_error != JVMTI_ERROR_NONE) { + return nullptr; + } + + switch (table[index].signature[0]) { + case 'J': // long + reflect_class = env->FindClass("java/lang/Long"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(J)Ljava/lang/Long;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, j_val); + break; + case 'F': // float + reflect_class = env->FindClass("java/lang/Float"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(F)Ljava/lang/Float;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, f_val); + break; + case 'D': // double + reflect_class = env->FindClass("java/lang/Double"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(D)Ljava/lang/Double;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, d_val); + break; + // INTEGER TYPES + case 'I': // int + reflect_class = env->FindClass("java/lang/Integer"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(I)Ljava/lang/Integer;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); + break; + case 'S': // short + reflect_class = env->FindClass("java/lang/Short"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(S)Ljava/lang/Short;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); + break; + case 'C': // char + reflect_class = env->FindClass("java/lang/Character"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(C)Ljava/lang/Character;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); + break; + case 'B': // byte + reflect_class = env->FindClass("java/lang/Byte"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(B)Ljava/lang/Byte;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); + break; + case 'Z': // boolean + reflect_class = env->FindClass("java/lang/Boolean"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(Z)Ljava/lang/Boolean;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); + break; + default: // jobject + break; + } + + return result; +} + +static void makeLocalVariable(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, + jint depth, jclass local_class, jmethodID live_ctor, + jlocation location, jobjectArray locals, + jvmtiLocalVariableEntry *table, int index) { + jstring name; + jobject value; + jobject local; + + name = env->NewStringUTF(table[index].name); + + if (location >= table[index].start_location && location <= (table[index].start_location + table[index].length)) { + value = getLocalValue(jvmti, env, thread, depth, table, index); + local = env->NewObject(local_class, live_ctor, name, value); + } else { + // dead object, use null + local = nullptr; + } + + env->SetObjectArrayElement(locals, index, local); +} + +static jobject makeFrameObject(jvmtiEnv* jvmti, JNIEnv *env, jmethodID method, jobjectArray locals) { + jvmtiError jvmti_error; + jclass method_class; + jobject frame_method; + jclass frame_class; + jmethodID ctor; + + jvmti_error = jvmti->GetMethodDeclaringClass(method, &method_class); + if (jvmti_error != JVMTI_ERROR_NONE) { + throwException(env, "java/lang/RuntimeException", "Could not get the declaring class of the method."); + return nullptr; + } + + frame_method = env->ToReflectedMethod(method_class, method, (jboolean) true); + if (frame_method == nullptr) { + return nullptr; // ToReflectedMethod raised an exception + } + + frame_class = env->FindClass("io/sentry/jvmti/Frame"); + if (frame_class == nullptr) { + return nullptr; + } + + ctor = env->GetMethodID(frame_class, "", + "(Ljava/lang/reflect/Method;[Lio/sentry/jvmti/Frame$LocalVariable;)V"); + if (ctor == nullptr) { + return nullptr; + } + + return env->NewObject(frame_class, ctor, frame_method, locals); +} + +static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, + jmethodID method, jlocation location) { + jvmtiError jvmti_error; + jvmtiLocalVariableEntry *local_var_table; + jint num_entries; + jobject value_ptr; + jobjectArray locals; + jclass local_class; + jmethodID live_ctor; + int i; + value_ptr = nullptr; + + jvmti_error = jvmti->GetLocalVariableTable(method, &num_entries, &local_var_table); + if (jvmti_error != JVMTI_ERROR_NONE) { + locals = nullptr; + switch(jvmti_error) { + // Pass cases + case JVMTI_ERROR_ABSENT_INFORMATION: + case JVMTI_ERROR_NATIVE_METHOD: + break; + // Error cases + case JVMTI_ERROR_MUST_POSSESS_CAPABILITY: + throwException(env, "java/lang/RuntimeException", "The access_local_variables capability is not enabled."); + return nullptr; + case JVMTI_ERROR_INVALID_METHODID: + throwException(env, "java/lang/IllegalArgumentException", "Illegal jmethodID."); + return nullptr; + case JVMTI_ERROR_NULL_POINTER: + throwException(env, "java/lang/NullPointerException", "Passed null to GetLocalVariableTable()."); + return nullptr; + default: + throwException(env, "java/lang/RuntimeException", "Unknown JVMTI Error."); + return nullptr; + } + } else { + local_class = env->FindClass("io/sentry/jvmti/Frame$LocalVariable"); + live_ctor = env->GetMethodID(local_class, "", "(Ljava/lang/String;Ljava/lang/Object;)V"); + locals = env->NewObjectArray(num_entries, local_class, nullptr); + for (i = 0; i < num_entries; i++) { + makeLocalVariable(jvmti, env, thread, depth, local_class, live_ctor, location, locals, local_var_table, i); + } + jvmti->Deallocate((unsigned char *) local_var_table); + } + + jvmti_error = jvmti->GetLocalObject(thread, depth, 0, &value_ptr); + if (jvmti_error != JVMTI_ERROR_NONE) { + value_ptr = nullptr; + } + + return makeFrameObject(jvmti, env, method, locals); +} + +jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, + jint start_depth, jint num_frames) { + log(TRACE, "buildStackTraceFrames called."); + + jvmtiFrameInfo* frames; + jclass result_class; + jint num_frames_returned; + jvmtiError jvmti_error; + jobjectArray result; + jobject frame; + + jvmti_error = jvmti->Allocate(num_frames * (int)sizeof(jvmtiFrameInfo), (unsigned char **) &frames); + if (jvmti_error != JVMTI_ERROR_NONE) { + throwException(env, "java/lang/RuntimeException", "Could not allocate frame buffer."); + return nullptr; + } + + jvmti_error = jvmti->GetStackTrace(thread, start_depth, num_frames, frames, &num_frames_returned); + if (jvmti_error != JVMTI_ERROR_NONE) { + jvmti->Deallocate((unsigned char *)frames); + throwException(env, "java/lang/RuntimeException", "Could not get stack trace."); + return nullptr; + } + + result_class = env->FindClass("io/sentry/jvmti/Frame"); + result = env->NewObjectArray(num_frames_returned, result_class, nullptr); + if (result == nullptr) { + jvmti->Deallocate((unsigned char *) frames); + return nullptr; // OutOfMemory + } + + for (int i = 0; i < num_frames_returned; i++) { + frame = buildFrame(jvmti, env, thread, start_depth + i, frames[i].method, frames[i].location); + if (frame == nullptr) { + jvmti->Deallocate((unsigned char *) frames); + throwException(env, "java/lang/RuntimeException", "Error accessing frame object."); + return nullptr; + } + env->SetObjectArrayElement(result, i, frame); + } + + jvmti->Deallocate((unsigned char *) frames); + + log(TRACE, "buildStackTraceFrames exit."); + return result; +} diff --git a/agent/lib.h b/agent/lib.h new file mode 100644 index 00000000000..19ce0f71597 --- /dev/null +++ b/agent/lib.h @@ -0,0 +1,20 @@ +#include +#include "jvmti.h" + +#ifndef SENTRY_JAVA_AGENT_LIB_H +#define SENTRY_JAVA_AGENT_LIB_H + +enum Level { + TRACE, + DEBUG, + INFO, + WARN, + ERROR +}; + +void log(Level level, std::string message); + +jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, + jint start_depth, jint num_frames); + +#endif //SENTRY_JAVA_AGENT_LIB_H diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts deleted file mode 100644 index 8abe9f55283..00000000000 --- a/build-logic/build.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -plugins { - `kotlin-dsl` -} - -repositories { - gradlePluginPortal() -} - -dependencies { - implementation(libs.spotlessLib) -} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts deleted file mode 100644 index aa5e146f1c7..00000000000 --- a/build-logic/settings.gradle.kts +++ /dev/null @@ -1,9 +0,0 @@ -dependencyResolutionManagement { - versionCatalogs { - create("libs") { - from(files("../gradle/libs.versions.toml")) - } - } -} - -rootProject.name = "build-logic" diff --git a/build-logic/src/main/kotlin/io.sentry.javadoc.aggregate.gradle.kts b/build-logic/src/main/kotlin/io.sentry.javadoc.aggregate.gradle.kts deleted file mode 100644 index e06cb677319..00000000000 --- a/build-logic/src/main/kotlin/io.sentry.javadoc.aggregate.gradle.kts +++ /dev/null @@ -1,29 +0,0 @@ -import io.sentry.gradle.AggregateJavadoc -import org.gradle.api.attributes.Category -import org.gradle.api.attributes.LibraryElements -import org.gradle.kotlin.dsl.creating -import org.gradle.kotlin.dsl.getValue -import org.gradle.kotlin.dsl.named - -val javadocPublisher by configurations.creating { - isCanBeConsumed = false - isCanBeResolved = true - attributes { - attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION)) - attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named("javadoc")) - } -} - -subprojects { - javadocPublisher.dependencies.add(dependencies.create(this)) -} - -val javadocCollection = javadocPublisher.incoming.artifactView { lenient(true) }.files - -tasks.register("aggregateJavadocs", AggregateJavadoc::class) { - group = "documentation" - description = "Aggregates Javadocs from all subprojects into a single directory." - javadocFiles.set(javadocCollection) - rootDir.set(layout.projectDirectory) - outputDir.set(layout.buildDirectory.dir("docs/javadoc")) -} diff --git a/build-logic/src/main/kotlin/io.sentry.javadoc.gradle.kts b/build-logic/src/main/kotlin/io.sentry.javadoc.gradle.kts deleted file mode 100644 index 7eb796a02ff..00000000000 --- a/build-logic/src/main/kotlin/io.sentry.javadoc.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -val javadocConfig: Configuration by configurations.creating { - isCanBeResolved = false - isCanBeConsumed = true - - attributes { - attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION)) - attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named("javadoc")) - } -} - -tasks.withType().configureEach { - setDestinationDir(project.layout.buildDirectory.file("docs/javadoc").get().asFile) - title = "${project.name} $version API" - val opts = options as StandardJavadocDocletOptions - opts.quiet() - opts.encoding = "UTF-8" - opts.memberLevel = JavadocMemberLevel.PROTECTED - opts.links = listOf( - "https://docs.oracle.com/javase/8/docs/api/", - "https://docs.spring.io/spring-framework/docs/current/javadoc-api/", - "https://docs.spring.io/spring-boot/docs/current/api/" - ) -} - -artifacts { - add(javadocConfig.name, tasks.named("javadoc")) -} diff --git a/build-logic/src/main/kotlin/io.sentry.spotless.gradle.kts b/build-logic/src/main/kotlin/io.sentry.spotless.gradle.kts deleted file mode 100644 index 9b53fd8a4cd..00000000000 --- a/build-logic/src/main/kotlin/io.sentry.spotless.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -import com.diffplug.spotless.LineEnding - -plugins { - id("com.diffplug.spotless") -} - -spotless { - lineEndings = LineEnding.UNIX - java { - target("src/*/java/**/*.java") - removeUnusedImports() - googleJavaFormat() - targetExclude("src/**/java/io/sentry/vendor/**") - } - kotlin { - target("src/*/kotlin/**/*.kt", "src/*/java/**/*.kt") - ktfmt().googleStyle() - targetExclude("src/test/java/io/sentry/apollo4/generated/**", "src/test/java/io/sentry/apollo3/adapter/**") - } - kotlinGradle { - target("*.gradle.kts") - ktfmt().googleStyle() - } -} - diff --git a/build-logic/src/main/kotlin/io/sentry/gradle/AggregateJavadoc.kt b/build-logic/src/main/kotlin/io/sentry/gradle/AggregateJavadoc.kt deleted file mode 100644 index f6b9ec6a0ff..00000000000 --- a/build-logic/src/main/kotlin/io/sentry/gradle/AggregateJavadoc.kt +++ /dev/null @@ -1,41 +0,0 @@ -package io.sentry.gradle - -import org.gradle.api.DefaultTask -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.FileCollection -import org.gradle.api.file.FileSystemOperations -import org.gradle.api.provider.Property -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.TaskAction -import javax.inject.Inject - -abstract class AggregateJavadoc @Inject constructor( - @get:Internal val fs: FileSystemOperations -) : DefaultTask() { - @get:InputFiles - abstract val javadocFiles: Property - - // Marked as Internal since this is only used to relativize the paths for the output directories - @get:Internal - abstract val rootDir: DirectoryProperty - - @get:OutputDirectory - abstract val outputDir: DirectoryProperty - - @TaskAction - fun aggregate() { - javadocFiles.get().forEach { file -> - fs.copy { - // Get the relative path of the project directory to the root directory - val relativePath = file.relativeTo(rootDir.get().asFile) - // Remove the 'build/docs/javadoc' part from the path - val projectPath = relativePath.path.replace("build/docs/javadoc", "") - from(file) - // Use the project name as the output directory name so that each javadoc goes into its own directory - into(outputDir.get().file(projectPath)) - } - } - } -} diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index b89b7deed10..00000000000 --- a/build.gradle.kts +++ /dev/null @@ -1,313 +0,0 @@ -import com.vanniktech.maven.publish.JavaLibrary -import com.vanniktech.maven.publish.JavadocJar -import com.vanniktech.maven.publish.MavenPublishBaseExtension -import groovy.util.Node -import io.gitlab.arturbosch.detekt.extensions.DetektExtension -import kotlinx.kover.gradle.plugin.dsl.KoverReportExtension -import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import org.gradle.api.tasks.testing.logging.TestLogEvent - -plugins { - `java-library` - alias(libs.plugins.spotless) apply false - jacoco - alias(libs.plugins.detekt) - `maven-publish` - alias(libs.plugins.binary.compatibility.validator) - alias(libs.plugins.jacoco.android) apply false - alias(libs.plugins.kover) apply false - alias(libs.plugins.vanniktech.maven.publish) apply false - alias(libs.plugins.kotlin.android) apply false - alias(libs.plugins.kotlin.multiplatform) apply false - alias(libs.plugins.kotlin.jvm) apply false - alias(libs.plugins.kotlin.spring) apply false - alias(libs.plugins.buildconfig) apply false - // dokka is required by gradle-maven-publish-plugin. - alias(libs.plugins.dokka) apply false - alias(libs.plugins.dokka.javadoc) apply false - alias(libs.plugins.kotlin.compose) apply false - alias(libs.plugins.errorprone) apply false - alias(libs.plugins.gradle.versions) apply false - alias(libs.plugins.spring.dependency.management) apply false - id("io.sentry.javadoc.aggregate") - alias(libs.plugins.sentry) apply false -} - -buildscript { - repositories { - google() - } - dependencies { - classpath(Config.BuildPlugins.androidGradle) - - // add classpath of sentry android gradle plugin - // classpath("io.sentry:sentry-android-gradle-plugin:{version}") - - classpath(libs.commons.compress) - } -} - -apiValidation { - ignoredPackages.addAll( - setOf( - "io.sentry.android.core.internal" - ) - ) - ignoredProjects.addAll( - listOf( - "sentry-samples-android", - "sentry-samples-console", - "sentry-samples-console-opentelemetry-noagent", - "sentry-samples-jul", - "sentry-samples-log4j2", - "sentry-samples-logback", - "sentry-samples-openfeign", - "sentry-samples-servlet", - "sentry-samples-spring", - "sentry-samples-spring-jakarta", - "sentry-samples-spring-7", - "sentry-samples-spring-boot", - "sentry-samples-spring-boot-opentelemetry", - "sentry-samples-spring-boot-opentelemetry-noagent", - "sentry-samples-spring-boot-jakarta", - "sentry-samples-spring-boot-jakarta-opentelemetry", - "sentry-samples-spring-boot-jakarta-opentelemetry-noagent", - "sentry-samples-spring-boot-webflux", - "sentry-samples-spring-boot-webflux-jakarta", - "sentry-samples-spring-boot-4", - "sentry-samples-spring-boot-4-opentelemetry", - "sentry-samples-spring-boot-4-opentelemetry-noagent", - "sentry-samples-spring-boot-4-webflux", - "sentry-samples-ktor-client", - "sentry-uitest-android", - "sentry-uitest-android-benchmark", - "sentry-uitest-android-critical", - "test-app-plain", - "test-app-sentry", - "test-app-size", - "sentry-samples-netflix-dgs" - ) - ) -} - -allprojects { - group = Config.Sentry.group - version = properties[Config.Sentry.versionNameProp].toString() - description = Config.Sentry.description - tasks { - withType().configureEach { - testLogging.showStandardStreams = true - testLogging.exceptionFormat = TestExceptionFormat.FULL - testLogging.events = setOf( - TestLogEvent.SKIPPED, - TestLogEvent.PASSED, - TestLogEvent.FAILED - ) - - // Cap JVM args per test - minHeapSize = "256m" - maxHeapSize = "2g" - } - withType().configureEach { - options.compilerArgs.addAll(arrayOf("-Xlint:all", "-Werror", "-Xlint:-classfile", "-Xlint:-processing", "-Xlint:-try")) - } - } -} - -subprojects { - apply { plugin("io.sentry.spotless") } - - val jacocoAndroidModules = listOf( - "sentry-android-core", - "sentry-android-fragment", - "sentry-android-navigation", - "sentry-android-ndk", - "sentry-android-sqlite", - "sentry-android-replay", - "sentry-android-timber" - ) - if (jacocoAndroidModules.contains(name)) { - afterEvaluate { - jacoco { - toolVersion = "0.8.10" - } - - tasks.withType().configureEach { - configure { - isIncludeNoLocationClasses = true - excludes = listOf("jdk.internal.*") - } - } - } - } - - val koverKmpModules = listOf("sentry-compose") - if (koverKmpModules.contains(name)) { - afterEvaluate { - configure { - androidReports("release") { - xml { - // Change the report file name so the Codecov Github action can find it - setReportFile(project.layout.buildDirectory.file("reports/kover/report.xml").get().asFile) - } - } - } - } - } - - plugins.withId(Config.QualityPlugins.detektPlugin) { - configure { - buildUponDefaultConfig = true - allRules = true - config.setFrom("${rootProject.rootDir}/detekt.yml") - } - } - - if (!this.name.contains("sample") && !this.name.contains("integration-tests") && this.name != "sentry-system-test-support" && this.name != "sentry-test-support") { - apply() - apply() - - val sep = File.separator - - configure { - if (this@subprojects.name.contains("-compose")) { - this.configureForMultiplatform(this@subprojects) - } else { - this.configureForJvm(this@subprojects) - } - // craft only uses zip archives - this.forEach { dist -> - if (dist.name == DistributionPlugin.MAIN_DISTRIBUTION_NAME) { - tasks.named("distTar").configure { enabled = false } - } else { - tasks.named(dist.name + "DistTar").configure { enabled = false } - } - } - } - - tasks.named("distZip").configure { - this.dependsOn("publishToMavenLocal") - val file = this.project.layout.buildDirectory.file("distributions${sep}${this.project.name}-${this.project.version}.zip").get().asFile - this.doLast { - if (!file.exists()) throw IllegalStateException("Distribution file: ${file.absolutePath} does not exist") - if (file.length() == 0L) throw IllegalStateException("Distribution file: ${file.absolutePath} is empty") - } - } - - plugins.withId("java-library") { - configure { - // we have to disable javadoc publication in maven-publish plugin as it's not - // including it in the .module file https://github.com/vanniktech/gradle-maven-publish-plugin/issues/861 - // and do it ourselves - configure(JavaLibrary(JavadocJar.None(), sourcesJar = true)) - } - - configure { - withJavadocJar() - - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - } - - afterEvaluate { - apply() - - configure { - assignAarTypes() - } - - // this is needed for sentry-unity to consume our artifacts locally as proper maven publication - configure { - repositories { - maven { - name = "unityMaven" - url = rootProject.layout.buildDirectory.file("unityMaven").get().asFile.toURI() - } - } - } - - // maven central info go to: - // ~/.gradle/gradle.properties - - // maven central info: - // mavenCentralUsername=user name - // mavenCentralPassword=password - } - } -} - -tasks.register("buildForCodeQL") { - subprojects - .filter { - !it.displayName.contains("sample") && - !it.displayName.contains("integration-tests") && - !it.displayName.contains("bom") && - it.name != "sentry-opentelemetry" - } - .forEach { proj -> - if (proj.plugins.hasPlugin("com.android.library")) { - this.dependsOn(proj.tasks.findByName("compileReleaseUnitTestSources")) - } else { - this.dependsOn(proj.tasks.findByName("testClasses")) - } - } -} - -// Workaround for https://youtrack.jetbrains.com/issue/IDEA-316081/Gradle-8-toolchain-error-Toolchain-from-executable-property-does-not-match-toolchain-from-javaLauncher-property-when-different -gradle.taskGraph.whenReady { - val task = this.allTasks.find { it.name.endsWith(".main()") } as? JavaExec - task?.let { - it.setExecutable(it.javaLauncher.get().executablePath.asFile.absolutePath) - } -} - -/* - * Adapted from https://github.com/androidx/androidx/blob/c799cba927a71f01ea6b421a8f83c181682633fb/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt#L524-L549 - * - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Workaround for https://github.com/gradle/gradle/issues/3170 -fun MavenPublishBaseExtension.assignAarTypes() { - pom { - withXml { - val dependencies = asNode().children().find { - it is Node && it.name().toString().endsWith("dependencies") - } as Node? - - dependencies?.children()?.forEach { dep -> - if (dep !is Node) { - return@forEach - } - val group = dep.children().firstOrNull { - it is Node && it.name().toString().endsWith("groupId") - } as? Node - val groupValue = group?.children()?.firstOrNull() as? String - - val artifactId = dep.children().firstOrNull { - it is Node && it.name().toString().endsWith("artifactId") - } as? Node - val artifactIdValue = artifactId?.children()?.firstOrNull() as? String - - if (artifactIdValue in Config.BuildScript.androidLibs) { - dep.appendNode("type", "aar") - } else if ("$groupValue:$artifactIdValue" in Config.BuildScript.androidXLibs) { - dep.appendNode("type", "aar") - } - } - } - } -} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts deleted file mode 100644 index 451e5827ed9..00000000000 --- a/buildSrc/build.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - `kotlin-dsl` -} - -repositories { - mavenCentral() -} - -tasks.withType().configureEach { - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 -} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts deleted file mode 100644 index 12b905adc13..00000000000 --- a/buildSrc/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "sentry-buildSrc" diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt deleted file mode 100644 index 3b6a08ad26b..00000000000 --- a/buildSrc/src/main/java/Config.kt +++ /dev/null @@ -1,112 +0,0 @@ - -import java.math.BigDecimal - -object Config { - val AGP = System.getenv("VERSION_AGP") ?: "8.6.0" - val kotlinStdLib = "stdlib-jdk8" - val kotlinStdLibVersionAndroid = "1.9.24" - val kotlinTestJunit = "test-junit" - - object BuildPlugins { - val androidGradle = "com.android.tools.build:gradle:$AGP" - } - - object Android { - val abiFilters = listOf("x86", "armeabi-v7a", "x86_64", "arm64-v8a") - - fun shouldSkipDebugVariant(name: String?): Boolean { - return System.getenv("CI")?.toBoolean() ?: false && name == "debug" - } - } - - object Libs { - val springWeb = "org.springframework:spring-webmvc" - val springWebflux = "org.springframework:spring-webflux" - val springSecurityWeb = "org.springframework.security:spring-security-web" - val springSecurityConfig = "org.springframework.security:spring-security-config" - val springAop = "org.springframework:spring-aop" - val aspectj = "org.aspectj:aspectjweaver" - - val kotlinReflect = "org.jetbrains.kotlin:kotlin-reflect" - val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib" - } - - object AnnotationProcessors { - val springBootAutoConfigure = "org.springframework.boot:spring-boot-autoconfigure-processor" - val springBootConfiguration = "org.springframework.boot:spring-boot-configuration-processor" - } - - object QualityPlugins { - object Jacoco { - // TODO [POTEL] add tests and restore - val minimumCoverage = BigDecimal.valueOf(0.1) - } - - // this can be removed when we upgrade to Gradle 8, which allows us to use a getter for the plugin ID - val detektPlugin = "io.gitlab.arturbosch.detekt" - } - - object Sentry { - val SENTRY_JAVA_SDK_NAME = "sentry.java" - val SENTRY_ANDROID_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.android" - val SENTRY_TIMBER_SDK_NAME = "$SENTRY_ANDROID_SDK_NAME.timber" - val SENTRY_LOGBACK_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.logback" - val SENTRY_JUL_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.jul" - val SENTRY_LOG4J2_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.log4j2" - val SENTRY_SPRING_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring" - val SENTRY_SPRING_JAKARTA_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring.jakarta" - val SENTRY_SPRING_7_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-7" - val SENTRY_SPRING_BOOT_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot" - val SENTRY_SPRING_BOOT_STARTER_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot-starter" - val SENTRY_SPRING_BOOT_JAKARTA_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot.jakarta" - val SENTRY_SPRING_BOOT_STARTER_JAKARTA_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot-starter.jakarta" - val SENTRY_SPRING_BOOT_4_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot-4" - val SENTRY_SPRING_BOOT_4_STARTER_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot-4-starter" - val SENTRY_OPENTELEMETRY_BOOTSTRAP_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.bootstrap" - val SENTRY_OPENTELEMETRY_CORE_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.core" - val SENTRY_OPENTELEMETRY_AGENT_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.agent" - val SENTRY_OPENTELEMETRY_AGENTLESS_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.agentless" - val SENTRY_OPENTELEMETRY_AGENTLESS_SPRING_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.agentless-spring" - val SENTRY_OPENTELEMETRY_AGENTCUSTOMIZATION_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.agentcustomization" - val SENTRY_OPENFEIGN_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.openfeign" - val SENTRY_APOLLO3_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.apollo3" - val SENTRY_APOLLO4_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.apollo4" - val SENTRY_APOLLO_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.apollo" - val SENTRY_GRAPHQL_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.graphql" - val SENTRY_GRAPHQL_CORE_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.graphql-core" - val SENTRY_GRAPHQL22_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.graphql22" - val SENTRY_QUARTZ_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.quartz" - val SENTRY_JDBC_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.jdbc" - val SENTRY_OPENFEATURE_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.openfeature" - val SENTRY_LAUNCHDARKLY_SERVER_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.launchdarkly-server" - val SENTRY_LAUNCHDARKLY_ANDROID_SDK_NAME = "$SENTRY_ANDROID_SDK_NAME.launchdarkly" - val SENTRY_SERVLET_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.servlet" - val SENTRY_SERVLET_JAKARTA_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.servlet.jakarta" - val SENTRY_COMPOSE_HELPER_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.compose.helper" - val SENTRY_OKHTTP_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.okhttp" - val SENTRY_REACTOR_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.reactor" - val SENTRY_KOTLIN_EXTENSIONS_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.kotlin-extensions" - val SENTRY_ASYNC_PROFILER_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.async-profiler" - val SENTRY_KTOR_CLIENT_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.ktor-client" - val group = "io.sentry" - val description = "SDK for sentry.io" - val versionNameProp = "versionName" - } - - object BuildScript { - val androidLibs = setOf( - "sentry-android-core", - "sentry-android-ndk", - "sentry-android-fragment", - "sentry-android-navigation", - "sentry-android-timber", - "sentry-compose-android", - "sentry-android-sqlite", - "sentry-android-replay" - ) - - val androidXLibs = listOf( - "androidx.core:core" - ) - } -} diff --git a/buildSrc/src/main/java/Publication.kt b/buildSrc/src/main/java/Publication.kt deleted file mode 100644 index 0aa717a5630..00000000000 --- a/buildSrc/src/main/java/Publication.kt +++ /dev/null @@ -1,107 +0,0 @@ -import org.gradle.api.Project -import org.gradle.api.distribution.DistributionContainer -import org.gradle.api.file.CopySpec -import java.io.File - -private object Consts { - val taskRegex = Regex("(.*)DistZip") -} - -// configure distZip tasks for multiplatform -fun DistributionContainer.configureForMultiplatform(project: Project) { - val sep = File.separator - val version = project.properties["versionName"].toString() - val name = project.name - - this.maybeCreate("android").contents { - from("build${sep}publications${sep}androidRelease") { - renameModule(name, "android", version = version) - } - from("build${sep}outputs${sep}aar") { - include("*-release*") - rename { - it.replace("-release", "-android-release") - } - } - from("build${sep}libs") { - include("*android*") - include("*androidRelease-javadoc*") - rename { - it.replace("androidRelease-javadoc", "android") - } - } - } - this.getByName("main").contents { - from("build${sep}publications${sep}kotlinMultiplatform") { - renameModule(name, version = version) - } - from("build${sep}kotlinToolingMetadata") - from("build${sep}libs") { - include("*compose-kotlin*") - include("*compose-metadata*") - rename { - it.replace("-kotlin", "") - .replace("-metadata", "") - .replace("Multiplatform-javadoc", "") - } - } - } - this.maybeCreate("desktop").contents { - // kotlin multiplatform modules - from("build${sep}publications${sep}desktop") { - renameModule(name, "desktop", version = version) - } - from("build${sep}libs") { - include("*desktop*") - include("*desktop-javadoc*") - rename { - it.replace("desktop-javadoc", "desktop") - } - } - } - - // make other distZip tasks run together with the main distZip - val platformDists = project.tasks.filter { task -> - task.name.matches(Consts.taskRegex) - }.toTypedArray() - project.tasks.getByName("distZip").finalizedBy(*platformDists) -} - -fun DistributionContainer.configureForJvm(project: Project) { - val sep = File.separator - val version = project.properties["versionName"].toString() - val name = project.name - - this.getByName("main").contents { - // non android modules - from("build${sep}libs") - from("build${sep}publications${sep}maven") { - renameModule(name, version = version) - } - // android modules - from("build${sep}outputs${sep}aar") { - include("*-release*") - } - from("build${sep}publications${sep}release") { - renameModule(name, version = version) - } - from("build${sep}intermediates${sep}java_doc_jar${sep}release") { - include("*javadoc*") - rename { it.replace("release", "$name-$version") } - } - from("build${sep}intermediates${sep}source_jar${sep}release") { - include("*sources*") - rename { it.replace("release", "$name-$version") } - } - } -} - -private fun CopySpec.renameModule(projectName: String, renameTo: String = "", version: String) { - var target = "" - if (renameTo.isNotEmpty()) { - target = "-$renameTo" - } - rename { - it.replace("module.json", "$projectName$target-$version.module") - } -} diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 3a53b1f7b3f..00000000000 --- a/codecov.yml +++ /dev/null @@ -1,23 +0,0 @@ -comment: no -codecov: - require_ci_to_pass: no - max_report_age: off - -coverage: - status: - project: - default: - target: 78% - threshold: 4% - patch: off - range: 78...100 - precision: 3 - round: down - -ignore: - - "**/src/test/*" - - "sentry-android-integration-tests/*" - - "sentry-system-test-support/*" - - "sentry-test-support/*" - - "sentry-samples/*" - - "sentry-async-profiler/src/main/java/io/sentry/asyncprofiler/vendor/asyncprofiler/**" diff --git a/debug.keystore b/debug.keystore deleted file mode 100644 index 7da7480dd09..00000000000 Binary files a/debug.keystore and /dev/null differ diff --git a/detekt.yml b/detekt.yml deleted file mode 100644 index 49f91ae64fb..00000000000 --- a/detekt.yml +++ /dev/null @@ -1,3 +0,0 @@ -style: - ReturnCount: - max: 3 diff --git a/devenv/config.ini b/devenv/config.ini deleted file mode 100644 index b546b762c0c..00000000000 --- a/devenv/config.ini +++ /dev/null @@ -1,16 +0,0 @@ -[devenv] -minimum_version = 1.22.1 - -[uv] -darwin_arm64 = https://github.com/astral-sh/uv/releases/download/0.8.2/uv-aarch64-apple-darwin.tar.gz -darwin_arm64_sha256 = 954d24634d5f37fa26c7af75eb79893d11623fc81b4de4b82d60d1ade4bfca22 -darwin_x86_64 = https://github.com/astral-sh/uv/releases/download/0.8.2/uv-x86_64-apple-darwin.tar.gz -darwin_x86_64_sha256 = ae755df53c8c2c1f3dfbee6e3d2e00be0dfbc9c9b4bdffdb040b96f43678b7ce -linux_arm64 = https://github.com/astral-sh/uv/releases/download/0.8.2/uv-aarch64-unknown-linux-gnu.tar.gz -linux_arm64_sha256 = 27da35ef54e9131c2e305de67dd59a07c19257882c6b1f3cf4d8d5fbb8eaf4ca -linux_x86_64 = https://github.com/astral-sh/uv/releases/download/0.8.2/uv-x86_64-unknown-linux-gnu.tar.gz -linux_x86_64_sha256 = 6dcb28a541868a455aefb2e8d4a1283dd6bf888605a2db710f0530cec888b0ad -# used for autoupdate -# NOTE: if using uv-build as a build backend, you'll have to make sure the versions match -version = 0.8.2 - diff --git a/devenv/sync.py b/devenv/sync.py deleted file mode 100644 index 45e663cd99a..00000000000 --- a/devenv/sync.py +++ /dev/null @@ -1,23 +0,0 @@ -from devenv import constants -from devenv.lib import config, proc, uv -import os - -def main(context: dict[str, str]) -> int: - reporoot = context["reporoot"] - cfg = config.get_repo(reporoot) - - uv.install( - cfg["uv"]["version"], - cfg["uv"][constants.SYSTEM_MACHINE], - cfg["uv"][f"{constants.SYSTEM_MACHINE}_sha256"], - reporoot, - ) - - # reporoot/.venv is the default venv location - print(f"syncing .venv ...") - if not os.path.exists(".venv"): - proc.run(("uv", "venv", "--seed")) - proc.run(("uv", "sync", "--frozen", "--quiet")) - - return 0 - diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000000..eb904faf2c3 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,4 @@ +# Examples + +Please see [the Sentry examples repo](https://github.com/getsentry/examples) for Java and Android example projects. + diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 0cd9576e99b..00000000000 --- a/gradle.properties +++ /dev/null @@ -1,56 +0,0 @@ -# Daemons heap size -org.gradle.jvmargs=-Xmx12g -XX:MaxMetaspaceSize=4g -XX:+CrashOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -org.gradle.caching=true -org.gradle.parallel=true -org.gradle.configureondemand=true -org.gradle.configuration-cache=true - -org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled - -# AndroidX required by AGP >= 3.6.x -android.useAndroidX=true -android.experimental.lint.version=8.9.0 - -# Release information -versionName=8.32.0 - -# Override the SDK name on native crashes on Android -sentryAndroidSdkName=sentry.native.android - -# disable renderscript, it's enabled by default -android.defaults.buildfeatures.renderscript=false - -# disable shader compilation, it's enabled by default -android.defaults.buildfeatures.shaders=false - -# disable aidl files, it's enabled by default -android.defaults.buildfeatures.aidl=false - -# disable Resource Values generation -android.defaults.buildfeatures.resvalues=false - -# disable automatically adding Kotlin stdlib to compile dependencies -kotlin.stdlib.default.dependency=false - -# TODO: Enable Prefab https://android-developers.googleblog.com/2020/02/native-dependencies-in-android-studio-40.html -# android.enablePrefab=true -# android.prefabVersion=1.0.0 - -# publication pom properties -POM_NAME=Sentry SDK -POM_DESCRIPTION=SDK for sentry.io -POM_URL=https://github.com/getsentry/sentry-java -POM_SCM_URL=https://github.com/getsentry/sentry-java -POM_SCM_CONNECTION=scm:git:git://github.com/getsentry/sentry-java.git -POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/getsentry/sentry-java.git - -POM_LICENCE_NAME=MIT -POM_LICENCE_URL=http://www.opensource.org/licenses/mit-license.php - -POM_DEVELOPER_ID=getsentry -POM_DEVELOPER_NAME=Sentry Team and Contributors -POM_DEVELOPER_URL=https://github.com/getsentry/ - -systemProp.org.gradle.internal.http.socketTimeout=120000 - -android.nonTransitiveRClass=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml deleted file mode 100644 index 0df43650549..00000000000 --- a/gradle/libs.versions.toml +++ /dev/null @@ -1,238 +0,0 @@ -[versions] -apollo = "2.5.9" -androidxLifecycle = "2.2.0" -androidxNavigation = "2.4.2" -androidxTestCore = "1.7.0" -androidxCompose = "1.6.3" -asyncProfiler = "4.2" -composeCompiler = "1.5.14" -coroutines = "1.6.1" -espresso = "3.7.0" -feign = "11.6" -jacoco = "0.8.7" -jackson = "2.18.3" -jetbrainsCompose = "1.6.11" -kotlin = "2.2.0" -kotlinSpring7 = "2.2.0" -kotlin-compatible-version = "1.9" -ktorClient = "3.0.0" -logback = "1.2.9" -log4j2 = "2.20.0" -nopen = "1.0.1" -# see https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-compatibility-and-versioning.html#kotlin-compatibility -# see https://developer.android.com/jetpack/androidx/releases/compose-kotlin -okhttp = "4.9.2" -otel = "1.57.0" -otelInstrumentation = "2.23.0" -otelInstrumentationAlpha = "2.23.0-alpha" -# check https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/dependencyManagement/build.gradle.kts#L49 for release version above to find a compatible version -otelSemanticConventions = "1.37.0" -otelSemanticConventionsAlpha = "1.37.0-alpha" -retrofit = "2.9.0" -slf4j = "1.7.30" -springboot2 = "2.7.18" -springboot3 = "3.5.0" -springboot4 = "4.0.0" -# Android -targetSdk = "36" -compileSdk = "36" -minSdk = "21" -spotless = "7.0.4" -gummyBears = "0.12.0" -camerax = "1.3.0" -openfeature = "1.18.2" -protobuf = "3.25.8" - -[plugins] -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -kotlin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" } -kotlin-spring7 = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlinSpring7" } -kotlin-jvm-spring7 = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlinSpring7" } -kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } -buildconfig = { id = "com.github.gmazzo.buildconfig", version = "5.6.5" } -dokka = { id = "org.jetbrains.dokka", version = "2.0.0" } -dokka-javadoc = { id = "org.jetbrains.dokka-javadoc", version = "2.0.0" } -binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version = "0.13.0" } -errorprone = { id = "net.ltgt.errorprone", version = "3.0.1" } -gradle-versions = { id = "com.github.ben-manes.versions", version = "0.42.0" } -spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } -detekt = { id = "io.gitlab.arturbosch.detekt", version = "1.23.8" } -jacoco-android = { id = "com.mxalbert.gradle.jacoco-android", version = "0.2.0" } -kover = { id = "org.jetbrains.kotlinx.kover", version = "0.7.3" } -protobuf = { id = "com.google.protobuf", version = "0.9.5" } -vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version = "0.30.0" } -springboot2 = { id = "org.springframework.boot", version.ref = "springboot2" } -springboot3 = { id = "org.springframework.boot", version.ref = "springboot3" } -springboot4 = { id = "org.springframework.boot", version.ref = "springboot4" } -spring-dependency-management = { id = "io.spring.dependency-management", version = "1.0.11.RELEASE" } -gretty = { id = "org.gretty", version = "4.0.0" } -animalsniffer = { id = "ru.vyarus.animalsniffer", version = "2.0.1" } -sentry = { id = "io.sentry.android.gradle", version = "6.0.0-alpha.6"} - -[libraries] -apache-httpclient = { module = "org.apache.httpcomponents.client5:httpclient5", version = "5.0.4" } -apollo2-coroutines = { module = "com.apollographql.apollo:apollo-coroutines-support", version.ref = "apollo" } -apollo2-runtime = { module = "com.apollographql.apollo:apollo-runtime", version.ref = "apollo" } -apollo3-kotlin = { module = "com.apollographql.apollo3:apollo-runtime", version = "3.8.2" } -apollo4-kotlin = { module = "com.apollographql.apollo:apollo-runtime", version = "4.1.1" } -androidx-appcompat = { module = "androidx.appcompat:appcompat", version = "1.3.0" } -androidx-annotation = { module = "androidx.annotation:annotation", version = "1.9.1" } -androidx-activity-compose = { module = "androidx.activity:activity-compose", version = "1.8.2" } -androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "androidxCompose" } -androidx-compose-foundation-layout = { module = "androidx.compose.foundation:foundation-layout", version.ref = "androidxCompose" } -androidx-compose-material3 = { module = "androidx.compose.material3:material3", version = "1.4.0" } -androidx-compose-material-icons-core = { module = "androidx.compose.material:material-icons-core", version="1.7.8" } -androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version="1.7.8" } -androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "androidxCompose" } -# Note: don't change without testing forwards compatibility -androidx-compose-ui-replay = { module = "androidx.compose.ui:ui", version = "1.5.0" } -androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.1.3" } -androidx-core = { module = "androidx.core:core", version = "1.3.2" } -androidx-core-ktx = { module = "androidx.core:core-ktx", version = "1.7.0" } -androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version = "1.3.5" } -androidx-lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "androidxLifecycle" } -androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "androidxLifecycle" } -androidx-navigation-runtime = { module = "androidx.navigation:navigation-runtime", version.ref = "androidxNavigation" } -androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidxNavigation" } -androidx-sqlite = { module = "androidx.sqlite:sqlite", version = "2.5.2" } -androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.2.1" } -androidx-browser = { module = "androidx.browser:browser", version = "1.8.0" } -async-profiler = { module = "tools.profiler:async-profiler", version.ref = "asyncProfiler" } -async-profiler-jfr-converter = { module = "tools.profiler:jfr-converter", version.ref = "asyncProfiler" } -coil-compose = { module = "io.coil-kt:coil-compose", version = "2.6.0" } -commons-compress = {module = "org.apache.commons:commons-compress", version = "1.25.0"} -context-propagation = { module = "io.micrometer:context-propagation", version = "1.1.0" } -errorprone-core = { module = "com.google.errorprone:error_prone_core", version = "2.11.0" } -feign-core = { module = "io.github.openfeign:feign-core", version.ref = "feign" } -feign-gson = { module = "io.github.openfeign:feign-gson", version.ref = "feign" } -graphql-java17 = { module = "com.graphql-java:graphql-java", version = "17.3" } -graphql-java22 = { module = "com.graphql-java:graphql-java", version = "22.1" } -graphql-java24 = { module = "com.graphql-java:graphql-java", version = "24.0" } -jackson-bom = { module = "com.fasterxml.jackson:jackson-bom", version.ref = "jackson" } -jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } -jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } -jetbrains-annotations = { module = "org.jetbrains:annotations", version = "23.0.0" } -kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" } -kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } -kotlin-test-junit-spring7 = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlinSpring7" } -kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } -kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } -ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorClient" } -ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktorClient" } -launchdarkly-android = { module = "com.launchdarkly:launchdarkly-android-client-sdk", version = "5.9.2" } -launchdarkly-server = { module = "com.launchdarkly:launchdarkly-java-server-sdk", version = "7.10.2" } -log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j2" } -log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j2" } -leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version = "2.14" } -logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } -nopen-annotations = { module = "com.jakewharton.nopen:nopen-annotations", version.ref = "nopen" } -nopen-checker = { module = "com.jakewharton.nopen:nopen-checker", version.ref = "nopen" } -nullaway = { module = "com.uber.nullaway:nullaway", version = "0.9.5" } -okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } -okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttp" } -openfeature = { module = "dev.openfeature:sdk", version.ref = "openfeature" } -otel = { module = "io.opentelemetry:opentelemetry-sdk", version.ref = "otel" } -otel-extension-autoconfigure = { module = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure", version.ref = "otel" } -otel-extension-autoconfigure-spi = { module = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi", version.ref = "otel" } -otel-instrumentation-bom = { module = "io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom", version.ref = "otelInstrumentation" } -otel-javaagent = { module = "io.opentelemetry.javaagent:opentelemetry-javaagent", version.ref = "otelInstrumentation" } -otel-javaagent-tooling = { module = "io.opentelemetry.javaagent:opentelemetry-javaagent-tooling", version.ref = "otelInstrumentationAlpha" } -otel-javaagent-extension-api = { module = "io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api", version.ref = "otelInstrumentationAlpha" } -otel-semconv = { module = "io.opentelemetry.semconv:opentelemetry-semconv", version.ref = "otelSemanticConventions" } -otel-semconv-incubating = { module = "io.opentelemetry.semconv:opentelemetry-semconv-incubating", version.ref = "otelSemanticConventionsAlpha" } -p6spy = { module = "p6spy:p6spy", version = "3.9.1" } -protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf"} -protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" } -quartz = { module = "org.quartz-scheduler:quartz", version = "2.3.0" } -reactor-core = { module = "io.projectreactor:reactor-core", version = "3.5.3" } -retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } -retrofit-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" } -sentry-native-ndk = { module = "io.sentry:sentry-native-ndk", version = "0.12.7" } -servlet-api = { module = "javax.servlet:javax.servlet-api", version = "3.1.0" } -servlet-jakarta-api = { module = "jakarta.servlet:jakarta.servlet-api", version = "6.1.0" } -slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } -slf4j-jdk14 = { module = "org.slf4j:slf4j-jdk14", version.ref = "slf4j" } -slf4j2-api = { module = "org.slf4j:slf4j-api", version = "2.0.5" } -spotlessLib = { module = "com.diffplug.spotless:com.diffplug.spotless.gradle.plugin", version.ref = "spotless"} -springboot-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "springboot2" } -springboot-starter-graphql = { module = "org.springframework.boot:spring-boot-starter-graphql", version.ref = "springboot2" } -springboot-starter-quartz = { module = "org.springframework.boot:spring-boot-starter-quartz", version.ref = "springboot2" } -springboot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test", version.ref = "springboot2" } -springboot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "springboot2" } -springboot-starter-websocket = { module = "org.springframework.boot:spring-boot-starter-websocket", version.ref = "springboot2" } -springboot-starter-webflux = { module = "org.springframework.boot:spring-boot-starter-webflux", version.ref = "springboot2" } -springboot-starter-aop = { module = "org.springframework.boot:spring-boot-starter-aop", version.ref = "springboot2" } -springboot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security", version.ref = "springboot2" } -springboot-starter-jdbc = { module = "org.springframework.boot:spring-boot-starter-jdbc", version.ref = "springboot2" } -springboot-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator", version.ref = "springboot2" } -springboot3-otel = { module = "io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter", version.ref = "otelInstrumentation" } -springboot3-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "springboot3" } -springboot3-starter-graphql = { module = "org.springframework.boot:spring-boot-starter-graphql", version.ref = "springboot3" } -springboot3-starter-quartz = { module = "org.springframework.boot:spring-boot-starter-quartz", version.ref = "springboot3" } -springboot3-starter-test = { module = "org.springframework.boot:spring-boot-starter-test", version.ref = "springboot3" } -springboot3-starter-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "springboot3" } -springboot3-starter-websocket = { module = "org.springframework.boot:spring-boot-starter-websocket", version.ref = "springboot3" } -springboot3-starter-webflux = { module = "org.springframework.boot:spring-boot-starter-webflux", version.ref = "springboot3" } -springboot3-starter-aop = { module = "org.springframework.boot:spring-boot-starter-aop", version.ref = "springboot3" } -springboot3-starter-security = { module = "org.springframework.boot:spring-boot-starter-security", version.ref = "springboot3" } -springboot3-starter-jdbc = { module = "org.springframework.boot:spring-boot-starter-jdbc", version.ref = "springboot3" } -springboot3-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator", version.ref = "springboot3" } -springboot4-otel = { module = "io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter", version.ref = "otelInstrumentation" } -springboot4-resttestclient = { module = "org.springframework.boot:spring-boot-resttestclient", version.ref = "springboot4" } -springboot4-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "springboot4" } -springboot4-starter-graphql = { module = "org.springframework.boot:spring-boot-starter-graphql", version.ref = "springboot4" } -springboot4-starter-quartz = { module = "org.springframework.boot:spring-boot-starter-quartz", version.ref = "springboot4" } -springboot4-starter-test = { module = "org.springframework.boot:spring-boot-starter-test", version.ref = "springboot4" } -springboot4-starter-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "springboot4" } -springboot4-starter-websocket = { module = "org.springframework.boot:spring-boot-starter-websocket", version.ref = "springboot4" } -springboot4-starter-webflux = { module = "org.springframework.boot:spring-boot-starter-webflux", version.ref = "springboot4" } -springboot4-starter-aspectj = { module = "org.springframework.boot:spring-boot-starter-aspectj", version.ref = "springboot4" } -springboot4-starter-security = { module = "org.springframework.boot:spring-boot-starter-security", version.ref = "springboot4" } -springboot4-starter-restclient = { module = "org.springframework.boot:spring-boot-starter-restclient", version.ref = "springboot4" } -springboot4-starter-webclient = { module = "org.springframework.boot:spring-boot-starter-webclient", version.ref = "springboot4" } -springboot4-starter-jdbc = { module = "org.springframework.boot:spring-boot-starter-jdbc", version.ref = "springboot4" } -springboot4-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator", version.ref = "springboot4" } -timber = { module = "com.jakewharton.timber:timber", version = "4.7.1" } - -# Animalsniffer signature -gummy-bears-api21 = { module = "com.toasttab.android:gummy-bears-api-21", version.ref = "gummyBears" } - -# tomcat libraries -tomcat-catalina = { module = "org.apache.tomcat:tomcat-catalina", version = "9.0.108" } -tomcat-embed-jasper = { module = "org.apache.tomcat.embed:tomcat-embed-jasper", version = "9.0.108" } -tomcat-catalina-jakarta = { module = "org.apache.tomcat:tomcat-catalina", version = "11.0.10" } -tomcat-embed-jasper-jakarta = { module = "org.apache.tomcat.embed:tomcat-embed-jasper", version = "11.0.10" } - -# test libraries -androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version = "1.9.5" } -androidx-test-core = { module = "androidx.test:core", version.ref = "androidxTestCore" } -androidx-test-core-ktx = { module = "androidx.test:core-ktx", version.ref = "androidxTestCore" } -androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" } -androidx-test-espresso-idling-resource = { module = "androidx.test.espresso:espresso-idling-resource", version.ref = "espresso" } -androidx-test-ext-junit = { module = "androidx.test.ext:junit", version = "1.3.0" } -androidx-test-orchestrator = { module = "androidx.test:orchestrator", version = "1.6.1" } -androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidxTestCore" } -androidx-test-runner = { module = "androidx.test:runner", version = "1.7.0" } -awaitility-kotlin = { module = "org.awaitility:awaitility-kotlin", version = "4.1.1" } -awaitility-kotlin-spring7 = { module = "org.awaitility:awaitility-kotlin", version = "4.3.0" } -awaitility3-kotlin = { module = "org.awaitility:awaitility-kotlin", version = "3.1.6" } - -# CameraX dependencies -camerax-core = { module = "androidx.camera:camera-core", version.ref = "camerax" } -camerax-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" } -camerax-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax" } -camerax-view = { module = "androidx.camera:camera-view", version.ref = "camerax" } - -hsqldb = { module = "org.hsqldb:hsqldb", version = "2.6.1" } -javafaker = { module = "com.github.javafaker:javafaker", version = "1.0.2" } -kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } -leakcanary-instrumentation = { module = "com.squareup.leakcanary:leakcanary-android-instrumentation", version = "2.14" } -mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "4.1.0" } -mockito-kotlin-spring7 = { module = "org.mockito.kotlin:mockito-kotlin", version = "6.0.0" } -mockito-inline = { module = "org.mockito:mockito-inline", version = "4.8.0" } -msgpack = { module = "org.msgpack:msgpack-core", version = "0.9.8" } -okhttp-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" } -okio = { module = "com.squareup.okio:okio", version = "1.13.0" } -roboelectric = { module = "org.robolectric:robolectric", version = "4.14" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 1b33c55baab..00000000000 Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index d4081da476b..00000000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew deleted file mode 100755 index 23d15a93670..00000000000 --- a/gradlew +++ /dev/null @@ -1,251 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH="\\\"\\\"" - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index db3a6ac207e..00000000000 --- a/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH= - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/hooks/pre-commit b/hooks/pre-commit deleted file mode 100755 index 5d72e4d248d..00000000000 --- a/hooks/pre-commit +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# From gist at https://gist.github.com/chadmaughan/5889802 - -echo '[git hook] executing gradle spotless check before commit' - -# run the spotlessCheck with the gradle wrapper -make checkFormat - -# store the last exit code in a variable -RESULT=$? - -# return the './gradlew spotlessCheck' exit code -exit $RESULT diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000000..38bb47ae304 --- /dev/null +++ b/pom.xml @@ -0,0 +1,496 @@ + + + 4.0.0 + + + org.sonatype.oss + oss-parent + 7 + + + io.sentry + sentry-all + 1.7.11-SNAPSHOT + pom + + Sentry-Java + Sentry client and appenders for diverse logging frameworks. + https://github.com/${github.repo} + 2012 + + + BSD New + http://opensource.org/licenses/BSD-3-Clause + repo + + + + + + kencochrane + Ken Cochrane + kencochrane+maven@gmail.com + + + roambe + Kevin Wetzels + kevin@roam.be + + + ColinHebert + Colin Hebert + hebert.colin@gmail.com + https://github.com/ColinHebert + + + bretthoerner + Brett Hoerner + brett@sentry.io + Sentry + https://sentry.io/ + + + + + David Cramer + + + Mark Philpot + + + Brad Chen + + + ccouturi + + + Felipe G Almeida + + + + + 3.2 + + + + sentry + sentry-android + sentry-appengine + sentry-log4j + sentry-logback + sentry-log4j2 + sentry-spring + + + + https://github.com/${github.repo} + scm:git:git://github.com/${github.repo}.git + scm:git:git@github.com:${github.repo}.git + HEAD + + + https://github.com/${github.repo}/issues + GitHub Issues + + + Travis-CI + https://travis-ci.org/${github.repo} + + + + + github-pages-site + Deployment through GitHub's site deployment plugin + site/${project.version} + + + + + UTF-8 + UTF-8 + + getsentry/sentry-java + github + + + 7 + 7 + + + 1.7.24 + 2.8.7 + 1.14 + 6.9.10 + 1.3 + 4.12 + 2.5.1 + + + + + + ${project.groupId} + sentry + ${project.version} + + + ${project.groupId} + sentry + ${project.version} + test-jar + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + org.jmockit + jmockit + ${jmockit.version} + + + org.testng + testng + ${testng.version} + + + org.hamcrest + hamcrest-core + ${hamcrest.version} + + + org.hamcrest + hamcrest-library + ${hamcrest.version} + + + junit + junit + ${junit.version} + + + com.github.tomakehurst + wiremock-standalone + ${wiremock.version} + + + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + v@{project.version} + true + false + true + + deploy + + forked-path + false + -Psonatype-oss-release + + + + com.github.github + site-maven-plugin + 0.12 + + Creating site for ${project.artifactId} ${project.version} + ${project.distributionManagement.site.url} + true + + + + github-site + + site + + site-deploy + + + + + org.apache.maven.plugins + maven-site-plugin + 3.6 + + true + + + + org.codehaus.mojo + animal-sniffer-maven-plugin + 1.16 + + + org.codehaus.mojo.signature + java17 + 1.0 + + + + + check-compatibility + verify + + check + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.19.1 + + + integration-tests + + integration-test + verify + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.0.0 + + + analyze + verify + + analyze-only + + + ${skipTests} + true + true + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.17 + + src/checkstyle/checkstyle.xml + true + + + + com.puppycrawl.tools + checkstyle + + 6.19 + + + + + checkstyle + + ${skipTests} + + + check + + + + + + maven-clean-plugin + 3.0.0 + + + maven-compiler-plugin + 3.6.1 + + + maven-deploy-plugin + 2.8.2 + + + maven-install-plugin + 2.5.2 + + + maven-jar-plugin + 3.0.2 + + + maven-resources-plugin + 3.0.2 + + + maven-site-plugin + 3.6 + + + maven-war-plugin + 3.0.0 + + + + + + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.9 + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.19.1 + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.17 + + src/checkstyle/checkstyle.xml + + + + org.apache.maven.plugins + maven-jxr-plugin + 2.5 + + + + + + + + sonatype-oss-release + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.7 + true + + sonatype-nexus-staging + https://oss.sonatype.org/ + true + + + + + + + + pitest + + ${junit.version} + + + + junit + junit + ${junit.version} + test + + + + + + org.pitest + pitest-maven + 1.1.11 + + + *Test + + + equals() + toString() + hashCode() + + + + + + + + diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 55509e3912b..00000000000 --- a/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[project] -name = "javasdk" -version = "0.0.0" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index ce11ab2364c..00000000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -certifi==2025.7.14 -charset-normalizer==3.4.2 -idna==3.10 -requests==2.32.4 -urllib3==2.6.3 diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh deleted file mode 100755 index 0f68b3c4803..00000000000 --- a/scripts/bump-version.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -# ./scripts/bump-version.sh -# eg ./scripts/bump-version.sh "6.0.0-alpha.1" "6.0.0-alpha.2" - -set -eux - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -cd $SCRIPT_DIR/.. - -OLD_VERSION="$1" -NEW_VERSION="$2" - -GRADLE_FILEPATH="gradle.properties" - -# Replace `versionName` with the given version -VERSION_NAME_PATTERN="versionName" -perl -pi -e "s/$VERSION_NAME_PATTERN=.*$/$VERSION_NAME_PATTERN=$NEW_VERSION/g" $GRADLE_FILEPATH diff --git a/scripts/commit-formatted-code.sh b/scripts/commit-formatted-code.sh deleted file mode 100755 index 24bba6f6b6c..00000000000 --- a/scripts/commit-formatted-code.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -set -euo pipefail - -GITHUB_BRANCH="${1}" - -if [[ $(git status) == *"nothing to commit"* ]]; then - echo "Nothing to commit. All code formatted correctly." -else - echo "Formatted some code. Going to push the changes." - git config --global user.name 'Sentry Github Bot' - git config --global user.email 'bot+github-bot@sentry.io' - git fetch - git checkout ${GITHUB_BRANCH} - git commit -am "Format code" - git push --set-upstream origin ${GITHUB_BRANCH} -fi diff --git a/scripts/mvnw b/scripts/mvnw deleted file mode 100755 index 41c0f0c23db..00000000000 --- a/scripts/mvnw +++ /dev/null @@ -1,310 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/scripts/mvnw.cmd b/scripts/mvnw.cmd deleted file mode 100755 index 86115719e53..00000000000 --- a/scripts/mvnw.cmd +++ /dev/null @@ -1,182 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - -FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% diff --git a/scripts/settings.xml b/scripts/settings.xml deleted file mode 100755 index 8031c3ddf20..00000000000 --- a/scripts/settings.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - ossrh-staging-api - ${env.OSSRH_USERNAME} - ${env.OSSRH_PASSWORD} - - - diff --git a/scripts/test-ui-critical.sh b/scripts/test-ui-critical.sh deleted file mode 100755 index 7bb36eebec7..00000000000 --- a/scripts/test-ui-critical.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -set -e - -echo "Checking if ADB is installed..." -if ! command -v adb &> /dev/null; then - echo "ADB is not installed or not in PATH. Please install Android SDK platform tools and ensure ADB is in your PATH." - exit 1 -fi - -echo "Checking if an Android emulator is running..." -if ! adb devices | grep -q "emulator"; then - echo "No Android emulator is currently running. Please start an emulator before running this script." - exit 1 -fi - -echo "Checking if Maestro is installed..." -if ! command -v maestro &> /dev/null; then - echo "Maestro is not installed. Please install Maestro before running this script." - exit 1 -fi - -echo "Building the UI Test Critical app..." -make assembleUiTestCriticalRelease - -echo "Installing the UI Test Critical app on the emulator..." -baseDir="sentry-android-integration-tests/sentry-uitest-android-critical" -buildDir="build/outputs/apk/release" -apkName="sentry-uitest-android-critical-release.apk" -appPath="${baseDir}/${buildDir}/${apkName}" -adb install -r -d "$appPath" - -echo "Running the Maestro tests..." -maestro test \ - "${baseDir}/maestro" \ - --debug-output "${baseDir}/maestro-logs" diff --git a/scripts/toggle-codec-logs.sh b/scripts/toggle-codec-logs.sh deleted file mode 100755 index d54728818a3..00000000000 --- a/scripts/toggle-codec-logs.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash - -# --- Functions --- - -print_usage() { - echo "Usage: $0 [enable|disable]" - exit 1 -} - -# Check for adb -if ! command -v adb &> /dev/null; then - echo "❌ adb not found. Please install Android Platform Tools and ensure adb is in your PATH." - exit 1 -fi - -# Check for connected device -DEVICE_COUNT=$(adb devices | grep -w "device" | wc -l) -if [ "$DEVICE_COUNT" -eq 0 ]; then - echo "❌ No device connected. Please connect a device and enable USB debugging." - exit 1 -fi - -# --- Handle Argument --- - -ACTION=$(echo "$1" | tr '[:upper:]' '[:lower:]') - -case "$ACTION" in - enable) - echo "✅ Enabling native logs (DEBUG)..." - adb shell setprop log.tag.MPEG4Writer D - adb shell setprop log.tag.CCodec D - adb shell setprop log.tag.VQApply D - adb shell setprop log.tag.ColorUtils D - adb shell setprop log.tag.MediaCodec D - adb shell setprop log.tag.MediaCodecList D - adb shell setprop log.tag.MediaWriter D - adb shell setprop log.tag.CCodecConfig D - adb shell setprop log.tag.Codec2Client D - adb shell setprop log.tag.CCodecBufferChannel D - adb shell setprop log.tag.CodecProperties D - adb shell setprop log.tag.CodecSeeding D - adb shell setprop log.tag.C2Store D - adb shell setprop log.tag.C2NodeImpl D - adb shell setprop log.tag.GraphicBufferSource D - adb shell setprop log.tag.BufferQueueProducer D - adb shell setprop log.tag.ReflectedParamUpdater D - adb shell setprop log.tag.hw-BpHwBinder D - adb shell setprop log.tag.ACodec D - adb shell setprop log.tag.VideoCapabilities D - adb shell setprop log.tag.OMXUtils D - adb shell setprop log.tag.OMXClient D - echo "✅ Logs ENABLED" - ;; - disable) - echo "🚫 Disabling native logs (SILENT)..." - adb shell setprop log.tag.MPEG4Writer SILENT - adb shell setprop log.tag.CCodec SILENT - adb shell setprop log.tag.VQApply SILENT - adb shell setprop log.tag.ColorUtils SILENT - adb shell setprop log.tag.MediaCodec SILENT - adb shell setprop log.tag.MediaCodecList SILENT - adb shell setprop log.tag.MediaWriter SILENT - adb shell setprop log.tag.CCodecConfig SILENT - adb shell setprop log.tag.Codec2Client SILENT - adb shell setprop log.tag.CCodecBufferChannel SILENT - adb shell setprop log.tag.CodecProperties SILENT - adb shell setprop log.tag.CodecSeeding SILENT - adb shell setprop log.tag.C2Store SILENT - adb shell setprop log.tag.C2NodeImpl SILENT - adb shell setprop log.tag.GraphicBufferSource SILENT - adb shell setprop log.tag.BufferQueueProducer SILENT - adb shell setprop log.tag.ReflectedParamUpdater SILENT - adb shell setprop log.tag.hw-BpHwBinder SILENT - adb shell setprop log.tag.ACodec SILENT - adb shell setprop log.tag.VideoCapabilities SILENT - adb shell setprop log.tag.OMXUtils SILENT - adb shell setprop log.tag.OMXClient SILENT - echo "🚫 Logs DISABLED" - ;; - *) - echo "❓ Unknown or missing argument: '$1'" - print_usage - ;; -esac diff --git a/scripts/update-gradle.sh b/scripts/update-gradle.sh deleted file mode 100755 index c2bfe979224..00000000000 --- a/scripts/update-gradle.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -cd $(dirname "$0")/../ - -if [[ -n ${CI+x} ]]; then - export JAVA_HOME=$JAVA_HOME_17_X64 -fi - -case $1 in -get-version) - # `./gradlew` shows some info on the first run, breaking the parsing in the next step. - # Therefore, we run it once without checking any output. - ./gradlew --version >/dev/null - version="$(./gradlew --version | sed -E -n 's/.*Gradle +([0-9.]+).*/\1/p')" - - # Add trailing ".0" - gradlew outputs '7.1' instead of '7.1.0' - if [[ "$version" =~ ^[0-9]\.[0-9]$ ]]; then - version="$version.0" - fi - - echo "v$version" - ;; -get-repo) - echo "https://github.com/gradle/gradle.git" - ;; -set-version) - version=$2 - - # Remove leading "v" - if [[ "$version" == v* ]]; then - version="${version:1}" - fi - - echo "Setting gradle version to '$version'" - - # This sets version to gradle-wrapper.properties. - ./gradlew wrapper --gradle-version "$version" - - # Verify it works. - ./gradlew --version - ;; -*) - echo "Unknown argument $1" - exit 1 - ;; -esac diff --git a/scripts/update-sentry-native-ndk.sh b/scripts/update-sentry-native-ndk.sh deleted file mode 100755 index 0aef9ebb257..00000000000 --- a/scripts/update-sentry-native-ndk.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -cd $(dirname "$0")/../ -GRADLE_NDK_FILEPATH=gradle/libs.versions.toml - -case $1 in -get-version) - perl -ne 'print "$1\n" if ( m/module = "io\.sentry:sentry-native-ndk", version = "([0-9.]+)"/ )' "$GRADLE_NDK_FILEPATH" - ;; -get-repo) - echo "https://github.com/getsentry/sentry-native.git" - ;; -set-version) - version=$2 - - echo "Setting sentry-native-ndk version to '$version'" - - PATTERN='(module = "io\.sentry:sentry-native-ndk", version = ")[0-9.]+(")' - perl -pi -e "s/$PATTERN/\${1}$version\${2}/" "$GRADLE_NDK_FILEPATH" - ;; -*) - echo "Unknown argument $1" - exit 1 - ;; -esac diff --git a/sentry-android-core/.gitignore b/sentry-android-core/.gitignore deleted file mode 100644 index 772ea5870fc..00000000000 --- a/sentry-android-core/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -INSTALLATION -last_crash -.options-cache/ -.scope-cache/ -/sentry diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api deleted file mode 100644 index 69b3982af8d..00000000000 --- a/sentry-android-core/api/sentry-android-core.api +++ /dev/null @@ -1,686 +0,0 @@ -public final class io/sentry/android/core/ActivityBreadcrumbsIntegration : android/app/Application$ActivityLifecycleCallbacks, io/sentry/Integration, java/io/Closeable { - public fun (Landroid/app/Application;)V - public fun close ()V - public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V - public fun onActivityDestroyed (Landroid/app/Activity;)V - public fun onActivityPaused (Landroid/app/Activity;)V - public fun onActivityResumed (Landroid/app/Activity;)V - public fun onActivitySaveInstanceState (Landroid/app/Activity;Landroid/os/Bundle;)V - public fun onActivityStarted (Landroid/app/Activity;)V - public fun onActivityStopped (Landroid/app/Activity;)V - public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V -} - -public final class io/sentry/android/core/ActivityFramesTracker { - public fun (Lio/sentry/util/LoadClass;Lio/sentry/android/core/SentryAndroidOptions;)V - public fun (Lio/sentry/util/LoadClass;Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/MainLooperHandler;)V - public fun addActivity (Landroid/app/Activity;)V - public fun isFrameMetricsAggregatorAvailable ()Z - public fun setMetrics (Landroid/app/Activity;Lio/sentry/protocol/SentryId;)V - public fun stop ()V - public fun takeMetrics (Lio/sentry/protocol/SentryId;)Ljava/util/Map; -} - -public final class io/sentry/android/core/ActivityLifecycleIntegration : android/app/Application$ActivityLifecycleCallbacks, io/sentry/Integration, java/io/Closeable { - public fun (Landroid/app/Application;Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/android/core/ActivityFramesTracker;)V - public fun close ()V - public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V - public fun onActivityDestroyed (Landroid/app/Activity;)V - public fun onActivityPaused (Landroid/app/Activity;)V - public fun onActivityPostCreated (Landroid/app/Activity;Landroid/os/Bundle;)V - public fun onActivityPostResumed (Landroid/app/Activity;)V - public fun onActivityPostStarted (Landroid/app/Activity;)V - public fun onActivityPreCreated (Landroid/app/Activity;Landroid/os/Bundle;)V - public fun onActivityPrePaused (Landroid/app/Activity;)V - public fun onActivityPreStarted (Landroid/app/Activity;)V - public fun onActivityResumed (Landroid/app/Activity;)V - public fun onActivitySaveInstanceState (Landroid/app/Activity;Landroid/os/Bundle;)V - public fun onActivityStarted (Landroid/app/Activity;)V - public fun onActivityStopped (Landroid/app/Activity;)V - public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V -} - -public class io/sentry/android/core/AndroidContinuousProfiler : io/sentry/IContinuousProfiler, io/sentry/transport/RateLimiter$IRateLimitObserver { - public fun (Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/ILogger;Ljava/lang/String;ILio/sentry/util/LazyEvaluator$Evaluator;)V - public fun close (Z)V - public fun getChunkId ()Lio/sentry/protocol/SentryId; - public fun getProfilerId ()Lio/sentry/protocol/SentryId; - public fun getRootSpanCounter ()I - public fun isRunning ()Z - public fun onRateLimitChanged (Lio/sentry/transport/RateLimiter;)V - public fun reevaluateSampling ()V - public fun startProfiler (Lio/sentry/ProfileLifecycle;Lio/sentry/TracesSampler;)V - public fun stopProfiler (Lio/sentry/ProfileLifecycle;)V -} - -public final class io/sentry/android/core/AndroidCpuCollector : io/sentry/IPerformanceSnapshotCollector { - public fun (Lio/sentry/ILogger;)V - public fun collect (Lio/sentry/PerformanceCollectionData;)V - public fun setup ()V -} - -public final class io/sentry/android/core/AndroidDateUtils { - public fun ()V - public static fun getCurrentSentryDateTime ()Lio/sentry/SentryDate; -} - -public final class io/sentry/android/core/AndroidFatalLogger : io/sentry/ILogger { - public fun ()V - public fun (Ljava/lang/String;)V - public fun isEnabled (Lio/sentry/SentryLevel;)Z - public fun log (Lio/sentry/SentryLevel;Ljava/lang/String;Ljava/lang/Throwable;)V - public fun log (Lio/sentry/SentryLevel;Ljava/lang/String;[Ljava/lang/Object;)V - public fun log (Lio/sentry/SentryLevel;Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V -} - -public final class io/sentry/android/core/AndroidLogger : io/sentry/ILogger { - public fun ()V - public fun (Ljava/lang/String;)V - public fun isEnabled (Lio/sentry/SentryLevel;)Z - public fun log (Lio/sentry/SentryLevel;Ljava/lang/String;Ljava/lang/Throwable;)V - public fun log (Lio/sentry/SentryLevel;Ljava/lang/String;[Ljava/lang/Object;)V - public fun log (Lio/sentry/SentryLevel;Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V -} - -public final class io/sentry/android/core/AndroidLoggerBatchProcessor : io/sentry/logger/LoggerBatchProcessor, io/sentry/android/core/AppState$AppStateListener { - public fun (Lio/sentry/SentryOptions;Lio/sentry/ISentryClient;)V - public fun close (Z)V - public fun onBackground ()V - public fun onForeground ()V -} - -public final class io/sentry/android/core/AndroidLoggerBatchProcessorFactory : io/sentry/logger/ILoggerBatchProcessorFactory { - public fun ()V - public fun create (Lio/sentry/SentryOptions;Lio/sentry/SentryClient;)Lio/sentry/logger/ILoggerBatchProcessor; -} - -public class io/sentry/android/core/AndroidMemoryCollector : io/sentry/IPerformanceSnapshotCollector { - public fun ()V - public fun collect (Lio/sentry/PerformanceCollectionData;)V - public fun setup ()V -} - -public final class io/sentry/android/core/AndroidMetricsBatchProcessor : io/sentry/metrics/MetricsBatchProcessor, io/sentry/android/core/AppState$AppStateListener { - public fun (Lio/sentry/SentryOptions;Lio/sentry/ISentryClient;)V - public fun close (Z)V - public fun onBackground ()V - public fun onForeground ()V -} - -public final class io/sentry/android/core/AndroidMetricsBatchProcessorFactory : io/sentry/metrics/IMetricsBatchProcessorFactory { - public fun ()V - public fun create (Lio/sentry/SentryOptions;Lio/sentry/SentryClient;)Lio/sentry/metrics/IMetricsBatchProcessor; -} - -public class io/sentry/android/core/AndroidProfiler { - protected final field lock Lio/sentry/util/AutoClosableReentrantLock; - public fun (Ljava/lang/String;ILio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/util/LazyEvaluator$Evaluator;Lio/sentry/ILogger;)V - public fun close ()V - public fun endAndCollect (ZLjava/util/List;)Lio/sentry/android/core/AndroidProfiler$ProfileEndData; - public fun start ()Lio/sentry/android/core/AndroidProfiler$ProfileStartData; -} - -public class io/sentry/android/core/AndroidProfiler$ProfileEndData { - public final field didTimeout Z - public final field endCpuMillis J - public final field endNanos J - public final field measurementsMap Ljava/util/Map; - public final field traceFile Ljava/io/File; - public fun (JJZLjava/io/File;Ljava/util/Map;)V -} - -public class io/sentry/android/core/AndroidProfiler$ProfileStartData { - public final field startCpuMillis J - public final field startNanos J - public final field startTimestamp Ljava/util/Date; - public fun (JJLjava/util/Date;)V -} - -public final class io/sentry/android/core/AndroidSocketTagger : io/sentry/ISocketTagger { - public static fun getInstance ()Lio/sentry/android/core/AndroidSocketTagger; - public fun tagSockets ()V - public fun untagSockets ()V -} - -public final class io/sentry/android/core/AnrIntegration : io/sentry/Integration, java/io/Closeable { - public fun (Landroid/content/Context;)V - public fun close ()V - public final fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V -} - -public final class io/sentry/android/core/AnrIntegrationFactory { - public fun ()V - public static fun create (Landroid/content/Context;Lio/sentry/android/core/BuildInfoProvider;)Lio/sentry/Integration; -} - -public class io/sentry/android/core/AnrV2Integration : io/sentry/Integration, java/io/Closeable { - public fun (Landroid/content/Context;)V - public fun close ()V - public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V -} - -public final class io/sentry/android/core/AnrV2Integration$AnrV2Hint : io/sentry/hints/BlockingFlushHint, io/sentry/hints/AbnormalExit, io/sentry/hints/Backfillable { - public fun (JLio/sentry/ILogger;JZZ)V - public fun ignoreCurrentThread ()Z - public fun isFlushable (Lio/sentry/protocol/SentryId;)Z - public fun mechanism ()Ljava/lang/String; - public fun setFlushable (Lio/sentry/protocol/SentryId;)V - public fun shouldEnrich ()Z - public fun timestamp ()Ljava/lang/Long; -} - -public final class io/sentry/android/core/AppComponentsBreadcrumbsIntegration : android/content/ComponentCallbacks2, io/sentry/Integration, java/io/Closeable { - public fun (Landroid/content/Context;)V - public fun close ()V - public fun onConfigurationChanged (Landroid/content/res/Configuration;)V - public fun onLowMemory ()V - public fun onTrimMemory (I)V - public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V -} - -public final class io/sentry/android/core/AppLifecycleIntegration : io/sentry/Integration, java/io/Closeable { - public fun ()V - public fun close ()V - public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V -} - -public final class io/sentry/android/core/AppState : java/io/Closeable { - public fun addAppStateListener (Lio/sentry/android/core/AppState$AppStateListener;)V - public fun close ()V - public static fun getInstance ()Lio/sentry/android/core/AppState; - public fun getLifecycleObserver ()Lio/sentry/android/core/AppState$LifecycleObserver; - public fun isInBackground ()Ljava/lang/Boolean; - public fun registerLifecycleObserver (Lio/sentry/SentryOptions;)V - public fun removeAppStateListener (Lio/sentry/android/core/AppState$AppStateListener;)V - public fun resetInstance ()V - public fun unregisterLifecycleObserver ()V -} - -public abstract interface class io/sentry/android/core/AppState$AppStateListener { - public abstract fun onBackground ()V - public abstract fun onForeground ()V -} - -public final class io/sentry/android/core/AppState$LifecycleObserver : androidx/lifecycle/DefaultLifecycleObserver { - public fun (Lio/sentry/android/core/AppState;)V - public fun getListeners ()Ljava/util/List; - public fun onStart (Landroidx/lifecycle/LifecycleOwner;)V - public fun onStop (Landroidx/lifecycle/LifecycleOwner;)V -} - -public final class io/sentry/android/core/ApplicationExitInfoEventProcessor : io/sentry/BackfillingEventProcessor { - public fun (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/BuildInfoProvider;)V - public fun getOrder ()Ljava/lang/Long; - public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; - public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction; -} - -public final class io/sentry/android/core/BuildConfig { - public static final field BUILD_TYPE Ljava/lang/String; - public static final field DEBUG Z - public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String; - public static final field SENTRY_ANDROID_SDK_NAME Ljava/lang/String; - public static final field VERSION_NAME Ljava/lang/String; - public fun ()V -} - -public final class io/sentry/android/core/BuildInfoProvider { - public fun (Lio/sentry/ILogger;)V - public fun getBuildTags ()Ljava/lang/String; - public fun getManufacturer ()Ljava/lang/String; - public fun getModel ()Ljava/lang/String; - public fun getSdkInfoVersion ()I - public fun getVersionRelease ()Ljava/lang/String; - public fun isEmulator ()Ljava/lang/Boolean; -} - -public final class io/sentry/android/core/ContextUtils { - public static fun appIsLibraryForComposePreview (Landroid/content/Context;)Z - public static fun getApplicationContext (Landroid/content/Context;)Landroid/content/Context; - public static fun isForegroundImportance ()Z -} - -public class io/sentry/android/core/CurrentActivityHolder { - public fun clearActivity ()V - public fun clearActivity (Landroid/app/Activity;)V - public fun getActivity ()Landroid/app/Activity; - public static fun getInstance ()Lio/sentry/android/core/CurrentActivityHolder; - public fun setActivity (Landroid/app/Activity;)V -} - -public final class io/sentry/android/core/DeviceInfoUtil { - public fun (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;)V - public fun collectDeviceInformation (ZZ)Lio/sentry/protocol/Device; - public static fun getBatteryLevel (Landroid/content/Intent;Lio/sentry/SentryOptions;)Ljava/lang/Float; - public static fun getInstance (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;)Lio/sentry/android/core/DeviceInfoUtil; - public fun getOperatingSystem ()Lio/sentry/protocol/OperatingSystem; - public fun getSideLoadedInfo ()Lio/sentry/android/core/ContextUtils$SideLoadedInfo; - public fun getSplitApksInfo ()Lio/sentry/android/core/ContextUtils$SplitApksInfo; - public fun getTotalMemory ()Ljava/lang/Long; - public static fun isCharging (Landroid/content/Intent;Lio/sentry/SentryOptions;)Ljava/lang/Boolean; - public static fun resetInstance ()V -} - -public abstract class io/sentry/android/core/EnvelopeFileObserverIntegration : io/sentry/Integration, java/io/Closeable { - protected final field startLock Lio/sentry/util/AutoClosableReentrantLock; - public fun ()V - public fun close ()V - public static fun getOutboxFileObserver ()Lio/sentry/android/core/EnvelopeFileObserverIntegration; - public final fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V -} - -public abstract interface class io/sentry/android/core/IDebugImagesLoader { - public abstract fun clearDebugImages ()V - public abstract fun loadDebugImages ()Ljava/util/List; - public abstract fun loadDebugImagesForAddresses (Ljava/util/Set;)Ljava/util/Set; -} - -public final class io/sentry/android/core/InternalSentrySdk { - public fun ()V - public static fun captureEnvelope ([BZ)Lio/sentry/protocol/SentryId; - public static fun getAppStartMeasurement ()Ljava/util/Map; - public static fun getCurrentScope ()Lio/sentry/IScope; - public static fun serializeScope (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/IScope;)Ljava/util/Map; - public static fun setTrace (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;)V -} - -public final class io/sentry/android/core/LoadClass : io/sentry/util/LoadClass { - public fun ()V - public fun isClassAvailable (Ljava/lang/String;Lio/sentry/ILogger;)Z - public fun isClassAvailable (Ljava/lang/String;Lio/sentry/SentryOptions;)Z - public fun loadClass (Ljava/lang/String;Lio/sentry/ILogger;)Ljava/lang/Class; -} - -public final class io/sentry/android/core/NativeEventCollector { - public fun (Lio/sentry/android/core/SentryAndroidOptions;)V - public fun collect ()V - public fun deleteNativeEventFile (Lio/sentry/android/core/NativeEventCollector$NativeEventData;)Z - public fun findAndRemoveMatchingNativeEvent (J)Lio/sentry/android/core/NativeEventCollector$NativeEventData; -} - -public final class io/sentry/android/core/NativeEventCollector$NativeEventData { - public fun getEnvelope ()Lio/sentry/SentryEnvelope; - public fun getEvent ()Lio/sentry/SentryEvent; - public fun getFile ()Ljava/io/File; -} - -public final class io/sentry/android/core/NdkHandlerStrategy : java/lang/Enum { - public static final field SENTRY_HANDLER_STRATEGY_CHAIN_AT_START Lio/sentry/android/core/NdkHandlerStrategy; - public static final field SENTRY_HANDLER_STRATEGY_DEFAULT Lio/sentry/android/core/NdkHandlerStrategy; - public fun getValue ()I - public static fun valueOf (Ljava/lang/String;)Lio/sentry/android/core/NdkHandlerStrategy; - public static fun values ()[Lio/sentry/android/core/NdkHandlerStrategy; -} - -public final class io/sentry/android/core/NdkIntegration : io/sentry/Integration, java/io/Closeable { - public static final field SENTRY_NDK_CLASS_NAME Ljava/lang/String; - public fun (Ljava/lang/Class;)V - public fun close ()V - public final fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V -} - -public final class io/sentry/android/core/NetworkBreadcrumbsIntegration : io/sentry/Integration, java/io/Closeable { - public fun (Landroid/content/Context;Lio/sentry/android/core/BuildInfoProvider;)V - public fun close ()V - public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V -} - -public final class io/sentry/android/core/ScreenshotEventProcessor : io/sentry/EventProcessor { - public fun (Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/BuildInfoProvider;)V - public fun getOrder ()Ljava/lang/Long; - public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; - public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction; -} - -public final class io/sentry/android/core/SentryAndroid { - public static fun init (Landroid/content/Context;)V - public static fun init (Landroid/content/Context;Lio/sentry/ILogger;)V - public static fun init (Landroid/content/Context;Lio/sentry/ILogger;Lio/sentry/Sentry$OptionsConfiguration;)V - public static fun init (Landroid/content/Context;Lio/sentry/Sentry$OptionsConfiguration;)V -} - -public final class io/sentry/android/core/SentryAndroidDateProvider : io/sentry/SentryDateProvider { - public fun ()V - public fun now ()Lio/sentry/SentryDate; -} - -public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/SentryOptions { - public fun ()V - public fun enableAllAutoBreadcrumbs (Z)V - public fun getAnrTimeoutIntervalMillis ()J - public fun getBeforeScreenshotCaptureCallback ()Lio/sentry/android/core/SentryAndroidOptions$BeforeCaptureCallback; - public fun getBeforeViewHierarchyCaptureCallback ()Lio/sentry/android/core/SentryAndroidOptions$BeforeCaptureCallback; - public fun getDebugImagesLoader ()Lio/sentry/android/core/IDebugImagesLoader; - public fun getFrameMetricsCollector ()Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector; - public fun getNativeSdkName ()Ljava/lang/String; - public fun getNdkHandlerStrategy ()I - public fun getStartupCrashDurationThresholdMillis ()J - public fun isAnrEnabled ()Z - public fun isAnrReportInDebug ()Z - public fun isAttachAnrThreadDump ()Z - public fun isAttachScreenshot ()Z - public fun isAttachViewHierarchy ()Z - public fun isCollectAdditionalContext ()Z - public fun isCollectExternalStorageContext ()Z - public fun isEnableActivityLifecycleBreadcrumbs ()Z - public fun isEnableActivityLifecycleTracingAutoFinish ()Z - public fun isEnableAppComponentBreadcrumbs ()Z - public fun isEnableAppLifecycleBreadcrumbs ()Z - public fun isEnableAutoActivityLifecycleTracing ()Z - public fun isEnableAutoTraceIdGeneration ()Z - public fun isEnableFramesTracking ()Z - public fun isEnableNdk ()Z - public fun isEnableNetworkEventBreadcrumbs ()Z - public fun isEnablePerformanceV2 ()Z - public fun isEnableRootCheck ()Z - public fun isEnableScopeSync ()Z - public fun isEnableSystemEventBreadcrumbs ()Z - public fun isEnableSystemEventBreadcrumbsExtras ()Z - public fun isReportHistoricalAnrs ()Z - public fun isReportHistoricalTombstones ()Z - public fun isTombstoneEnabled ()Z - public fun setAnrEnabled (Z)V - public fun setAnrReportInDebug (Z)V - public fun setAnrTimeoutIntervalMillis (J)V - public fun setAttachAnrThreadDump (Z)V - public fun setAttachScreenshot (Z)V - public fun setAttachViewHierarchy (Z)V - public fun setBeforeScreenshotCaptureCallback (Lio/sentry/android/core/SentryAndroidOptions$BeforeCaptureCallback;)V - public fun setBeforeViewHierarchyCaptureCallback (Lio/sentry/android/core/SentryAndroidOptions$BeforeCaptureCallback;)V - public fun setCollectAdditionalContext (Z)V - public fun setCollectExternalStorageContext (Z)V - public fun setDebugImagesLoader (Lio/sentry/android/core/IDebugImagesLoader;)V - public fun setEnableActivityLifecycleBreadcrumbs (Z)V - public fun setEnableActivityLifecycleTracingAutoFinish (Z)V - public fun setEnableAppComponentBreadcrumbs (Z)V - public fun setEnableAppLifecycleBreadcrumbs (Z)V - public fun setEnableAutoActivityLifecycleTracing (Z)V - public fun setEnableAutoTraceIdGeneration (Z)V - public fun setEnableFramesTracking (Z)V - public fun setEnableNdk (Z)V - public fun setEnableNetworkEventBreadcrumbs (Z)V - public fun setEnablePerformanceV2 (Z)V - public fun setEnableRootCheck (Z)V - public fun setEnableScopeSync (Z)V - public fun setEnableSystemEventBreadcrumbs (Z)V - public fun setEnableSystemEventBreadcrumbsExtras (Z)V - public fun setFrameMetricsCollector (Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;)V - public fun setNativeHandlerStrategy (Lio/sentry/android/core/NdkHandlerStrategy;)V - public fun setNativeSdkName (Ljava/lang/String;)V - public fun setReportHistoricalAnrs (Z)V - public fun setReportHistoricalTombstones (Z)V - public fun setTombstoneEnabled (Z)V -} - -public abstract interface class io/sentry/android/core/SentryAndroidOptions$BeforeCaptureCallback { - public abstract fun execute (Lio/sentry/SentryEvent;Lio/sentry/Hint;Z)Z -} - -public final class io/sentry/android/core/SentryInitProvider { - public fun ()V - public fun attachInfo (Landroid/content/Context;Landroid/content/pm/ProviderInfo;)V - public fun getType (Landroid/net/Uri;)Ljava/lang/String; - public fun onCreate ()Z - public fun shutdown ()V -} - -public final class io/sentry/android/core/SentryLogcatAdapter { - public fun ()V - public static fun d (Ljava/lang/String;Ljava/lang/String;)I - public static fun d (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I - public static fun e (Ljava/lang/String;Ljava/lang/String;)I - public static fun e (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I - public static fun i (Ljava/lang/String;Ljava/lang/String;)I - public static fun i (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I - public static fun v (Ljava/lang/String;Ljava/lang/String;)I - public static fun v (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I - public static fun w (Ljava/lang/String;Ljava/lang/String;)I - public static fun w (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I - public static fun w (Ljava/lang/String;Ljava/lang/Throwable;)I - public static fun wtf (Ljava/lang/String;Ljava/lang/String;)I - public static fun wtf (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I - public static fun wtf (Ljava/lang/String;Ljava/lang/Throwable;)I -} - -public final class io/sentry/android/core/SentryPerformanceProvider { - public fun ()V - public fun attachInfo (Landroid/content/Context;Landroid/content/pm/ProviderInfo;)V - public fun getType (Landroid/net/Uri;)Ljava/lang/String; - public fun onCreate ()Z - public fun shutdown ()V -} - -public class io/sentry/android/core/SentryUserFeedbackButton : android/widget/Button { - public fun (Landroid/content/Context;)V - public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V - public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V - public fun (Landroid/content/Context;Landroid/util/AttributeSet;II)V - public fun setOnClickListener (Landroid/view/View$OnClickListener;)V -} - -public final class io/sentry/android/core/SentryUserFeedbackDialog : android/app/AlertDialog { - public fun setCancelable (Z)V - public fun setOnDismissListener (Landroid/content/DialogInterface$OnDismissListener;)V - public fun show ()V -} - -public class io/sentry/android/core/SentryUserFeedbackDialog$Builder { - public fun (Landroid/content/Context;)V - public fun (Landroid/content/Context;I)V - public fun (Landroid/content/Context;ILio/sentry/android/core/SentryUserFeedbackDialog$OptionsConfiguration;)V - public fun (Landroid/content/Context;Lio/sentry/android/core/SentryUserFeedbackDialog$OptionsConfiguration;)V - public fun associatedEventId (Lio/sentry/protocol/SentryId;)Lio/sentry/android/core/SentryUserFeedbackDialog$Builder; - public fun configurator (Lio/sentry/SentryFeedbackOptions$OptionsConfigurator;)Lio/sentry/android/core/SentryUserFeedbackDialog$Builder; - public fun create ()Lio/sentry/android/core/SentryUserFeedbackDialog; -} - -public abstract interface class io/sentry/android/core/SentryUserFeedbackDialog$OptionsConfiguration { - public abstract fun configure (Landroid/content/Context;Lio/sentry/SentryFeedbackOptions;)V -} - -public class io/sentry/android/core/SpanFrameMetricsCollector : io/sentry/IPerformanceContinuousCollector, io/sentry/android/core/internal/util/SentryFrameMetricsCollector$FrameMetricsCollectorListener { - protected final field lock Lio/sentry/util/AutoClosableReentrantLock; - public fun (Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;)V - public fun clear ()V - public fun onFrameMetricCollected (JJJJZZF)V - public fun onSpanFinished (Lio/sentry/ISpan;)V - public fun onSpanStarted (Lio/sentry/ISpan;)V -} - -public final class io/sentry/android/core/SystemEventsBreadcrumbsIntegration : io/sentry/Integration, io/sentry/android/core/AppState$AppStateListener, java/io/Closeable { - public fun (Landroid/content/Context;)V - public fun (Landroid/content/Context;Landroid/os/Handler;)V - public fun (Landroid/content/Context;Ljava/util/List;)V - public fun close ()V - public static fun getDefaultActions ()Ljava/util/List; - public fun onBackground ()V - public fun onForeground ()V - public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V -} - -public class io/sentry/android/core/TombstoneIntegration : io/sentry/Integration, java/io/Closeable { - public fun (Landroid/content/Context;)V - public fun close ()V - public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V -} - -public final class io/sentry/android/core/TombstoneIntegration$TombstoneHint : io/sentry/hints/BlockingFlushHint, io/sentry/hints/Backfillable, io/sentry/hints/NativeCrashExit { - public fun (JLio/sentry/ILogger;JZ)V - public fun isFlushable (Lio/sentry/protocol/SentryId;)Z - public fun setFlushable (Lio/sentry/protocol/SentryId;)V - public fun shouldEnrich ()Z - public fun timestamp ()Ljava/lang/Long; -} - -public class io/sentry/android/core/TombstoneIntegration$TombstonePolicy : io/sentry/android/core/ApplicationExitInfoHistoryDispatcher$ApplicationExitInfoPolicy { - public fun (Lio/sentry/android/core/SentryAndroidOptions;Landroid/content/Context;)V - public fun buildReport (Landroid/app/ApplicationExitInfo;Z)Lio/sentry/android/core/ApplicationExitInfoHistoryDispatcher$Report; - public fun getLabel ()Ljava/lang/String; - public fun getLastReportedTimestamp ()Ljava/lang/Long; - public fun getTargetReason ()I - public fun shouldReportHistorical ()Z -} - -public final class io/sentry/android/core/UserInteractionIntegration : android/app/Application$ActivityLifecycleCallbacks, io/sentry/Integration, java/io/Closeable { - public fun (Landroid/app/Application;Lio/sentry/util/LoadClass;)V - public fun close ()V - public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V - public fun onActivityDestroyed (Landroid/app/Activity;)V - public fun onActivityPaused (Landroid/app/Activity;)V - public fun onActivityResumed (Landroid/app/Activity;)V - public fun onActivitySaveInstanceState (Landroid/app/Activity;Landroid/os/Bundle;)V - public fun onActivityStarted (Landroid/app/Activity;)V - public fun onActivityStopped (Landroid/app/Activity;)V - public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V -} - -public final class io/sentry/android/core/ViewHierarchyEventProcessor : io/sentry/EventProcessor { - public fun (Lio/sentry/android/core/SentryAndroidOptions;)V - public fun getOrder ()Ljava/lang/Long; - public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; - public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction; - public static fun snapshotViewHierarchy (Landroid/app/Activity;Lio/sentry/ILogger;)Lio/sentry/protocol/ViewHierarchy; - public static fun snapshotViewHierarchy (Landroid/app/Activity;Ljava/util/List;Lio/sentry/util/thread/IThreadChecker;Lio/sentry/ILogger;)Lio/sentry/protocol/ViewHierarchy; - public static fun snapshotViewHierarchy (Landroid/view/View;)Lio/sentry/protocol/ViewHierarchy; - public static fun snapshotViewHierarchy (Landroid/view/View;Ljava/util/List;)Lio/sentry/protocol/ViewHierarchy; - public static fun snapshotViewHierarchyAsData (Landroid/app/Activity;Lio/sentry/util/thread/IThreadChecker;Lio/sentry/ISerializer;Lio/sentry/ILogger;)[B -} - -public final class io/sentry/android/core/cache/AndroidEnvelopeCache : io/sentry/cache/EnvelopeCache { - public static final field LAST_ANR_MARKER_LABEL Ljava/lang/String; - public static final field LAST_ANR_REPORT Ljava/lang/String; - public static final field LAST_TOMBSTONE_MARKER_LABEL Ljava/lang/String; - public static final field LAST_TOMBSTONE_REPORT Ljava/lang/String; - public fun (Lio/sentry/android/core/SentryAndroidOptions;)V - public fun getDirectory ()Ljava/io/File; - public static fun hasStartupCrashMarker (Lio/sentry/SentryOptions;)Z - public static fun lastReportedAnr (Lio/sentry/SentryOptions;)Ljava/lang/Long; - public static fun lastReportedTombstone (Lio/sentry/SentryOptions;)Ljava/lang/Long; - public fun store (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)V - public fun storeEnvelope (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)Z -} - -public class io/sentry/android/core/performance/ActivityLifecycleCallbacksAdapter : android/app/Application$ActivityLifecycleCallbacks { - public fun ()V - public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V - public fun onActivityDestroyed (Landroid/app/Activity;)V - public fun onActivityPaused (Landroid/app/Activity;)V - public fun onActivityResumed (Landroid/app/Activity;)V - public fun onActivitySaveInstanceState (Landroid/app/Activity;Landroid/os/Bundle;)V - public fun onActivityStarted (Landroid/app/Activity;)V - public fun onActivityStopped (Landroid/app/Activity;)V -} - -public class io/sentry/android/core/performance/ActivityLifecycleSpanHelper { - public fun (Ljava/lang/String;)V - public fun clear ()V - public fun createAndStopOnCreateSpan (Lio/sentry/ISpan;)V - public fun createAndStopOnStartSpan (Lio/sentry/ISpan;)V - public fun getOnCreateSpan ()Lio/sentry/ISpan; - public fun getOnCreateStartTimestamp ()Lio/sentry/SentryDate; - public fun getOnStartSpan ()Lio/sentry/ISpan; - public fun getOnStartStartTimestamp ()Lio/sentry/SentryDate; - public fun saveSpanToAppStartMetrics ()V - public fun setOnCreateStartTimestamp (Lio/sentry/SentryDate;)V - public fun setOnStartStartTimestamp (Lio/sentry/SentryDate;)V -} - -public class io/sentry/android/core/performance/ActivityLifecycleTimeSpan : java/lang/Comparable { - public fun ()V - public fun compareTo (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)I - public synthetic fun compareTo (Ljava/lang/Object;)I - public final fun getOnCreate ()Lio/sentry/android/core/performance/TimeSpan; - public final fun getOnStart ()Lio/sentry/android/core/performance/TimeSpan; -} - -public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/android/core/performance/ActivityLifecycleCallbacksAdapter { - public static final field staticLock Lio/sentry/util/AutoClosableReentrantLock; - public fun ()V - public fun addActivityLifecycleTimeSpans (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)V - public fun clear ()V - public fun createProcessInitSpan ()Lio/sentry/android/core/performance/TimeSpan; - public fun getActivityLifecycleTimeSpans ()Ljava/util/List; - public fun getAppStartContinuousProfiler ()Lio/sentry/IContinuousProfiler; - public fun getAppStartProfiler ()Lio/sentry/ITransactionProfiler; - public fun getAppStartSamplingDecision ()Lio/sentry/TracesSamplingDecision; - public fun getAppStartTimeSpan ()Lio/sentry/android/core/performance/TimeSpan; - public fun getAppStartTimeSpanWithFallback (Lio/sentry/android/core/SentryAndroidOptions;)Lio/sentry/android/core/performance/TimeSpan; - public fun getAppStartType ()Lio/sentry/android/core/performance/AppStartMetrics$AppStartType; - public fun getApplicationOnCreateTimeSpan ()Lio/sentry/android/core/performance/TimeSpan; - public fun getClassLoadedUptimeMs ()J - public fun getContentProviderOnCreateTimeSpans ()Ljava/util/List; - public static fun getInstance ()Lio/sentry/android/core/performance/AppStartMetrics; - public fun getSdkInitTimeSpan ()Lio/sentry/android/core/performance/TimeSpan; - public fun isAppLaunchedInForeground ()Z - public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V - public fun onActivityDestroyed (Landroid/app/Activity;)V - public fun onActivityPaused (Landroid/app/Activity;)V - public fun onActivityResumed (Landroid/app/Activity;)V - public fun onActivityStarted (Landroid/app/Activity;)V - public fun onActivityStopped (Landroid/app/Activity;)V - public fun onAppStartSpansSent ()V - public static fun onApplicationCreate (Landroid/app/Application;)V - public static fun onApplicationPostCreate (Landroid/app/Application;)V - public static fun onContentProviderCreate (Landroid/content/ContentProvider;)V - public static fun onContentProviderPostCreate (Landroid/content/ContentProvider;)V - public fun registerLifecycleCallbacks (Landroid/app/Application;)V - public fun setAppLaunchedInForeground (Z)V - public fun setAppStartContinuousProfiler (Lio/sentry/IContinuousProfiler;)V - public fun setAppStartProfiler (Lio/sentry/ITransactionProfiler;)V - public fun setAppStartSamplingDecision (Lio/sentry/TracesSamplingDecision;)V - public fun setAppStartType (Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;)V - public fun setClassLoadedUptimeMs (J)V - public fun shouldSendStartMeasurements ()Z -} - -public final class io/sentry/android/core/performance/AppStartMetrics$AppStartType : java/lang/Enum { - public static final field COLD Lio/sentry/android/core/performance/AppStartMetrics$AppStartType; - public static final field UNKNOWN Lio/sentry/android/core/performance/AppStartMetrics$AppStartType; - public static final field WARM Lio/sentry/android/core/performance/AppStartMetrics$AppStartType; - public static fun valueOf (Ljava/lang/String;)Lio/sentry/android/core/performance/AppStartMetrics$AppStartType; - public static fun values ()[Lio/sentry/android/core/performance/AppStartMetrics$AppStartType; -} - -public class io/sentry/android/core/performance/TimeSpan : java/lang/Comparable { - public fun ()V - public fun compareTo (Lio/sentry/android/core/performance/TimeSpan;)I - public synthetic fun compareTo (Ljava/lang/Object;)I - public fun getDescription ()Ljava/lang/String; - public fun getDurationMs ()J - public fun getProjectedStopTimestamp ()Lio/sentry/SentryDate; - public fun getProjectedStopTimestampMs ()J - public fun getProjectedStopTimestampSecs ()D - public fun getStartTimestamp ()Lio/sentry/SentryDate; - public fun getStartTimestampMs ()J - public fun getStartTimestampSecs ()D - public fun getStartUptimeMs ()J - public fun hasNotStarted ()Z - public fun hasNotStopped ()Z - public fun hasStarted ()Z - public fun hasStopped ()Z - public fun reset ()V - public fun setDescription (Ljava/lang/String;)V - public fun setStartUnixTimeMs (J)V - public fun setStartedAt (J)V - public fun setStoppedAt (J)V - public fun setup (Ljava/lang/String;JJJ)V - public fun start ()V - public fun stop ()V -} - -public class io/sentry/android/core/performance/WindowContentChangedCallback : io/sentry/android/core/internal/gestures/WindowCallbackAdapter { - public fun (Landroid/view/Window$Callback;Ljava/lang/Runnable;)V - public fun onContentChanged ()V -} - -public final class io/sentry/android/core/util/AndroidLazyEvaluator { - public fun (Lio/sentry/android/core/util/AndroidLazyEvaluator$AndroidEvaluator;)V - public fun getValue (Landroid/content/Context;)Ljava/lang/Object; - public fun resetValue ()V - public fun setValue (Ljava/lang/Object;)V -} - -public abstract interface class io/sentry/android/core/util/AndroidLazyEvaluator$AndroidEvaluator { - public abstract fun evaluate (Landroid/content/Context;)Ljava/lang/Object; -} - diff --git a/sentry-android-core/build.gradle.kts b/sentry-android-core/build.gradle.kts deleted file mode 100644 index 8d5f73fbf44..00000000000 --- a/sentry-android-core/build.gradle.kts +++ /dev/null @@ -1,121 +0,0 @@ -import net.ltgt.gradle.errorprone.errorprone -import org.jetbrains.kotlin.config.KotlinCompilerVersion - -plugins { - id("com.android.library") - alias(libs.plugins.kotlin.android) - jacoco - alias(libs.plugins.jacoco.android) - alias(libs.plugins.errorprone) - alias(libs.plugins.gradle.versions) - alias(libs.plugins.protobuf) -} - -android { - compileSdk = libs.versions.compileSdk.get().toInt() - namespace = "io.sentry.android.core" - - defaultConfig { - minSdk = libs.versions.minSdk.get().toInt() - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - - buildConfigField( - "String", - "SENTRY_ANDROID_SDK_NAME", - "\"${Config.Sentry.SENTRY_ANDROID_SDK_NAME}\"", - ) - - // for AGP 4.1 - buildConfigField("String", "VERSION_NAME", "\"${project.version}\"") - } - - buildTypes { - getByName("debug") { consumerProguardFiles("proguard-rules.pro") } - getByName("release") { consumerProguardFiles("proguard-rules.pro") } - } - - kotlin { compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8 } - - testOptions { - animationsDisabled = true - unitTests.apply { - isReturnDefaultValues = true - isIncludeAndroidResources = true - } - } - - lint { - warningsAsErrors = true - checkDependencies = true - - // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks. - checkReleaseBuilds = false - } - - buildFeatures { buildConfig = true } - - // needed because of Kotlin 1.4.x - configurations.all { resolutionStrategy.force(libs.jetbrains.annotations.get()) } - - androidComponents.beforeVariants { - it.enable = !Config.Android.shouldSkipDebugVariant(it.buildType) - } -} - -tasks.withType().configureEach { - options.errorprone { - check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.ERROR) - option("NullAway:AnnotatedPackages", "io.sentry") - } -} - -dependencies { - api(projects.sentry) - compileOnly(libs.jetbrains.annotations) - compileOnly(libs.nopen.annotations) - compileOnly(projects.sentryAndroidFragment) - compileOnly(projects.sentryAndroidTimber) - compileOnly(projects.sentryAndroidReplay) - compileOnly(projects.sentryCompose) - compileOnly(projects.sentryAndroidDistribution) - - // lifecycle processor, session tracking - implementation(libs.androidx.lifecycle.common.java8) - implementation(libs.androidx.lifecycle.process) - implementation(libs.androidx.core) - implementation(libs.protobuf.javalite) - - errorprone(libs.errorprone.core) - errorprone(libs.nopen.checker) - errorprone(libs.nullaway) - - // tests - testImplementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) - testImplementation(libs.roboelectric) - testImplementation(libs.kotlin.test.junit) - testImplementation(libs.androidx.core.ktx) - testImplementation(libs.androidx.test.core) - testImplementation(libs.androidx.test.ext.junit) - testImplementation(libs.androidx.test.runner) - testImplementation(libs.awaitility.kotlin) - testImplementation(libs.mockito.kotlin) - testImplementation(libs.mockito.inline) - testImplementation(projects.sentryTestSupport) - testImplementation(projects.sentrySpotlight) - testImplementation(projects.sentryAndroidFragment) - testImplementation(projects.sentryAndroidTimber) - testImplementation(projects.sentryAndroidReplay) - testImplementation(projects.sentryCompose) - testImplementation(projects.sentryAndroidNdk) - testRuntimeOnly(libs.androidx.compose.ui) - testRuntimeOnly(libs.androidx.fragment.ktx) - testRuntimeOnly(libs.timber) -} - -protobuf { - protoc { artifact = libs.protoc.get().toString() } - generateProtoTasks { - all().forEach { task -> task.builtins { create("java") { option("lite") } } } - } -} diff --git a/sentry-android-core/proguard-rules.pro b/sentry-android-core/proguard-rules.pro deleted file mode 100644 index 25086b4d2b6..00000000000 --- a/sentry-android-core/proguard-rules.pro +++ /dev/null @@ -1,94 +0,0 @@ -##---------------Begin: proguard configuration for android-core ---------- - -##---------------Begin: proguard configuration for androidx.core ---------- --keep class androidx.core.view.GestureDetectorCompat { (...); } --keep class androidx.core.app.FrameMetricsAggregator { (...); } --keep interface androidx.core.view.ScrollingView { *; } -##---------------End: proguard configuration for androidx.core ---------- - -##---------------Begin: proguard configuration for androidx.lifecycle ---------- --keep interface androidx.lifecycle.DefaultLifecycleObserver { *; } --keep class androidx.lifecycle.ProcessLifecycleOwner { (...); } -##---------------End: proguard configuration for androidx.lifecycle ---------- - -# To mitigate the issue on R8 site (https://issuetracker.google.com/issues/235733922) -# which comes through AGP 7.3.0-betaX and 7.4.0-alphaX --keepclassmembers enum io.sentry.** { *; } - -# To filter out io.sentry frames from stacktraces --keeppackagenames io.sentry.** - -# don't warn jetbrains annotations --dontwarn org.jetbrains.annotations.** -# don't warn about missing classes, as we are checking for their presence at runtime --dontwarn io.sentry.android.timber.SentryTimberIntegration --dontwarn io.sentry.android.fragment.FragmentLifecycleIntegration --dontwarn io.sentry.compose.gestures.ComposeGestureTargetLocator --dontwarn io.sentry.compose.viewhierarchy.ComposeViewHierarchyExporter - -# To ensure that stack traces is unambiguous -# https://developer.android.com/studio/build/shrink-code#decode-stack-trace --keepattributes LineNumberTable,SourceFile - -# Keep Classnames for integrations --keepnames class * implements io.sentry.Integration - --dontwarn io.sentry.apollo.SentryApolloInterceptor --keepnames class io.sentry.apollo.SentryApolloInterceptor - --dontwarn io.sentry.apollo3.SentryApollo3HttpInterceptor --keepnames class io.sentry.apollo3.SentryApollo3HttpInterceptor - --dontwarn io.sentry.android.okhttp.SentryOkHttpInterceptor --keepnames class io.sentry.android.okhttp.SentryOkHttpInterceptor - --dontwarn io.sentry.android.navigation.SentryNavigationListener --keepnames class io.sentry.android.navigation.SentryNavigationListener - --keepnames class io.sentry.android.core.ScreenshotEventProcessor --keepnames class io.sentry.android.core.ViewHierarchyEventProcessor - -# Keep any custom option classes like SentryAndroidOptions, as they're loaded via reflection -# Also keep method names, as they're e.g. used by native via JNI calls --keep class * extends io.sentry.SentryOptions { *; } - --keepnames class io.sentry.android.core.ApplicationNotResponding - -# protobuf-java lite -# https://github.com/protocolbuffers/protobuf/blob/5d876c9fec1a6f2feb0750694f803f89312bffff/java/lite.md#r8-rule-to-make-production-app-builds-work --keep class * extends com.google.protobuf.GeneratedMessageLite { *; } - -##---------------End: proguard configuration for android-core ---------- - -##---------------Begin: proguard configuration for sentry-apollo-3 ---------- - -# don't warn about missing classes, as it depends on the sentry-apollo-3 jar dependency. --dontwarn io.sentry.apollo3.SentryApollo3ClientException - -# we don't want this class to be obfuscated, otherwise issue's titles are obfuscated as well. --keep class io.sentry.apollo3.SentryApollo3ClientException { (...); } - -##---------------End: proguard configuration for sentry-apollo-3 ---------- - -##---------------Begin: proguard configuration for sentry-okhttp ---------- - -# we don't want this class to be obfuscated, otherwise issue's titles are obfuscated as well. --keepnames class io.sentry.exception.SentryHttpClientException - -##---------------End: proguard configuration for sentry-okhttp ---------- - -##---------------Begin: proguard configuration for sentry-android-replay ---------- --dontwarn io.sentry.android.replay.ReplayIntegration --dontwarn io.sentry.android.replay.DefaultReplayBreadcrumbConverter --keepnames class io.sentry.android.replay.ReplayIntegration -##---------------End: proguard configuration for sentry-android-replay ---------- - -##---------------Begin: proguard configuration for sentry-android-distribution ---------- --dontwarn io.sentry.android.distribution.DistributionIntegration --keepnames class io.sentry.android.distribution.DistributionIntegration -##---------------End: proguard configuration for sentry-android-distribution ---------- - -##---------------Begin: proguard configuration for sentry-spotlight ---------- --dontwarn io.sentry.spotlight.SpotlightIntegration --keepnames class io.sentry.spotlight.SpotlightIntegration -##---------------End: proguard configuration for sentry-spotlight ---------- diff --git a/sentry-android-core/src/main/AndroidManifest.xml b/sentry-android-core/src/main/AndroidManifest.xml deleted file mode 100644 index a304ee075bb..00000000000 --- a/sentry-android-core/src/main/AndroidManifest.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ANRWatchDog.java b/sentry-android-core/src/main/java/io/sentry/android/core/ANRWatchDog.java deleted file mode 100644 index b726dd0c881..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ANRWatchDog.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Adapted from https://github.com/SalomonBrys/ANR-WatchDog/blob/1969075f75f5980e9000eaffbaa13b0daf282dcb/anr-watchdog/src/main/java/com/github/anrwatchdog/ANRWatchDog.java - * - * The MIT License (MIT) - * - * Copyright (c) 2016 Salomon BRYS - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package io.sentry.android.core; - -import static android.app.ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING; - -import android.app.ActivityManager; -import android.content.Context; -import android.os.Debug; -import android.os.SystemClock; -import io.sentry.ILogger; -import io.sentry.SentryLevel; -import io.sentry.transport.ICurrentDateProvider; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.TestOnly; - -/** A watchdog timer thread that detects when the UI thread has frozen. */ -@SuppressWarnings("UnusedReturnValue") -final class ANRWatchDog extends Thread { - - private final boolean reportInDebug; - private final ANRListener anrListener; - private final MainLooperHandler uiHandler; - private final ICurrentDateProvider timeProvider; - - /** the interval in which we check if there's an ANR, in ms */ - private long pollingIntervalMs; - - private final long timeoutIntervalMillis; - private final @NotNull ILogger logger; - - private volatile long lastKnownActiveUiTimestampMs = 0; - private final AtomicBoolean reported = new AtomicBoolean(false); - - private final @NotNull Context context; - - @SuppressWarnings("UnnecessaryLambda") - private final Runnable ticker; - - ANRWatchDog( - long timeoutIntervalMillis, - boolean reportInDebug, - @NotNull ANRListener listener, - @NotNull ILogger logger, - final @NotNull Context context) { - // avoid method refs on Android due to some issues with older AGP setups - // noinspection Convert2MethodRef - this( - () -> SystemClock.uptimeMillis(), - timeoutIntervalMillis, - 500, - reportInDebug, - listener, - logger, - new MainLooperHandler(), - context); - } - - @TestOnly - ANRWatchDog( - @NotNull final ICurrentDateProvider timeProvider, - long timeoutIntervalMillis, - long pollingIntervalMillis, - boolean reportInDebug, - @NotNull ANRListener listener, - @NotNull ILogger logger, - @NotNull MainLooperHandler uiHandler, - final @NotNull Context context) { - - super("|ANR-WatchDog|"); - - this.timeProvider = timeProvider; - this.timeoutIntervalMillis = timeoutIntervalMillis; - this.pollingIntervalMs = pollingIntervalMillis; - this.reportInDebug = reportInDebug; - this.anrListener = listener; - this.logger = logger; - this.uiHandler = uiHandler; - this.context = context; - this.ticker = - () -> { - lastKnownActiveUiTimestampMs = timeProvider.getCurrentTimeMillis(); - reported.set(false); - }; - - if (timeoutIntervalMillis < (pollingIntervalMs * 2)) { - throw new IllegalArgumentException( - String.format( - "ANRWatchDog: timeoutIntervalMillis has to be at least %d ms", - pollingIntervalMs * 2)); - } - } - - @Override - public void run() { - // right when the watchdog gets started, let's assume there's no ANR - ticker.run(); - - while (!isInterrupted()) { - uiHandler.post(ticker); - - try { - Thread.sleep(pollingIntervalMs); - } catch (InterruptedException e) { - try { - Thread.currentThread().interrupt(); - } catch (SecurityException ignored) { - logger.log( - SentryLevel.WARNING, - "Failed to interrupt due to SecurityException: %s", - e.getMessage()); - return; - } - logger.log(SentryLevel.WARNING, "Interrupted: %s", e.getMessage()); - return; - } - - final long unresponsiveDurationMs = - timeProvider.getCurrentTimeMillis() - lastKnownActiveUiTimestampMs; - - // If the main thread has not handled ticker, it is blocked. ANR. - if (unresponsiveDurationMs > timeoutIntervalMillis) { - if (!reportInDebug && (Debug.isDebuggerConnected() || Debug.waitingForDebugger())) { - logger.log( - SentryLevel.DEBUG, - "An ANR was detected but ignored because the debugger is connected."); - reported.set(true); - continue; - } - - if (isProcessNotResponding() && reported.compareAndSet(false, true)) { - final String message = - "Application Not Responding for at least " + timeoutIntervalMillis + " ms."; - - final ApplicationNotResponding error = - new ApplicationNotResponding(message, uiHandler.getThread()); - anrListener.onAppNotResponding(error); - } - } - } - } - - private boolean isProcessNotResponding() { - // we only raise an ANR event if the process is in ANR state. - // if ActivityManager is not available, we'll still be able to send ANRs - final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - - if (am != null) { - List processesInErrorState = null; - try { - // It can throw RuntimeException or OutOfMemoryError - processesInErrorState = am.getProcessesInErrorState(); - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Error getting ActivityManager#getProcessesInErrorState.", e); - } - // if list is null, there's no process in ANR state. - if (processesInErrorState != null) { - for (ActivityManager.ProcessErrorStateInfo item : processesInErrorState) { - if (item.condition == NOT_RESPONDING) { - return true; - } - } - } - // when list is empty, or there's no element NOT_RESPONDING, we can assume the app is not - // blocked - return false; - } - return true; - } - - public interface ANRListener { - /** - * Called when an ANR is detected. - * - * @param error The error describing the ANR. - */ - void onAppNotResponding(@NotNull ApplicationNotResponding error); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java deleted file mode 100644 index 3cd7709c4be..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java +++ /dev/null @@ -1,142 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.TypeCheckHint.ANDROID_ACTIVITY; -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import android.app.Activity; -import android.app.Application; -import android.os.Bundle; -import io.sentry.Breadcrumb; -import io.sentry.Hint; -import io.sentry.IScopes; -import io.sentry.ISentryLifecycleToken; -import io.sentry.Integration; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.Objects; -import java.io.Closeable; -import java.io.IOException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** Automatically adds breadcrumbs for Activity Lifecycle Events. */ -public final class ActivityBreadcrumbsIntegration - implements Integration, Closeable, Application.ActivityLifecycleCallbacks { - - private final @NotNull Application application; - private @Nullable IScopes scopes; - private boolean enabled; - private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - - // TODO check if locking is even required at all for lifecycle methods - public ActivityBreadcrumbsIntegration(final @NotNull Application application) { - this.application = Objects.requireNonNull(application, "Application is required"); - } - - @Override - public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - final SentryAndroidOptions androidOptions = - Objects.requireNonNull( - (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, - "SentryAndroidOptions is required"); - - this.scopes = Objects.requireNonNull(scopes, "Scopes are required"); - this.enabled = androidOptions.isEnableActivityLifecycleBreadcrumbs(); - options - .getLogger() - .log(SentryLevel.DEBUG, "ActivityBreadcrumbsIntegration enabled: %s", enabled); - - if (enabled) { - application.registerActivityLifecycleCallbacks(this); - options.getLogger().log(SentryLevel.DEBUG, "ActivityBreadcrumbIntegration installed."); - addIntegrationToSdkVersion("ActivityBreadcrumbs"); - } - } - - @Override - public void close() throws IOException { - if (enabled) { - application.unregisterActivityLifecycleCallbacks(this); - if (scopes != null) { - scopes - .getOptions() - .getLogger() - .log(SentryLevel.DEBUG, "ActivityBreadcrumbsIntegration removed."); - } - } - } - - @Override - public void onActivityCreated( - final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - addBreadcrumb(activity, "created"); - } - } - - @Override - public void onActivityStarted(final @NotNull Activity activity) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - addBreadcrumb(activity, "started"); - } - } - - @Override - public void onActivityResumed(final @NotNull Activity activity) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - addBreadcrumb(activity, "resumed"); - } - } - - @Override - public void onActivityPaused(final @NotNull Activity activity) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - addBreadcrumb(activity, "paused"); - } - } - - @Override - public void onActivityStopped(final @NotNull Activity activity) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - addBreadcrumb(activity, "stopped"); - } - } - - @Override - public void onActivitySaveInstanceState( - final @NotNull Activity activity, final @NotNull Bundle outState) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - addBreadcrumb(activity, "saveInstanceState"); - } - } - - @Override - public void onActivityDestroyed(final @NotNull Activity activity) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - addBreadcrumb(activity, "destroyed"); - } - } - - private void addBreadcrumb(final @NotNull Activity activity, final @NotNull String state) { - if (scopes == null) { - return; - } - - final Breadcrumb breadcrumb = new Breadcrumb(); - breadcrumb.setType("navigation"); - breadcrumb.setData("state", state); - breadcrumb.setData("screen", getActivityName(activity)); - breadcrumb.setCategory("ui.lifecycle"); - breadcrumb.setLevel(SentryLevel.INFO); - - final Hint hint = new Hint(); - hint.set(ANDROID_ACTIVITY, activity); - - scopes.addBreadcrumb(breadcrumb, hint); - } - - private @NotNull String getActivityName(final @NotNull Activity activity) { - return activity.getClass().getSimpleName(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java deleted file mode 100644 index 3895819fc94..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java +++ /dev/null @@ -1,263 +0,0 @@ -package io.sentry.android.core; - -import android.app.Activity; -import android.util.SparseIntArray; -import androidx.core.app.FrameMetricsAggregator; -import io.sentry.ISentryLifecycleToken; -import io.sentry.MeasurementUnit; -import io.sentry.SentryLevel; -import io.sentry.android.core.internal.util.AndroidThreadChecker; -import io.sentry.protocol.MeasurementValue; -import io.sentry.protocol.SentryId; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.LazyEvaluator; -import java.util.HashMap; -import java.util.Map; -import java.util.WeakHashMap; -import java.util.concurrent.ConcurrentHashMap; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; -import org.jetbrains.annotations.VisibleForTesting; - -/** - * A class that tracks slow and frozen frames using the FrameMetricsAggregator class from - * androidx.core package. It also checks if the FrameMetricsAggregator class is available at - * runtime. - * - *

If performance-v2 is enabled, frame metrics are recorded using {@link - * io.sentry.android.core.internal.util.SentryFrameMetricsCollector} via {@link - * SpanFrameMetricsCollector} instead and this implementation will no-op. - */ -public final class ActivityFramesTracker { - - private @NotNull LazyEvaluator frameMetricsAggregator; - private @NotNull final SentryAndroidOptions options; - - private final @NotNull Map> - activityMeasurements = new ConcurrentHashMap<>(); - private final @NotNull Map frameCountAtStartSnapshots = - new WeakHashMap<>(); - - private final @NotNull MainLooperHandler handler; - protected @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - - private final @NotNull LazyEvaluator androidXAvailable; - - public ActivityFramesTracker( - final @NotNull io.sentry.util.LoadClass loadClass, - final @NotNull SentryAndroidOptions options, - final @NotNull MainLooperHandler handler) { - - androidXAvailable = - loadClass.isClassAvailableLazy( - "androidx.core.app.FrameMetricsAggregator", options.getLogger()); - frameMetricsAggregator = new LazyEvaluator<>(() -> new FrameMetricsAggregator()); - - this.options = options; - this.handler = handler; - } - - public ActivityFramesTracker( - final @NotNull io.sentry.util.LoadClass loadClass, - final @NotNull SentryAndroidOptions options) { - this(loadClass, options, new MainLooperHandler()); - } - - @TestOnly - ActivityFramesTracker( - final @NotNull io.sentry.util.LoadClass loadClass, - final @NotNull SentryAndroidOptions options, - final @NotNull MainLooperHandler handler, - final @NotNull FrameMetricsAggregator frameMetricsAggregator) { - - this(loadClass, options, handler); - this.frameMetricsAggregator = new LazyEvaluator<>(() -> frameMetricsAggregator); - } - - @VisibleForTesting - public boolean isFrameMetricsAggregatorAvailable() { - return androidXAvailable.getValue() - && options.isEnableFramesTracking() - && !options.isEnablePerformanceV2(); - } - - @SuppressWarnings("NullAway") - public void addActivity(final @NotNull Activity activity) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (!isFrameMetricsAggregatorAvailable()) { - return; - } - - runSafelyOnUiThread( - () -> frameMetricsAggregator.getValue().add(activity), "FrameMetricsAggregator.add"); - snapshotFrameCountsAtStart(activity); - } - } - - private void snapshotFrameCountsAtStart(final @NotNull Activity activity) { - FrameCounts frameCounts = calculateCurrentFrameCounts(); - if (frameCounts != null) { - frameCountAtStartSnapshots.put(activity, frameCounts); - } - } - - private @Nullable FrameCounts calculateCurrentFrameCounts() { - if (!isFrameMetricsAggregatorAvailable()) { - return null; - } - - if (!androidXAvailable.getValue()) { - return null; - } - - final @Nullable SparseIntArray[] framesRates = frameMetricsAggregator.getValue().getMetrics(); - - int totalFrames = 0; - int slowFrames = 0; - int frozenFrames = 0; - - if (framesRates != null && framesRates.length > 0) { - final SparseIntArray totalIndexArray = framesRates[FrameMetricsAggregator.TOTAL_INDEX]; - if (totalIndexArray != null) { - for (int i = 0; i < totalIndexArray.size(); i++) { - int frameTime = totalIndexArray.keyAt(i); - int numFrames = totalIndexArray.valueAt(i); - totalFrames += numFrames; - // hard coded values, its also in the official android docs and frame metrics API - if (frameTime > 700) { - // frozen frames, threshold is 700ms - frozenFrames += numFrames; - } else if (frameTime > 16) { - // slow frames, above 16ms, 60 frames/second - slowFrames += numFrames; - } - } - } - } - - return new FrameCounts(totalFrames, slowFrames, frozenFrames); - } - - @SuppressWarnings("NullAway") - public void setMetrics(final @NotNull Activity activity, final @NotNull SentryId transactionId) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (!isFrameMetricsAggregatorAvailable()) { - return; - } - - // NOTE: removing an activity does not reset the frame counts, only reset() does - // throws IllegalArgumentException when attempting to remove - // OnFrameMetricsAvailableListener - // that was never added. - // there's no contains method. - // throws NullPointerException when attempting to remove - // OnFrameMetricsAvailableListener and - // there was no - // Observers, See - // https://android.googlesource.com/platform/frameworks/base/+/140ff5ea8e2d99edc3fbe63a43239e459334c76b - runSafelyOnUiThread(() -> frameMetricsAggregator.getValue().remove(activity), null); - - final @Nullable FrameCounts frameCounts = diffFrameCountsAtEnd(activity); - - if (frameCounts == null - || (frameCounts.totalFrames == 0 - && frameCounts.slowFrames == 0 - && frameCounts.frozenFrames == 0)) { - return; - } - - final MeasurementValue tfValues = - new MeasurementValue(frameCounts.totalFrames, MeasurementUnit.NONE); - final MeasurementValue sfValues = - new MeasurementValue(frameCounts.slowFrames, MeasurementUnit.NONE); - final MeasurementValue ffValues = - new MeasurementValue(frameCounts.frozenFrames, MeasurementUnit.NONE); - final Map measurements = new HashMap<>(); - measurements.put(MeasurementValue.KEY_FRAMES_TOTAL, tfValues); - measurements.put(MeasurementValue.KEY_FRAMES_SLOW, sfValues); - measurements.put(MeasurementValue.KEY_FRAMES_FROZEN, ffValues); - - activityMeasurements.put(transactionId, measurements); - } - } - - private @Nullable FrameCounts diffFrameCountsAtEnd(final @NotNull Activity activity) { - @Nullable final FrameCounts frameCountsAtStart = frameCountAtStartSnapshots.remove(activity); - if (frameCountsAtStart == null) { - return null; - } - - @Nullable final FrameCounts frameCountsAtEnd = calculateCurrentFrameCounts(); - if (frameCountsAtEnd == null) { - return null; - } - - final int diffTotalFrames = frameCountsAtEnd.totalFrames - frameCountsAtStart.totalFrames; - final int diffSlowFrames = frameCountsAtEnd.slowFrames - frameCountsAtStart.slowFrames; - final int diffFrozenFrames = frameCountsAtEnd.frozenFrames - frameCountsAtStart.frozenFrames; - - return new FrameCounts(diffTotalFrames, diffSlowFrames, diffFrozenFrames); - } - - @Nullable - public Map takeMetrics(final @NotNull SentryId transactionId) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (!isFrameMetricsAggregatorAvailable()) { - return null; - } - - final Map stringMeasurementValueMap = - activityMeasurements.get(transactionId); - activityMeasurements.remove(transactionId); - return stringMeasurementValueMap; - } - } - - @SuppressWarnings("NullAway") - public void stop() { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (isFrameMetricsAggregatorAvailable()) { - runSafelyOnUiThread( - () -> frameMetricsAggregator.getValue().stop(), "FrameMetricsAggregator.stop"); - frameMetricsAggregator.getValue().reset(); - } - activityMeasurements.clear(); - } - } - - private void runSafelyOnUiThread(final Runnable runnable, final String tag) { - try { - if (AndroidThreadChecker.getInstance().isMainThread()) { - runnable.run(); - } else { - handler.post( - () -> { - try { - runnable.run(); - } catch (Throwable ignored) { - if (tag != null) { - options.getLogger().log(SentryLevel.WARNING, "Failed to execute " + tag); - } - } - }); - } - } catch (Throwable ignored) { - if (tag != null) { - options.getLogger().log(SentryLevel.WARNING, "Failed to execute " + tag); - } - } - } - - private static final class FrameCounts { - private final int totalFrames; - private final int slowFrames; - private final int frozenFrames; - - private FrameCounts(final int totalFrames, final int slowFrames, final int frozenFrames) { - this.totalFrames = totalFrames; - this.slowFrames = slowFrames; - this.frozenFrames = frozenFrames; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java deleted file mode 100644 index 9d748e5a27a..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java +++ /dev/null @@ -1,799 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.MeasurementUnit.Duration.MILLISECOND; -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import android.app.Activity; -import android.app.Application; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import io.sentry.FullyDisplayedReporter; -import io.sentry.IScope; -import io.sentry.IScopes; -import io.sentry.ISentryLifecycleToken; -import io.sentry.ISpan; -import io.sentry.ITransaction; -import io.sentry.Instrumenter; -import io.sentry.Integration; -import io.sentry.NoOpTransaction; -import io.sentry.SentryDate; -import io.sentry.SentryLevel; -import io.sentry.SentryNanotimeDate; -import io.sentry.SentryOptions; -import io.sentry.SpanOptions; -import io.sentry.SpanStatus; -import io.sentry.TracesSamplingDecision; -import io.sentry.TransactionContext; -import io.sentry.TransactionOptions; -import io.sentry.android.core.internal.util.ClassUtil; -import io.sentry.android.core.internal.util.FirstDrawDoneListener; -import io.sentry.android.core.performance.ActivityLifecycleSpanHelper; -import io.sentry.android.core.performance.AppStartMetrics; -import io.sentry.android.core.performance.TimeSpan; -import io.sentry.protocol.MeasurementValue; -import io.sentry.protocol.TransactionNameSource; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.Objects; -import io.sentry.util.TracingUtils; -import java.io.Closeable; -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.Date; -import java.util.Map; -import java.util.WeakHashMap; -import java.util.concurrent.Future; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.TimeUnit; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; -import org.jetbrains.annotations.VisibleForTesting; - -public final class ActivityLifecycleIntegration - implements Integration, Closeable, Application.ActivityLifecycleCallbacks { - - static final String UI_LOAD_OP = "ui.load"; - static final String APP_START_WARM = "app.start.warm"; - static final String APP_START_COLD = "app.start.cold"; - static final String TTID_OP = "ui.load.initial_display"; - static final String TTFD_OP = "ui.load.full_display"; - static final long TTFD_TIMEOUT_MILLIS = 25000; - private static final String TRACE_ORIGIN = "auto.ui.activity"; - - private final @NotNull Application application; - private final @NotNull BuildInfoProvider buildInfoProvider; - private @Nullable IScopes scopes; - private @Nullable SentryAndroidOptions options; - - private boolean performanceEnabled = false; - - private boolean timeToFullDisplaySpanEnabled = false; - - private boolean isAllActivityCallbacksAvailable; - - private boolean firstActivityCreated = false; - - private @Nullable FullyDisplayedReporter fullyDisplayedReporter = null; - private @Nullable ISpan appStartSpan; - private final @NotNull WeakHashMap ttidSpanMap = new WeakHashMap<>(); - private final @NotNull WeakHashMap ttfdSpanMap = new WeakHashMap<>(); - private final @NotNull WeakHashMap activitySpanHelpers = - new WeakHashMap<>(); - private @NotNull SentryDate lastPausedTime = new SentryNanotimeDate(new Date(0), 0); - private @Nullable Future ttfdAutoCloseFuture = null; - - // WeakHashMap isn't thread safe but ActivityLifecycleCallbacks is only called from the - // main-thread - private final @NotNull WeakHashMap activitiesWithOngoingTransactions = - new WeakHashMap<>(); - - private final @NotNull ActivityFramesTracker activityFramesTracker; - private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - private boolean fullyDisplayedCalled = false; - private final @NotNull AutoClosableReentrantLock fullyDisplayedLock = - new AutoClosableReentrantLock(); - - public ActivityLifecycleIntegration( - final @NotNull Application application, - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull ActivityFramesTracker activityFramesTracker) { - this.application = Objects.requireNonNull(application, "Application is required"); - this.buildInfoProvider = - Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required"); - this.activityFramesTracker = - Objects.requireNonNull(activityFramesTracker, "ActivityFramesTracker is required"); - - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.Q) { - isAllActivityCallbacksAvailable = true; - } - } - - @Override - public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - this.options = - Objects.requireNonNull( - (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, - "SentryAndroidOptions is required"); - - this.scopes = Objects.requireNonNull(scopes, "Scopes are required"); - - performanceEnabled = isPerformanceEnabled(this.options); - fullyDisplayedReporter = this.options.getFullyDisplayedReporter(); - timeToFullDisplaySpanEnabled = this.options.isEnableTimeToFullDisplayTracing(); - - application.registerActivityLifecycleCallbacks(this); - this.options.getLogger().log(SentryLevel.DEBUG, "ActivityLifecycleIntegration installed."); - addIntegrationToSdkVersion("ActivityLifecycle"); - } - - private boolean isPerformanceEnabled(final @NotNull SentryAndroidOptions options) { - return options.isTracingEnabled() && options.isEnableAutoActivityLifecycleTracing(); - } - - @Override - public void close() throws IOException { - application.unregisterActivityLifecycleCallbacks(this); - - if (options != null) { - options.getLogger().log(SentryLevel.DEBUG, "ActivityLifecycleIntegration removed."); - } - - activityFramesTracker.stop(); - } - - private @NotNull String getActivityName(final @NotNull Activity activity) { - return activity.getClass().getSimpleName(); - } - - private void stopPreviousTransactions() { - for (final Map.Entry entry : - activitiesWithOngoingTransactions.entrySet()) { - final ITransaction transaction = entry.getValue(); - final ISpan ttidSpan = ttidSpanMap.get(entry.getKey()); - final ISpan ttfdSpan = ttfdSpanMap.get(entry.getKey()); - finishTransaction(transaction, ttidSpan, ttfdSpan); - } - } - - private void startTracing(final @NotNull Activity activity) { - WeakReference weakActivity = new WeakReference<>(activity); - if (scopes != null && !isRunningTransactionOrTrace(activity)) { - if (!performanceEnabled) { - activitiesWithOngoingTransactions.put(activity, NoOpTransaction.getInstance()); - if (options.isEnableAutoTraceIdGeneration()) { - TracingUtils.startNewTrace(scopes); - } - } else { - // as we allow a single transaction running on the bound Scope, we finish the previous ones - stopPreviousTransactions(); - - final String activityName = getActivityName(activity); - - final @Nullable SentryDate appStartTime; - final @Nullable Boolean coldStart; - final @NotNull TimeSpan appStartTimeSpan = - AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options); - - // we only track app start for processes that will show an Activity (full launch). - // Here we check the process importance which will tell us that. - final boolean foregroundImportance = ContextUtils.isForegroundImportance(); - if (foregroundImportance && appStartTimeSpan.hasStarted()) { - appStartTime = appStartTimeSpan.getStartTimestamp(); - coldStart = - AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD; - } else { - appStartTime = null; - coldStart = null; - } - - final TransactionOptions transactionOptions = new TransactionOptions(); - - // Set deadline timeout based on configured option - final long deadlineTimeoutMillis = options.getDeadlineTimeout(); - // No deadline when zero or negative value is set - transactionOptions.setDeadlineTimeout( - deadlineTimeoutMillis <= 0 ? null : deadlineTimeoutMillis); - - if (options.isEnableActivityLifecycleTracingAutoFinish()) { - transactionOptions.setIdleTimeout(options.getIdleTimeout()); - transactionOptions.setTrimEnd(true); - } - transactionOptions.setWaitForChildren(true); - transactionOptions.setTransactionFinishedCallback( - (finishingTransaction) -> { - @Nullable Activity unwrappedActivity = weakActivity.get(); - if (unwrappedActivity != null) { - activityFramesTracker.setMetrics( - unwrappedActivity, finishingTransaction.getEventId()); - } else { - if (options != null) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "Unable to track activity frames as the Activity %s has been destroyed.", - activityName); - } - } - }); - - // This will be the start timestamp of the transaction, as well as the ttid/ttfd spans - final @NotNull SentryDate ttidStartTime; - final @Nullable TracesSamplingDecision appStartSamplingDecision; - - if (!(firstActivityCreated || appStartTime == null || coldStart == null)) { - // The first activity ttid/ttfd spans should start at the app start time - ttidStartTime = appStartTime; - // The app start transaction inherits the sampling decision from the app start profiling, - // then clears it - appStartSamplingDecision = AppStartMetrics.getInstance().getAppStartSamplingDecision(); - AppStartMetrics.getInstance().setAppStartSamplingDecision(null); - } else { - // The ttid/ttfd spans should start when the previous activity called its onPause method - ttidStartTime = lastPausedTime; - appStartSamplingDecision = null; - } - transactionOptions.setStartTimestamp(ttidStartTime); - transactionOptions.setAppStartTransaction(appStartSamplingDecision != null); - setSpanOrigin(transactionOptions); - - // we can only bind to the scope if there's no running transaction - ITransaction transaction = - scopes.startTransaction( - new TransactionContext( - activityName, - TransactionNameSource.COMPONENT, - UI_LOAD_OP, - appStartSamplingDecision), - transactionOptions); - - final SpanOptions spanOptions = new SpanOptions(); - setSpanOrigin(spanOptions); - - // in case appStartTime isn't available, we don't create a span for it. - if (!(firstActivityCreated || appStartTime == null || coldStart == null)) { - // start specific span for app start - appStartSpan = - transaction.startChild( - getAppStartOp(coldStart), - getAppStartDesc(coldStart), - appStartTime, - Instrumenter.SENTRY, - spanOptions); - - // in case there's already an end time (e.g. due to deferred SDK init) - // we can finish the app-start span - finishAppStartSpan(); - } - final @NotNull ISpan ttidSpan = - transaction.startChild( - TTID_OP, - getTtidDesc(activityName), - ttidStartTime, - Instrumenter.SENTRY, - spanOptions); - ttidSpanMap.put(activity, ttidSpan); - - if (timeToFullDisplaySpanEnabled && fullyDisplayedReporter != null && options != null) { - final @NotNull ISpan ttfdSpan = - transaction.startChild( - TTFD_OP, - getTtfdDesc(activityName), - ttidStartTime, - Instrumenter.SENTRY, - spanOptions); - try { - ttfdSpanMap.put(activity, ttfdSpan); - ttfdAutoCloseFuture = - options - .getExecutorService() - .schedule( - () -> finishExceededTtfdSpan(ttfdSpan, ttidSpan), TTFD_TIMEOUT_MILLIS); - } catch (RejectedExecutionException e) { - options - .getLogger() - .log( - SentryLevel.ERROR, - "Failed to call the executor. Time to full display span will not be finished automatically. Did you call Sentry.close()?", - e); - } - } - - // lets bind to the scope so other integrations can pick it up - scopes.configureScope( - scope -> { - applyScope(scope, transaction); - }); - - activitiesWithOngoingTransactions.put(activity, transaction); - } - } - } - - private void setSpanOrigin(final @NotNull SpanOptions spanOptions) { - spanOptions.setOrigin(TRACE_ORIGIN); - } - - @VisibleForTesting - void applyScope(final @NotNull IScope scope, final @NotNull ITransaction transaction) { - scope.withTransaction( - scopeTransaction -> { - // we'd not like to overwrite existent transactions bound to the Scope - // manually. - if (scopeTransaction == null) { - scope.setTransaction(transaction); - } else if (options != null) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Transaction '%s' won't be bound to the Scope since there's one already in there.", - transaction.getName()); - } - }); - } - - @VisibleForTesting - void clearScope(final @NotNull IScope scope, final @NotNull ITransaction transaction) { - scope.withTransaction( - scopeTransaction -> { - if (scopeTransaction == transaction) { - scope.clearTransaction(); - } - }); - } - - private boolean isRunningTransactionOrTrace(final @NotNull Activity activity) { - return activitiesWithOngoingTransactions.containsKey(activity); - } - - private void stopTracing(final @NotNull Activity activity, final boolean shouldFinishTracing) { - if (performanceEnabled && shouldFinishTracing) { - final ITransaction transaction = activitiesWithOngoingTransactions.get(activity); - finishTransaction(transaction, null, null); - } - } - - private void finishTransaction( - final @Nullable ITransaction transaction, - final @Nullable ISpan ttidSpan, - final @Nullable ISpan ttfdSpan) { - if (transaction != null) { - // if io.sentry.traces.activity.auto-finish.enable is disabled, transaction may be already - // finished manually when this method is called. - if (transaction.isFinished()) { - return; - } - - // in case the ttidSpan isn't completed yet, we finish it as cancelled to avoid memory leak - finishSpan(ttidSpan, SpanStatus.DEADLINE_EXCEEDED); - finishExceededTtfdSpan(ttfdSpan, ttidSpan); - cancelTtfdAutoClose(); - - SpanStatus status = transaction.getStatus(); - // status might be set by other integrations, let's not overwrite it - if (status == null) { - status = SpanStatus.OK; - } - transaction.finish(status); - if (scopes != null) { - // make sure to remove the transaction from scope, as it may contain running children, - // therefore `finish` method will not remove it from scope - scopes.configureScope( - scope -> { - clearScope(scope, transaction); - }); - } - } - } - - @Override - public void onActivityPreCreated( - final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) { - final ActivityLifecycleSpanHelper helper = - new ActivityLifecycleSpanHelper(activity.getClass().getName()); - activitySpanHelpers.put(activity, helper); - // The very first activity start timestamp cannot be set to the class instantiation time, as it - // may happen before an activity is started (service, broadcast receiver, etc). So we set it - // here. - if (firstActivityCreated) { - return; - } - lastPausedTime = - scopes != null - ? scopes.getOptions().getDateProvider().now() - : AndroidDateUtils.getCurrentSentryDateTime(); - helper.setOnCreateStartTimestamp(lastPausedTime); - } - - @Override - public void onActivityCreated( - final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) { - if (!isAllActivityCallbacksAvailable) { - onActivityPreCreated(activity, savedInstanceState); - } - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (scopes != null && options != null && options.isEnableScreenTracking()) { - final @Nullable String activityClassName = ClassUtil.getClassName(activity); - scopes.configureScope(scope -> scope.setScreen(activityClassName)); - } - startTracing(activity); - final @Nullable ISpan ttidSpan = ttidSpanMap.get(activity); - final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity); - - firstActivityCreated = true; - - if (performanceEnabled - && ttidSpan != null - && ttfdSpan != null - && fullyDisplayedReporter != null) { - fullyDisplayedReporter.registerFullyDrawnListener( - () -> onFullFrameDrawn(ttidSpan, ttfdSpan)); - } - } - } - - @Override - public void onActivityPostCreated( - final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) { - final ActivityLifecycleSpanHelper helper = activitySpanHelpers.get(activity); - if (helper != null) { - helper.createAndStopOnCreateSpan( - appStartSpan != null ? appStartSpan : activitiesWithOngoingTransactions.get(activity)); - } - } - - @Override - public void onActivityPreStarted(final @NotNull Activity activity) { - final ActivityLifecycleSpanHelper helper = activitySpanHelpers.get(activity); - if (helper != null) { - helper.setOnStartStartTimestamp( - options != null - ? options.getDateProvider().now() - : AndroidDateUtils.getCurrentSentryDateTime()); - } - } - - @Override - public void onActivityStarted(final @NotNull Activity activity) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (!isAllActivityCallbacksAvailable) { - onActivityPostCreated(activity, null); - onActivityPreStarted(activity); - } - if (performanceEnabled) { - // The docs on the screen rendering performance tracing - // (https://firebase.google.com/docs/perf-mon/screen-traces?platform=android#definition), - // state that the tracing starts for every Activity class when the app calls - // .onActivityStarted. - // Adding an Activity in onActivityCreated leads to Window.FEATURE_NO_TITLE not - // working. Moving this to onActivityStarted fixes the problem. - activityFramesTracker.addActivity(activity); - } - } - } - - @Override - public void onActivityPostStarted(final @NotNull Activity activity) { - final ActivityLifecycleSpanHelper helper = activitySpanHelpers.get(activity); - if (helper != null) { - helper.createAndStopOnStartSpan( - appStartSpan != null ? appStartSpan : activitiesWithOngoingTransactions.get(activity)); - // Needed to handle hybrid SDKs - helper.saveSpanToAppStartMetrics(); - } - } - - @Override - public void onActivityResumed(final @NotNull Activity activity) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (!isAllActivityCallbacksAvailable) { - onActivityPostStarted(activity); - } - if (performanceEnabled) { - - final @Nullable ISpan ttidSpan = ttidSpanMap.get(activity); - final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity); - if (activity.getWindow() != null) { - FirstDrawDoneListener.registerForNextDraw( - activity, () -> onFirstFrameDrawn(ttfdSpan, ttidSpan), buildInfoProvider); - } else { - // Posting a task to the main thread's handler will make it executed after it finished - // its current job. That is, right after the activity draws the layout. - new Handler(Looper.getMainLooper()).post(() -> onFirstFrameDrawn(ttfdSpan, ttidSpan)); - } - } - } - } - - @Override - public void onActivityPostResumed(@NotNull Activity activity) { - // empty override, required to avoid a api-level breaking super.onActivityPostResumed() calls - } - - @Override - public void onActivityPrePaused(@NotNull Activity activity) { - // only executed if API >= 29 otherwise it happens on onActivityPaused - // as the SDK may gets (re-)initialized mid activity lifecycle, ensure we set the flag here as - // well - // this ensures any newly launched activity will not use the app start timestamp as txn start - firstActivityCreated = true; - lastPausedTime = - scopes != null - ? scopes.getOptions().getDateProvider().now() - : AndroidDateUtils.getCurrentSentryDateTime(); - } - - @Override - public void onActivityPaused(final @NotNull Activity activity) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - // only executed if API < 29 otherwise it happens on onActivityPrePaused - if (!isAllActivityCallbacksAvailable) { - onActivityPrePaused(activity); - } - } - } - - @Override - public void onActivityStopped(final @NotNull Activity activity) { - // no-op (acquire lock if this no longer is no-op) - } - - @Override - public void onActivitySaveInstanceState( - final @NotNull Activity activity, final @NotNull Bundle outState) { - // no-op (acquire lock if this no longer is no-op) - } - - @Override - public void onActivityDestroyed(final @NotNull Activity activity) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - final ActivityLifecycleSpanHelper helper = activitySpanHelpers.remove(activity); - if (helper != null) { - helper.clear(); - } - if (performanceEnabled) { - - // in case the appStartSpan isn't completed yet, we finish it as cancelled to avoid - // memory leak - finishSpan(appStartSpan, SpanStatus.CANCELLED); - - // we finish the ttidSpan as cancelled in case it isn't completed yet - final ISpan ttidSpan = ttidSpanMap.get(activity); - final ISpan ttfdSpan = ttfdSpanMap.get(activity); - finishSpan(ttidSpan, SpanStatus.DEADLINE_EXCEEDED); - - // we finish the ttfdSpan as deadline_exceeded in case it isn't completed yet - finishExceededTtfdSpan(ttfdSpan, ttidSpan); - cancelTtfdAutoClose(); - - // in case people opt-out enableActivityLifecycleTracingAutoFinish and forgot to finish it, - // we make sure to finish it when the activity gets destroyed. - stopTracing(activity, true); - - // set it to null in case its been just finished as cancelled - appStartSpan = null; - ttidSpanMap.remove(activity); - ttfdSpanMap.remove(activity); - } - - // clear it up, so we don't start again for the same activity if the activity is in the - // activity stack still. - // if the activity is opened again and not in memory, transactions will be created normally. - activitiesWithOngoingTransactions.remove(activity); - - if (activitiesWithOngoingTransactions.isEmpty() && !activity.isChangingConfigurations()) { - clear(); - } - } - } - - private void clear() { - firstActivityCreated = false; - lastPausedTime = new SentryNanotimeDate(new Date(0), 0); - activitySpanHelpers.clear(); - } - - private void finishSpan(final @Nullable ISpan span) { - if (span != null && !span.isFinished()) { - span.finish(); - } - } - - private void finishSpan(final @Nullable ISpan span, final @NotNull SentryDate endTimestamp) { - finishSpan(span, endTimestamp, null); - } - - private void finishSpan( - final @Nullable ISpan span, - final @NotNull SentryDate endTimestamp, - final @Nullable SpanStatus spanStatus) { - if (span != null && !span.isFinished()) { - final @NotNull SpanStatus status = - spanStatus != null - ? spanStatus - : span.getStatus() != null ? span.getStatus() : SpanStatus.OK; - span.finish(status, endTimestamp); - } - } - - private void finishSpan(final @Nullable ISpan span, final @NotNull SpanStatus status) { - if (span != null && !span.isFinished()) { - span.finish(status); - } - } - - private void cancelTtfdAutoClose() { - if (ttfdAutoCloseFuture != null) { - ttfdAutoCloseFuture.cancel(false); - ttfdAutoCloseFuture = null; - } - } - - private void onFirstFrameDrawn(final @Nullable ISpan ttfdSpan, final @Nullable ISpan ttidSpan) { - // app start span - final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance(); - final @NotNull TimeSpan appStartTimeSpan = appStartMetrics.getAppStartTimeSpan(); - final @NotNull TimeSpan sdkInitTimeSpan = appStartMetrics.getSdkInitTimeSpan(); - - // and we need to set the end time of the app start here, after the first frame is drawn. - if (appStartTimeSpan.hasStarted() && appStartTimeSpan.hasNotStopped()) { - appStartTimeSpan.stop(); - } - if (sdkInitTimeSpan.hasStarted() && sdkInitTimeSpan.hasNotStopped()) { - sdkInitTimeSpan.stop(); - } - finishAppStartSpan(); - - // Sentry.reportFullyDisplayed can be run in any thread, so we have to ensure synchronization - // with first frame drawn - try (final @NotNull ISentryLifecycleToken ignored = fullyDisplayedLock.acquire()) { - if (options != null && ttidSpan != null) { - final SentryDate endDate = options.getDateProvider().now(); - final long durationNanos = endDate.diff(ttidSpan.getStartDate()); - final long durationMillis = TimeUnit.NANOSECONDS.toMillis(durationNanos); - ttidSpan.setMeasurement( - MeasurementValue.KEY_TIME_TO_INITIAL_DISPLAY, durationMillis, MILLISECOND); - // If Sentry.reportFullyDisplayed was called before the first frame is drawn, we finish - // the ttfd now - if (ttfdSpan != null && fullyDisplayedCalled) { - fullyDisplayedCalled = false; - ttidSpan.setMeasurement( - MeasurementValue.KEY_TIME_TO_FULL_DISPLAY, durationMillis, MILLISECOND); - ttfdSpan.setMeasurement( - MeasurementValue.KEY_TIME_TO_FULL_DISPLAY, durationMillis, MILLISECOND); - finishSpan(ttfdSpan, endDate); - } - - finishSpan(ttidSpan, endDate); - } else { - finishSpan(ttidSpan); - if (fullyDisplayedCalled) { - finishSpan(ttfdSpan); - } - } - } - } - - private void onFullFrameDrawn(final @NotNull ISpan ttidSpan, final @NotNull ISpan ttfdSpan) { - cancelTtfdAutoClose(); - // Sentry.reportFullyDisplayed can be run in any thread, so we have to ensure synchronization - // with first frame drawn - try (final @NotNull ISentryLifecycleToken ignored = fullyDisplayedLock.acquire()) { - // If the TTID span didn't finish, it means the first frame was not drawn yet, which means - // Sentry.reportFullyDisplayed was called too early. We set a flag, so that whenever the TTID - // will finish, we will finish the TTFD span as well. - if (!ttidSpan.isFinished()) { - fullyDisplayedCalled = true; - return; - } - if (options != null) { - final SentryDate endDate = options.getDateProvider().now(); - final long durationNanos = endDate.diff(ttfdSpan.getStartDate()); - final long durationMillis = TimeUnit.NANOSECONDS.toMillis(durationNanos); - ttfdSpan.setMeasurement( - MeasurementValue.KEY_TIME_TO_FULL_DISPLAY, durationMillis, MILLISECOND); - finishSpan(ttfdSpan, endDate); - } else { - finishSpan(ttfdSpan); - } - } - } - - private void finishExceededTtfdSpan( - final @Nullable ISpan ttfdSpan, final @Nullable ISpan ttidSpan) { - if (ttfdSpan == null || ttfdSpan.isFinished()) { - return; - } - ttfdSpan.setDescription(getExceededTtfdDesc(ttfdSpan)); - // We set the end timestamp of the ttfd span to be equal to the ttid span. - final @Nullable SentryDate ttidEndDate = ttidSpan != null ? ttidSpan.getFinishDate() : null; - final @NotNull SentryDate ttfdEndDate = - ttidEndDate != null ? ttidEndDate : ttfdSpan.getStartDate(); - finishSpan(ttfdSpan, ttfdEndDate, SpanStatus.DEADLINE_EXCEEDED); - } - - @TestOnly - @NotNull - WeakHashMap getActivitiesWithOngoingTransactions() { - return activitiesWithOngoingTransactions; - } - - @TestOnly - @NotNull - WeakHashMap getActivitySpanHelpers() { - return activitySpanHelpers; - } - - @TestOnly - void setFirstActivityCreated(boolean firstActivityCreated) { - this.firstActivityCreated = firstActivityCreated; - } - - @TestOnly - @NotNull - ActivityFramesTracker getActivityFramesTracker() { - return activityFramesTracker; - } - - @TestOnly - @Nullable - ISpan getAppStartSpan() { - return appStartSpan; - } - - @TestOnly - @NotNull - WeakHashMap getTtidSpanMap() { - return ttidSpanMap; - } - - @TestOnly - @NotNull - WeakHashMap getTtfdSpanMap() { - return ttfdSpanMap; - } - - private @NotNull String getTtidDesc(final @NotNull String activityName) { - return activityName + " initial display"; - } - - private @NotNull String getTtfdDesc(final @NotNull String activityName) { - return activityName + " full display"; - } - - private @NotNull String getExceededTtfdDesc(final @NotNull ISpan ttfdSpan) { - final @Nullable String ttfdCurrentDescription = ttfdSpan.getDescription(); - if (ttfdCurrentDescription != null && ttfdCurrentDescription.endsWith(" - Deadline Exceeded")) - return ttfdCurrentDescription; - return ttfdSpan.getDescription() + " - Deadline Exceeded"; - } - - private @NotNull String getAppStartDesc(final boolean coldStart) { - if (coldStart) { - return "Cold Start"; - } else { - return "Warm Start"; - } - } - - private @NotNull String getAppStartOp(final boolean coldStart) { - if (coldStart) { - return APP_START_COLD; - } else { - return APP_START_WARM; - } - } - - private void finishAppStartSpan() { - final @Nullable SentryDate appStartEndTime = - AppStartMetrics.getInstance() - .getAppStartTimeSpanWithFallback(options) - .getProjectedStopTimestamp(); - if (performanceEnabled && appStartEndTime != null) { - finishSpan(appStartSpan, appStartEndTime); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java deleted file mode 100644 index 41362c9d93e..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java +++ /dev/null @@ -1,409 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.DataCategory.All; -import static io.sentry.IConnectionStatusProvider.ConnectionStatus.DISCONNECTED; -import static java.util.concurrent.TimeUnit.SECONDS; - -import android.os.Build; -import io.sentry.CompositePerformanceCollector; -import io.sentry.DataCategory; -import io.sentry.IContinuousProfiler; -import io.sentry.ILogger; -import io.sentry.IScopes; -import io.sentry.ISentryExecutorService; -import io.sentry.ISentryLifecycleToken; -import io.sentry.NoOpScopes; -import io.sentry.PerformanceCollectionData; -import io.sentry.ProfileChunk; -import io.sentry.ProfileLifecycle; -import io.sentry.Sentry; -import io.sentry.SentryDate; -import io.sentry.SentryLevel; -import io.sentry.SentryNanotimeDate; -import io.sentry.SentryOptions; -import io.sentry.TracesSampler; -import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; -import io.sentry.protocol.SentryId; -import io.sentry.transport.RateLimiter; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.LazyEvaluator; -import io.sentry.util.SentryRandom; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Future; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.VisibleForTesting; - -@ApiStatus.Internal -public class AndroidContinuousProfiler - implements IContinuousProfiler, RateLimiter.IRateLimitObserver { - private static final long MAX_CHUNK_DURATION_MILLIS = 60000; - - private final @NotNull ILogger logger; - private final @Nullable String profilingTracesDirPath; - private final int profilingTracesHz; - private final @NotNull LazyEvaluator.Evaluator executorServiceSupplier; - private final @NotNull BuildInfoProvider buildInfoProvider; - private boolean isInitialized = false; - private final @NotNull SentryFrameMetricsCollector frameMetricsCollector; - private @Nullable AndroidProfiler profiler = null; - private boolean isRunning = false; - private @Nullable IScopes scopes; - private @Nullable Future stopFuture; - private @Nullable CompositePerformanceCollector performanceCollector; - private final @NotNull List payloadBuilders = new ArrayList<>(); - private @NotNull SentryId profilerId = SentryId.EMPTY_ID; - private @NotNull SentryId chunkId = SentryId.EMPTY_ID; - private final @NotNull AtomicBoolean isClosed = new AtomicBoolean(false); - private @NotNull SentryDate startProfileChunkTimestamp = new SentryNanotimeDate(); - private volatile boolean shouldSample = true; - private boolean shouldStop = false; - private boolean isSampled = false; - private int rootSpanCounter = 0; - - private final AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - private final AutoClosableReentrantLock payloadLock = new AutoClosableReentrantLock(); - - public AndroidContinuousProfiler( - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull SentryFrameMetricsCollector frameMetricsCollector, - final @NotNull ILogger logger, - final @Nullable String profilingTracesDirPath, - final int profilingTracesHz, - final @NotNull LazyEvaluator.Evaluator executorServiceSupplier) { - this.logger = logger; - this.frameMetricsCollector = frameMetricsCollector; - this.buildInfoProvider = buildInfoProvider; - this.profilingTracesDirPath = profilingTracesDirPath; - this.profilingTracesHz = profilingTracesHz; - this.executorServiceSupplier = executorServiceSupplier; - } - - private void init() { - // We initialize it only once - if (isInitialized) { - return; - } - isInitialized = true; - if (profilingTracesDirPath == null) { - logger.log( - SentryLevel.WARNING, - "Disabling profiling because no profiling traces dir path is defined in options."); - return; - } - if (profilingTracesHz <= 0) { - logger.log( - SentryLevel.WARNING, - "Disabling profiling because trace rate is set to %d", - profilingTracesHz); - return; - } - - profiler = - new AndroidProfiler( - profilingTracesDirPath, - (int) SECONDS.toMicros(1) / profilingTracesHz, - frameMetricsCollector, - null, - logger); - } - - @Override - public void startProfiler( - final @NotNull ProfileLifecycle profileLifecycle, - final @NotNull TracesSampler tracesSampler) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (shouldSample) { - isSampled = tracesSampler.sampleSessionProfile(SentryRandom.current().nextDouble()); - shouldSample = false; - } - if (!isSampled) { - logger.log(SentryLevel.DEBUG, "Profiler was not started due to sampling decision."); - return; - } - switch (profileLifecycle) { - case TRACE: - // rootSpanCounter should never be negative, unless the user changed profile lifecycle - // while - // the profiler is running or close() is called. This is just a safety check. - if (rootSpanCounter < 0) { - rootSpanCounter = 0; - } - rootSpanCounter++; - break; - case MANUAL: - // We check if the profiler is already running and log a message only in manual mode, - // since - // in trace mode we can have multiple concurrent traces - if (isRunning()) { - logger.log(SentryLevel.DEBUG, "Profiler is already running."); - return; - } - break; - } - if (!isRunning()) { - logger.log(SentryLevel.DEBUG, "Started Profiler."); - start(); - } - } - } - - private void initScopes() { - if ((scopes == null || scopes == NoOpScopes.getInstance()) - && Sentry.getCurrentScopes() != NoOpScopes.getInstance()) { - this.scopes = Sentry.getCurrentScopes(); - this.performanceCollector = - Sentry.getCurrentScopes().getOptions().getCompositePerformanceCollector(); - final @Nullable RateLimiter rateLimiter = scopes.getRateLimiter(); - if (rateLimiter != null) { - rateLimiter.addRateLimitObserver(this); - } - } - } - - private void start() { - initScopes(); - - // Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler - // causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392 - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return; - - // Let's initialize trace folder and profiling interval - init(); - // init() didn't create profiler, should never happen - if (profiler == null) { - return; - } - - if (scopes != null) { - final @Nullable RateLimiter rateLimiter = scopes.getRateLimiter(); - if (rateLimiter != null - && (rateLimiter.isActiveForCategory(All) - || rateLimiter.isActiveForCategory(DataCategory.ProfileChunkUi))) { - logger.log(SentryLevel.WARNING, "SDK is rate limited. Stopping profiler."); - // Let's stop and reset profiler id, as the profile is now broken anyway - stop(false); - return; - } - - // If device is offline, we don't start the profiler, to avoid flooding the cache - // TODO .getConnectionStatus() may be blocking, investigate if this can be done async - if (scopes.getOptions().getConnectionStatusProvider().getConnectionStatus() == DISCONNECTED) { - logger.log(SentryLevel.WARNING, "Device is offline. Stopping profiler."); - // Let's stop and reset profiler id, as the profile is now broken anyway - stop(false); - return; - } - startProfileChunkTimestamp = scopes.getOptions().getDateProvider().now(); - } else { - startProfileChunkTimestamp = new SentryNanotimeDate(); - } - final AndroidProfiler.ProfileStartData startData = profiler.start(); - // check if profiling started - if (startData == null) { - return; - } - - isRunning = true; - - if (profilerId.equals(SentryId.EMPTY_ID)) { - profilerId = new SentryId(); - } - - if (chunkId.equals(SentryId.EMPTY_ID)) { - chunkId = new SentryId(); - } - - if (performanceCollector != null) { - performanceCollector.start(chunkId.toString()); - } - - try { - stopFuture = - executorServiceSupplier.evaluate().schedule(() -> stop(true), MAX_CHUNK_DURATION_MILLIS); - } catch (RejectedExecutionException e) { - logger.log( - SentryLevel.ERROR, - "Failed to schedule profiling chunk finish. Did you call Sentry.close()?", - e); - shouldStop = true; - } - } - - @Override - public void stopProfiler(final @NotNull ProfileLifecycle profileLifecycle) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - switch (profileLifecycle) { - case TRACE: - rootSpanCounter--; - // If there are active spans, and profile lifecycle is trace, we don't stop the profiler - if (rootSpanCounter > 0) { - return; - } - // rootSpanCounter should never be negative, unless the user changed profile lifecycle - // while the profiler is running or close() is called. This is just a safety check. - if (rootSpanCounter < 0) { - rootSpanCounter = 0; - } - shouldStop = true; - break; - case MANUAL: - shouldStop = true; - break; - } - } - } - - private void stop(final boolean restartProfiler) { - initScopes(); - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (stopFuture != null) { - stopFuture.cancel(true); - } - // check if profiler was created and it's running - if (profiler == null || !isRunning) { - // When the profiler is stopped due to an error (e.g. offline or rate limited), reset the - // ids - profilerId = SentryId.EMPTY_ID; - chunkId = SentryId.EMPTY_ID; - return; - } - - // onTransactionStart() is only available since Lollipop_MR1 - // and SystemClock.elapsedRealtimeNanos() since Jelly Bean - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) { - return; - } - - List performanceCollectionData = null; - if (performanceCollector != null) { - performanceCollectionData = performanceCollector.stop(chunkId.toString()); - } - - final AndroidProfiler.ProfileEndData endData = - profiler.endAndCollect(false, performanceCollectionData); - - // check if profiler end successfully - if (endData == null) { - logger.log( - SentryLevel.ERROR, - "An error occurred while collecting a profile chunk, and it won't be sent."); - } else { - // The scopes can be null if the profiler is started before the SDK is initialized (app - // start profiling), meaning there's no scopes to send the chunks. In that case, we store - // the data in a list and send it when the next chunk is finished. - try (final @NotNull ISentryLifecycleToken ignored2 = payloadLock.acquire()) { - payloadBuilders.add( - new ProfileChunk.Builder( - profilerId, - chunkId, - endData.measurementsMap, - endData.traceFile, - startProfileChunkTimestamp, - ProfileChunk.PLATFORM_ANDROID)); - } - } - - isRunning = false; - // A chunk is finished. Next chunk will have a different id. - chunkId = SentryId.EMPTY_ID; - - if (scopes != null) { - sendChunks(scopes, scopes.getOptions()); - } - - if (restartProfiler && !shouldStop) { - logger.log(SentryLevel.DEBUG, "Profile chunk finished. Starting a new one."); - start(); - } else { - // When the profiler is stopped manually, we have to reset its id - profilerId = SentryId.EMPTY_ID; - logger.log(SentryLevel.DEBUG, "Profile chunk finished."); - } - } - } - - public void reevaluateSampling() { - shouldSample = true; - } - - @Override - public void close(final boolean isTerminating) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - rootSpanCounter = 0; - shouldStop = true; - if (isTerminating) { - stop(false); - isClosed.set(true); - } - } - } - - @Override - public @NotNull SentryId getProfilerId() { - return profilerId; - } - - @Override - public @NotNull SentryId getChunkId() { - return chunkId; - } - - private void sendChunks(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - try { - options - .getExecutorService() - .submit( - () -> { - // SDK is closed, we don't send the chunks - if (isClosed.get()) { - return; - } - final ArrayList payloads = new ArrayList<>(payloadBuilders.size()); - try (final @NotNull ISentryLifecycleToken ignored = payloadLock.acquire()) { - for (ProfileChunk.Builder builder : payloadBuilders) { - payloads.add(builder.build(options)); - } - payloadBuilders.clear(); - } - for (ProfileChunk payload : payloads) { - scopes.captureProfileChunk(payload); - } - }); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.DEBUG, "Failed to send profile chunks.", e); - } - } - - @Override - public boolean isRunning() { - return isRunning; - } - - @VisibleForTesting - @Nullable - Future getStopFuture() { - return stopFuture; - } - - @VisibleForTesting - public int getRootSpanCounter() { - return rootSpanCounter; - } - - @Override - public void onRateLimitChanged(@NotNull RateLimiter rateLimiter) { - // We stop the profiler as soon as we are rate limited, to avoid the performance overhead - if (rateLimiter.isActiveForCategory(All) - || rateLimiter.isActiveForCategory(DataCategory.ProfileChunkUi)) { - logger.log(SentryLevel.WARNING, "SDK is rate limited. Stopping profiler."); - stop(false); - } - // If we are not rate limited anymore, we don't do anything: the profile is broken, so it's - // useless to restart it automatically - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidCpuCollector.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidCpuCollector.java deleted file mode 100644 index ea7a20deab1..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidCpuCollector.java +++ /dev/null @@ -1,109 +0,0 @@ -package io.sentry.android.core; - -import android.os.SystemClock; -import android.system.Os; -import android.system.OsConstants; -import io.sentry.ILogger; -import io.sentry.IPerformanceSnapshotCollector; -import io.sentry.PerformanceCollectionData; -import io.sentry.SentryLevel; -import io.sentry.util.FileUtils; -import io.sentry.util.Objects; -import java.io.File; -import java.io.IOException; -import java.util.regex.Pattern; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -// The approach to get the cpu usage info was taken from -// https://eng.lyft.com/monitoring-cpu-performance-of-lyfts-android-applications-4e36fafffe12 -// The content of the /proc/self/stat file is specified in -// https://man7.org/linux/man-pages/man5/proc.5.html -@ApiStatus.Internal -public final class AndroidCpuCollector implements IPerformanceSnapshotCollector { - - private long lastRealtimeNanos = 0; - private long lastCpuNanos = 0; - - /** Number of clock ticks per second. */ - private long clockSpeedHz = 1; - - private long numCores = 1; - private final long NANOSECOND_PER_SECOND = 1_000_000_000; - - /** Number of nanoseconds per clock tick. */ - private double nanosecondsPerClockTick = NANOSECOND_PER_SECOND / (double) clockSpeedHz; - - /** File containing stats about this process. */ - private final @NotNull File selfStat = new File("/proc/self/stat"); - - private final @NotNull ILogger logger; - private boolean isEnabled = false; - private final @NotNull Pattern newLinePattern = Pattern.compile("[\n\t\r ]"); - - public AndroidCpuCollector(final @NotNull ILogger logger) { - this.logger = Objects.requireNonNull(logger, "Logger is required."); - } - - @Override - public void setup() { - isEnabled = true; - clockSpeedHz = Os.sysconf(OsConstants._SC_CLK_TCK); - numCores = Os.sysconf(OsConstants._SC_NPROCESSORS_CONF); - nanosecondsPerClockTick = NANOSECOND_PER_SECOND / (double) clockSpeedHz; - lastCpuNanos = readTotalCpuNanos(); - } - - @Override - public void collect(final @NotNull PerformanceCollectionData performanceCollectionData) { - if (!isEnabled) { - return; - } - final long nowNanos = SystemClock.elapsedRealtimeNanos(); - final long realTimeNanosDiff = nowNanos - lastRealtimeNanos; - lastRealtimeNanos = nowNanos; - final long cpuNanos = readTotalCpuNanos(); - final long cpuNanosDiff = cpuNanos - lastCpuNanos; - lastCpuNanos = cpuNanos; - // Later we need to divide the percentage by the number of cores, otherwise we could - // get a percentage value higher than 1. We also want to send the percentage as a - // number from 0 to 100, so we are going to multiply it by 100 - final double cpuUsagePercentage = cpuNanosDiff / (double) realTimeNanosDiff; - - performanceCollectionData.setCpuUsagePercentage( - (cpuUsagePercentage / (double) numCores) * 100.0); - } - - /** Read the /proc/self/stat file and parses the result. */ - private long readTotalCpuNanos() { - String stat = null; - try { - stat = FileUtils.readText(selfStat); - } catch (IOException e) { - // If an error occurs when reading the file, we avoid reading it again until the setup method - // is called again - isEnabled = false; - logger.log( - SentryLevel.WARNING, "Unable to read /proc/self/stat file. Disabling cpu collection.", e); - } - if (stat != null) { - stat = stat.trim(); - String[] stats = newLinePattern.split(stat); - try { - // Amount of clock ticks this process has been scheduled in user mode - long uTime = Long.parseLong(stats[13]); - // Amount of clock ticks this process has been scheduled in kernel mode - long sTime = Long.parseLong(stats[14]); - // Amount of clock ticks this process' waited-for children has been scheduled in user mode - long cuTime = Long.parseLong(stats[15]); - // Amount of clock ticks this process' waited-for children has been scheduled in kernel mode - long csTime = Long.parseLong(stats[16]); - return (long) ((uTime + sTime + cuTime + csTime) * nanosecondsPerClockTick); - } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { - logger.log(SentryLevel.ERROR, "Error parsing /proc/self/stat file.", e); - return 0; - } - } - return 0; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidDateUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidDateUtils.java deleted file mode 100644 index 55c1e2c7cec..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidDateUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.sentry.android.core; - -import io.sentry.SentryDate; -import io.sentry.SentryDateProvider; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public final class AndroidDateUtils { - - private static final SentryDateProvider dateProvider = new SentryAndroidDateProvider(); - - /** - * Get the current SentryDate (UTC). - * - *

NOTE: options.getDateProvider() should be preferred. This is only a fallback for static - * invocations. - * - * @return the UTC SentryDate - */ - public static @NotNull SentryDate getCurrentSentryDateTime() { - return dateProvider.now(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidFatalLogger.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidFatalLogger.java deleted file mode 100644 index 76d6ba99784..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidFatalLogger.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.sentry.android.core; - -import android.util.Log; -import io.sentry.ILogger; -import io.sentry.SentryLevel; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public final class AndroidFatalLogger implements ILogger { - - private final @NotNull String tag; - - public AndroidFatalLogger() { - this("Sentry"); - } - - public AndroidFatalLogger(final @NotNull String tag) { - this.tag = tag; - } - - @SuppressWarnings("AnnotateFormatMethod") - @Override - public void log( - final @NotNull SentryLevel level, - final @NotNull String message, - final @Nullable Object... args) { - if (args == null || args.length == 0) { - Log.println(toLogcatLevel(level), tag, message); - } else { - Log.println(toLogcatLevel(level), tag, String.format(message, args)); - } - } - - @SuppressWarnings("AnnotateFormatMethod") - @Override - public void log( - final @NotNull SentryLevel level, - final @Nullable Throwable throwable, - final @NotNull String message, - final @Nullable Object... args) { - if (args == null || args.length == 0) { - log(level, message, throwable); - } else { - log(level, String.format(message, args), throwable); - } - } - - @Override - public void log( - final @NotNull SentryLevel level, - final @NotNull String message, - final @Nullable Throwable throwable) { - Log.wtf(tag, message, throwable); - } - - @Override - public boolean isEnabled(@Nullable SentryLevel level) { - return true; - } - - private int toLogcatLevel(final @NotNull SentryLevel sentryLevel) { - return Log.ASSERT; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLogger.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLogger.java deleted file mode 100644 index ef943c16968..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLogger.java +++ /dev/null @@ -1,94 +0,0 @@ -package io.sentry.android.core; - -import android.util.Log; -import io.sentry.ILogger; -import io.sentry.SentryLevel; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public final class AndroidLogger implements ILogger { - - private final @NotNull String tag; - - public AndroidLogger() { - this("Sentry"); - } - - public AndroidLogger(final @NotNull String tag) { - this.tag = tag; - } - - @SuppressWarnings("AnnotateFormatMethod") - @Override - public void log( - final @NotNull SentryLevel level, - final @NotNull String message, - final @Nullable Object... args) { - if (args == null || args.length == 0) { - Log.println(toLogcatLevel(level), tag, message); - } else { - Log.println(toLogcatLevel(level), tag, String.format(message, args)); - } - } - - @SuppressWarnings("AnnotateFormatMethod") - @Override - public void log( - final @NotNull SentryLevel level, - final @Nullable Throwable throwable, - final @NotNull String message, - final @Nullable Object... args) { - if (args == null || args.length == 0) { - log(level, message, throwable); - } else { - log(level, String.format(message, args), throwable); - } - } - - @Override - public void log( - final @NotNull SentryLevel level, - final @NotNull String message, - final @Nullable Throwable throwable) { - - switch (level) { - case INFO: - Log.i(tag, message, throwable); - break; - case WARNING: - Log.w(tag, message, throwable); - break; - case ERROR: - Log.e(tag, message, throwable); - break; - case FATAL: - Log.wtf(tag, message, throwable); - break; - case DEBUG: - default: - Log.d(tag, message, throwable); - break; - } - } - - @Override - public boolean isEnabled(@Nullable SentryLevel level) { - return true; - } - - private int toLogcatLevel(final @NotNull SentryLevel sentryLevel) { - switch (sentryLevel) { - case INFO: - return Log.INFO; - case WARNING: - return Log.WARN; - case FATAL: - return Log.ASSERT; - case DEBUG: - default: - return Log.DEBUG; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLoggerBatchProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLoggerBatchProcessor.java deleted file mode 100644 index 13b12dc702a..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLoggerBatchProcessor.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.sentry.android.core; - -import io.sentry.ISentryClient; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.logger.LoggerBatchProcessor; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public final class AndroidLoggerBatchProcessor extends LoggerBatchProcessor - implements AppState.AppStateListener { - - public AndroidLoggerBatchProcessor( - @NotNull SentryOptions options, @NotNull ISentryClient client) { - super(options, client); - AppState.getInstance().addAppStateListener(this); - } - - @Override - public void onForeground() { - // no-op - } - - @Override - public void onBackground() { - try { - options - .getExecutorService() - .submit( - new Runnable() { - @Override - public void run() { - flush(LoggerBatchProcessor.FLUSH_AFTER_MS); - } - }); - } catch (Throwable t) { - options.getLogger().log(SentryLevel.ERROR, t, "Failed to submit log flush in onBackground()"); - } - } - - @Override - public void close(boolean isRestarting) { - AppState.getInstance().removeAppStateListener(this); - super.close(isRestarting); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLoggerBatchProcessorFactory.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLoggerBatchProcessorFactory.java deleted file mode 100644 index 694f94c7f7b..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLoggerBatchProcessorFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.sentry.android.core; - -import io.sentry.SentryClient; -import io.sentry.SentryOptions; -import io.sentry.logger.ILoggerBatchProcessor; -import io.sentry.logger.ILoggerBatchProcessorFactory; -import org.jetbrains.annotations.NotNull; - -public final class AndroidLoggerBatchProcessorFactory implements ILoggerBatchProcessorFactory { - @Override - public @NotNull ILoggerBatchProcessor create( - @NotNull SentryOptions options, @NotNull SentryClient client) { - return new AndroidLoggerBatchProcessor(options, client); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidMemoryCollector.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidMemoryCollector.java deleted file mode 100644 index 6775d818b4e..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidMemoryCollector.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.sentry.android.core; - -import android.os.Debug; -import io.sentry.IPerformanceSnapshotCollector; -import io.sentry.PerformanceCollectionData; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public class AndroidMemoryCollector implements IPerformanceSnapshotCollector { - - @Override - public void setup() {} - - @Override - public void collect(final @NotNull PerformanceCollectionData performanceCollectionData) { - long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); - long usedNativeMemory = Debug.getNativeHeapSize() - Debug.getNativeHeapFreeSize(); - performanceCollectionData.setUsedHeapMemory(usedMemory); - performanceCollectionData.setUsedNativeMemory(usedNativeMemory); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidMetricsBatchProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidMetricsBatchProcessor.java deleted file mode 100644 index 290f2a9d4ed..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidMetricsBatchProcessor.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.sentry.android.core; - -import io.sentry.ISentryClient; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.metrics.MetricsBatchProcessor; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public final class AndroidMetricsBatchProcessor extends MetricsBatchProcessor - implements AppState.AppStateListener { - - public AndroidMetricsBatchProcessor( - final @NotNull SentryOptions options, final @NotNull ISentryClient client) { - super(options, client); - AppState.getInstance().addAppStateListener(this); - } - - @Override - public void onForeground() { - // no-op - } - - @Override - public void onBackground() { - try { - options - .getExecutorService() - .submit( - new Runnable() { - @Override - public void run() { - flush(MetricsBatchProcessor.FLUSH_AFTER_MS); - } - }); - } catch (Throwable t) { - options - .getLogger() - .log(SentryLevel.ERROR, t, "Failed to submit metrics flush in onBackground()"); - } - } - - @Override - public void close(boolean isRestarting) { - AppState.getInstance().removeAppStateListener(this); - super.close(isRestarting); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidMetricsBatchProcessorFactory.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidMetricsBatchProcessorFactory.java deleted file mode 100644 index 319440c27a9..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidMetricsBatchProcessorFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.sentry.android.core; - -import io.sentry.SentryClient; -import io.sentry.SentryOptions; -import io.sentry.metrics.IMetricsBatchProcessor; -import io.sentry.metrics.IMetricsBatchProcessorFactory; -import org.jetbrains.annotations.NotNull; - -public final class AndroidMetricsBatchProcessorFactory implements IMetricsBatchProcessorFactory { - @Override - public @NotNull IMetricsBatchProcessor create( - final @NotNull SentryOptions options, final @NotNull SentryClient client) { - return new AndroidMetricsBatchProcessor(options, client); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java deleted file mode 100644 index 81ac3b35d6a..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ /dev/null @@ -1,501 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.android.core.NdkIntegration.SENTRY_NDK_CLASS_NAME; - -import android.app.Application; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.os.Build; -import io.sentry.CompositePerformanceCollector; -import io.sentry.DeduplicateMultithreadedEventProcessor; -import io.sentry.DefaultCompositePerformanceCollector; -import io.sentry.DefaultVersionDetector; -import io.sentry.IContinuousProfiler; -import io.sentry.ILogger; -import io.sentry.ISentryLifecycleToken; -import io.sentry.ITransactionProfiler; -import io.sentry.NoOpCompositePerformanceCollector; -import io.sentry.NoOpConnectionStatusProvider; -import io.sentry.NoOpContinuousProfiler; -import io.sentry.NoOpReplayBreadcrumbConverter; -import io.sentry.NoOpSocketTagger; -import io.sentry.NoOpTransactionProfiler; -import io.sentry.NoopVersionDetector; -import io.sentry.ScopeType; -import io.sentry.SendFireAndForgetEnvelopeSender; -import io.sentry.SendFireAndForgetOutboxSender; -import io.sentry.SentryLevel; -import io.sentry.SentryOpenTelemetryMode; -import io.sentry.android.core.cache.AndroidEnvelopeCache; -import io.sentry.android.core.internal.debugmeta.AssetsDebugMetaLoader; -import io.sentry.android.core.internal.gestures.AndroidViewGestureTargetLocator; -import io.sentry.android.core.internal.modules.AssetsModulesLoader; -import io.sentry.android.core.internal.util.AndroidConnectionStatusProvider; -import io.sentry.android.core.internal.util.AndroidCurrentDateProvider; -import io.sentry.android.core.internal.util.AndroidRuntimeManager; -import io.sentry.android.core.internal.util.AndroidThreadChecker; -import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; -import io.sentry.android.core.performance.AppStartMetrics; -import io.sentry.android.distribution.DistributionIntegration; -import io.sentry.android.fragment.FragmentLifecycleIntegration; -import io.sentry.android.replay.DefaultReplayBreadcrumbConverter; -import io.sentry.android.replay.ReplayIntegration; -import io.sentry.android.timber.SentryTimberIntegration; -import io.sentry.cache.PersistingOptionsObserver; -import io.sentry.cache.PersistingScopeObserver; -import io.sentry.compose.gestures.ComposeGestureTargetLocator; -import io.sentry.compose.viewhierarchy.ComposeViewHierarchyExporter; -import io.sentry.internal.debugmeta.NoOpDebugMetaLoader; -import io.sentry.internal.gestures.GestureTargetLocator; -import io.sentry.internal.modules.NoOpModulesLoader; -import io.sentry.internal.viewhierarchy.ViewHierarchyExporter; -import io.sentry.protocol.SentryId; -import io.sentry.transport.CurrentDateProvider; -import io.sentry.transport.NoOpEnvelopeCache; -import io.sentry.transport.NoOpTransportGate; -import io.sentry.util.LazyEvaluator; -import io.sentry.util.Objects; -import io.sentry.util.thread.NoOpThreadChecker; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -/** - * Android Options initializer, it reads configurations from AndroidManifest and sets to the - * SentryAndroidOptions. It also adds default values for some fields. - */ -@SuppressWarnings("Convert2MethodRef") // older AGP versions do not support method references -final class AndroidOptionsInitializer { - - static final long DEFAULT_FLUSH_TIMEOUT_MS = 4000; - - static final String SENTRY_COMPOSE_GESTURE_INTEGRATION_CLASS_NAME = - "io.sentry.compose.gestures.ComposeGestureTargetLocator"; - - static final String SENTRY_COMPOSE_VIEW_HIERARCHY_INTEGRATION_CLASS_NAME = - "io.sentry.compose.viewhierarchy.ComposeViewHierarchyExporter"; - - static final String COMPOSE_CLASS_NAME = "androidx.compose.ui.node.Owner"; - - /** private ctor */ - private AndroidOptionsInitializer() {} - - /** - * Init method of the Android Options initializer - * - * @param options the SentryAndroidOptions - * @param context the Application context - */ - @TestOnly - static void loadDefaultAndMetadataOptions( - final @NotNull SentryAndroidOptions options, final @NotNull Context context) { - final ILogger logger = new AndroidLogger(); - loadDefaultAndMetadataOptions(options, context, logger, new BuildInfoProvider(logger)); - } - - /** - * Init method of the Android Options initializer - * - * @param options the SentryAndroidOptions - * @param context the Application context - * @param logger the ILogger interface - * @param buildInfoProvider the BuildInfoProvider interface - */ - static void loadDefaultAndMetadataOptions( - final @NotNull SentryAndroidOptions options, - @NotNull Context context, - final @NotNull ILogger logger, - final @NotNull BuildInfoProvider buildInfoProvider) { - Objects.requireNonNull(context, "The context is required."); - - @NotNull final Context finalContext = ContextUtils.getApplicationContext(context); - - Objects.requireNonNull(options, "The options object is required."); - Objects.requireNonNull(logger, "The ILogger object is required."); - - // Firstly set the logger, if `debug=true` configured, logging can start asap. - options.setLogger(logger); - options.setFatalLogger(new AndroidFatalLogger()); - - options.setDefaultScopeType(ScopeType.CURRENT); - options.setOpenTelemetryMode(SentryOpenTelemetryMode.OFF); - options.setDateProvider(new SentryAndroidDateProvider()); - options.setRuntimeManager(new AndroidRuntimeManager()); - options.getLogs().setLoggerBatchProcessorFactory(new AndroidLoggerBatchProcessorFactory()); - options.getMetrics().setMetricsBatchProcessorFactory(new AndroidMetricsBatchProcessorFactory()); - - // set a lower flush timeout on Android to avoid ANRs - options.setFlushTimeoutMillis(DEFAULT_FLUSH_TIMEOUT_MS); - - options.setFrameMetricsCollector( - new SentryFrameMetricsCollector(finalContext, logger, buildInfoProvider)); - - ManifestMetadataReader.applyMetadata(finalContext, options, buildInfoProvider); - - options.setCacheDirPath( - options - .getRuntimeManager() - .runWithRelaxedPolicy(() -> getCacheDir(finalContext).getAbsolutePath())); - - readDefaultOptionValues(options, finalContext, buildInfoProvider); - AppState.getInstance().registerLifecycleObserver(options); - } - - @TestOnly - static void initializeIntegrationsAndProcessors( - final @NotNull SentryAndroidOptions options, - final @NotNull Context context, - final @NotNull io.sentry.util.LoadClass loadClass, - final @NotNull ActivityFramesTracker activityFramesTracker, - final boolean isReplayAvailable) { - initializeIntegrationsAndProcessors( - options, - context, - new BuildInfoProvider(new AndroidLogger()), - loadClass, - activityFramesTracker, - isReplayAvailable); - } - - static void initializeIntegrationsAndProcessors( - final @NotNull SentryAndroidOptions options, - final @NotNull Context context, - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull io.sentry.util.LoadClass loadClass, - final @NotNull ActivityFramesTracker activityFramesTracker, - final boolean isReplayAvailable) { - - if (options.getCacheDirPath() != null - && options.getEnvelopeDiskCache() instanceof NoOpEnvelopeCache) { - options.setEnvelopeDiskCache(new AndroidEnvelopeCache(options)); - } - - if (options.getConnectionStatusProvider() instanceof NoOpConnectionStatusProvider) { - options.setConnectionStatusProvider( - new AndroidConnectionStatusProvider( - context, options, buildInfoProvider, AndroidCurrentDateProvider.getInstance())); - } - - if (options.getCacheDirPath() != null) { - options.addScopeObserver(new PersistingScopeObserver(options)); - options.addOptionsObserver(new PersistingOptionsObserver(options)); - } - - options.addEventProcessor(new DeduplicateMultithreadedEventProcessor(options)); - options.addEventProcessor( - new DefaultAndroidEventProcessor(context, buildInfoProvider, options)); - options.addEventProcessor(new PerformanceAndroidEventProcessor(options, activityFramesTracker)); - options.addEventProcessor(new ScreenshotEventProcessor(options, buildInfoProvider)); - options.addEventProcessor(new ViewHierarchyEventProcessor(options)); - options.addEventProcessor( - new ApplicationExitInfoEventProcessor(context, options, buildInfoProvider)); - if (options.getTransportGate() instanceof NoOpTransportGate) { - options.setTransportGate(new AndroidTransportGate(options)); - } - - final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance(); - - if (options.getModulesLoader() instanceof NoOpModulesLoader) { - options.setModulesLoader(new AssetsModulesLoader(context, options.getLogger())); - } - if (options.getDebugMetaLoader() instanceof NoOpDebugMetaLoader) { - options.setDebugMetaLoader(new AssetsDebugMetaLoader(context, options.getLogger())); - } - if (options.getVersionDetector() instanceof NoopVersionDetector) { - options.setVersionDetector(new DefaultVersionDetector(options)); - } - - final @NotNull LazyEvaluator isAndroidXScrollViewAvailable = - loadClass.isClassAvailableLazy("androidx.core.view.ScrollingView", options); - final boolean isComposeUpstreamAvailable = - loadClass.isClassAvailable(COMPOSE_CLASS_NAME, options); - - if (options.getGestureTargetLocators().isEmpty()) { - final List gestureTargetLocators = new ArrayList<>(2); - gestureTargetLocators.add(new AndroidViewGestureTargetLocator(isAndroidXScrollViewAvailable)); - - final boolean isComposeAvailable = - (isComposeUpstreamAvailable - && loadClass.isClassAvailable( - SENTRY_COMPOSE_GESTURE_INTEGRATION_CLASS_NAME, options)); - - if (isComposeAvailable) { - gestureTargetLocators.add(new ComposeGestureTargetLocator(options.getLogger())); - } - options.setGestureTargetLocators(gestureTargetLocators); - } - - if (options.getViewHierarchyExporters().isEmpty() - && isComposeUpstreamAvailable - && loadClass.isClassAvailable( - SENTRY_COMPOSE_VIEW_HIERARCHY_INTEGRATION_CLASS_NAME, options)) { - - final List viewHierarchyExporters = new ArrayList<>(1); - viewHierarchyExporters.add(new ComposeViewHierarchyExporter(options.getLogger())); - options.setViewHierarchyExporters(viewHierarchyExporters); - } - - if (options.getThreadChecker() instanceof NoOpThreadChecker) { - options.setThreadChecker(AndroidThreadChecker.getInstance()); - } - if (options.getSocketTagger() instanceof NoOpSocketTagger) { - options.setSocketTagger(AndroidSocketTagger.getInstance()); - } - - if (options.getPerformanceCollectors().isEmpty()) { - options.addPerformanceCollector(new AndroidMemoryCollector()); - options.addPerformanceCollector(new AndroidCpuCollector(options.getLogger())); - - if (options.isEnablePerformanceV2()) { - options.addPerformanceCollector( - new SpanFrameMetricsCollector( - options, - Objects.requireNonNull( - options.getFrameMetricsCollector(), - "options.getFrameMetricsCollector is required"))); - } - } - if (options.getCompositePerformanceCollector() instanceof NoOpCompositePerformanceCollector) { - options.setCompositePerformanceCollector(new DefaultCompositePerformanceCollector(options)); - } - - if (isReplayAvailable - && options.getReplayController().getBreadcrumbConverter() - instanceof NoOpReplayBreadcrumbConverter) { - options - .getReplayController() - .setBreadcrumbConverter(new DefaultReplayBreadcrumbConverter(options)); - } - - // Check if the profiler was already instantiated in the app start. - // We use the Android profiler, that uses a global start/stop api, so we need to preserve the - // state of the profiler, and it's only possible retaining the instance. - final @Nullable ITransactionProfiler appStartTransactionProfiler; - final @Nullable IContinuousProfiler appStartContinuousProfiler; - try (final @NotNull ISentryLifecycleToken ignored = AppStartMetrics.staticLock.acquire()) { - appStartTransactionProfiler = appStartMetrics.getAppStartProfiler(); - appStartContinuousProfiler = appStartMetrics.getAppStartContinuousProfiler(); - appStartMetrics.setAppStartProfiler(null); - appStartMetrics.setAppStartContinuousProfiler(null); - } - - setupProfiler( - options, - context, - buildInfoProvider, - appStartTransactionProfiler, - appStartContinuousProfiler, - options.getCompositePerformanceCollector()); - } - - /** Setup the correct profiler (transaction or continuous) based on the options. */ - private static void setupProfiler( - final @NotNull SentryAndroidOptions options, - final @NotNull Context context, - final @NotNull BuildInfoProvider buildInfoProvider, - final @Nullable ITransactionProfiler appStartTransactionProfiler, - final @Nullable IContinuousProfiler appStartContinuousProfiler, - final @NotNull CompositePerformanceCollector performanceCollector) { - if (options.isProfilingEnabled() || options.getProfilesSampleRate() != null) { - options.setContinuousProfiler(NoOpContinuousProfiler.getInstance()); - // This is a safeguard, but it should never happen, as the app start profiler should be the - // continuous one. - if (appStartContinuousProfiler != null) { - appStartContinuousProfiler.close(true); - } - if (appStartTransactionProfiler != null) { - options.setTransactionProfiler(appStartTransactionProfiler); - } else { - options.setTransactionProfiler( - new AndroidTransactionProfiler( - context, - options, - buildInfoProvider, - Objects.requireNonNull( - options.getFrameMetricsCollector(), - "options.getFrameMetricsCollector is required"))); - } - } else { - options.setTransactionProfiler(NoOpTransactionProfiler.getInstance()); - // This is a safeguard, but it should never happen, as the app start profiler should be the - // transaction one. - if (appStartTransactionProfiler != null) { - appStartTransactionProfiler.close(); - } - if (appStartContinuousProfiler != null) { - options.setContinuousProfiler(appStartContinuousProfiler); - // If the profiler is running, we start the performance collector too, otherwise we'd miss - // measurements in app launch profiles - final @NotNull SentryId chunkId = appStartContinuousProfiler.getChunkId(); - if (appStartContinuousProfiler.isRunning() && !chunkId.equals(SentryId.EMPTY_ID)) { - performanceCollector.start(chunkId.toString()); - } - } else { - options.setContinuousProfiler( - new AndroidContinuousProfiler( - buildInfoProvider, - Objects.requireNonNull( - options.getFrameMetricsCollector(), - "options.getFrameMetricsCollector is required"), - options.getLogger(), - options.getProfilingTracesDirPath(), - options.getProfilingTracesHz(), - () -> options.getExecutorService())); - } - } - } - - static void installDefaultIntegrations( - final @NotNull Context context, - final @NotNull SentryAndroidOptions options, - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull io.sentry.util.LoadClass loadClass, - final @NotNull ActivityFramesTracker activityFramesTracker, - final boolean isFragmentAvailable, - final boolean isTimberAvailable, - final boolean isReplayAvailable, - final boolean isDistributionAvailable) { - - // Integration MUST NOT cache option values in ctor, as they will be configured later by the - // user - - // read the startup crash marker here to avoid doing double-IO for the SendCachedEnvelope - // integrations below - LazyEvaluator startupCrashMarkerEvaluator = - new LazyEvaluator<>(() -> AndroidEnvelopeCache.hasStartupCrashMarker(options)); - - options.addIntegration( - new SendCachedEnvelopeIntegration( - new SendFireAndForgetEnvelopeSender(() -> options.getCacheDirPath()), - startupCrashMarkerEvaluator)); - - // Integrations are registered in the same order. NDK before adding Watch outbox, - // because sentry-native move files around and we don't want to watch that. - final Class sentryNdkClass = loadClass.loadClass(SENTRY_NDK_CLASS_NAME, options.getLogger()); - options.addIntegration(new NdkIntegration(sentryNdkClass)); - - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.S) { - options.addIntegration(new TombstoneIntegration(context)); - } - - // this integration uses android.os.FileObserver, we can't move to sentry - // before creating a pure java impl. - options.addIntegration(EnvelopeFileObserverIntegration.getOutboxFileObserver()); - - // Send cached envelopes from outbox path - // this should be executed after NdkIntegration because sentry-native move files on init. - // and we'd like to send them right away - options.addIntegration( - new SendCachedEnvelopeIntegration( - new SendFireAndForgetOutboxSender(() -> options.getOutboxPath()), - startupCrashMarkerEvaluator)); - - // AppLifecycleIntegration has to be installed before AnrIntegration, because AnrIntegration - // relies on AppState set by it - options.addIntegration(new AppLifecycleIntegration()); - // AnrIntegration must be installed before ReplayIntegration, as ReplayIntegration relies on - // it to set the replayId in case of an ANR - options.addIntegration(AnrIntegrationFactory.create(context, buildInfoProvider)); - - // registerActivityLifecycleCallbacks is only available if Context is an AppContext - if (context instanceof Application) { - options.addIntegration( - new ActivityLifecycleIntegration( - (Application) context, buildInfoProvider, activityFramesTracker)); - options.addIntegration(new ActivityBreadcrumbsIntegration((Application) context)); - options.addIntegration(new UserInteractionIntegration((Application) context, loadClass)); - if (isFragmentAvailable) { - options.addIntegration(new FragmentLifecycleIntegration((Application) context, true, true)); - } - } else { - options - .getLogger() - .log( - SentryLevel.WARNING, - "ActivityLifecycle, FragmentLifecycle and UserInteraction Integrations need an Application class to be installed."); - } - - if (isTimberAvailable) { - options.addIntegration(new SentryTimberIntegration()); - } - options.addIntegration(new AppComponentsBreadcrumbsIntegration(context)); - options.addIntegration(new SystemEventsBreadcrumbsIntegration(context)); - options.addIntegration(new NetworkBreadcrumbsIntegration(context, buildInfoProvider)); - if (isReplayAvailable) { - final ReplayIntegration replay = - new ReplayIntegration(context, CurrentDateProvider.getInstance()); - options.addIntegration(replay); - options.setReplayController(replay); - } - if (isDistributionAvailable) { - final DistributionIntegration distribution = new DistributionIntegration((context)); - options.setDistributionController(distribution); - options.addIntegration(distribution); - } - options - .getFeedbackOptions() - .setDialogHandler(new SentryAndroidOptions.AndroidUserFeedbackIDialogHandler()); - } - - /** - * Reads and sets default option values that are Android specific like release and inApp - * - * @param options the SentryAndroidOptions - * @param context the Android context methods - */ - private static void readDefaultOptionValues( - final @NotNull SentryAndroidOptions options, - final @NotNull Context context, - final @NotNull BuildInfoProvider buildInfoProvider) { - final @Nullable PackageInfo packageInfo = - ContextUtils.getPackageInfo(context, buildInfoProvider); - if (packageInfo != null) { - // Sets App's release if not set by Manifest - if (options.getRelease() == null) { - options.setRelease( - getSentryReleaseVersion( - packageInfo, ContextUtils.getVersionCode(packageInfo, buildInfoProvider))); - } - - // Sets the App's package name as InApp - final String packageName = packageInfo.packageName; - if (packageName != null && !packageName.startsWith("android.")) { - options.addInAppInclude(packageName); - } - } - - if (options.getDistinctId() == null) { - try { - options.setDistinctId( - options.getRuntimeManager().runWithRelaxedPolicy(() -> Installation.id(context))); - } catch (RuntimeException e) { - options.getLogger().log(SentryLevel.ERROR, "Could not generate distinct Id.", e); - } - } - } - - /** - * Returns the sentry release version (eg io.sentry.sample@1.0.0+10000) - - * packageName@versionName+buildVersion - * - * @param packageInfo the PackageInfo - * @param versionCode the versionCode - * @return the sentry release version as a String - */ - private static @NotNull String getSentryReleaseVersion( - final @NotNull PackageInfo packageInfo, final @NotNull String versionCode) { - return packageInfo.packageName + "@" + packageInfo.versionName + "+" + versionCode; - } - - /** - * Retrieve the Sentry cache dir. - * - * @param context the Application context - */ - static @NotNull File getCacheDir(final @NotNull Context context) { - return new File(context.getCacheDir(), "sentry"); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java deleted file mode 100644 index f4357b010f7..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java +++ /dev/null @@ -1,365 +0,0 @@ -package io.sentry.android.core; - -import android.annotation.SuppressLint; -import android.os.Debug; -import android.os.Process; -import android.os.SystemClock; -import io.sentry.DateUtils; -import io.sentry.ILogger; -import io.sentry.ISentryExecutorService; -import io.sentry.ISentryLifecycleToken; -import io.sentry.PerformanceCollectionData; -import io.sentry.SentryLevel; -import io.sentry.SentryNanotimeDate; -import io.sentry.SentryUUID; -import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; -import io.sentry.profilemeasurements.ProfileMeasurement; -import io.sentry.profilemeasurements.ProfileMeasurementValue; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.LazyEvaluator; -import io.sentry.util.Objects; -import java.io.File; -import java.util.ArrayDeque; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Future; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.TimeUnit; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public class AndroidProfiler { - public static class ProfileStartData { - public final long startNanos; - public final long startCpuMillis; - public final @NotNull Date startTimestamp; - - public ProfileStartData( - final long startNanos, final long startCpuMillis, final @NotNull Date startTimestamp) { - this.startNanos = startNanos; - this.startCpuMillis = startCpuMillis; - this.startTimestamp = startTimestamp; - } - } - - public static class ProfileEndData { - public final long endNanos; - public final long endCpuMillis; - public final @NotNull File traceFile; - public final @NotNull Map measurementsMap; - public final boolean didTimeout; - - public ProfileEndData( - final long endNanos, - final long endCpuMillis, - final boolean didTimeout, - final @NotNull File traceFile, - final @NotNull Map measurementsMap) { - this.endNanos = endNanos; - this.traceFile = traceFile; - this.endCpuMillis = endCpuMillis; - this.measurementsMap = measurementsMap; - this.didTimeout = didTimeout; - } - } - - /** - * This appears to correspond to the buffer size of the data part of the file, excluding the key - * part. Once the buffer is full, new records are ignored, but the resulting trace file will be - * valid. - * - *

30 second traces can require a buffer of a few MB. 8MB is the default buffer size for - * [Debug.startMethodTracingSampling], but 3 should be enough for most cases. We can adjust this - * in the future if we notice that traces are being truncated in some applications. - */ - private static final int BUFFER_SIZE_BYTES = 3_000_000; - - private static final int PROFILING_TIMEOUT_MILLIS = 30_000; - private long profileStartNanos = 0; - private final @NotNull File traceFilesDir; - private final int intervalUs; - private @Nullable Future scheduledFinish = null; - private @Nullable File traceFile = null; - private @Nullable String frameMetricsCollectorId; - private final @NotNull SentryFrameMetricsCollector frameMetricsCollector; - private final @NotNull ArrayDeque screenFrameRateMeasurements = - new ArrayDeque<>(); - private final @NotNull ArrayDeque slowFrameRenderMeasurements = - new ArrayDeque<>(); - private final @NotNull ArrayDeque frozenFrameRenderMeasurements = - new ArrayDeque<>(); - private final @NotNull Map measurementsMap = new HashMap<>(); - private final @Nullable LazyEvaluator.Evaluator - timeoutExecutorServiceSupplier; - private final @NotNull ILogger logger; - private volatile boolean isRunning = false; - protected final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - - public AndroidProfiler( - final @NotNull String tracesFilesDirPath, - final int intervalUs, - final @NotNull SentryFrameMetricsCollector frameMetricsCollector, - final @Nullable LazyEvaluator.Evaluator - timeoutExecutorServiceSupplier, - final @NotNull ILogger logger) { - this.traceFilesDir = - new File(Objects.requireNonNull(tracesFilesDirPath, "TracesFilesDirPath is required")); - this.intervalUs = intervalUs; - this.logger = Objects.requireNonNull(logger, "Logger is required"); - // Timeout executor is nullable, as timeouts will not be there for continuous profiling - this.timeoutExecutorServiceSupplier = timeoutExecutorServiceSupplier; - this.frameMetricsCollector = - Objects.requireNonNull(frameMetricsCollector, "SentryFrameMetricsCollector is required"); - } - - @SuppressLint("NewApi") - public @Nullable ProfileStartData start() { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - // intervalUs is 0 only if there was a problem in the init - if (intervalUs == 0) { - logger.log( - SentryLevel.WARNING, "Disabling profiling because intervaUs is set to %d", intervalUs); - return null; - } - - if (isRunning) { - logger.log(SentryLevel.WARNING, "Profiling has already started..."); - return null; - } - - // We create a file with a uuid name, so no need to check if it already exists - traceFile = new File(traceFilesDir, SentryUUID.generateSentryId() + ".trace"); - - measurementsMap.clear(); - screenFrameRateMeasurements.clear(); - slowFrameRenderMeasurements.clear(); - frozenFrameRenderMeasurements.clear(); - - frameMetricsCollectorId = - frameMetricsCollector.startCollection( - new SentryFrameMetricsCollector.FrameMetricsCollectorListener() { - float lastRefreshRate = 0; - - @Override - public void onFrameMetricCollected( - final long frameStartNanos, - final long frameEndNanos, - final long durationNanos, - final long delayNanos, - final boolean isSlow, - final boolean isFrozen, - final float refreshRate) { - // profileStartNanos is calculated through SystemClock.elapsedRealtimeNanos(), - // but frameEndNanos uses System.nanotime(), so we convert it to get the timestamp - // relative to profileStartNanos - final long timestampNanos = new SentryNanotimeDate().nanoTimestamp(); - final long frameTimestampRelativeNanos = - frameEndNanos - - System.nanoTime() - + SystemClock.elapsedRealtimeNanos() - - profileStartNanos; - - // We don't allow negative relative timestamps. - // So we add a check, even if this should never happen. - if (frameTimestampRelativeNanos < 0) { - return; - } - if (isFrozen) { - frozenFrameRenderMeasurements.addLast( - new ProfileMeasurementValue( - frameTimestampRelativeNanos, durationNanos, timestampNanos)); - } else if (isSlow) { - slowFrameRenderMeasurements.addLast( - new ProfileMeasurementValue( - frameTimestampRelativeNanos, durationNanos, timestampNanos)); - } - if (refreshRate != lastRefreshRate) { - lastRefreshRate = refreshRate; - screenFrameRateMeasurements.addLast( - new ProfileMeasurementValue( - frameTimestampRelativeNanos, refreshRate, timestampNanos)); - } - } - }); - - // We stop profiling after a timeout to avoid huge profiles to be sent - try { - if (timeoutExecutorServiceSupplier != null) { - scheduledFinish = - timeoutExecutorServiceSupplier - .evaluate() - .schedule(() -> endAndCollect(true, null), PROFILING_TIMEOUT_MILLIS); - } - } catch (RejectedExecutionException e) { - logger.log( - SentryLevel.ERROR, - "Failed to call the executor. Profiling will not be automatically finished. Did you call Sentry.close()?", - e); - } - - profileStartNanos = SystemClock.elapsedRealtimeNanos(); - final @NotNull Date profileStartTimestamp = DateUtils.getCurrentDateTime(); - long profileStartCpuMillis = Process.getElapsedCpuTime(); - - // We don't make any check on the file existence or writeable state, because we don't want to - // make file IO in the main thread. - // We cannot offload the work to the executorService, as if that's very busy, profiles could - // start/stop with a lot of delay and even cause ANRs. - try { - // If there is any problem with the file this method will throw (but it will not throw in - // tests) - Debug.startMethodTracingSampling(traceFile.getPath(), BUFFER_SIZE_BYTES, intervalUs); - isRunning = true; - return new ProfileStartData( - profileStartNanos, profileStartCpuMillis, profileStartTimestamp); - } catch (Throwable e) { - endAndCollect(false, null); - logger.log(SentryLevel.ERROR, "Unable to start a profile: ", e); - isRunning = false; - return null; - } - } - } - - @SuppressLint("NewApi") - public @Nullable ProfileEndData endAndCollect( - final boolean isTimeout, - final @Nullable List performanceCollectionData) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (!isRunning) { - logger.log(SentryLevel.WARNING, "Profiler not running"); - return null; - } - - try { - // If there is any problem with the file this method could throw, but the start is also - // wrapped, so this should never happen (except for tests, where this is the only method - // that throws) - Debug.stopMethodTracing(); - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Error while stopping profiling: ", e); - } finally { - isRunning = false; - } - frameMetricsCollector.stopCollection(frameMetricsCollectorId); - - long transactionEndNanos = SystemClock.elapsedRealtimeNanos(); - long transactionEndCpuMillis = Process.getElapsedCpuTime(); - - if (traceFile == null) { - logger.log(SentryLevel.ERROR, "Trace file does not exists"); - return null; - } - - if (!slowFrameRenderMeasurements.isEmpty()) { - measurementsMap.put( - ProfileMeasurement.ID_SLOW_FRAME_RENDERS, - new ProfileMeasurement( - ProfileMeasurement.UNIT_NANOSECONDS, slowFrameRenderMeasurements)); - } - if (!frozenFrameRenderMeasurements.isEmpty()) { - measurementsMap.put( - ProfileMeasurement.ID_FROZEN_FRAME_RENDERS, - new ProfileMeasurement( - ProfileMeasurement.UNIT_NANOSECONDS, frozenFrameRenderMeasurements)); - } - if (!screenFrameRateMeasurements.isEmpty()) { - measurementsMap.put( - ProfileMeasurement.ID_SCREEN_FRAME_RATES, - new ProfileMeasurement(ProfileMeasurement.UNIT_HZ, screenFrameRateMeasurements)); - } - putPerformanceCollectionDataInMeasurements(performanceCollectionData); - - if (scheduledFinish != null) { - scheduledFinish.cancel(true); - scheduledFinish = null; - } - - return new ProfileEndData( - transactionEndNanos, transactionEndCpuMillis, isTimeout, traceFile, measurementsMap); - } - } - - public void close() { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - // we cancel any scheduled work - if (scheduledFinish != null) { - scheduledFinish.cancel(true); - scheduledFinish = null; - } - - // stop profiling if running - if (isRunning) { - endAndCollect(true, null); - } - } - } - - @SuppressLint("NewApi") - private void putPerformanceCollectionDataInMeasurements( - final @Nullable List performanceCollectionData) { - - // This difference is required, since the PerformanceCollectionData timestamps are expressed in - // terms of System.currentTimeMillis() and measurements timestamps require the nanoseconds since - // the beginning, expressed with SystemClock.elapsedRealtimeNanos() - long timestampDiff = - SystemClock.elapsedRealtimeNanos() - - profileStartNanos - - TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); - if (performanceCollectionData != null) { - final @NotNull ArrayDeque memoryUsageMeasurements = - new ArrayDeque<>(performanceCollectionData.size()); - final @NotNull ArrayDeque nativeMemoryUsageMeasurements = - new ArrayDeque<>(performanceCollectionData.size()); - final @NotNull ArrayDeque cpuUsageMeasurements = - new ArrayDeque<>(performanceCollectionData.size()); - - synchronized (performanceCollectionData) { - for (final @NotNull PerformanceCollectionData data : performanceCollectionData) { - final long nanoTimestamp = data.getNanoTimestamp(); - final long relativeStartNs = nanoTimestamp + timestampDiff; - final @Nullable Double cpuUsagePercentage = data.getCpuUsagePercentage(); - final @Nullable Long usedHeapMemory = data.getUsedHeapMemory(); - final @Nullable Long usedNativeMemory = data.getUsedNativeMemory(); - - if (cpuUsagePercentage != null) { - cpuUsageMeasurements.add( - new ProfileMeasurementValue(relativeStartNs, cpuUsagePercentage, nanoTimestamp)); - } - if (usedHeapMemory != null) { - memoryUsageMeasurements.add( - new ProfileMeasurementValue(relativeStartNs, usedHeapMemory, nanoTimestamp)); - } - if (usedNativeMemory != null) { - nativeMemoryUsageMeasurements.add( - new ProfileMeasurementValue(relativeStartNs, usedNativeMemory, nanoTimestamp)); - } - } - } - - if (!cpuUsageMeasurements.isEmpty()) { - measurementsMap.put( - ProfileMeasurement.ID_CPU_USAGE, - new ProfileMeasurement(ProfileMeasurement.UNIT_PERCENT, cpuUsageMeasurements)); - } - if (!memoryUsageMeasurements.isEmpty()) { - measurementsMap.put( - ProfileMeasurement.ID_MEMORY_FOOTPRINT, - new ProfileMeasurement(ProfileMeasurement.UNIT_BYTES, memoryUsageMeasurements)); - } - if (!nativeMemoryUsageMeasurements.isEmpty()) { - measurementsMap.put( - ProfileMeasurement.ID_MEMORY_NATIVE_FOOTPRINT, - new ProfileMeasurement(ProfileMeasurement.UNIT_BYTES, nativeMemoryUsageMeasurements)); - } - } - } - - boolean isRunning() { - return isRunning; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidSocketTagger.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidSocketTagger.java deleted file mode 100644 index 7c4afd309f2..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidSocketTagger.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.sentry.android.core; - -import android.net.TrafficStats; -import io.sentry.ISocketTagger; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -public final class AndroidSocketTagger implements ISocketTagger { - - // just a random number to tag outgoing traffic from the Sentry SDK - private static final int SENTRY_TAG = 0xF001; - - private static final AndroidSocketTagger instance = new AndroidSocketTagger(); - - private AndroidSocketTagger() {} - - public static AndroidSocketTagger getInstance() { - return instance; - } - - @Override - public void tagSockets() { - TrafficStats.setThreadStatsTag(SENTRY_TAG); - } - - @Override - public void untagSockets() { - TrafficStats.clearThreadStatsTag(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java deleted file mode 100644 index e44ed746a08..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java +++ /dev/null @@ -1,347 +0,0 @@ -package io.sentry.android.core; - -import static java.util.concurrent.TimeUnit.SECONDS; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.Build; -import io.sentry.DateUtils; -import io.sentry.ILogger; -import io.sentry.ISentryExecutorService; -import io.sentry.ISentryLifecycleToken; -import io.sentry.ITransaction; -import io.sentry.ITransactionProfiler; -import io.sentry.PerformanceCollectionData; -import io.sentry.ProfilingTraceData; -import io.sentry.ProfilingTransactionData; -import io.sentry.ScopesAdapter; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.android.core.internal.util.CpuInfoUtils; -import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.LazyEvaluator; -import io.sentry.util.Objects; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -final class AndroidTransactionProfiler implements ITransactionProfiler { - private final @NotNull Context context; - private final @NotNull ILogger logger; - private final @Nullable String profilingTracesDirPath; - private final boolean isProfilingEnabled; - private final int profilingTracesHz; - private final @NotNull LazyEvaluator.Evaluator executorServiceSupplier; - private final @NotNull BuildInfoProvider buildInfoProvider; - private boolean isInitialized = false; - private final @NotNull AtomicBoolean isRunning = new AtomicBoolean(false); - private final @NotNull SentryFrameMetricsCollector frameMetricsCollector; - private volatile @Nullable ProfilingTransactionData currentProfilingTransactionData; - - /** - * The underlying profiler instance. It is thread safe to call it after checking if it's not null, - * because we never nullify it after instantiation. - */ - private volatile @Nullable AndroidProfiler profiler = null; - - private long profileStartNanos; - private long profileStartCpuMillis; - private @NotNull Date profileStartTimestamp; - private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - - public AndroidTransactionProfiler( - final @NotNull Context context, - final @NotNull SentryAndroidOptions sentryAndroidOptions, - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull SentryFrameMetricsCollector frameMetricsCollector) { - this( - context, - buildInfoProvider, - frameMetricsCollector, - sentryAndroidOptions.getLogger(), - sentryAndroidOptions.getProfilingTracesDirPath(), - sentryAndroidOptions.isProfilingEnabled(), - sentryAndroidOptions.getProfilingTracesHz(), - () -> sentryAndroidOptions.getExecutorService()); - } - - public AndroidTransactionProfiler( - final @NotNull Context context, - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull SentryFrameMetricsCollector frameMetricsCollector, - final @NotNull ILogger logger, - final @Nullable String profilingTracesDirPath, - final boolean isProfilingEnabled, - final int profilingTracesHz, - final @NotNull ISentryExecutorService executorService) { - this( - context, - buildInfoProvider, - frameMetricsCollector, - logger, - profilingTracesDirPath, - isProfilingEnabled, - profilingTracesHz, - () -> executorService); - } - - public AndroidTransactionProfiler( - final @NotNull Context context, - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull SentryFrameMetricsCollector frameMetricsCollector, - final @NotNull ILogger logger, - final @Nullable String profilingTracesDirPath, - final boolean isProfilingEnabled, - final int profilingTracesHz, - final @NotNull LazyEvaluator.Evaluator executorServiceSupplier) { - this.context = - Objects.requireNonNull( - ContextUtils.getApplicationContext(context), "The application context is required"); - this.logger = Objects.requireNonNull(logger, "ILogger is required"); - this.frameMetricsCollector = - Objects.requireNonNull(frameMetricsCollector, "SentryFrameMetricsCollector is required"); - this.buildInfoProvider = - Objects.requireNonNull(buildInfoProvider, "The BuildInfoProvider is required."); - this.profilingTracesDirPath = profilingTracesDirPath; - this.isProfilingEnabled = isProfilingEnabled; - this.profilingTracesHz = profilingTracesHz; - this.executorServiceSupplier = - Objects.requireNonNull( - executorServiceSupplier, "A supplier for ISentryExecutorService is required."); - this.profileStartTimestamp = DateUtils.getCurrentDateTime(); - } - - private void init() { - // We initialize it only once - if (isInitialized) { - return; - } - isInitialized = true; - - if (!isProfilingEnabled) { - logger.log(SentryLevel.INFO, "Profiling is disabled in options."); - return; - } - if (profilingTracesDirPath == null) { - logger.log( - SentryLevel.WARNING, - "Disabling profiling because no profiling traces dir path is defined in options."); - return; - } - if (profilingTracesHz <= 0) { - logger.log( - SentryLevel.WARNING, - "Disabling profiling because trace rate is set to %d", - profilingTracesHz); - return; - } - - profiler = - new AndroidProfiler( - profilingTracesDirPath, - (int) SECONDS.toMicros(1) / profilingTracesHz, - frameMetricsCollector, - executorServiceSupplier, - logger); - } - - @Override - public void start() { - // Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler - // causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392 - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return; - - // When the first transaction is starting, we can start profiling - if (!isRunning.getAndSet(true)) { - // Let's initialize trace folder and profiling interval - init(); - - if (onFirstStart()) { - logger.log(SentryLevel.DEBUG, "Profiler started."); - } else { - // If profiler is not null and is running, it means that a profile is already running - if (profiler != null && profiler.isRunning()) { - logger.log( - SentryLevel.WARNING, "A profile is already running. This profile will be ignored."); - } else { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - // Ensure we unbind any transaction data, just in case of concurrent starts - currentProfilingTransactionData = null; - } - // Otherwise we update the flag, because it means the profiler is not running - isRunning.set(false); - } - } - } - } - - @SuppressLint("NewApi") - private boolean onFirstStart() { - // init() didn't create profiler, should never happen - if (profiler == null) { - return false; - } - - final AndroidProfiler.ProfileStartData startData = profiler.start(); - // check if profiling started - if (startData == null) { - return false; - } - profileStartNanos = startData.startNanos; - profileStartCpuMillis = startData.startCpuMillis; - profileStartTimestamp = startData.startTimestamp; - return true; - } - - @Override - public void bindTransaction(final @NotNull ITransaction transaction) { - // If the profiler is running, but no profilingTransactionData is set, we bind it here - if (isRunning.get() && currentProfilingTransactionData == null) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - // If the profiler is running, but no profilingTransactionData is set, we bind it here - if (isRunning.get() && currentProfilingTransactionData == null) { - currentProfilingTransactionData = - new ProfilingTransactionData(transaction, profileStartNanos, profileStartCpuMillis); - } - } - } - } - - @Override - public @Nullable ProfilingTraceData onTransactionFinish( - final @NotNull ITransaction transaction, - final @Nullable List performanceCollectionData, - final @NotNull SentryOptions options) { - return onTransactionFinish( - transaction.getName(), - transaction.getEventId().toString(), - transaction.getSpanContext().getTraceId().toString(), - false, - performanceCollectionData, - options); - } - - @SuppressLint("NewApi") - private @Nullable ProfilingTraceData onTransactionFinish( - final @NotNull String transactionName, - final @NotNull String transactionId, - final @NotNull String traceId, - final boolean isTimeout, - final @Nullable List performanceCollectionData, - final @NotNull SentryOptions options) { - - // onTransactionStart() is only available since Lollipop_MR1 - // and SystemClock.elapsedRealtimeNanos() since Jelly Bean - // and SUPPORTED_ABIS since KITKAT - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return null; - - // check if profiler was created - if (profiler == null) { - return null; - } - - final ProfilingTransactionData txData; - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - txData = currentProfilingTransactionData; - - // Transaction finished, but it's not in the current profile - if (txData == null || !txData.getId().equals(transactionId)) { - // A transaction is finishing, but it's not profiled. We can skip it - logger.log( - SentryLevel.INFO, - "Transaction %s (%s) finished, but was not currently being profiled. Skipping", - transactionName, - traceId); - return null; - } - currentProfilingTransactionData = null; - } - - logger.log(SentryLevel.DEBUG, "Transaction %s (%s) finished.", transactionName, traceId); - - final AndroidProfiler.ProfileEndData endData = - profiler.endAndCollect(false, performanceCollectionData); - - isRunning.set(false); - - // check if profiler end successfully - if (endData == null) { - return null; - } - - long transactionDurationNanos = endData.endNanos - profileStartNanos; - - final @NotNull List transactionList = new ArrayList<>(1); - transactionList.add(txData); - txData.notifyFinish( - endData.endNanos, profileStartNanos, endData.endCpuMillis, profileStartCpuMillis); - - String totalMem = "0"; - final @Nullable Long memory = - (options instanceof SentryAndroidOptions) - ? DeviceInfoUtil.getInstance(context, (SentryAndroidOptions) options).getTotalMemory() - : null; - if (memory != null) { - totalMem = Long.toString(memory); - } - final String[] abis = Build.SUPPORTED_ABIS; - - // cpu max frequencies are read with a lambda because reading files is involved, so it will be - // done in the background when the trace file is read - return new ProfilingTraceData( - endData.traceFile, - profileStartTimestamp, - transactionList, - transactionName, - transactionId, - traceId, - Long.toString(transactionDurationNanos), - buildInfoProvider.getSdkInfoVersion(), - abis != null && abis.length > 0 ? abis[0] : "", - () -> CpuInfoUtils.getInstance().readMaxFrequencies(), - buildInfoProvider.getManufacturer(), - buildInfoProvider.getModel(), - buildInfoProvider.getVersionRelease(), - buildInfoProvider.isEmulator(), - totalMem, - options.getProguardUuid(), - options.getRelease(), - options.getEnvironment(), - (endData.didTimeout || isTimeout) - ? ProfilingTraceData.TRUNCATION_REASON_TIMEOUT - : ProfilingTraceData.TRUNCATION_REASON_NORMAL, - endData.measurementsMap); - } - - @Override - public boolean isRunning() { - return isRunning.get(); - } - - @Override - public void close() { - final @Nullable ProfilingTransactionData txData = currentProfilingTransactionData; - // we stop profiling - if (txData != null) { - onTransactionFinish( - txData.getName(), - txData.getId(), - txData.getTraceId(), - true, - null, - ScopesAdapter.getInstance().getOptions()); - } - // in case the app start profiling is running, and it's not bound to a transaction, we still - // stop profiling, but we also have to manually update the flag. - isRunning.set(false); - - // we have to first stop profiling otherwise we would lost the last profile - if (profiler != null) { - profiler.close(); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransportGate.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransportGate.java deleted file mode 100644 index fa138a802f2..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransportGate.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.sentry.android.core; - -import io.sentry.IConnectionStatusProvider; -import io.sentry.SentryOptions; -import io.sentry.transport.ITransportGate; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.TestOnly; - -final class AndroidTransportGate implements ITransportGate { - - private final @NotNull SentryOptions options; - - AndroidTransportGate(final @NotNull SentryOptions options) { - this.options = options; - } - - @Override - public boolean isConnected() { - return isConnected(options.getConnectionStatusProvider().getConnectionStatus()); - } - - @TestOnly - boolean isConnected(final @NotNull IConnectionStatusProvider.ConnectionStatus status) { - // let's assume its connected if there's no permission or something as we can't really know - // whether is online or not. - switch (status) { - case CONNECTED: - case UNKNOWN: - case NO_PERMISSION: - return true; - default: - return false; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java deleted file mode 100644 index 8243493a50b..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java +++ /dev/null @@ -1,201 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import android.annotation.SuppressLint; -import android.content.Context; -import io.sentry.Hint; -import io.sentry.IScopes; -import io.sentry.ISentryLifecycleToken; -import io.sentry.Integration; -import io.sentry.SentryEvent; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.exception.ExceptionMechanismException; -import io.sentry.hints.AbnormalExit; -import io.sentry.hints.TransactionEnd; -import io.sentry.protocol.Mechanism; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.HintUtils; -import io.sentry.util.Objects; -import java.io.Closeable; -import java.io.IOException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -/** - * When the UI thread of an Android app is blocked for too long, an "Application Not Responding" - * (ANR) error is triggered. Sends an event if an ANR happens - */ -public final class AnrIntegration implements Integration, Closeable { - - private final @NotNull Context context; - private boolean isClosed = false; - private final @NotNull AutoClosableReentrantLock startLock = new AutoClosableReentrantLock(); - - public AnrIntegration(final @NotNull Context context) { - this.context = ContextUtils.getApplicationContext(context); - } - - /** - * Responsible for watching UI thread. being static to avoid multiple instances running at the - * same time. - */ - @SuppressLint("StaticFieldLeak") // we have watchDogLock to avoid those leaks - private static @Nullable ANRWatchDog anrWatchDog; - - private @Nullable SentryOptions options; - - protected static final @NotNull AutoClosableReentrantLock watchDogLock = - new AutoClosableReentrantLock(); - - @Override - public final void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - this.options = Objects.requireNonNull(options, "SentryOptions is required"); - register(scopes, (SentryAndroidOptions) options); - } - - private void register( - final @NotNull IScopes scopes, final @NotNull SentryAndroidOptions options) { - options - .getLogger() - .log(SentryLevel.DEBUG, "AnrIntegration enabled: %s", options.isAnrEnabled()); - - if (options.isAnrEnabled()) { - addIntegrationToSdkVersion("Anr"); - try { - options - .getExecutorService() - .submit( - () -> { - try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { - if (!isClosed) { - startAnrWatchdog(scopes, options); - } - } - }); - } catch (Throwable e) { - options - .getLogger() - .log(SentryLevel.DEBUG, "Failed to start AnrIntegration on executor thread.", e); - } - } - } - - private void startAnrWatchdog( - final @NotNull IScopes scopes, final @NotNull SentryAndroidOptions options) { - try (final @NotNull ISentryLifecycleToken ignored = watchDogLock.acquire()) { - if (anrWatchDog == null) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "ANR timeout in milliseconds: %d", - options.getAnrTimeoutIntervalMillis()); - - anrWatchDog = - new ANRWatchDog( - options.getAnrTimeoutIntervalMillis(), - options.isAnrReportInDebug(), - error -> reportANR(scopes, options, error), - options.getLogger(), - context); - anrWatchDog.start(); - - options.getLogger().log(SentryLevel.DEBUG, "AnrIntegration installed."); - } - } - } - - @TestOnly - void reportANR( - final @NotNull IScopes scopes, - final @NotNull SentryAndroidOptions options, - final @NotNull ApplicationNotResponding error) { - options.getLogger().log(SentryLevel.INFO, "ANR triggered with message: %s", error.getMessage()); - - // if LifecycleWatcher isn't available, we always assume the ANR is foreground - final boolean isAppInBackground = Boolean.TRUE.equals(AppState.getInstance().isInBackground()); - - @SuppressWarnings("ThrowableNotThrown") - final Throwable anrThrowable = buildAnrThrowable(isAppInBackground, options, error); - - final SentryEvent event = new SentryEvent(anrThrowable); - event.setLevel(SentryLevel.ERROR); - - final AnrHint anrHint = new AnrHint(isAppInBackground); - final Hint hint = HintUtils.createWithTypeCheckHint(anrHint); - scopes.captureEvent(event, hint); - } - - private @NotNull Throwable buildAnrThrowable( - final boolean isAppInBackground, - final @NotNull SentryAndroidOptions options, - final @NotNull ApplicationNotResponding anr) { - - String message = "ANR for at least " + options.getAnrTimeoutIntervalMillis() + " ms."; - if (isAppInBackground) { - message = "Background " + message; - } - - final ApplicationNotResponding error = new ApplicationNotResponding(message, anr.getThread()); - final Mechanism mechanism = new Mechanism(); - mechanism.setType("ANR"); - - return new ExceptionMechanismException(mechanism, error, error.getThread(), true); - } - - @TestOnly - @Nullable - ANRWatchDog getANRWatchDog() { - return anrWatchDog; - } - - @Override - public void close() throws IOException { - try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { - isClosed = true; - } - try (final @NotNull ISentryLifecycleToken ignored = watchDogLock.acquire()) { - if (anrWatchDog != null) { - anrWatchDog.interrupt(); - anrWatchDog = null; - if (options != null) { - options.getLogger().log(SentryLevel.DEBUG, "AnrIntegration removed."); - } - } - } - } - - /** - * ANR is an abnormal session exit, according to Develop Docs - * because we don't know whether the app has recovered after it or not. - */ - static final class AnrHint implements AbnormalExit, TransactionEnd { - - private final boolean isBackgroundAnr; - - AnrHint(final boolean isBackgroundAnr) { - this.isBackgroundAnr = isBackgroundAnr; - } - - @Override - public String mechanism() { - return isBackgroundAnr ? "anr_background" : "anr_foreground"; - } - - // We don't want the current thread (watchdog) to be marked as crashed, otherwise the Sentry - // Console prioritizes it over the main thread in the thread's list. - @Override - public boolean ignoreCurrentThread() { - return true; - } - - @Override - public @Nullable Long timestamp() { - return null; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegrationFactory.java b/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegrationFactory.java deleted file mode 100644 index cff8f72cd36..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegrationFactory.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.sentry.android.core; - -import android.content.Context; -import android.os.Build; -import io.sentry.Integration; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public final class AnrIntegrationFactory { - - @NotNull - public static Integration create( - final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) { - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.R) { - return new AnrV2Integration(context); - } else { - return new AnrIntegration(context); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java deleted file mode 100644 index af3a942c8cc..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java +++ /dev/null @@ -1,332 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import android.annotation.SuppressLint; -import android.app.ActivityManager; -import android.app.ApplicationExitInfo; -import android.content.Context; -import io.sentry.Attachment; -import io.sentry.DateUtils; -import io.sentry.Hint; -import io.sentry.ILogger; -import io.sentry.IScopes; -import io.sentry.Integration; -import io.sentry.SentryEvent; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.android.core.cache.AndroidEnvelopeCache; -import io.sentry.android.core.internal.threaddump.Lines; -import io.sentry.android.core.internal.threaddump.ThreadDumpParser; -import io.sentry.hints.AbnormalExit; -import io.sentry.hints.Backfillable; -import io.sentry.hints.BlockingFlushHint; -import io.sentry.protocol.DebugImage; -import io.sentry.protocol.DebugMeta; -import io.sentry.protocol.Message; -import io.sentry.protocol.SentryId; -import io.sentry.protocol.SentryThread; -import io.sentry.transport.CurrentDateProvider; -import io.sentry.transport.ICurrentDateProvider; -import io.sentry.util.HintUtils; -import io.sentry.util.Objects; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.List; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@SuppressLint("NewApi") // we check this in AnrIntegrationFactory -public class AnrV2Integration implements Integration, Closeable { - - private final @NotNull Context context; - private final @NotNull ICurrentDateProvider dateProvider; - private @Nullable SentryAndroidOptions options; - - public AnrV2Integration(final @NotNull Context context) { - // using CurrentDateProvider instead of AndroidCurrentDateProvider as AppExitInfo uses - // System.currentTimeMillis - this(context, CurrentDateProvider.getInstance()); - } - - AnrV2Integration( - final @NotNull Context context, final @NotNull ICurrentDateProvider dateProvider) { - this.context = ContextUtils.getApplicationContext(context); - this.dateProvider = dateProvider; - } - - @SuppressLint("NewApi") // we do the check in the AnrIntegrationFactory - @Override - public void register(@NotNull IScopes scopes, @NotNull SentryOptions options) { - this.options = - Objects.requireNonNull( - (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, - "SentryAndroidOptions is required"); - - this.options - .getLogger() - .log(SentryLevel.DEBUG, "AnrIntegration enabled: %s", this.options.isAnrEnabled()); - - if (this.options.getCacheDirPath() == null) { - this.options - .getLogger() - .log(SentryLevel.INFO, "Cache dir is not set, unable to process ANRs"); - return; - } - - if (this.options.isAnrEnabled()) { - try { - options - .getExecutorService() - .submit( - new ApplicationExitInfoHistoryDispatcher( - context, scopes, this.options, dateProvider, new AnrV2Policy(this.options))); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.DEBUG, "Failed to start ANR processor.", e); - } - options.getLogger().log(SentryLevel.DEBUG, "AnrV2Integration installed."); - addIntegrationToSdkVersion("AnrV2"); - } - } - - @Override - public void close() throws IOException { - if (options != null) { - options.getLogger().log(SentryLevel.DEBUG, "AnrV2Integration removed."); - } - } - - private static final class AnrV2Policy - implements ApplicationExitInfoHistoryDispatcher.ApplicationExitInfoPolicy { - - private final @NotNull SentryAndroidOptions options; - - AnrV2Policy(final @NotNull SentryAndroidOptions options) { - this.options = options; - } - - @Override - public @NotNull String getLabel() { - return "ANR"; - } - - @Override - public int getTargetReason() { - return ApplicationExitInfo.REASON_ANR; - } - - @Override - public boolean shouldReportHistorical() { - return options.isReportHistoricalAnrs(); - } - - @Override - public @Nullable Long getLastReportedTimestamp() { - return AndroidEnvelopeCache.lastReportedAnr(options); - } - - @Override - public @Nullable ApplicationExitInfoHistoryDispatcher.Report buildReport( - final @NotNull ApplicationExitInfo exitInfo, final boolean shouldEnrich) { - final long anrTimestamp = exitInfo.getTimestamp(); - final boolean isBackground = - exitInfo.getImportance() != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; - - final ParseResult result = parseThreadDump(exitInfo, isBackground); - if (result.type == ParseResult.Type.NO_DUMP) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "Not reporting ANR event as there was no thread dump for the ANR %s", - exitInfo.toString()); - return null; - } - final AnrV2Hint anrHint = - new AnrV2Hint( - options.getFlushTimeoutMillis(), - options.getLogger(), - anrTimestamp, - shouldEnrich, - isBackground); - - final Hint hint = HintUtils.createWithTypeCheckHint(anrHint); - - final SentryEvent event = new SentryEvent(); - if (result.type == ParseResult.Type.ERROR) { - final Message sentryMessage = new Message(); - sentryMessage.setFormatted( - "Sentry Android SDK failed to parse system thread dump for " - + "this ANR. We recommend enabling [SentryOptions.isAttachAnrThreadDump] option " - + "to attach the thread dump as plain text and report this issue on GitHub."); - event.setMessage(sentryMessage); - } else if (result.type == ParseResult.Type.DUMP) { - event.setThreads(result.threads); - if (result.debugImages != null) { - final DebugMeta debugMeta = new DebugMeta(); - debugMeta.setImages(result.debugImages); - event.setDebugMeta(debugMeta); - } - } - event.setLevel(SentryLevel.FATAL); - event.setTimestamp(DateUtils.getDateTime(anrTimestamp)); - - if (options.isAttachAnrThreadDump()) { - if (result.dump != null) { - hint.setThreadDump(Attachment.fromThreadDump(result.dump)); - } - } - - return new ApplicationExitInfoHistoryDispatcher.Report(event, hint, anrHint); - } - - private @NotNull ParseResult parseThreadDump( - final @NotNull ApplicationExitInfo exitInfo, final boolean isBackground) { - final byte[] dump; - - try (final InputStream trace = exitInfo.getTraceInputStream()) { - if (trace == null) { - return new ParseResult(ParseResult.Type.NO_DUMP); - } - dump = getDumpBytes(trace); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.WARNING, "Failed to read ANR thread dump", e); - return new ParseResult(ParseResult.Type.NO_DUMP); - } - - try (final BufferedReader reader = - new BufferedReader(new InputStreamReader(new ByteArrayInputStream(dump)))) { - final Lines lines = Lines.readLines(reader); - - final ThreadDumpParser threadDumpParser = new ThreadDumpParser(options, isBackground); - threadDumpParser.parse(lines); - - final @NotNull List threads = threadDumpParser.getThreads(); - final @NotNull List debugImages = threadDumpParser.getDebugImages(); - - if (threads.isEmpty()) { - // if the list is empty this means the system failed to capture a proper thread dump of - // the android threads, and only contains kernel-level threads and statuses, those ANRs - // are not actionable and neither they are reported by Google Play Console, so we just - // fall back to not reporting them - return new ParseResult(ParseResult.Type.NO_DUMP); - } - return new ParseResult(ParseResult.Type.DUMP, dump, threads, debugImages); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.WARNING, "Failed to parse ANR thread dump", e); - return new ParseResult(ParseResult.Type.ERROR, dump); - } - } - - private byte[] getDumpBytes(final @NotNull InputStream trace) throws IOException { - try (final ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { - - int nRead; - final byte[] data = new byte[1024]; - - while ((nRead = trace.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); - } - - return buffer.toByteArray(); - } - } - } - - @ApiStatus.Internal - public static final class AnrV2Hint extends BlockingFlushHint - implements Backfillable, AbnormalExit { - - private final long timestamp; - - private final boolean shouldEnrich; - - private final boolean isBackgroundAnr; - - public AnrV2Hint( - final long flushTimeoutMillis, - final @NotNull ILogger logger, - final long timestamp, - final boolean shouldEnrich, - final boolean isBackgroundAnr) { - super(flushTimeoutMillis, logger); - this.timestamp = timestamp; - this.shouldEnrich = shouldEnrich; - this.isBackgroundAnr = isBackgroundAnr; - } - - @Override - public boolean ignoreCurrentThread() { - return false; - } - - @NotNull - @Override - public Long timestamp() { - return timestamp; - } - - @Override - public boolean shouldEnrich() { - return shouldEnrich; - } - - @Override - public String mechanism() { - return isBackgroundAnr ? "anr_background" : "anr_foreground"; - } - - @Override - public boolean isFlushable(@Nullable SentryId eventId) { - return true; - } - - @Override - public void setFlushable(@NotNull SentryId eventId) {} - } - - static final class ParseResult { - - enum Type { - DUMP, - NO_DUMP, - ERROR - } - - final Type type; - final byte[] dump; - final @Nullable List threads; - final @Nullable List debugImages; - - ParseResult(final @NotNull Type type) { - this.type = type; - this.dump = null; - this.threads = null; - this.debugImages = null; - } - - ParseResult(final @NotNull Type type, final byte[] dump) { - this.type = type; - this.dump = dump; - this.threads = null; - this.debugImages = null; - } - - ParseResult( - final @NotNull Type type, - final byte[] dump, - final @Nullable List threads, - final @Nullable List debugImages) { - this.type = type; - this.dump = dump; - this.threads = threads; - this.debugImages = debugImages; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java deleted file mode 100644 index 43ed3422cd9..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java +++ /dev/null @@ -1,181 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.TypeCheckHint.ANDROID_CONFIGURATION; -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import android.content.ComponentCallbacks2; -import android.content.Context; -import android.content.res.Configuration; -import io.sentry.Breadcrumb; -import io.sentry.Hint; -import io.sentry.IScopes; -import io.sentry.Integration; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.android.core.internal.util.AndroidCurrentDateProvider; -import io.sentry.android.core.internal.util.Debouncer; -import io.sentry.android.core.internal.util.DeviceOrientations; -import io.sentry.protocol.Device; -import io.sentry.util.Objects; -import java.io.Closeable; -import java.io.IOException; -import java.util.Locale; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public final class AppComponentsBreadcrumbsIntegration - implements Integration, Closeable, ComponentCallbacks2 { - - private static final long DEBOUNCE_WAIT_TIME_MS = 60 * 1000; - // pre-allocate hint to avoid creating it every time for the low memory case - private static final @NotNull Hint EMPTY_HINT = new Hint(); - - private final @NotNull Context context; - private @Nullable IScopes scopes; - private @Nullable SentryAndroidOptions options; - - private final @NotNull Debouncer trimMemoryDebouncer = - new Debouncer(AndroidCurrentDateProvider.getInstance(), DEBOUNCE_WAIT_TIME_MS, 0); - - public AppComponentsBreadcrumbsIntegration(final @NotNull Context context) { - this.context = - Objects.requireNonNull(ContextUtils.getApplicationContext(context), "Context is required"); - } - - @Override - public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - this.scopes = Objects.requireNonNull(scopes, "Scopes are required"); - this.options = - Objects.requireNonNull( - (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, - "SentryAndroidOptions is required"); - - this.options - .getLogger() - .log( - SentryLevel.DEBUG, - "AppComponentsBreadcrumbsIntegration enabled: %s", - this.options.isEnableAppComponentBreadcrumbs()); - - if (this.options.isEnableAppComponentBreadcrumbs()) { - try { - // if its a ContextImpl, registerComponentCallbacks can't be used - context.registerComponentCallbacks(this); - options - .getLogger() - .log(SentryLevel.DEBUG, "AppComponentsBreadcrumbsIntegration installed."); - addIntegrationToSdkVersion("AppComponentsBreadcrumbs"); - } catch (Throwable e) { - this.options.setEnableAppComponentBreadcrumbs(false); - options.getLogger().log(SentryLevel.INFO, e, "ComponentCallbacks2 is not available."); - } - } - } - - @Override - public void close() throws IOException { - try { - // if its a ContextImpl, unregisterComponentCallbacks can't be used - context.unregisterComponentCallbacks(this); - } catch (Throwable ignored) { - // fine, might throw on older versions - if (options != null) { - options - .getLogger() - .log(SentryLevel.DEBUG, ignored, "It was not possible to unregisterComponentCallbacks"); - } - } - - if (options != null) { - options.getLogger().log(SentryLevel.DEBUG, "AppComponentsBreadcrumbsIntegration removed."); - } - } - - @SuppressWarnings("deprecation") - @Override - public void onConfigurationChanged(@NotNull Configuration newConfig) { - final long now = System.currentTimeMillis(); - executeInBackground(() -> captureConfigurationChangedBreadcrumb(now, newConfig)); - } - - @SuppressWarnings("deprecation") - @Override - public void onLowMemory() { - // we do this in onTrimMemory below already, this is legacy API (14 or below) - } - - @Override - public void onTrimMemory(final int level) { - if (level < TRIM_MEMORY_BACKGROUND) { - // only add breadcrumb if TRIM_MEMORY_BACKGROUND, TRIM_MEMORY_MODERATE or - // TRIM_MEMORY_COMPLETE. - // Release as much memory as the process can. - - // TRIM_MEMORY_UI_HIDDEN, TRIM_MEMORY_RUNNING_MODERATE, TRIM_MEMORY_RUNNING_LOW and - // TRIM_MEMORY_RUNNING_CRITICAL. - // Release any memory that your app doesn't need to run. - // So they are still not so critical at the point of killing the process. - // https://developer.android.com/topic/performance/memory - return; - } - - if (trimMemoryDebouncer.checkForDebounce()) { - // if we received trim_memory within 1 minute time, ignore this call - return; - } - - final long now = System.currentTimeMillis(); - executeInBackground(() -> captureLowMemoryBreadcrumb(now, level)); - } - - private void captureLowMemoryBreadcrumb(final long timeMs, final int level) { - if (scopes != null) { - final Breadcrumb breadcrumb = new Breadcrumb(timeMs); - breadcrumb.setType("system"); - breadcrumb.setCategory("device.event"); - breadcrumb.setMessage("Low memory"); - breadcrumb.setData("action", "LOW_MEMORY"); - breadcrumb.setData("level", level); - breadcrumb.setLevel(SentryLevel.WARNING); - scopes.addBreadcrumb(breadcrumb, EMPTY_HINT); - } - } - - private void captureConfigurationChangedBreadcrumb( - final long timeMs, final @NotNull Configuration newConfig) { - if (scopes != null) { - final Device.DeviceOrientation deviceOrientation = - DeviceOrientations.getOrientation(context.getResources().getConfiguration().orientation); - - String orientation; - if (deviceOrientation != null) { - orientation = deviceOrientation.name().toLowerCase(Locale.ROOT); - } else { - orientation = "undefined"; - } - - final Breadcrumb breadcrumb = new Breadcrumb(timeMs); - breadcrumb.setType("navigation"); - breadcrumb.setCategory("device.orientation"); - breadcrumb.setData("position", orientation); - breadcrumb.setLevel(SentryLevel.INFO); - - final Hint hint = new Hint(); - hint.set(ANDROID_CONFIGURATION, newConfig); - - scopes.addBreadcrumb(breadcrumb, hint); - } - } - - private void executeInBackground(final @NotNull Runnable runnable) { - if (options != null) { - try { - options.getExecutorService().submit(runnable); - } catch (Throwable t) { - options - .getLogger() - .log(SentryLevel.ERROR, t, "Failed to submit app components breadcrumb task"); - } - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AppLifecycleIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AppLifecycleIntegration.java deleted file mode 100644 index 9fd90b23099..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AppLifecycleIntegration.java +++ /dev/null @@ -1,92 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import io.sentry.IScopes; -import io.sentry.ISentryLifecycleToken; -import io.sentry.Integration; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.Objects; -import java.io.Closeable; -import java.io.IOException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -public final class AppLifecycleIntegration implements Integration, Closeable { - - private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - @TestOnly @Nullable volatile LifecycleWatcher watcher; - - private @Nullable SentryAndroidOptions options; - - @Override - public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - Objects.requireNonNull(scopes, "Scopes are required"); - this.options = - Objects.requireNonNull( - (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, - "SentryAndroidOptions is required"); - - this.options - .getLogger() - .log( - SentryLevel.DEBUG, - "enableSessionTracking enabled: %s", - this.options.isEnableAutoSessionTracking()); - - this.options - .getLogger() - .log( - SentryLevel.DEBUG, - "enableAppLifecycleBreadcrumbs enabled: %s", - this.options.isEnableAppLifecycleBreadcrumbs()); - - if (this.options.isEnableAutoSessionTracking() - || this.options.isEnableAppLifecycleBreadcrumbs()) { - try (final ISentryLifecycleToken ignored = lock.acquire()) { - if (watcher != null) { - return; - } - - watcher = - new LifecycleWatcher( - scopes, - this.options.getSessionTrackingIntervalMillis(), - this.options.isEnableAutoSessionTracking(), - this.options.isEnableAppLifecycleBreadcrumbs()); - - AppState.getInstance().addAppStateListener(watcher); - } - - options.getLogger().log(SentryLevel.DEBUG, "AppLifecycleIntegration installed."); - addIntegrationToSdkVersion("AppLifecycle"); - } - } - - private void removeObserver() { - final @Nullable LifecycleWatcher watcherRef; - try (final ISentryLifecycleToken ignored = lock.acquire()) { - watcherRef = watcher; - watcher = null; - } - - if (watcherRef != null) { - AppState.getInstance().removeAppStateListener(watcherRef); - if (options != null) { - options.getLogger().log(SentryLevel.DEBUG, "AppLifecycleIntegration removed."); - } - } - } - - @Override - public void close() throws IOException { - removeObserver(); - // TODO: probably should move it to Scopes.close(), but that'd require a new interface and - // different implementations for Java and Android. This is probably fine like this too, because - // integrations are closed in the same place - AppState.getInstance().unregisterLifecycleObserver(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AppState.java b/sentry-android-core/src/main/java/io/sentry/android/core/AppState.java deleted file mode 100644 index 74522f7aaac..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AppState.java +++ /dev/null @@ -1,221 +0,0 @@ -package io.sentry.android.core; - -import androidx.annotation.NonNull; -import androidx.lifecycle.DefaultLifecycleObserver; -import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.ProcessLifecycleOwner; -import io.sentry.ILogger; -import io.sentry.ISentryLifecycleToken; -import io.sentry.NoOpLogger; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.android.core.internal.util.AndroidThreadChecker; -import io.sentry.util.AutoClosableReentrantLock; -import java.io.Closeable; -import java.io.IOException; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -/** AppState holds the state of the App, e.g. whether the app is in background/foreground, etc. */ -@ApiStatus.Internal -public final class AppState implements Closeable { - private static @NotNull AppState instance = new AppState(); - private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - private volatile LifecycleObserver lifecycleObserver; - private MainLooperHandler handler = new MainLooperHandler(); - - private AppState() {} - - public static @NotNull AppState getInstance() { - return instance; - } - - private volatile @Nullable Boolean inBackground = null; - - @TestOnly - void setHandler(final @NotNull MainLooperHandler handler) { - this.handler = handler; - } - - @ApiStatus.Internal - @TestOnly - public void resetInstance() { - instance = new AppState(); - } - - @ApiStatus.Internal - @TestOnly - public LifecycleObserver getLifecycleObserver() { - return lifecycleObserver; - } - - public @Nullable Boolean isInBackground() { - return inBackground; - } - - void setInBackground(final boolean inBackground) { - this.inBackground = inBackground; - } - - public void addAppStateListener(final @NotNull AppStateListener listener) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - ensureLifecycleObserver(NoOpLogger.getInstance()); - - if (lifecycleObserver != null) { - lifecycleObserver.listeners.add(listener); - } - } - } - - public void removeAppStateListener(final @NotNull AppStateListener listener) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (lifecycleObserver != null) { - lifecycleObserver.listeners.remove(listener); - } - } - } - - @ApiStatus.Internal - public void registerLifecycleObserver(final @Nullable SentryOptions options) { - if (lifecycleObserver != null) { - return; - } - - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - ensureLifecycleObserver(options != null ? options.getLogger() : NoOpLogger.getInstance()); - } - } - - private void ensureLifecycleObserver(final @NotNull ILogger logger) { - if (lifecycleObserver != null) { - return; - } - try { - Class.forName("androidx.lifecycle.ProcessLifecycleOwner"); - // create it right away, so it's available in addAppStateListener in case it's posted to main - // thread - lifecycleObserver = new LifecycleObserver(); - - if (AndroidThreadChecker.getInstance().isMainThread()) { - addObserverInternal(logger); - } else { - // some versions of the androidx lifecycle-process require this to be executed on the main - // thread. - handler.post(() -> addObserverInternal(logger)); - } - } catch (ClassNotFoundException e) { - logger.log( - SentryLevel.WARNING, - "androidx.lifecycle is not available, some features might not be properly working," - + "e.g. Session Tracking, Network and System Events breadcrumbs, etc."); - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "AppState could not register lifecycle observer", e); - } - } - - private void addObserverInternal(final @NotNull ILogger logger) { - final @Nullable LifecycleObserver observerRef = lifecycleObserver; - try { - // might already be unregistered/removed so we have to check for nullability - if (observerRef != null) { - ProcessLifecycleOwner.get().getLifecycle().addObserver(observerRef); - } - } catch (Throwable e) { - // This is to handle a potential 'AbstractMethodError' gracefully. The error is triggered in - // connection with conflicting dependencies of the androidx.lifecycle. - // //See the issue here: https://github.com/getsentry/sentry-java/pull/2228 - lifecycleObserver = null; - logger.log( - SentryLevel.ERROR, - "AppState failed to get Lifecycle and could not install lifecycle observer.", - e); - } - } - - @ApiStatus.Internal - public void unregisterLifecycleObserver() { - if (lifecycleObserver == null) { - return; - } - - final @Nullable LifecycleObserver ref; - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - ref = lifecycleObserver; - lifecycleObserver.listeners.clear(); - lifecycleObserver = null; - } - - if (AndroidThreadChecker.getInstance().isMainThread()) { - removeObserverInternal(ref); - } else { - // some versions of the androidx lifecycle-process require this to be executed on the main - // thread. - // avoid method refs on Android due to some issues with older AGP setups - // noinspection Convert2MethodRef - handler.post(() -> removeObserverInternal(ref)); - } - } - - private void removeObserverInternal(final @Nullable LifecycleObserver ref) { - if (ref != null) { - ProcessLifecycleOwner.get().getLifecycle().removeObserver(ref); - } - } - - @Override - public void close() throws IOException { - unregisterLifecycleObserver(); - } - - @ApiStatus.Internal - public final class LifecycleObserver implements DefaultLifecycleObserver { - final List listeners = - new CopyOnWriteArrayList() { - @Override - public boolean add(AppStateListener appStateListener) { - final boolean addResult = super.add(appStateListener); - // notify the listeners immediately to let them "catch up" with the current state - // (mimics the behavior of androidx.lifecycle) - if (Boolean.FALSE.equals(inBackground)) { - appStateListener.onForeground(); - } else if (Boolean.TRUE.equals(inBackground)) { - appStateListener.onBackground(); - } - return addResult; - } - }; - - @ApiStatus.Internal - @TestOnly - public List getListeners() { - return listeners; - } - - @Override - public void onStart(@NonNull LifecycleOwner owner) { - setInBackground(false); - for (AppStateListener listener : listeners) { - listener.onForeground(); - } - } - - @Override - public void onStop(@NonNull LifecycleOwner owner) { - setInBackground(true); - for (AppStateListener listener : listeners) { - listener.onBackground(); - } - } - } - - // If necessary, we can adjust this and add other callbacks in the future - public interface AppStateListener { - void onForeground(); - - void onBackground(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java deleted file mode 100644 index ec60bd8f128..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java +++ /dev/null @@ -1,781 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.cache.PersistingOptionsObserver.DIST_FILENAME; -import static io.sentry.cache.PersistingOptionsObserver.ENVIRONMENT_FILENAME; -import static io.sentry.cache.PersistingOptionsObserver.PROGUARD_UUID_FILENAME; -import static io.sentry.cache.PersistingOptionsObserver.RELEASE_FILENAME; -import static io.sentry.cache.PersistingOptionsObserver.REPLAY_ERROR_SAMPLE_RATE_FILENAME; -import static io.sentry.cache.PersistingOptionsObserver.SDK_VERSION_FILENAME; -import static io.sentry.cache.PersistingScopeObserver.BREADCRUMBS_FILENAME; -import static io.sentry.cache.PersistingScopeObserver.CONTEXTS_FILENAME; -import static io.sentry.cache.PersistingScopeObserver.EXTRAS_FILENAME; -import static io.sentry.cache.PersistingScopeObserver.FINGERPRINT_FILENAME; -import static io.sentry.cache.PersistingScopeObserver.LEVEL_FILENAME; -import static io.sentry.cache.PersistingScopeObserver.REPLAY_FILENAME; -import static io.sentry.cache.PersistingScopeObserver.REQUEST_FILENAME; -import static io.sentry.cache.PersistingScopeObserver.TRACE_FILENAME; -import static io.sentry.cache.PersistingScopeObserver.TRANSACTION_FILENAME; -import static io.sentry.cache.PersistingScopeObserver.USER_FILENAME; -import static io.sentry.protocol.Contexts.REPLAY_ID; - -import android.annotation.SuppressLint; -import android.app.ActivityManager; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.os.Build; -import android.util.DisplayMetrics; -import androidx.annotation.WorkerThread; -import io.sentry.BackfillingEventProcessor; -import io.sentry.Breadcrumb; -import io.sentry.Hint; -import io.sentry.IpAddressUtils; -import io.sentry.SentryBaseEvent; -import io.sentry.SentryEvent; -import io.sentry.SentryExceptionFactory; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.SentryStackTraceFactory; -import io.sentry.SpanContext; -import io.sentry.android.core.internal.util.CpuInfoUtils; -import io.sentry.cache.PersistingOptionsObserver; -import io.sentry.cache.PersistingScopeObserver; -import io.sentry.hints.AbnormalExit; -import io.sentry.hints.Backfillable; -import io.sentry.protocol.App; -import io.sentry.protocol.Contexts; -import io.sentry.protocol.DebugImage; -import io.sentry.protocol.DebugMeta; -import io.sentry.protocol.Device; -import io.sentry.protocol.Mechanism; -import io.sentry.protocol.OperatingSystem; -import io.sentry.protocol.Request; -import io.sentry.protocol.SdkVersion; -import io.sentry.protocol.SentryStackTrace; -import io.sentry.protocol.SentryThread; -import io.sentry.protocol.SentryTransaction; -import io.sentry.protocol.User; -import io.sentry.util.HintUtils; -import io.sentry.util.SentryRandom; -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Processes cached ApplicationExitInfo events (ANRs, tombstones) on a background thread, so we can - * safely read data from disk synchronously. - */ -@ApiStatus.Internal -@WorkerThread -public final class ApplicationExitInfoEventProcessor implements BackfillingEventProcessor { - - private final @NotNull Context context; - - private final @NotNull SentryAndroidOptions options; - - private final @NotNull BuildInfoProvider buildInfoProvider; - - private final @NotNull SentryExceptionFactory sentryExceptionFactory; - - private final @Nullable PersistingScopeObserver persistingScopeObserver; - - // Only ANRv2 events are currently enriched with hint-specific content. - // This can be extended to other hints like NativeCrashExit. - private final @NotNull List hintEnrichers = - Collections.singletonList(new AnrHintEnricher()); - - public ApplicationExitInfoEventProcessor( - final @NotNull Context context, - final @NotNull SentryAndroidOptions options, - final @NotNull BuildInfoProvider buildInfoProvider) { - this.context = ContextUtils.getApplicationContext(context); - this.options = options; - this.buildInfoProvider = buildInfoProvider; - this.persistingScopeObserver = options.findPersistingScopeObserver(); - - final SentryStackTraceFactory sentryStackTraceFactory = - new SentryStackTraceFactory(this.options); - - sentryExceptionFactory = new SentryExceptionFactory(sentryStackTraceFactory); - } - - private @Nullable HintEnricher findEnricher(final @NotNull Object hint) { - for (HintEnricher enricher : hintEnrichers) { - if (enricher.supports(hint)) { - return enricher; - } - } - return null; - } - - @Override - public @NotNull SentryTransaction process( - @NotNull SentryTransaction transaction, @NotNull Hint hint) { - // that's only necessary because on newer versions of Unity, if not overriding this method, it's - // throwing 'java.lang.AbstractMethodError: abstract method' and the reason is probably - // compilation mismatch - return transaction; - } - - @Override - public @Nullable SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) { - final Object unwrappedHint = HintUtils.getSentrySdkHint(hint); - if (!(unwrappedHint instanceof Backfillable)) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "The event is not Backfillable, but has been passed to BackfillingEventProcessor, skipping."); - return event; - } - final @NotNull Backfillable backfillable = (Backfillable) unwrappedHint; - final @Nullable HintEnricher hintEnricher = findEnricher(unwrappedHint); - - if (hintEnricher != null) { - hintEnricher.applyPreEnrichment(event, backfillable, unwrappedHint); - } - - // We always set os and device even if the ApplicationExitInfo event is not enrich-able. - // The OS context may change in the meantime (OS update); we consider this an edge-case. - mergeOS(event); - setDevice(event); - - if (!backfillable.shouldEnrich()) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "The event is Backfillable, but should not be enriched, skipping."); - return event; - } - - backfillScope(event); - - backfillOptions(event); - - setStaticValues(event); - - if (hintEnricher != null) { - hintEnricher.applyPostEnrichment(event, backfillable, unwrappedHint); - } - - return event; - } - - // region scope persisted values - private void backfillScope(final @NotNull SentryEvent event) { - setRequest(event); - setUser(event); - setScopeTags(event); - setBreadcrumbs(event); - setExtras(event); - setContexts(event); - setTransaction(event); - setFingerprints(event); - setLevel(event); - setTrace(event); - setReplayId(event); - } - - private boolean sampleReplay(final @NotNull SentryEvent event) { - final @Nullable String replayErrorSampleRate = - PersistingOptionsObserver.read(options, REPLAY_ERROR_SAMPLE_RATE_FILENAME, String.class); - - if (replayErrorSampleRate == null) { - return false; - } - - try { - // we have to sample here with the old sample rate, because it may change between app launches - final double replayErrorSampleRateDouble = Double.parseDouble(replayErrorSampleRate); - if (replayErrorSampleRateDouble < SentryRandom.current().nextDouble()) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Not capturing replay for ANR %s due to not being sampled.", - event.getEventId()); - return false; - } - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error parsing replay sample rate.", e); - return false; - } - - return true; - } - - private void setReplayId(final @NotNull SentryEvent event) { - @Nullable String persistedReplayId = readFromDisk(options, REPLAY_FILENAME, String.class); - @Nullable String cacheDirPath = options.getCacheDirPath(); - if (cacheDirPath == null) { - return; - } - final @NotNull File replayFolder = new File(cacheDirPath, "replay_" + persistedReplayId); - if (!replayFolder.exists()) { - if (!sampleReplay(event)) { - return; - } - // if the replay folder does not exist (e.g. running in buffer mode), we need to find the - // latest replay folder that was modified before the ANR event. - persistedReplayId = null; - long lastModified = Long.MIN_VALUE; - final File[] dirs = new File(cacheDirPath).listFiles(); - if (dirs != null) { - for (File dir : dirs) { - if (dir.isDirectory() && dir.getName().startsWith("replay_")) { - if (dir.lastModified() > lastModified - && dir.lastModified() <= event.getTimestamp().getTime()) { - lastModified = dir.lastModified(); - persistedReplayId = dir.getName().substring("replay_".length()); - } - } - } - } - } - - if (persistedReplayId == null) { - return; - } - - // store the relevant replayId so ReplayIntegration can pick it up and finalize that replay - PersistingScopeObserver.store(options, persistedReplayId, REPLAY_FILENAME); - event.getContexts().put(REPLAY_ID, persistedReplayId); - } - - private void setTrace(final @NotNull SentryEvent event) { - final SpanContext spanContext = readFromDisk(options, TRACE_FILENAME, SpanContext.class); - if (event.getContexts().getTrace() == null) { - if (spanContext != null) { - event.getContexts().setTrace(spanContext); - } - } - } - - private void setLevel(final @NotNull SentryEvent event) { - final SentryLevel level = readFromDisk(options, LEVEL_FILENAME, SentryLevel.class); - if (event.getLevel() == null) { - event.setLevel(level); - } - } - - @SuppressWarnings("unchecked") - private void setFingerprints(final @NotNull SentryEvent event) { - final List fingerprint = - (List) readFromDisk(options, FINGERPRINT_FILENAME, List.class); - if (event.getFingerprints() == null) { - event.setFingerprints(fingerprint); - } - } - - private void setTransaction(final @NotNull SentryEvent event) { - final String transaction = readFromDisk(options, TRANSACTION_FILENAME, String.class); - if (event.getTransaction() == null) { - event.setTransaction(transaction); - } - } - - private void setContexts(final @NotNull SentryBaseEvent event) { - final Contexts persistedContexts = readFromDisk(options, CONTEXTS_FILENAME, Contexts.class); - if (persistedContexts == null) { - return; - } - final Contexts eventContexts = event.getContexts(); - for (Map.Entry entry : new Contexts(persistedContexts).entrySet()) { - final Object value = entry.getValue(); - if (SpanContext.TYPE.equals(entry.getKey()) && value instanceof SpanContext) { - // we fill it in setTrace later on - continue; - } - if (!eventContexts.containsKey(entry.getKey())) { - eventContexts.put(entry.getKey(), value); - } - } - } - - @SuppressWarnings("unchecked") - private void setExtras(final @NotNull SentryBaseEvent event) { - final Map extras = - (Map) readFromDisk(options, EXTRAS_FILENAME, Map.class); - if (extras == null) { - return; - } - if (event.getExtras() == null) { - event.setExtras(new HashMap<>(extras)); - } else { - for (Map.Entry item : extras.entrySet()) { - if (!event.getExtras().containsKey(item.getKey())) { - event.getExtras().put(item.getKey(), item.getValue()); - } - } - } - } - - @SuppressWarnings("unchecked") - private void setBreadcrumbs(final @NotNull SentryBaseEvent event) { - final List breadcrumbs = - (List) readFromDisk(options, BREADCRUMBS_FILENAME, List.class); - if (breadcrumbs == null) { - return; - } - if (event.getBreadcrumbs() == null) { - event.setBreadcrumbs(breadcrumbs); - } else { - event.getBreadcrumbs().addAll(breadcrumbs); - } - } - - @SuppressWarnings("unchecked") - private void setScopeTags(final @NotNull SentryBaseEvent event) { - final Map tags = - (Map) - readFromDisk(options, PersistingScopeObserver.TAGS_FILENAME, Map.class); - if (tags == null) { - return; - } - if (event.getTags() == null) { - event.setTags(new HashMap<>(tags)); - } else { - for (Map.Entry item : tags.entrySet()) { - if (!event.getTags().containsKey(item.getKey())) { - event.setTag(item.getKey(), item.getValue()); - } - } - } - } - - private void setUser(final @NotNull SentryBaseEvent event) { - if (event.getUser() == null) { - final User user = readFromDisk(options, USER_FILENAME, User.class); - event.setUser(user); - } - } - - private void setRequest(final @NotNull SentryBaseEvent event) { - if (event.getRequest() == null) { - final Request request = readFromDisk(options, REQUEST_FILENAME, Request.class); - event.setRequest(request); - } - } - - private @Nullable T readFromDisk( - final @NotNull SentryOptions options, - final @NotNull String fileName, - final @NotNull Class clazz) { - if (persistingScopeObserver == null) { - return null; - } - - return persistingScopeObserver.read(options, fileName, clazz); - } - - // endregion - - // region options persisted values - private void backfillOptions(final @NotNull SentryEvent event) { - setRelease(event); - setEnvironment(event); - setDist(event); - setDebugMeta(event); - setSdk(event); - setApp(event); - setOptionsTags(event); - } - - private void setApp(final @NotNull SentryBaseEvent event) { - App app = event.getContexts().getApp(); - if (app == null) { - app = new App(); - } - app.setAppName(ContextUtils.getApplicationName(context)); - - final PackageInfo packageInfo = ContextUtils.getPackageInfo(context, buildInfoProvider); - if (packageInfo != null) { - app.setAppIdentifier(packageInfo.packageName); - } - - // backfill versionName and versionCode from the persisted release string - final String release = - event.getRelease() != null - ? event.getRelease() - : PersistingOptionsObserver.read(options, RELEASE_FILENAME, String.class); - if (release != null) { - try { - final String versionName = - release.substring(release.indexOf('@') + 1, release.indexOf('+')); - final String versionCode = release.substring(release.indexOf('+') + 1); - app.setAppVersion(versionName); - app.setAppBuild(versionCode); - } catch (Throwable e) { - options - .getLogger() - .log(SentryLevel.WARNING, "Failed to parse release from scope cache: %s", release); - } - } - - try { - final ContextUtils.SplitApksInfo splitApksInfo = - DeviceInfoUtil.getInstance(context, options).getSplitApksInfo(); - if (splitApksInfo != null) { - app.setSplitApks(splitApksInfo.isSplitApks()); - if (splitApksInfo.getSplitNames() != null) { - app.setSplitNames(Arrays.asList(splitApksInfo.getSplitNames())); - } - } - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting split apks info.", e); - } - - event.getContexts().setApp(app); - } - - private void setRelease(final @NotNull SentryBaseEvent event) { - if (event.getRelease() == null) { - final String release = - PersistingOptionsObserver.read(options, RELEASE_FILENAME, String.class); - event.setRelease(release); - } - } - - private void setEnvironment(final @NotNull SentryBaseEvent event) { - if (event.getEnvironment() == null) { - final String environment = - PersistingOptionsObserver.read(options, ENVIRONMENT_FILENAME, String.class); - event.setEnvironment(environment != null ? environment : options.getEnvironment()); - } - } - - private void setDebugMeta(final @NotNull SentryBaseEvent event) { - DebugMeta debugMeta = event.getDebugMeta(); - - if (debugMeta == null) { - debugMeta = new DebugMeta(); - } - if (debugMeta.getImages() == null) { - debugMeta.setImages(new ArrayList<>()); - } - List images = debugMeta.getImages(); - if (images != null) { - final String proguardUuid = - PersistingOptionsObserver.read(options, PROGUARD_UUID_FILENAME, String.class); - - if (proguardUuid != null) { - final DebugImage debugImage = new DebugImage(); - debugImage.setType(DebugImage.PROGUARD); - debugImage.setUuid(proguardUuid); - images.add(debugImage); - } - event.setDebugMeta(debugMeta); - } - } - - private void setDist(final @NotNull SentryBaseEvent event) { - if (event.getDist() == null) { - final String dist = PersistingOptionsObserver.read(options, DIST_FILENAME, String.class); - event.setDist(dist); - } - // if there's no user-set dist, fall back to versionCode from the persisted release string - if (event.getDist() == null) { - final String release = - PersistingOptionsObserver.read(options, RELEASE_FILENAME, String.class); - if (release != null) { - try { - final String versionCode = release.substring(release.indexOf('+') + 1); - event.setDist(versionCode); - } catch (Throwable e) { - options - .getLogger() - .log(SentryLevel.WARNING, "Failed to parse release from scope cache: %s", release); - } - } - } - } - - private void setSdk(final @NotNull SentryBaseEvent event) { - if (event.getSdk() == null) { - final SdkVersion sdkVersion = - PersistingOptionsObserver.read(options, SDK_VERSION_FILENAME, SdkVersion.class); - event.setSdk(sdkVersion); - } - } - - @SuppressWarnings("unchecked") - private void setOptionsTags(final @NotNull SentryBaseEvent event) { - final Map tags = - (Map) - PersistingOptionsObserver.read( - options, PersistingOptionsObserver.TAGS_FILENAME, Map.class); - if (tags == null) { - return; - } - if (event.getTags() == null) { - event.setTags(new HashMap<>(tags)); - } else { - for (Map.Entry item : tags.entrySet()) { - if (!event.getTags().containsKey(item.getKey())) { - event.setTag(item.getKey(), item.getValue()); - } - } - } - } - - // endregion - - @Override - public @Nullable Long getOrder() { - return 12000L; - } - - // region static values - private void setStaticValues(final @NotNull SentryEvent event) { - mergeUser(event); - setSideLoadedInfo(event); - } - - private void setDefaultPlatform(final @NotNull SentryBaseEvent event) { - if (event.getPlatform() == null) { - // this actually means JVM related. - event.setPlatform(SentryBaseEvent.DEFAULT_PLATFORM); - } - } - - private void mergeUser(final @NotNull SentryBaseEvent event) { - @Nullable User user = event.getUser(); - if (user == null) { - user = new User(); - event.setUser(user); - } - - // userId should be set even if event is Cached as the userId is static and won't change anyway. - if (user.getId() == null) { - user.setId(getDeviceId()); - } - if (user.getIpAddress() == null && options.isSendDefaultPii()) { - user.setIpAddress(IpAddressUtils.DEFAULT_IP_ADDRESS); - } - } - - private @Nullable String getDeviceId() { - try { - return options.getRuntimeManager().runWithRelaxedPolicy(() -> Installation.id(context)); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting installationId.", e); - } - return null; - } - - private void setSideLoadedInfo(final @NotNull SentryBaseEvent event) { - try { - final ContextUtils.SideLoadedInfo sideLoadedInfo = - DeviceInfoUtil.getInstance(context, options).getSideLoadedInfo(); - if (sideLoadedInfo != null) { - final @NotNull Map tags = sideLoadedInfo.asTags(); - for (Map.Entry entry : tags.entrySet()) { - event.setTag(entry.getKey(), entry.getValue()); - } - } - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting side loaded info.", e); - } - } - - private void setDevice(final @NotNull SentryBaseEvent event) { - if (event.getContexts().getDevice() == null) { - event.getContexts().setDevice(getDevice()); - } - } - - // only use static data that does not change between app launches (e.g. timezone, boottime, - // battery level will change) - @SuppressLint("NewApi") - private @NotNull Device getDevice() { - Device device = new Device(); - device.setManufacturer(Build.MANUFACTURER); - device.setBrand(Build.BRAND); - device.setFamily(ContextUtils.getFamily(options.getLogger())); - device.setModel(Build.MODEL); - device.setModelId(Build.ID); - device.setArchs(ContextUtils.getArchitectures()); - - final ActivityManager.MemoryInfo memInfo = - ContextUtils.getMemInfo(context, options.getLogger()); - if (memInfo != null) { - // in bytes - device.setMemorySize(getMemorySize(memInfo)); - } - - device.setSimulator(buildInfoProvider.isEmulator()); - - DisplayMetrics displayMetrics = ContextUtils.getDisplayMetrics(context, options.getLogger()); - if (displayMetrics != null) { - device.setScreenWidthPixels(displayMetrics.widthPixels); - device.setScreenHeightPixels(displayMetrics.heightPixels); - device.setScreenDensity(displayMetrics.density); - device.setScreenDpi(displayMetrics.densityDpi); - } - - if (device.getId() == null) { - device.setId(getDeviceId()); - } - - final @NotNull List cpuFrequencies = CpuInfoUtils.getInstance().readMaxFrequencies(); - if (!cpuFrequencies.isEmpty()) { - device.setProcessorFrequency(Collections.max(cpuFrequencies).doubleValue()); - device.setProcessorCount(cpuFrequencies.size()); - } - - return device; - } - - private @NotNull Long getMemorySize(final @NotNull ActivityManager.MemoryInfo memInfo) { - return memInfo.totalMem; - } - - private void mergeOS(final @NotNull SentryBaseEvent event) { - final OperatingSystem currentOS = event.getContexts().getOperatingSystem(); - final OperatingSystem androidOS = - DeviceInfoUtil.getInstance(context, options).getOperatingSystem(); - - // make Android OS the main OS using the 'os' key - event.getContexts().setOperatingSystem(androidOS); - - if (currentOS != null) { - // add additional OS which was already part of the SentryEvent (eg Linux read from NDK) - String osNameKey = currentOS.getName(); - if (osNameKey != null && !osNameKey.isEmpty()) { - osNameKey = "os_" + osNameKey.trim().toLowerCase(Locale.ROOT); - } else { - osNameKey = "os_1"; - } - event.getContexts().put(osNameKey, currentOS); - } - // endregion - } - - private interface HintEnricher { - boolean supports(@NotNull Object hint); - - void applyPreEnrichment( - @NotNull SentryEvent event, @NotNull Backfillable hint, @NotNull Object rawHint); - - void applyPostEnrichment( - @NotNull SentryEvent event, @NotNull Backfillable hint, @NotNull Object rawHint); - } - - private final class AnrHintEnricher implements HintEnricher { - - @Override - public boolean supports(@NotNull Object hint) { - // While this is specifically an ANR enricher we discriminate enrichment application - // on the broader AbnormalExit hints for now. - return hint instanceof AbnormalExit; - } - - // by default we assume that the ANR is foreground, unless abnormalMechanism is "anr_background" - private boolean isBackgroundAnr(final @NotNull Object hint) { - if (hint instanceof AbnormalExit) { - final String abnormalMechanism = ((AbnormalExit) hint).mechanism(); - return "anr_background".equals(abnormalMechanism); - } - return false; - } - - @Override - public void applyPreEnrichment( - @NotNull SentryEvent event, @NotNull Backfillable hint, @NotNull Object rawHint) { - final boolean isBackgroundAnr = isBackgroundAnr(rawHint); - // we always set exception values and default platform even if the ANR is not enrich-able - setDefaultPlatform(event); - setAnrExceptions(event, hint, isBackgroundAnr); - } - - @Override - public void applyPostEnrichment( - @NotNull SentryEvent event, @NotNull Backfillable hint, @NotNull Object rawHint) { - final boolean isBackgroundAnr = isBackgroundAnr(rawHint); - setAppForeground(event, !isBackgroundAnr); - setDefaultAnrFingerprint(event, isBackgroundAnr); - } - - private void setDefaultAnrFingerprint( - final @NotNull SentryEvent event, final boolean isBackgroundAnr) { - // sentry does not yet have a capability to provide default server-side fingerprint rules, - // so we're doing this on the SDK side to group background and foreground ANRs separately - // even if they have similar stacktraces. - if (event.getFingerprints() == null) { - event.setFingerprints( - Arrays.asList("{{ default }}", isBackgroundAnr ? "background-anr" : "foreground-anr")); - } - } - - private void setAppForeground( - final @NotNull SentryBaseEvent event, final boolean inForeground) { - App app = event.getContexts().getApp(); - if (app == null) { - app = new App(); - event.getContexts().setApp(app); - } - // TODO: not entirely correct, because we define background ANRs as not the ones of - // IMPORTANCE_FOREGROUND, but this doesn't mean the app was in foreground when an ANR - // happened but it's our best effort for now. We could serialize AppState in theory. - if (app.getInForeground() == null) { - app.setInForeground(inForeground); - } - } - - @Nullable - private SentryThread findMainThread(final @Nullable List threads) { - if (threads != null) { - for (SentryThread thread : threads) { - final String name = thread.getName(); - if (name != null && name.equals("main")) { - return thread; - } - } - } - return null; - } - - private void setAnrExceptions( - final @NotNull SentryEvent event, - final @NotNull Backfillable hint, - final boolean isBackgroundAnr) { - if (event.getExceptions() != null) { - return; - } - // AnrV2 threads contain a thread dump from the OS, so we just search for the main thread dump - // and make an exception out of its stacktrace - final Mechanism mechanism = new Mechanism(); - if (!hint.shouldEnrich()) { - // we only enrich the latest ANR in the list, so this is historical - mechanism.setType("HistoricalAppExitInfo"); - } else { - mechanism.setType("AppExitInfo"); - } - - String message = "ANR"; - if (isBackgroundAnr) { - message = "Background " + message; - } - final ApplicationNotResponding anr = - new ApplicationNotResponding(message, Thread.currentThread()); - - SentryThread mainThread = findMainThread(event.getThreads()); - if (mainThread == null) { - // if there's no main thread in the event threads, we just create a dummy thread so the - // exception is properly created as well, but without stacktrace - mainThread = new SentryThread(); - mainThread.setStacktrace(new SentryStackTrace()); - } - event.setExceptions( - sentryExceptionFactory.getSentryExceptionsFromThread(mainThread, mechanism, anr)); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoHistoryDispatcher.java b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoHistoryDispatcher.java deleted file mode 100644 index 79c3f19c6e5..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoHistoryDispatcher.java +++ /dev/null @@ -1,247 +0,0 @@ -package io.sentry.android.core; - -import android.app.ActivityManager; -import android.app.ApplicationExitInfo; -import android.content.Context; -import android.os.Build; -import androidx.annotation.RequiresApi; -import io.sentry.Hint; -import io.sentry.IScopes; -import io.sentry.SentryEvent; -import io.sentry.SentryLevel; -import io.sentry.cache.EnvelopeCache; -import io.sentry.cache.IEnvelopeCache; -import io.sentry.hints.BlockingFlushHint; -import io.sentry.protocol.SentryId; -import io.sentry.transport.ICurrentDateProvider; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.TimeUnit; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -final class ApplicationExitInfoHistoryDispatcher implements Runnable { - - // using 91 to avoid timezone change hassle, 90 days is how long Sentry keeps the events - static final long NINETY_DAYS_THRESHOLD = TimeUnit.DAYS.toMillis(91); - - private final @NotNull Context context; - private final @NotNull IScopes scopes; - private final @NotNull SentryAndroidOptions options; - private final @NotNull ApplicationExitInfoPolicy policy; - private final long threshold; - - ApplicationExitInfoHistoryDispatcher( - final @NotNull Context context, - final @NotNull IScopes scopes, - final @NotNull SentryAndroidOptions options, - final @NotNull ICurrentDateProvider dateProvider, - final @NotNull ApplicationExitInfoPolicy policy) { - this.context = ContextUtils.getApplicationContext(context); - this.scopes = scopes; - this.options = options; - this.policy = policy; - this.threshold = dateProvider.getCurrentTimeMillis() - NINETY_DAYS_THRESHOLD; - } - - @RequiresApi(api = Build.VERSION_CODES.R) - @Override - public void run() { - final ActivityManager activityManager = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - - if (activityManager == null) { - options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve ActivityManager."); - return; - } - - final List applicationExitInfoList = - activityManager.getHistoricalProcessExitReasons(null, 0, 0); - - if (applicationExitInfoList.isEmpty()) { - options.getLogger().log(SentryLevel.DEBUG, "No records in historical exit reasons."); - return; - } - - waitPreviousSessionFlush(); - - final List exitInfos = new ArrayList<>(applicationExitInfoList); - final @Nullable Long lastReportedTimestamp = policy.getLastReportedTimestamp(); - - final ApplicationExitInfo latest = removeLatest(exitInfos); - if (latest == null) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "No %ss have been found in the historical exit reasons list.", - policy.getLabel()); - return; - } - - if (latest.getTimestamp() < threshold) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Latest %s happened too long ago, returning early.", - policy.getLabel()); - return; - } - - if (lastReportedTimestamp != null && latest.getTimestamp() <= lastReportedTimestamp) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Latest %s has already been reported, returning early.", - policy.getLabel()); - return; - } - - if (policy.shouldReportHistorical()) { - reportHistorical(exitInfos, lastReportedTimestamp); - } - - report(latest, true); - } - - private void waitPreviousSessionFlush() { - final IEnvelopeCache cache = options.getEnvelopeDiskCache(); - if (cache instanceof EnvelopeCache) { - if (options.isEnableAutoSessionTracking() - && !((EnvelopeCache) cache).waitPreviousSessionFlush()) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "Timed out waiting to flush previous session to its own file."); - - // if we timed out waiting here, we can already flush the latch, because the timeout is - // big enough to wait for it only once and we don't have to wait again in - // PreviousSessionFinalizer - ((EnvelopeCache) cache).flushPreviousSession(); - } - } - } - - @RequiresApi(api = Build.VERSION_CODES.R) - private @Nullable ApplicationExitInfo removeLatest( - final @NotNull List exitInfos) { - for (Iterator it = exitInfos.iterator(); it.hasNext(); ) { - ApplicationExitInfo applicationExitInfo = it.next(); - if (applicationExitInfo.getReason() == policy.getTargetReason()) { - it.remove(); - return applicationExitInfo; - } - } - return null; - } - - @RequiresApi(api = Build.VERSION_CODES.R) - private void reportHistorical( - final @NotNull List exitInfos, - final @Nullable Long lastReportedTimestamp) { - Collections.reverse(exitInfos); - for (ApplicationExitInfo applicationExitInfo : exitInfos) { - if (applicationExitInfo.getReason() == policy.getTargetReason()) { - if (applicationExitInfo.getTimestamp() < threshold) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "%s happened too long ago %s.", - policy.getLabel(), - applicationExitInfo); - continue; - } - - if (lastReportedTimestamp != null - && applicationExitInfo.getTimestamp() <= lastReportedTimestamp) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "%s has already been reported %s.", - policy.getLabel(), - applicationExitInfo); - continue; - } - - report(applicationExitInfo, false); // do not enrich past events - } - } - } - - private void report(final @NotNull ApplicationExitInfo exitInfo, final boolean enrich) { - final @Nullable Report report = policy.buildReport(exitInfo, enrich); - - if (report == null) { - return; - } - - final @NotNull SentryId sentryId = scopes.captureEvent(report.getEvent(), report.getHint()); - final boolean isEventDropped = sentryId.equals(SentryId.EMPTY_ID); - if (!isEventDropped) { - final @Nullable BlockingFlushHint flushHint = report.getFlushHint(); - if (flushHint != null && !flushHint.waitFlush()) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "Timed out waiting to flush %s event to disk. Event: %s", - policy.getLabel(), - report.getEvent().getEventId()); - } - } - } - - interface ApplicationExitInfoPolicy { - @NotNull - String getLabel(); - - int getTargetReason(); - - boolean shouldReportHistorical(); - - @Nullable - Long getLastReportedTimestamp(); - - @Nullable - Report buildReport(@NotNull ApplicationExitInfo exitInfo, boolean enrich); - } - - public static final class Report { - private final @NotNull SentryEvent event; - private final @NotNull Hint hint; - private final @Nullable BlockingFlushHint flushHint; - - Report( - final @NotNull SentryEvent event, - final @NotNull Hint hint, - final @Nullable BlockingFlushHint flushHint) { - this.event = event; - this.hint = hint; - this.flushHint = flushHint; - } - - @NotNull - public SentryEvent getEvent() { - return event; - } - - @NotNull - public Hint getHint() { - return hint; - } - - @Nullable - public BlockingFlushHint getFlushHint() { - return flushHint; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java deleted file mode 100644 index f4998240f81..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java +++ /dev/null @@ -1,31 +0,0 @@ -// https://github.com/SalomonBrys/ANR-WatchDog/blob/1969075f75f5980e9000eaffbaa13b0daf282dcb/anr-watchdog/src/main/java/com/github/anrwatchdog/ANRError.java -// Based on the class above. The API unnecessary here was removed. -package io.sentry.android.core; - -import io.sentry.util.Objects; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Error thrown by ANRWatchDog when an ANR is detected. Contains the stack trace of the frozen UI - * thread. - * - *

It is important to notice that, in an ApplicationNotResponding, all the "Caused by" are not - * really the cause of the exception. Each "Caused by" is the stack trace of a running thread. Note - * that the main thread always comes first. - */ -final class ApplicationNotResponding extends RuntimeException { - private static final long serialVersionUID = 252541144579117016L; - - private final @NotNull Thread thread; - - ApplicationNotResponding(final @Nullable String message, final @NotNull Thread thread) { - super(message); - this.thread = Objects.requireNonNull(thread, "Thread must be provided."); - setStackTrace(this.thread.getStackTrace()); - } - - public @NotNull Thread getThread() { - return thread; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/BuildInfoProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/BuildInfoProvider.java deleted file mode 100644 index 5b0786ffb7f..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/BuildInfoProvider.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.sentry.android.core; - -import android.os.Build; -import io.sentry.ILogger; -import io.sentry.SentryLevel; -import io.sentry.util.Objects; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** The Android Impl. of BuildInfoProvider which returns the Build class info. */ -@ApiStatus.Internal -public final class BuildInfoProvider { - - final @NotNull ILogger logger; - - public BuildInfoProvider(final @NotNull ILogger logger) { - this.logger = Objects.requireNonNull(logger, "The ILogger object is required."); - } - - /** - * Returns the Build.VERSION.SDK_INT - * - * @return the Build.VERSION.SDK_INT - */ - public int getSdkInfoVersion() { - return Build.VERSION.SDK_INT; - } - - public @Nullable String getBuildTags() { - return Build.TAGS; - } - - public @Nullable String getManufacturer() { - return Build.MANUFACTURER; - } - - public @Nullable String getModel() { - return Build.MODEL; - } - - public @Nullable String getVersionRelease() { - return Build.VERSION.RELEASE; - } - - /** - * Check whether the application is running in an emulator. - * https://github.com/flutter/plugins/blob/master/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/DeviceInfoPlugin.java#L105 - * - * @return true if the application is running in an emulator, false otherwise - */ - public @Nullable Boolean isEmulator() { - try { - return (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) - || Build.FINGERPRINT.startsWith("generic") - || Build.FINGERPRINT.startsWith("unknown") - || Build.HARDWARE.contains("goldfish") - || Build.HARDWARE.contains("ranchu") - || Build.MODEL.contains("google_sdk") - || Build.MODEL.contains("Emulator") - || Build.MODEL.contains("Android SDK built for x86") - || Build.MANUFACTURER.contains("Genymotion") - || Build.PRODUCT.contains("sdk_google") - || Build.PRODUCT.contains("google_sdk") - || Build.PRODUCT.contains("sdk") - || Build.PRODUCT.contains("sdk_x86") - || Build.PRODUCT.contains("vbox86p") - || Build.PRODUCT.contains("emulator") - || Build.PRODUCT.contains("simulator"); - } catch (Throwable e) { - logger.log( - SentryLevel.ERROR, "Error checking whether application is running in an emulator.", e); - return null; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java deleted file mode 100644 index 60ae00f2ead..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java +++ /dev/null @@ -1,551 +0,0 @@ -package io.sentry.android.core; - -import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; -import static android.content.Context.ACTIVITY_SERVICE; -import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; - -import android.annotation.SuppressLint; -import android.app.ActivityManager; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Handler; -import android.util.DisplayMetrics; -import io.sentry.ILogger; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.android.core.util.AndroidLazyEvaluator; -import io.sentry.protocol.App; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -@ApiStatus.Internal -public final class ContextUtils { - - static class SideLoadedInfo { - private final boolean isSideLoaded; - private final @Nullable String installerStore; - - public SideLoadedInfo(final boolean isSideLoaded, final @Nullable String installerStore) { - this.isSideLoaded = isSideLoaded; - this.installerStore = installerStore; - } - - public boolean isSideLoaded() { - return isSideLoaded; - } - - public @Nullable String getInstallerStore() { - return installerStore; - } - - public @NotNull Map asTags() { - final Map data = new HashMap<>(); - data.put("isSideLoaded", String.valueOf(isSideLoaded)); - if (installerStore != null) { - data.put("installerStore", installerStore); - } - return data; - } - } - - static class SplitApksInfo { - // https://github.com/google/bundletool/blob/master/src/main/java/com/android/tools/build/bundletool/model/AndroidManifest.java#L257-L263 - static final String SPLITS_REQUIRED = "com.android.vending.splits.required"; - - private final boolean isSplitApks; - private final String[] splitNames; - - public SplitApksInfo(final boolean isSplitApks, final String[] splitNames) { - this.isSplitApks = isSplitApks; - this.splitNames = splitNames; - } - - public boolean isSplitApks() { - return isSplitApks; - } - - public @Nullable String[] getSplitNames() { - return splitNames; - } - } - - private ContextUtils() {} - - // to avoid doing a bunch of Binder calls we use LazyEvaluator to cache the values that are static - // during the app process running - - /** - * Since this packageInfo uses flags 0 we can assume it's static and cache it as the package name - * or version code cannot change during runtime, only after app update (which will spin up a new - * process). - */ - @SuppressLint("NewApi") - private static final @NotNull AndroidLazyEvaluator staticPackageInfo33 = - new AndroidLazyEvaluator<>( - context -> { - try { - return context - .getPackageManager() - .getPackageInfo(context.getPackageName(), PackageManager.PackageInfoFlags.of(0)); - } catch (Throwable e) { - return null; - } - }); - - private static final @NotNull AndroidLazyEvaluator staticPackageInfo = - new AndroidLazyEvaluator<>( - context -> { - try { - return context.getPackageManager().getPackageInfo(context.getPackageName(), 0); - } catch (Throwable e) { - return null; - } - }); - - private static final @NotNull AndroidLazyEvaluator applicationName = - new AndroidLazyEvaluator<>( - context -> { - try { - final ApplicationInfo applicationInfo = context.getApplicationInfo(); - final int stringId = applicationInfo.labelRes; - if (stringId == 0) { - if (applicationInfo.nonLocalizedLabel != null) { - return applicationInfo.nonLocalizedLabel.toString(); - } - return context.getPackageManager().getApplicationLabel(applicationInfo).toString(); - } else { - return context.getString(stringId); - } - } catch (Throwable e) { - return null; - } - }); - - /** - * Since this applicationInfo uses the same flag (METADATA) we can assume it's static and cache it - * as the manifest metadata cannot change during runtime, only after app update (which will spin - * up a new process). - */ - @SuppressLint("NewApi") - private static final @NotNull AndroidLazyEvaluator staticAppInfo33 = - new AndroidLazyEvaluator<>( - context -> { - try { - return context - .getPackageManager() - .getApplicationInfo( - context.getPackageName(), - PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA)); - } catch (Throwable e) { - return null; - } - }); - - private static final @NotNull AndroidLazyEvaluator staticAppInfo = - new AndroidLazyEvaluator<>( - context -> { - try { - return context - .getPackageManager() - .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); - } catch (Throwable e) { - return null; - } - }); - - /** - * Return the Application's PackageInfo if possible, or null. - * - * @return the Application's PackageInfo if possible, or null - */ - @Nullable - static PackageInfo getPackageInfo( - final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) { - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.TIRAMISU) { - return staticPackageInfo33.getValue(context); - } else { - return staticPackageInfo.getValue(context); - } - } - - /** - * Return the Application's PackageInfo with the specified flags if possible, or null. - * - * @return the Application's PackageInfo if possible, or null - */ - @SuppressLint("NewApi") - @Nullable - @SuppressWarnings("deprecation") - static PackageInfo getPackageInfo( - final @NotNull Context context, - final int flags, - final @NotNull ILogger logger, - final @NotNull BuildInfoProvider buildInfoProvider) { - try { - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.TIRAMISU) { - return context - .getPackageManager() - .getPackageInfo(context.getPackageName(), PackageManager.PackageInfoFlags.of(flags)); - } else { - return context.getPackageManager().getPackageInfo(context.getPackageName(), flags); - } - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Error getting package info.", e); - return null; - } - } - - /** - * Return the Application's ApplicationInfo if possible. Throws @{@link - * android.content.pm.PackageManager.NameNotFoundException} if the package is not found. - * - * @return the Application's ApplicationInfo if possible, or throws - */ - @SuppressLint("NewApi") - @Nullable - @SuppressWarnings("deprecation") - static ApplicationInfo getApplicationInfo( - final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) { - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.TIRAMISU) { - return staticAppInfo33.getValue(context); - } else { - return staticAppInfo.getValue(context); - } - } - - /** - * Returns the App's version code based on the PackageInfo - * - * @param packageInfo the PackageInfo class - * @return the versionCode or LongVersionCode based on your API version - */ - @SuppressLint("NewApi") - @NotNull - static String getVersionCode( - final @NotNull PackageInfo packageInfo, final @NotNull BuildInfoProvider buildInfoProvider) { - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.P) { - return Long.toString(packageInfo.getLongVersionCode()); - } - return getVersionCodeDep(packageInfo); - } - - /** - * Returns the App's version name based on the PackageInfo - * - * @param packageInfo the PackageInfo class - * @return the versionName - */ - @Nullable - static String getVersionName(final @NotNull PackageInfo packageInfo) { - return packageInfo.versionName; - } - - @SuppressWarnings("deprecation") - private static @NotNull String getVersionCodeDep(final @NotNull PackageInfo packageInfo) { - return Integer.toString(packageInfo.versionCode); - } - - /** - * Check if the Started process has IMPORTANCE_FOREGROUND importance which means that the process - * will start an Activity. - * - * @return true if IMPORTANCE_FOREGROUND and false otherwise - */ - @ApiStatus.Internal - public static boolean isForegroundImportance() { - try { - final ActivityManager.RunningAppProcessInfo appProcessInfo = - new ActivityManager.RunningAppProcessInfo(); - ActivityManager.getMyMemoryState(appProcessInfo); - return appProcessInfo.importance == IMPORTANCE_FOREGROUND; - } catch (Throwable ignored) { - // should never happen - } - return false; - } - - /** - * Determines if the app is a packaged android library for running Compose Preview Mode - * - * @param context the context - * @return true, if the app is actually a library running as an app for Compose Preview Mode - */ - @ApiStatus.Internal - public static boolean appIsLibraryForComposePreview(final @NotNull Context context) { - // Jetpack Compose Preview (aka "Run Preview on Device") - // uses the androidTest flavor for android library modules, - // so let's fail-fast by checking this first - if (context.getPackageName().endsWith(".test")) { - try { - final @NotNull ActivityManager activityManager = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - final @NotNull List appTasks = activityManager.getAppTasks(); - for (final ActivityManager.AppTask task : appTasks) { - final @Nullable ComponentName component = task.getTaskInfo().baseIntent.getComponent(); - if (component != null - && component.getClassName().equals("androidx.compose.ui.tooling.PreviewActivity")) { - return true; - } - } - } catch (Throwable t) { - // ignored - } - } - return false; - } - - /** - * Get the device's current kernel version, as a string. Attempts to read /proc/version, and falls - * back to the 'os.version' System Property. - * - * @return the device's current kernel version, as a string - */ - @SuppressWarnings("DefaultCharset") - static @Nullable String getKernelVersion(final @NotNull ILogger logger) { - // its possible to try to execute 'uname' and parse it or also another unix commands or even - // looking for well known root installed apps - final String errorMsg = "Exception while attempting to read kernel information"; - final String defaultVersion = System.getProperty("os.version"); - - final File file = new File("/proc/version"); - if (!file.canRead()) { - return defaultVersion; - } - try (BufferedReader br = new BufferedReader(new FileReader(file))) { - return br.readLine(); - } catch (IOException e) { - logger.log(SentryLevel.ERROR, errorMsg, e); - } - - return defaultVersion; - } - - @SuppressWarnings({"deprecation"}) - static @Nullable SideLoadedInfo retrieveSideLoadedInfo( - final @NotNull Context context, - final @NotNull ILogger logger, - final @NotNull BuildInfoProvider buildInfoProvider) { - String packageName = null; - try { - final PackageInfo packageInfo = getPackageInfo(context, buildInfoProvider); - final PackageManager packageManager = context.getPackageManager(); - - if (packageInfo != null && packageManager != null) { - packageName = packageInfo.packageName; - - // getInstallSourceInfo requires INSTALL_PACKAGES permission which is only given to system - // apps. - // if it's installed via adb, system apps or untrusted sources - // could be amazon, google play etc - or null in case of sideload - final String installerPackageName = packageManager.getInstallerPackageName(packageName); - return new SideLoadedInfo(installerPackageName == null, installerPackageName); - } - } catch (IllegalArgumentException e) { - // it'll never be thrown as we are querying its own App's package. - logger.log(SentryLevel.DEBUG, "%s package isn't installed.", packageName); - } - - return null; - } - - @SuppressWarnings({"deprecation"}) - static @Nullable SplitApksInfo retrieveSplitApksInfo( - final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) { - String[] splitNames = null; - final ApplicationInfo applicationInfo = getApplicationInfo(context, buildInfoProvider); - final PackageInfo packageInfo = getPackageInfo(context, buildInfoProvider); - - if (packageInfo != null) { - splitNames = packageInfo.splitNames; - boolean isSplitApks = false; - if (applicationInfo != null && applicationInfo.metaData != null) { - isSplitApks = applicationInfo.metaData.getBoolean(SplitApksInfo.SPLITS_REQUIRED); - } - - return new SplitApksInfo(isSplitApks, splitNames); - } - - return null; - } - - /** - * Get the human-facing Application name. - * - * @return Application name - */ - static @Nullable String getApplicationName(final @NotNull Context context) { - return applicationName.getValue(context); - } - - /** - * Get the DisplayMetrics object for the current application. - * - * @return the DisplayMetrics object for the current application - */ - static @Nullable DisplayMetrics getDisplayMetrics( - final @NotNull Context context, final @NotNull ILogger logger) { - try { - return context.getResources().getDisplayMetrics(); - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Error getting DisplayMetrics.", e); - return null; - } - } - - /** - * Fake the device family by using the first word in the Build.MODEL. Works well in most cases... - * "Nexus 6P" -> "Nexus", "Galaxy S7" -> "Galaxy". - * - * @return family name of the device, as best we can tell - */ - static @Nullable String getFamily(final @NotNull ILogger logger) { - try { - return Build.MODEL.split(" ", -1)[0]; - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Error getting device family.", e); - return null; - } - } - - static @NotNull String[] getArchitectures() { - return Build.SUPPORTED_ABIS; - } - - /** - * Get MemoryInfo object representing the memory state of the application. - * - * @return MemoryInfo object representing the memory state of the application - */ - static @Nullable ActivityManager.MemoryInfo getMemInfo( - final @NotNull Context context, final @NotNull ILogger logger) { - try { - final ActivityManager actManager = - (ActivityManager) context.getSystemService(ACTIVITY_SERVICE); - final ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); - if (actManager != null) { - actManager.getMemoryInfo(memInfo); - return memInfo; - } - logger.log(SentryLevel.INFO, "Error getting MemoryInfo."); - return null; - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Error getting MemoryInfo.", e); - return null; - } - } - - /** Register an exported BroadcastReceiver, independently from platform version. */ - static @Nullable Intent registerReceiver( - final @NotNull Context context, - final @NotNull SentryOptions options, - final @Nullable BroadcastReceiver receiver, - final @NotNull IntentFilter filter, - final @Nullable Handler handler) { - return registerReceiver( - context, new BuildInfoProvider(options.getLogger()), receiver, filter, handler); - } - - /** Register an exported BroadcastReceiver, independently from platform version. */ - @SuppressLint({"NewApi", "UnspecifiedRegisterReceiverFlag"}) - static @Nullable Intent registerReceiver( - final @NotNull Context context, - final @NotNull BuildInfoProvider buildInfoProvider, - final @Nullable BroadcastReceiver receiver, - final @NotNull IntentFilter filter, - final @Nullable Handler handler) { - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.TIRAMISU) { - // From https://developer.android.com/guide/components/broadcasts#context-registered-receivers - // If this receiver is listening for broadcasts sent from the system or from other apps, even - // other apps that you own—use the RECEIVER_EXPORTED flag. If instead this receiver is - // listening only for broadcasts sent by your app, use the RECEIVER_NOT_EXPORTED flag. - return context.registerReceiver( - receiver, filter, null, handler, Context.RECEIVER_NOT_EXPORTED); - } else { - return context.registerReceiver(receiver, filter, null, handler); - } - } - - static void setAppPackageInfo( - final @NotNull PackageInfo packageInfo, - final @NotNull BuildInfoProvider buildInfoProvider, - final @Nullable DeviceInfoUtil deviceInfoUtil, - final @NotNull App app) { - app.setAppIdentifier(packageInfo.packageName); - app.setAppVersion(packageInfo.versionName); - app.setAppBuild(ContextUtils.getVersionCode(packageInfo, buildInfoProvider)); - - final Map permissions = new HashMap<>(); - final String[] requestedPermissions = packageInfo.requestedPermissions; - final int[] requestedPermissionsFlags = packageInfo.requestedPermissionsFlags; - - if (requestedPermissions != null - && requestedPermissions.length > 0 - && requestedPermissionsFlags != null - && requestedPermissionsFlags.length > 0) { - for (int i = 0; i < requestedPermissions.length; i++) { - String permission = requestedPermissions[i]; - permission = permission.substring(permission.lastIndexOf('.') + 1); - - final boolean granted = - (requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED) - == REQUESTED_PERMISSION_GRANTED; - permissions.put(permission, granted ? "granted" : "not_granted"); - } - } - app.setPermissions(permissions); - - if (deviceInfoUtil != null) { - try { - final ContextUtils.SplitApksInfo splitApksInfo = deviceInfoUtil.getSplitApksInfo(); - if (splitApksInfo != null) { - app.setSplitApks(splitApksInfo.isSplitApks()); - if (splitApksInfo.getSplitNames() != null) { - app.setSplitNames(Arrays.asList(splitApksInfo.getSplitNames())); - } - } - } catch (Throwable e) { - } - } - } - - /** - * Get the app context - * - * @return the app context, or if not available, the provided context - */ - @NotNull - public static Context getApplicationContext(final @NotNull Context context) { - // it returns null if ContextImpl, so let's check for nullability - final @Nullable Context appContext = context.getApplicationContext(); - if (appContext != null) { - return appContext; - } - return context; - } - - @TestOnly - static void resetInstance() { - staticPackageInfo33.resetValue(); - staticPackageInfo.resetValue(); - applicationName.resetValue(); - staticAppInfo33.resetValue(); - staticAppInfo.resetValue(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/CurrentActivityHolder.java b/sentry-android-core/src/main/java/io/sentry/android/core/CurrentActivityHolder.java deleted file mode 100644 index a7733821c05..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/CurrentActivityHolder.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.sentry.android.core; - -import android.app.Activity; -import java.lang.ref.WeakReference; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public class CurrentActivityHolder { - - private static final @NotNull CurrentActivityHolder instance = new CurrentActivityHolder(); - - private CurrentActivityHolder() {} - - private @Nullable WeakReference currentActivity; - - public static @NotNull CurrentActivityHolder getInstance() { - return instance; - } - - public @Nullable Activity getActivity() { - if (currentActivity != null) { - return currentActivity.get(); - } - return null; - } - - public void setActivity(final @NotNull Activity activity) { - if (currentActivity != null && currentActivity.get() == activity) { - return; - } - - currentActivity = new WeakReference<>(activity); - } - - public void clearActivity() { - currentActivity = null; - } - - public void clearActivity(final @NotNull Activity activity) { - if (currentActivity != null && currentActivity.get() != activity) { - return; - } - currentActivity = null; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java deleted file mode 100644 index 14cafab224d..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java +++ /dev/null @@ -1,428 +0,0 @@ -package io.sentry.android.core; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Build; -import io.sentry.*; -import io.sentry.android.core.internal.util.AndroidThreadChecker; -import io.sentry.android.core.performance.AppStartMetrics; -import io.sentry.android.core.performance.TimeSpan; -import io.sentry.protocol.App; -import io.sentry.protocol.OperatingSystem; -import io.sentry.protocol.SentryException; -import io.sentry.protocol.SentryStackFrame; -import io.sentry.protocol.SentryStackTrace; -import io.sentry.protocol.SentryThread; -import io.sentry.protocol.SentryTransaction; -import io.sentry.protocol.User; -import io.sentry.util.HintUtils; -import io.sentry.util.LazyEvaluator; -import io.sentry.util.Objects; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.RejectedExecutionException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -final class DefaultAndroidEventProcessor implements EventProcessor { - - @TestOnly final Context context; - - private final @NotNull BuildInfoProvider buildInfoProvider; - private final @NotNull SentryAndroidOptions options; - @TestOnly final @Nullable Future deviceInfoUtil; - private final @NotNull LazyEvaluator deviceFamily = - new LazyEvaluator<>(() -> ContextUtils.getFamily(NoOpLogger.getInstance())); - - public DefaultAndroidEventProcessor( - final @NotNull Context context, - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull SentryAndroidOptions options) { - this.context = - Objects.requireNonNull( - ContextUtils.getApplicationContext(context), "The application context is required."); - this.buildInfoProvider = - Objects.requireNonNull(buildInfoProvider, "The BuildInfoProvider is required."); - this.options = Objects.requireNonNull(options, "The options object is required."); - - // don't ref. to method reference, theres a bug on it - // noinspection Convert2MethodRef - // some device info performs disk I/O, but it's result is cached, let's pre-cache it - @Nullable Future deviceInfoUtil; - final @NotNull ExecutorService executorService = Executors.newSingleThreadExecutor(); - try { - deviceInfoUtil = - executorService.submit(() -> DeviceInfoUtil.getInstance(this.context, options)); - } catch (RejectedExecutionException e) { - deviceInfoUtil = null; - options.getLogger().log(SentryLevel.WARNING, "Device info caching task rejected.", e); - } - this.deviceInfoUtil = deviceInfoUtil; - executorService.shutdown(); - } - - @Override - public @NotNull SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) { - final boolean applyScopeData = shouldApplyScopeData(event, hint); - if (applyScopeData) { - // we only set memory data if it's not a hard crash, when it's a hard crash the event is - // enriched on restart, so non static data might be wrong, eg lowMemory or availMem will - // be different if the App. crashes because of OOM. - processNonCachedEvent(event, hint); - setThreads(event, hint); - } - - setCommons(event, true, applyScopeData); - - fixExceptionOrder(event); - - return event; - } - - @Override - public @Nullable SentryLogEvent process(@NotNull SentryLogEvent event) { - setDevice(event); - setOs(event); - return event; - } - - @Override - public @Nullable SentryMetricsEvent process( - final @NotNull SentryMetricsEvent event, final @NotNull Hint hint) { - setDevice(event); - setOs(event); - return event; - } - - /** - * The last exception is usually used for picking the issue title, but the convention is to send - * inner exceptions first, e.g. [inner, outer] This doesn't work very well on Android, as some - * hooks like Application.onCreate is wrapped by Android framework with a RuntimeException. Thus, - * if the last exception is a RuntimeInit$MethodAndArgsCaller, reverse the order to get a better - * issue title. This is a quick fix, for more details see: #64074 #59679 #64088 - * - * @param event the event to process - */ - private static void fixExceptionOrder(final @NotNull SentryEvent event) { - boolean reverseExceptions = false; - - final @Nullable List exceptions = event.getExceptions(); - if (exceptions != null && exceptions.size() > 1) { - final @NotNull SentryException lastException = exceptions.get(exceptions.size() - 1); - if ("java.lang".equals(lastException.getModule())) { - final @Nullable SentryStackTrace stacktrace = lastException.getStacktrace(); - if (stacktrace != null) { - final @Nullable List frames = stacktrace.getFrames(); - if (frames != null) { - for (final @NotNull SentryStackFrame frame : frames) { - if ("com.android.internal.os.RuntimeInit$MethodAndArgsCaller" - .equals(frame.getModule())) { - reverseExceptions = true; - break; - } - } - } - } - } - } - - if (reverseExceptions) { - Collections.reverse(exceptions); - } - } - - private void setCommons( - final @NotNull SentryBaseEvent event, - final boolean errorEvent, - final boolean applyScopeData) { - mergeUser(event); - setDevice(event, errorEvent, applyScopeData); - setSideLoadedInfo(event); - } - - private boolean shouldApplyScopeData( - final @NotNull SentryBaseEvent event, final @NotNull Hint hint) { - if (HintUtils.shouldApplyScopeData(hint)) { - return true; - } else { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Event was cached so not applying data relevant to the current app execution/version: %s", - event.getEventId()); - return false; - } - } - - private void mergeUser(final @NotNull SentryBaseEvent event) { - @Nullable User user = event.getUser(); - if (user == null) { - user = new User(); - event.setUser(user); - } - - // userId should be set even if event is Cached as the userId is static and won't change anyway. - if (user.getId() == null) { - user.setId(options.getRuntimeManager().runWithRelaxedPolicy(() -> Installation.id(context))); - } - if (user.getIpAddress() == null && options.isSendDefaultPii()) { - user.setIpAddress(IpAddressUtils.DEFAULT_IP_ADDRESS); - } - } - - private void setDevice( - final @NotNull SentryBaseEvent event, - final boolean errorEvent, - final boolean applyScopeData) { - if (event.getContexts().getDevice() == null) { - if (deviceInfoUtil != null) { - try { - event - .getContexts() - .setDevice(deviceInfoUtil.get().collectDeviceInformation(errorEvent, applyScopeData)); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info", e); - } - } else { - options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info"); - } - mergeOS(event); - } - } - - private void mergeOS(final @NotNull SentryBaseEvent event) { - final OperatingSystem currentOS = event.getContexts().getOperatingSystem(); - - if (deviceInfoUtil != null) { - try { - final OperatingSystem androidOS = deviceInfoUtil.get().getOperatingSystem(); - // make Android OS the main OS using the 'os' key - event.getContexts().setOperatingSystem(androidOS); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve os system", e); - } - } else { - options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info"); - } - - if (currentOS != null) { - // add additional OS which was already part of the SentryEvent (eg Linux read from NDK) - String osNameKey = currentOS.getName(); - if (osNameKey != null && !osNameKey.isEmpty()) { - osNameKey = "os_" + osNameKey.trim().toLowerCase(Locale.ROOT); - } else { - osNameKey = "os_1"; - } - event.getContexts().put(osNameKey, currentOS); - } - } - - private void setDevice(final @NotNull SentryLogEvent event) { - try { - event.setAttribute( - "device.brand", - new SentryLogEventAttributeValue(SentryAttributeType.STRING, Build.BRAND)); - event.setAttribute( - "device.model", - new SentryLogEventAttributeValue(SentryAttributeType.STRING, Build.MODEL)); - event.setAttribute( - "device.family", - new SentryLogEventAttributeValue(SentryAttributeType.STRING, deviceFamily.getValue())); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info", e); - } - } - - private void setOs(final @NotNull SentryLogEvent event) { - try { - event.setAttribute( - "os.name", new SentryLogEventAttributeValue(SentryAttributeType.STRING, "Android")); - event.setAttribute( - "os.version", - new SentryLogEventAttributeValue(SentryAttributeType.STRING, Build.VERSION.RELEASE)); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve os system", e); - } - } - - private void setDevice(final @NotNull SentryMetricsEvent event) { - try { - event.setAttribute( - "device.brand", - new SentryLogEventAttributeValue(SentryAttributeType.STRING, Build.BRAND)); - event.setAttribute( - "device.model", - new SentryLogEventAttributeValue(SentryAttributeType.STRING, Build.MODEL)); - event.setAttribute( - "device.family", - new SentryLogEventAttributeValue(SentryAttributeType.STRING, deviceFamily.getValue())); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info", e); - } - } - - private void setOs(final @NotNull SentryMetricsEvent event) { - try { - event.setAttribute( - "os.name", new SentryLogEventAttributeValue(SentryAttributeType.STRING, "Android")); - event.setAttribute( - "os.version", - new SentryLogEventAttributeValue(SentryAttributeType.STRING, Build.VERSION.RELEASE)); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve os system", e); - } - } - - // Data to be applied to events that was created in the running process - private void processNonCachedEvent( - final @NotNull SentryBaseEvent event, final @NotNull Hint hint) { - App app = event.getContexts().getApp(); - if (app == null) { - app = new App(); - } - setAppExtras(app, hint); - setPackageInfo(event, app); - event.getContexts().setApp(app); - } - - private void setThreads(final @NotNull SentryEvent event, final @NotNull Hint hint) { - if (event.getThreads() != null) { - final boolean isHybridSDK = HintUtils.isFromHybridSdk(hint); - - for (final SentryThread thread : event.getThreads()) { - final boolean isMainThread = AndroidThreadChecker.getInstance().isMainThread(thread); - - // TODO: Fix https://github.com/getsentry/team-mobile/issues/47 - if (thread.isCurrent() == null) { - thread.setCurrent(isMainThread); - } - - // This should not be set by Hybrid SDKs since they have their own threading model - if (!isHybridSDK && thread.isMain() == null) { - thread.setMain(isMainThread); - } - } - } - } - - private void setPackageInfo(final @NotNull SentryBaseEvent event, final @NotNull App app) { - final PackageInfo packageInfo = - ContextUtils.getPackageInfo( - context, PackageManager.GET_PERMISSIONS, options.getLogger(), buildInfoProvider); - if (packageInfo != null) { - String versionCode = ContextUtils.getVersionCode(packageInfo, buildInfoProvider); - - setDist(event, versionCode); - - @Nullable DeviceInfoUtil deviceInfoUtil = null; - if (this.deviceInfoUtil != null) { - try { - deviceInfoUtil = this.deviceInfoUtil.get(); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info", e); - } - } else { - options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info"); - } - - ContextUtils.setAppPackageInfo(packageInfo, buildInfoProvider, deviceInfoUtil, app); - } - } - - private void setDist(final @NotNull SentryBaseEvent event, final @NotNull String versionCode) { - if (event.getDist() == null) { - event.setDist(versionCode); - } - } - - private void setAppExtras(final @NotNull App app, final @NotNull Hint hint) { - app.setAppName(ContextUtils.getApplicationName(context)); - final @NotNull TimeSpan appStartTimeSpan = - AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options); - if (appStartTimeSpan.hasStarted()) { - app.setAppStartTime(DateUtils.toUtilDate(appStartTimeSpan.getStartTimestamp())); - } - - // This should not be set by Hybrid SDKs since they have their own app's lifecycle - if (!HintUtils.isFromHybridSdk(hint) && app.getInForeground() == null) { - // This feature depends on the AppLifecycleIntegration being installed, so only if - // enableAutoSessionTracking or enableAppLifecycleBreadcrumbs are enabled. - final @Nullable Boolean isBackground = AppState.getInstance().isInBackground(); - if (isBackground != null) { - app.setInForeground(!isBackground); - } - } - } - - /** - * Sets the default user which contains only the userId. - * - * @return the User object - */ - public @NotNull User getDefaultUser(final @NotNull Context context) { - final @NotNull User user = new User(); - user.setId(options.getRuntimeManager().runWithRelaxedPolicy(() -> Installation.id(context))); - return user; - } - - private void setSideLoadedInfo(final @NotNull SentryBaseEvent event) { - if (deviceInfoUtil != null) { - try { - final ContextUtils.SideLoadedInfo sideLoadedInfo = deviceInfoUtil.get().getSideLoadedInfo(); - if (sideLoadedInfo != null) { - final @NotNull Map tags = sideLoadedInfo.asTags(); - for (Map.Entry entry : tags.entrySet()) { - event.setTag(entry.getKey(), entry.getValue()); - } - } - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting side loaded info.", e); - } - } else { - options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info"); - } - } - - @Override - public @NotNull SentryTransaction process( - final @NotNull SentryTransaction transaction, final @NotNull Hint hint) { - final boolean applyScopeData = shouldApplyScopeData(transaction, hint); - - if (applyScopeData) { - processNonCachedEvent(transaction, hint); - } - - setCommons(transaction, false, applyScopeData); - - return transaction; - } - - @Override - public @NotNull SentryReplayEvent process( - final @NotNull SentryReplayEvent event, final @NotNull Hint hint) { - final boolean applyScopeData = shouldApplyScopeData(event, hint); - if (applyScopeData) { - processNonCachedEvent(event, hint); - } - - setCommons(event, false, applyScopeData); - - return event; - } - - @Override - public @Nullable Long getOrder() { - return 8000L; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java b/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java deleted file mode 100644 index 5c06a558103..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java +++ /dev/null @@ -1,502 +0,0 @@ -package io.sentry.android.core; - -import static android.os.BatteryManager.EXTRA_TEMPERATURE; - -import android.annotation.SuppressLint; -import android.app.ActivityManager; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.BatteryManager; -import android.os.Build; -import android.os.Environment; -import android.os.LocaleList; -import android.os.StatFs; -import android.os.SystemClock; -import android.util.DisplayMetrics; -import io.sentry.DateUtils; -import io.sentry.ISentryLifecycleToken; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.android.core.internal.util.CpuInfoUtils; -import io.sentry.android.core.internal.util.DeviceOrientations; -import io.sentry.android.core.internal.util.RootChecker; -import io.sentry.protocol.Device; -import io.sentry.protocol.OperatingSystem; -import io.sentry.util.AutoClosableReentrantLock; -import java.io.File; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.TimeZone; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -@ApiStatus.Internal -public final class DeviceInfoUtil { - - @SuppressLint("StaticFieldLeak") - private static volatile DeviceInfoUtil instance; - - private static final @NotNull AutoClosableReentrantLock staticLock = - new AutoClosableReentrantLock(); - - private final @NotNull Context context; - private final @NotNull SentryAndroidOptions options; - private final @NotNull BuildInfoProvider buildInfoProvider; - private final @Nullable Boolean isEmulator; - private final @Nullable ContextUtils.SideLoadedInfo sideLoadedInfo; - private final @Nullable ContextUtils.SplitApksInfo splitApksInfo; - private final @NotNull OperatingSystem os; - - private final @Nullable Long totalMem; - - public DeviceInfoUtil( - final @NotNull Context context, final @NotNull SentryAndroidOptions options) { - this.context = context; - this.options = options; - this.buildInfoProvider = new BuildInfoProvider(options.getLogger()); - - // these are potentially expense IO operations - CpuInfoUtils.getInstance().readMaxFrequencies(); - os = retrieveOperatingSystemInformation(); - isEmulator = buildInfoProvider.isEmulator(); - sideLoadedInfo = - ContextUtils.retrieveSideLoadedInfo(context, options.getLogger(), buildInfoProvider); - splitApksInfo = ContextUtils.retrieveSplitApksInfo(context, buildInfoProvider); - final @Nullable ActivityManager.MemoryInfo memInfo = - ContextUtils.getMemInfo(context, options.getLogger()); - if (memInfo != null) { - totalMem = memInfo.totalMem; - } else { - totalMem = null; - } - } - - @NotNull - public static DeviceInfoUtil getInstance( - final @NotNull Context context, final @NotNull SentryAndroidOptions options) { - if (instance == null) { - try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { - if (instance == null) { - instance = new DeviceInfoUtil(ContextUtils.getApplicationContext(context), options); - } - } - } - return instance; - } - - @TestOnly - public static void resetInstance() { - instance = null; - } - - // we can get some inspiration here - // https://github.com/flutter/plugins/blob/master/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/DeviceInfoPlugin.java - @SuppressLint("NewApi") - @NotNull - public Device collectDeviceInformation( - final boolean collectDeviceIO, final boolean collectDynamicData) { - // TODO: missing usable memory - final @NotNull Device device = new Device(); - device.setManufacturer(Build.MANUFACTURER); - device.setBrand(Build.BRAND); - device.setFamily(ContextUtils.getFamily(options.getLogger())); - device.setModel(Build.MODEL); - device.setModelId(Build.ID); - device.setArchs(ContextUtils.getArchitectures()); - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.S) { - device.setChipset(Build.SOC_MANUFACTURER + " " + Build.SOC_MODEL); - } - - device.setOrientation(getOrientation()); - if (isEmulator != null) { - device.setSimulator(isEmulator); - } - - final @Nullable DisplayMetrics displayMetrics = - ContextUtils.getDisplayMetrics(context, options.getLogger()); - if (displayMetrics != null) { - device.setScreenWidthPixels(displayMetrics.widthPixels); - device.setScreenHeightPixels(displayMetrics.heightPixels); - device.setScreenDensity(displayMetrics.density); - device.setScreenDpi(displayMetrics.densityDpi); - } - - device.setBootTime(getBootTime()); - device.setTimezone(getTimeZone()); - - if (device.getId() == null) { - device.setId(getDeviceId()); - } - - final @NotNull Locale locale = Locale.getDefault(); - if (device.getLocale() == null) { - device.setLocale(locale.toString()); // eg en_US - } - - final @NotNull List cpuFrequencies = CpuInfoUtils.getInstance().readMaxFrequencies(); - if (!cpuFrequencies.isEmpty()) { - device.setProcessorFrequency(Collections.max(cpuFrequencies).doubleValue()); - device.setProcessorCount(cpuFrequencies.size()); - } - - device.setMemorySize(totalMem); - - // setting such values require IO hence we don't run for transactions - if (collectDeviceIO && options.isCollectAdditionalContext()) { - setDeviceIO(device, collectDynamicData, options.isCollectExternalStorageContext()); - } - - return device; - } - - @NotNull - public OperatingSystem getOperatingSystem() { - return os; - } - - @Nullable - public Long getTotalMemory() { - return totalMem; - } - - @NotNull - private OperatingSystem retrieveOperatingSystemInformation() { - - final OperatingSystem os = new OperatingSystem(); - os.setName("Android"); - os.setVersion(Build.VERSION.RELEASE); - os.setBuild(Build.DISPLAY); - - final @Nullable String kernelVersion = ContextUtils.getKernelVersion(options.getLogger()); - if (kernelVersion != null) { - os.setKernelVersion(kernelVersion); - } - - if (options.isEnableRootCheck()) { - final boolean rooted = - new RootChecker(context, buildInfoProvider, options.getLogger()).isDeviceRooted(); - os.setRooted(rooted); - } - return os; - } - - @Nullable - public ContextUtils.SideLoadedInfo getSideLoadedInfo() { - return sideLoadedInfo; - } - - @Nullable - public ContextUtils.SplitApksInfo getSplitApksInfo() { - return splitApksInfo; - } - - private void setDeviceIO( - final @NotNull Device device, - final boolean includeDynamicData, - final boolean includeExternalStorage) { - final Intent batteryIntent = getBatteryIntent(); - if (batteryIntent != null) { - device.setBatteryLevel(getBatteryLevel(batteryIntent, options)); - device.setCharging(isCharging(batteryIntent, options)); - device.setBatteryTemperature(getBatteryTemperature(batteryIntent)); - } - - // TODO .getConnectionStatus() may be blocking, investigate if this can be done async - Boolean connected; - switch (options.getConnectionStatusProvider().getConnectionStatus()) { - case DISCONNECTED: - connected = false; - break; - case CONNECTED: - connected = true; - break; - default: - connected = null; - } - device.setOnline(connected); - - final @Nullable ActivityManager.MemoryInfo memInfo = - ContextUtils.getMemInfo(context, options.getLogger()); - if (memInfo != null && includeDynamicData) { - // in bytes - device.setFreeMemory(memInfo.availMem); - device.setLowMemory(memInfo.lowMemory); - } - - // this way of getting the size of storage might be problematic for storages bigger than 2GB - // check the use of - // https://developer.android.com/reference/java/io/File.html#getFreeSpace%28%29 - options - .getRuntimeManager() - .runWithRelaxedPolicy( - () -> { - final @Nullable File dataDir = Environment.getDataDirectory(); - if (dataDir != null) { - StatFs internalStorageStat = new StatFs(dataDir.getPath()); - device.setStorageSize(getTotalInternalStorage(internalStorageStat)); - device.setFreeStorage(getUnusedInternalStorage(internalStorageStat)); - } - - if (includeExternalStorage) { - final @Nullable File internalStorageFile = context.getExternalFilesDir(null); - final @Nullable StatFs externalStorageStat = - getExternalStorageStat(internalStorageFile); - if (externalStorageStat != null) { - device.setExternalStorageSize(getTotalExternalStorage(externalStorageStat)); - device.setExternalFreeStorage(getUnusedExternalStorage(externalStorageStat)); - } - } - }); - - if (device.getConnectionType() == null) { - // wifi, ethernet or cellular, null if none - device.setConnectionType(options.getConnectionStatusProvider().getConnectionType()); - } - } - - @SuppressWarnings("NewApi") - @NotNull - private TimeZone getTimeZone() { - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.N) { - LocaleList locales = context.getResources().getConfiguration().getLocales(); - if (!locales.isEmpty()) { - Locale locale = locales.get(0); - return Calendar.getInstance(locale).getTimeZone(); - } - } - return Calendar.getInstance().getTimeZone(); - } - - @SuppressWarnings("JdkObsolete") - @Nullable - private Date getBootTime() { - try { - // if user changes the clock, will give a wrong answer, consider ACTION_TIME_CHANGED. - // currentTimeMillis returns UTC already - return DateUtils.getDateTime(System.currentTimeMillis() - SystemClock.elapsedRealtime()); - } catch (IllegalArgumentException e) { - options.getLogger().log(SentryLevel.ERROR, e, "Error getting the device's boot time."); - } - return null; - } - - @Nullable - private Intent getBatteryIntent() { - return ContextUtils.registerReceiver( - context, buildInfoProvider, null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED), null); - } - - /** - * Get the device's current battery level (as a percentage of total). - * - * @return the device's current battery level (as a percentage of total), or null if unknown - */ - @Nullable - public static Float getBatteryLevel( - final @NotNull Intent batteryIntent, final @NotNull SentryOptions options) { - try { - int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); - int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); - - if (level == -1 || scale == -1) { - return null; - } - - float percentMultiplier = 100.0f; - - return ((float) level / (float) scale) * percentMultiplier; - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting device battery level.", e); - return null; - } - } - - /** - * Checks whether or not the device is currently plugged in and charging, or null if unknown. - * - * @return whether or not the device is currently plugged in and charging, or null if unknown - */ - @Nullable - public static Boolean isCharging( - final @NotNull Intent batteryIntent, final @NotNull SentryOptions options) { - try { - int plugged = batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); - return plugged == BatteryManager.BATTERY_PLUGGED_AC - || plugged == BatteryManager.BATTERY_PLUGGED_USB; - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting device charging state.", e); - return null; - } - } - - @Nullable - private Float getBatteryTemperature(final @NotNull Intent batteryIntent) { - try { - int temperature = batteryIntent.getIntExtra(EXTRA_TEMPERATURE, -1); - if (temperature != -1) { - return ((float) temperature) / 10; // celsius - } - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting battery temperature.", e); - } - return null; - } - - /** - * Get the device's current screen orientation. - * - * @return the device's current screen orientation, or null if unknown - */ - @Nullable - private Device.DeviceOrientation getOrientation() { - Device.DeviceOrientation deviceOrientation = null; - try { - deviceOrientation = - DeviceOrientations.getOrientation(context.getResources().getConfiguration().orientation); - if (deviceOrientation == null) { - options - .getLogger() - .log( - SentryLevel.INFO, - "No device orientation available (ORIENTATION_SQUARE|ORIENTATION_UNDEFINED)"); - return null; - } - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting device orientation.", e); - } - return deviceOrientation; - } - - /** - * Get the total amount of internal storage, in bytes. - * - * @return the total amount of internal storage, in bytes - */ - @Nullable - private Long getTotalInternalStorage(final @NotNull StatFs stat) { - try { - long blockSize = stat.getBlockSizeLong(); - long totalBlocks = stat.getBlockCountLong(); - return totalBlocks * blockSize; - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting total internal storage amount.", e); - return null; - } - } - - /** - * Get the unused amount of internal storage, in bytes. - * - * @return the unused amount of internal storage, in bytes - */ - @Nullable - private Long getUnusedInternalStorage(final @NotNull StatFs stat) { - try { - long blockSize = stat.getBlockSizeLong(); - long availableBlocks = stat.getAvailableBlocksLong(); - return availableBlocks * blockSize; - } catch (Throwable e) { - options - .getLogger() - .log(SentryLevel.ERROR, "Error getting unused internal storage amount.", e); - return null; - } - } - - @Nullable - private StatFs getExternalStorageStat(final @Nullable File internalStorage) { - try { - File path = getExternalStorageDep(internalStorage); - if (path != null) { // && path.canRead()) { canRead() will read return false - return new StatFs(path.getPath()); - } - } catch (Throwable e) { - options.getLogger().log(SentryLevel.INFO, "Not possible to read external files directory"); - } - return null; - } - - @Nullable - private File getExternalStorageDep(final @Nullable File internalStorage) { - final @Nullable File[] externalFilesDirs = context.getExternalFilesDirs(null); - - if (externalFilesDirs != null) { - // return the 1st file which is not the emulated internal storage - String internalStoragePath = - internalStorage != null ? internalStorage.getAbsolutePath() : null; - for (File file : externalFilesDirs) { - // externalFilesDirs may contain null values :( - if (file == null) { - continue; - } - - // return the 1st file if you cannot compare with the internal one - if (internalStoragePath == null || internalStoragePath.isEmpty()) { - return file; - } - // if we are looking to the same directory, let's check the next one or no external storage - if (file.getAbsolutePath().contains(internalStoragePath)) { - continue; - } - return file; - } - } else { - options.getLogger().log(SentryLevel.INFO, "Not possible to read getExternalFilesDirs"); - } - return null; - } - - /** - * Get the total amount of external storage, in bytes, or null if no external storage is mounted. - * - * @return the total amount of external storage, in bytes, or null if no external storage is - * mounted - */ - @Nullable - private Long getTotalExternalStorage(final @NotNull StatFs stat) { - try { - final long blockSize = stat.getBlockSizeLong(); - final long totalBlocks = stat.getBlockCountLong(); - return totalBlocks * blockSize; - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting total external storage amount.", e); - return null; - } - } - - /** - * Get the unused amount of external storage, in bytes, or null if no external storage is mounted. - * - * @return the unused amount of external storage, in bytes, or null if no external storage is - * mounted - */ - @Nullable - private Long getUnusedExternalStorage(final @NotNull StatFs stat) { - try { - final long blockSize = stat.getBlockSizeLong(); - final long availableBlocks = stat.getAvailableBlocksLong(); - return availableBlocks * blockSize; - } catch (Throwable e) { - options - .getLogger() - .log(SentryLevel.ERROR, "Error getting unused external storage amount.", e); - return null; - } - } - - @Nullable - private String getDeviceId() { - try { - return options.getRuntimeManager().runWithRelaxedPolicy(() -> Installation.id(context)); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting installationId.", e); - } - return null; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/EmptySecureContentProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/EmptySecureContentProvider.java deleted file mode 100644 index ff8f2b4e980..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/EmptySecureContentProvider.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.sentry.android.core; - -import android.content.ContentProvider; -import android.content.ContentValues; -import android.database.Cursor; -import android.net.Uri; -import io.sentry.android.core.internal.util.ContentProviderSecurityChecker; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * A ContentProvider that does NOT store or provide any data for read or write operations. - * - *

This does not allow for overriding the abstract query, insert, update, and delete operations - * of the {@link ContentProvider}. Additionally, those functions are secure. - */ -@ApiStatus.Internal -abstract class EmptySecureContentProvider extends ContentProvider { - - private final ContentProviderSecurityChecker securityChecker = - new ContentProviderSecurityChecker(); - - @Override - public final @Nullable Cursor query( - @NotNull Uri uri, - @Nullable String[] strings, - @Nullable String s, - @Nullable String[] strings1, - @Nullable String s1) { - securityChecker.checkPrivilegeEscalation(this); - return null; - } - - @Override - public final @Nullable Uri insert(@NotNull Uri uri, @Nullable ContentValues contentValues) { - securityChecker.checkPrivilegeEscalation(this); - return null; - } - - @Override - public final int delete(@NotNull Uri uri, @Nullable String s, @Nullable String[] strings) { - securityChecker.checkPrivilegeEscalation(this); - return 0; - } - - @Override - public final int update( - @NotNull Uri uri, - @Nullable ContentValues contentValues, - @Nullable String s, - @Nullable String[] strings) { - securityChecker.checkPrivilegeEscalation(this); - return 0; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserver.java b/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserver.java deleted file mode 100644 index 4a3dc7a1786..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserver.java +++ /dev/null @@ -1,121 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.SentryLevel.ERROR; - -import android.os.FileObserver; -import io.sentry.Hint; -import io.sentry.IEnvelopeSender; -import io.sentry.ILogger; -import io.sentry.SentryLevel; -import io.sentry.hints.ApplyScopeData; -import io.sentry.hints.Cached; -import io.sentry.hints.Flushable; -import io.sentry.hints.Resettable; -import io.sentry.hints.Retryable; -import io.sentry.hints.SubmissionResult; -import io.sentry.util.HintUtils; -import io.sentry.util.Objects; -import java.io.File; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -final class EnvelopeFileObserver extends FileObserver { - - private final String rootPath; - private final IEnvelopeSender envelopeSender; - private @NotNull final ILogger logger; - private final long flushTimeoutMillis; - - // The preferred overload (Taking File instead of String) is only available from API 29 - @SuppressWarnings("deprecation") - EnvelopeFileObserver( - String path, - IEnvelopeSender envelopeSender, - @NotNull ILogger logger, - final long flushTimeoutMillis) { - super(path); - this.rootPath = path; - this.envelopeSender = Objects.requireNonNull(envelopeSender, "Envelope sender is required."); - this.logger = Objects.requireNonNull(logger, "Logger is required."); - this.flushTimeoutMillis = flushTimeoutMillis; - } - - @Override - public void onEvent(int eventType, @Nullable String relativePath) { - if (relativePath == null || eventType != FileObserver.CLOSE_WRITE) { - return; - } - - logger.log( - SentryLevel.DEBUG, - "onEvent fired for EnvelopeFileObserver with event type %d on path: %s for file %s.", - eventType, - this.rootPath, - relativePath); - - // TODO: Only some event types should be pass through? - - final CachedEnvelopeHint cachedHint = new CachedEnvelopeHint(flushTimeoutMillis, logger); - - final Hint hint = HintUtils.createWithTypeCheckHint(cachedHint); - - envelopeSender.processEnvelopeFile(this.rootPath + File.separator + relativePath, hint); - } - - private static final class CachedEnvelopeHint - implements Cached, Retryable, SubmissionResult, Flushable, ApplyScopeData, Resettable { - boolean retry; - boolean succeeded; - - private @NotNull CountDownLatch latch; - private final long flushTimeoutMillis; - private final @NotNull ILogger logger; - - public CachedEnvelopeHint(final long flushTimeoutMillis, final @NotNull ILogger logger) { - reset(); - this.flushTimeoutMillis = flushTimeoutMillis; - this.logger = Objects.requireNonNull(logger, "ILogger is required."); - } - - @Override - public boolean waitFlush() { - try { - return latch.await(flushTimeoutMillis, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - logger.log(ERROR, "Exception while awaiting on lock.", e); - } - return false; - } - - @Override - public boolean isRetry() { - return retry; - } - - @Override - public void setRetry(boolean retry) { - this.retry = retry; - } - - @Override - public void setResult(boolean succeeded) { - this.succeeded = succeeded; - latch.countDown(); - } - - @Override - public boolean isSuccess() { - return succeeded; - } - - @Override - public void reset() { - latch = new CountDownLatch(1); - retry = false; - succeeded = false; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserverIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserverIntegration.java deleted file mode 100644 index 482d90c6e6c..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserverIntegration.java +++ /dev/null @@ -1,119 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import io.sentry.ILogger; -import io.sentry.IScopes; -import io.sentry.ISentryLifecycleToken; -import io.sentry.Integration; -import io.sentry.OutboxSender; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.Objects; -import java.io.Closeable; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -/** Watches the envelope dir. and send them (events) over. */ -public abstract class EnvelopeFileObserverIntegration implements Integration, Closeable { - private @Nullable EnvelopeFileObserver observer; - private @Nullable ILogger logger; - private boolean isClosed = false; - protected final @NotNull AutoClosableReentrantLock startLock = new AutoClosableReentrantLock(); - - public static @NotNull EnvelopeFileObserverIntegration getOutboxFileObserver() { - return new OutboxEnvelopeFileObserverIntegration(); - } - - @Override - public final void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - Objects.requireNonNull(scopes, "Scopes are required"); - Objects.requireNonNull(options, "SentryOptions is required"); - - logger = options.getLogger(); - - final String path = getPath(options); - if (path == null) { - logger.log( - SentryLevel.WARNING, - "Null given as a path to EnvelopeFileObserverIntegration. Nothing will be registered."); - } else { - logger.log( - SentryLevel.DEBUG, "Registering EnvelopeFileObserverIntegration for path: %s", path); - - try { - options - .getExecutorService() - .submit( - () -> { - try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { - if (!isClosed) { - startOutboxSender(scopes, options, path); - } - } - }); - } catch (Throwable e) { - logger.log( - SentryLevel.DEBUG, - "Failed to start EnvelopeFileObserverIntegration on executor thread.", - e); - } - } - } - - private void startOutboxSender( - final @NotNull IScopes scopes, - final @NotNull SentryOptions options, - final @NotNull String path) { - final OutboxSender outboxSender = - new OutboxSender( - scopes, - options.getEnvelopeReader(), - options.getSerializer(), - options.getLogger(), - options.getFlushTimeoutMillis(), - options.getMaxQueueSize()); - - observer = - new EnvelopeFileObserver( - path, outboxSender, options.getLogger(), options.getFlushTimeoutMillis()); - try { - observer.startWatching(); - options.getLogger().log(SentryLevel.DEBUG, "EnvelopeFileObserverIntegration installed."); - addIntegrationToSdkVersion("EnvelopeFileObserver"); - } catch (Throwable e) { - // it could throw eg NoSuchFileException or NullPointerException - options - .getLogger() - .log(SentryLevel.ERROR, "Failed to initialize EnvelopeFileObserverIntegration.", e); - } - } - - @Override - public void close() { - try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { - isClosed = true; - } - if (observer != null) { - observer.stopWatching(); - - if (logger != null) { - logger.log(SentryLevel.DEBUG, "EnvelopeFileObserverIntegration removed."); - } - } - } - - @TestOnly - abstract @Nullable String getPath(final @NotNull SentryOptions options); - - private static final class OutboxEnvelopeFileObserverIntegration - extends EnvelopeFileObserverIntegration { - - @Override - protected @Nullable String getPath(final @NotNull SentryOptions options) { - return options.getOutboxPath(); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/IDebugImagesLoader.java b/sentry-android-core/src/main/java/io/sentry/android/core/IDebugImagesLoader.java deleted file mode 100644 index 7b98147aab8..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/IDebugImagesLoader.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.sentry.android.core; - -import io.sentry.protocol.DebugImage; -import java.util.List; -import java.util.Set; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -/** Used for loading the list of debug images from sentry-native. */ -@ApiStatus.Internal -public interface IDebugImagesLoader { - @Nullable - List loadDebugImages(); - - @Nullable - Set loadDebugImagesForAddresses(Set addresses); - - void clearDebugImages(); -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java b/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java deleted file mode 100644 index ba08e71342a..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java +++ /dev/null @@ -1,74 +0,0 @@ -package io.sentry.android.core; - -import android.content.Context; -import io.sentry.ISentryLifecycleToken; -import io.sentry.SentryUUID; -import io.sentry.util.AutoClosableReentrantLock; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.nio.charset.Charset; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -final class Installation { - @TestOnly static @Nullable String deviceId = null; - - @TestOnly static final String INSTALLATION = "INSTALLATION"; - - private static final Charset UTF_8 = Charset.forName("UTF-8"); - - protected static final @NotNull AutoClosableReentrantLock staticLock = - new AutoClosableReentrantLock(); - - private Installation() {} - - /** - * Generates a random UUID and writes to a file to be used as an unique installationId. Reads the - * installationId if already exists. - * - * @param context the Context - * @return the generated installationId - * @throws RuntimeException if not possible to read nor to write to the file. - */ - public static String id(final @NotNull Context context) throws RuntimeException { - try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { - if (deviceId == null) { - final File installation = new File(context.getFilesDir(), INSTALLATION); - try { - if (!installation.exists()) { - deviceId = writeInstallationFile(installation); - return deviceId; - } - deviceId = readInstallationFile(installation); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - return deviceId; - } - } - - @TestOnly - static @NotNull String readInstallationFile(final @NotNull File installation) throws IOException { - try (final RandomAccessFile f = new RandomAccessFile(installation, "r")) { - final byte[] bytes = new byte[(int) f.length()]; - f.readFully(bytes); - return new String(bytes, UTF_8); - } - } - - @TestOnly - static @NotNull String writeInstallationFile(final @NotNull File installation) - throws IOException { - try (final OutputStream out = new FileOutputStream(installation)) { - final String id = SentryUUID.generateSentryId(); - out.write(id.getBytes(UTF_8)); - out.flush(); - return id; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java deleted file mode 100644 index 7d0a7f77e58..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java +++ /dev/null @@ -1,354 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.Sentry.getCurrentScopes; -import static io.sentry.SentryLevel.DEBUG; -import static io.sentry.SentryLevel.INFO; -import static io.sentry.SentryLevel.WARNING; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import io.sentry.DateUtils; -import io.sentry.ILogger; -import io.sentry.IScope; -import io.sentry.IScopes; -import io.sentry.ISerializer; -import io.sentry.ObjectWriter; -import io.sentry.PropagationContext; -import io.sentry.ScopeType; -import io.sentry.ScopesAdapter; -import io.sentry.SentryEnvelope; -import io.sentry.SentryEnvelopeItem; -import io.sentry.SentryEvent; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.Session; -import io.sentry.android.core.performance.ActivityLifecycleTimeSpan; -import io.sentry.android.core.performance.AppStartMetrics; -import io.sentry.android.core.performance.TimeSpan; -import io.sentry.cache.EnvelopeCache; -import io.sentry.protocol.App; -import io.sentry.protocol.Device; -import io.sentry.protocol.SentryId; -import io.sentry.protocol.User; -import io.sentry.util.MapObjectWriter; -import io.sentry.util.TracingUtils; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** Sentry SDK internal API methods meant for being used by the Sentry Hybrid SDKs. */ -@ApiStatus.Internal -public final class InternalSentrySdk { - - /** - * @return a copy of the current scopes's topmost scope, or null in case the scopes is disabled - */ - @Nullable - public static IScope getCurrentScope() { - final @NotNull AtomicReference scopeRef = new AtomicReference<>(); - ScopesAdapter.getInstance() - .configureScope( - ScopeType.COMBINED, - scope -> { - scopeRef.set(scope.clone()); - }); - return scopeRef.get(); - } - - /** - * Serializes the provided scope. Specific data may be back-filled (e.g. device context) if the - * scope itself does not provide it. - * - * @param context Android context - * @param options Sentry Options - * @param scope the scope - * @return a map containing all relevant scope data (user, contexts, tags, extras, fingerprint, - * level, breadcrumbs) - */ - @NotNull - public static Map serializeScope( - final @NotNull Context context, - final @NotNull SentryAndroidOptions options, - final @Nullable IScope scope) { - - final @NotNull Map data = new HashMap<>(); - if (scope == null) { - return data; - } - try { - final @NotNull ILogger logger = options.getLogger(); - final @NotNull ObjectWriter writer = new MapObjectWriter(data); - - final @NotNull DeviceInfoUtil deviceInfoUtil = DeviceInfoUtil.getInstance(context, options); - final @NotNull Device deviceInfo = deviceInfoUtil.collectDeviceInformation(true, true); - scope.getContexts().setDevice(deviceInfo); - scope.getContexts().setOperatingSystem(deviceInfoUtil.getOperatingSystem()); - - // user - @Nullable User user = scope.getUser(); - if (user == null) { - user = new User(); - scope.setUser(user); - } - if (user.getId() == null) { - try { - user.setId( - options.getRuntimeManager().runWithRelaxedPolicy(() -> Installation.id(context))); - } catch (RuntimeException e) { - logger.log(SentryLevel.ERROR, "Could not retrieve installation ID", e); - } - } - - // app context - @Nullable App app = scope.getContexts().getApp(); - if (app == null) { - app = new App(); - } - app.setAppName(ContextUtils.getApplicationName(context)); - - final @NotNull TimeSpan appStartTimeSpan = - AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options); - if (appStartTimeSpan.hasStarted()) { - app.setAppStartTime(DateUtils.toUtilDate(appStartTimeSpan.getStartTimestamp())); - } - - final @NotNull BuildInfoProvider buildInfoProvider = - new BuildInfoProvider(options.getLogger()); - final @Nullable PackageInfo packageInfo = - ContextUtils.getPackageInfo( - context, PackageManager.GET_PERMISSIONS, options.getLogger(), buildInfoProvider); - if (packageInfo != null) { - ContextUtils.setAppPackageInfo(packageInfo, buildInfoProvider, deviceInfoUtil, app); - } - scope.getContexts().setApp(app); - - writer.name("user").value(logger, scope.getUser()); - writer.name("contexts").value(logger, scope.getContexts()); - writer.name("tags").value(logger, scope.getTags()); - writer.name("extras").value(logger, scope.getExtras()); - writer.name("fingerprint").value(logger, scope.getFingerprint()); - writer.name("level").value(logger, scope.getLevel()); - writer.name("breadcrumbs").value(logger, scope.getBreadcrumbs()); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Could not serialize scope.", e); - return new HashMap<>(); - } - - return data; - } - - /** - * Captures the provided envelope. Compared to {@link IScopes#captureEvent(SentryEvent)} this - * method
- * - will not enrich events with additional data (e.g. scope)
- * - will not execute beforeSend: it's up to the caller to take care of this
- * - will not perform any sampling: it's up to the caller to take care of this
- * - will enrich the envelope with a Session update if applicable
- * - * @param envelopeData the serialized envelope data - * @return The Id (SentryId object) of the event, or null in case the envelope could not be - * captured - */ - @Nullable - public static SentryId captureEnvelope( - final @NotNull byte[] envelopeData, final boolean maybeStartNewSession) { - final @NotNull IScopes scopes = ScopesAdapter.getInstance(); - final @NotNull SentryOptions options = scopes.getOptions(); - - try (final InputStream envelopeInputStream = new ByteArrayInputStream(envelopeData)) { - final @NotNull ISerializer serializer = options.getSerializer(); - final @Nullable SentryEnvelope envelope = - options.getEnvelopeReader().read(envelopeInputStream); - if (envelope == null) { - return null; - } - - final @NotNull List envelopeItems = new ArrayList<>(); - - // determine session state based on events inside envelope - @Nullable Session.State status = null; - boolean crashedOrErrored = false; - for (SentryEnvelopeItem item : envelope.getItems()) { - envelopeItems.add(item); - - final SentryEvent event = item.getEvent(serializer); - if (event != null) { - if (event.isCrashed()) { - status = Session.State.Crashed; - } - if (event.isCrashed() || event.isErrored()) { - crashedOrErrored = true; - } - } - } - - // update session and add it to envelope if necessary - final @Nullable Session session = updateSession(scopes, options, status, crashedOrErrored); - if (session != null) { - final SentryEnvelopeItem sessionItem = SentryEnvelopeItem.fromSession(serializer, session); - envelopeItems.add(sessionItem); - deleteCurrentSessionFile( - options, - // should be sync if going to crash or already not a main thread - !maybeStartNewSession || !scopes.getOptions().getThreadChecker().isMainThread()); - if (maybeStartNewSession) { - scopes.startSession(); - } - } - - final SentryEnvelope repackagedEnvelope = - new SentryEnvelope(envelope.getHeader(), envelopeItems); - return scopes.captureEnvelope(repackagedEnvelope); - } catch (Throwable t) { - options.getLogger().log(SentryLevel.ERROR, "Failed to capture envelope", t); - } - return null; - } - - public static Map getAppStartMeasurement() { - final @NotNull AppStartMetrics metrics = AppStartMetrics.getInstance(); - final @NotNull List> spans = new ArrayList<>(); - - addTimeSpanToSerializedSpans(metrics.createProcessInitSpan(), spans); - addTimeSpanToSerializedSpans(metrics.getApplicationOnCreateTimeSpan(), spans); - - for (final TimeSpan span : metrics.getContentProviderOnCreateTimeSpans()) { - addTimeSpanToSerializedSpans(span, spans); - } - - for (final ActivityLifecycleTimeSpan span : metrics.getActivityLifecycleTimeSpans()) { - addTimeSpanToSerializedSpans(span.getOnCreate(), spans); - addTimeSpanToSerializedSpans(span.getOnStart(), spans); - } - - final @NotNull Map result = new HashMap<>(); - result.put("spans", spans); - result.put("type", metrics.getAppStartType().toString().toLowerCase(Locale.ROOT)); - if (metrics.getAppStartTimeSpan().hasStarted()) { - result.put("app_start_timestamp_ms", metrics.getAppStartTimeSpan().getStartTimestampMs()); - } - - return result; - } - - private static void addTimeSpanToSerializedSpans(TimeSpan span, List> spans) { - if (span.hasNotStarted()) { - ScopesAdapter.getInstance() - .getOptions() - .getLogger() - .log(WARNING, "Can not convert not-started TimeSpan to Map for Hybrid SDKs."); - return; - } - - if (span.hasNotStopped()) { - ScopesAdapter.getInstance() - .getOptions() - .getLogger() - .log(WARNING, "Can not convert not-stopped TimeSpan to Map for Hybrid SDKs."); - return; - } - - final @NotNull Map spanMap = new HashMap<>(); - spanMap.put("description", span.getDescription()); - spanMap.put("start_timestamp_ms", span.getStartTimestampMs()); - spanMap.put("end_timestamp_ms", span.getProjectedStopTimestampMs()); - spans.add(spanMap); - } - - private static void deleteCurrentSessionFile( - final @NotNull SentryOptions options, boolean isSync) { - if (!isSync) { - try { - options - .getExecutorService() - .submit( - () -> { - deleteCurrentSessionFile(options); - }); - } catch (Throwable e) { - options - .getLogger() - .log(WARNING, "Submission of deletion of the current session file rejected.", e); - } - } else { - deleteCurrentSessionFile(options); - } - } - - private static void deleteCurrentSessionFile(final @NotNull SentryOptions options) { - final String cacheDirPath = options.getCacheDirPath(); - if (cacheDirPath == null) { - options.getLogger().log(INFO, "Cache dir is not set, not deleting the current session."); - return; - } - - if (!options.isEnableAutoSessionTracking()) { - options - .getLogger() - .log(DEBUG, "Session tracking is disabled, bailing from deleting current session file."); - return; - } - - final File sessionFile = EnvelopeCache.getCurrentSessionFile(cacheDirPath); - if (!sessionFile.delete()) { - options.getLogger().log(WARNING, "Failed to delete the current session file."); - } - } - - @Nullable - private static Session updateSession( - final @NotNull IScopes scopes, - final @NotNull SentryOptions options, - final @Nullable Session.State status, - final boolean crashedOrErrored) { - final @NotNull AtomicReference sessionRef = new AtomicReference<>(); - scopes.configureScope( - scope -> { - final @Nullable Session session = scope.getSession(); - if (session != null) { - final boolean updated = session.update(status, null, crashedOrErrored, null); - // if we have an uncaughtExceptionHint we can end the session. - if (updated) { - if (session.getStatus() == Session.State.Crashed) { - session.end(); - // Session needs to be removed from the scope, otherwise it will be send twice - // standalone and with the crash event - scope.clearSession(); - } - sessionRef.set(session); - } - } else { - options.getLogger().log(INFO, "Session is null on updateSession"); - } - }); - return sessionRef.get(); - } - - /** - * Allows a Hybrid SDK to set the trace on the native layer - * - * @param traceId the trace ID - * @param spanId the trace origin's span ID - * @param sampleRate the sample rate used by the origin of the trace - * @param sampleRand the random value used to sample with by the origin of the trace - */ - public static void setTrace( - final @NotNull String traceId, - final @NotNull String spanId, - final @Nullable Double sampleRate, - final @Nullable Double sampleRand) { - TracingUtils.setTrace( - getCurrentScopes(), - PropagationContext.fromExistingTrace(traceId, spanId, sampleRate, sampleRand)); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java b/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java deleted file mode 100644 index 3d4cedb1b53..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java +++ /dev/null @@ -1,156 +0,0 @@ -package io.sentry.android.core; - -import io.sentry.Breadcrumb; -import io.sentry.IScopes; -import io.sentry.ISentryLifecycleToken; -import io.sentry.SentryLevel; -import io.sentry.Session; -import io.sentry.transport.CurrentDateProvider; -import io.sentry.transport.ICurrentDateProvider; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.LazyEvaluator; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.atomic.AtomicLong; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -final class LifecycleWatcher implements AppState.AppStateListener { - - private final AtomicLong lastUpdatedSession = new AtomicLong(0L); - - private final long sessionIntervalMillis; - - private @Nullable TimerTask timerTask; - private final @NotNull LazyEvaluator timer = new LazyEvaluator<>(() -> new Timer(true)); - private final @NotNull AutoClosableReentrantLock timerLock = new AutoClosableReentrantLock(); - private final @NotNull IScopes scopes; - private final boolean enableSessionTracking; - private final boolean enableAppLifecycleBreadcrumbs; - - private final @NotNull ICurrentDateProvider currentDateProvider; - - LifecycleWatcher( - final @NotNull IScopes scopes, - final long sessionIntervalMillis, - final boolean enableSessionTracking, - final boolean enableAppLifecycleBreadcrumbs) { - this( - scopes, - sessionIntervalMillis, - enableSessionTracking, - enableAppLifecycleBreadcrumbs, - CurrentDateProvider.getInstance()); - } - - LifecycleWatcher( - final @NotNull IScopes scopes, - final long sessionIntervalMillis, - final boolean enableSessionTracking, - final boolean enableAppLifecycleBreadcrumbs, - final @NotNull ICurrentDateProvider currentDateProvider) { - this.sessionIntervalMillis = sessionIntervalMillis; - this.enableSessionTracking = enableSessionTracking; - this.enableAppLifecycleBreadcrumbs = enableAppLifecycleBreadcrumbs; - this.scopes = scopes; - this.currentDateProvider = currentDateProvider; - } - - @Override - public void onForeground() { - startSession(); - addAppBreadcrumb("foreground"); - } - - private void startSession() { - cancelTask(); - - final long currentTimeMillis = currentDateProvider.getCurrentTimeMillis(); - - scopes.configureScope( - scope -> { - if (lastUpdatedSession.get() == 0L) { - final @Nullable Session currentSession = scope.getSession(); - if (currentSession != null && currentSession.getStarted() != null) { - lastUpdatedSession.set(currentSession.getStarted().getTime()); - } - } - }); - - final long lastUpdatedSession = this.lastUpdatedSession.get(); - if (lastUpdatedSession == 0L - || (lastUpdatedSession + sessionIntervalMillis) <= currentTimeMillis) { - if (enableSessionTracking) { - scopes.startSession(); - } - scopes.getOptions().getReplayController().start(); - } - scopes.getOptions().getReplayController().resume(); - this.lastUpdatedSession.set(currentTimeMillis); - } - - // App went to background and triggered this callback after 700ms - // as no new screen was shown - @Override - public void onBackground() { - final long currentTimeMillis = currentDateProvider.getCurrentTimeMillis(); - this.lastUpdatedSession.set(currentTimeMillis); - - scopes.getOptions().getReplayController().pause(); - scheduleEndSession(); - - addAppBreadcrumb("background"); - } - - private void scheduleEndSession() { - try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) { - cancelTask(); - timerTask = - new TimerTask() { - @Override - public void run() { - if (enableSessionTracking) { - scopes.endSession(); - } - scopes.getOptions().getReplayController().stop(); - scopes.getOptions().getContinuousProfiler().close(false); - } - }; - - timer.getValue().schedule(timerTask, sessionIntervalMillis); - } - } - - private void cancelTask() { - try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) { - if (timerTask != null) { - timerTask.cancel(); - timerTask = null; - } - } - } - - private void addAppBreadcrumb(final @NotNull String state) { - if (enableAppLifecycleBreadcrumbs) { - final Breadcrumb breadcrumb = new Breadcrumb(); - breadcrumb.setType("navigation"); - breadcrumb.setData("state", state); - breadcrumb.setCategory("app.lifecycle"); - breadcrumb.setLevel(SentryLevel.INFO); - scopes.addBreadcrumb(breadcrumb); - } - } - - @TestOnly - @Nullable - TimerTask getTimerTask() { - return timerTask; - } - - @TestOnly - @NotNull - Timer getTimer() { - return timer.getValue(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/LoadClass.java b/sentry-android-core/src/main/java/io/sentry/android/core/LoadClass.java deleted file mode 100644 index 34b8d1d5f19..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/LoadClass.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.sentry.android.core; - -import io.sentry.ILogger; -import io.sentry.SentryOptions; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * An Adapter for making Class.forName testable - * - * @deprecated please use {@link io.sentry.util.LoadClass} instead. - */ -@Deprecated -public final class LoadClass extends io.sentry.util.LoadClass { - - private final io.sentry.util.LoadClass delegate; - - public LoadClass() { - delegate = new io.sentry.util.LoadClass(); - } - - /** - * Try to load a class via reflection - * - * @param clazz the full class name - * @param logger an instance of ILogger - * @return a Class if it's available, or null - */ - public @Nullable Class loadClass(final @NotNull String clazz, final @Nullable ILogger logger) { - return delegate.loadClass(clazz, logger); - } - - public boolean isClassAvailable(final @NotNull String clazz, final @Nullable ILogger logger) { - return delegate.isClassAvailable(clazz, logger); - } - - public boolean isClassAvailable( - final @NotNull String clazz, final @Nullable SentryOptions options) { - return delegate.isClassAvailable(clazz, options); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/MainLooperHandler.java b/sentry-android-core/src/main/java/io/sentry/android/core/MainLooperHandler.java deleted file mode 100644 index 46fb6d0fc4b..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/MainLooperHandler.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.sentry.android.core; - -import android.os.Handler; -import android.os.Looper; -import org.jetbrains.annotations.NotNull; - -final class MainLooperHandler { - private final @NotNull Handler handler; - - MainLooperHandler() { - this(Looper.getMainLooper()); - } - - MainLooperHandler(final @NotNull Looper looper) { - handler = new Handler(looper); - } - - public void post(final @NotNull Runnable runnable) { - handler.post(runnable); - } - - public @NotNull Thread getThread() { - return handler.getLooper().getThread(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java deleted file mode 100644 index 17d75b39c74..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java +++ /dev/null @@ -1,774 +0,0 @@ -package io.sentry.android.core; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.os.Bundle; -import io.sentry.ILogger; -import io.sentry.InitPriority; -import io.sentry.ProfileLifecycle; -import io.sentry.ScreenshotStrategyType; -import io.sentry.SentryFeedbackOptions; -import io.sentry.SentryIntegrationPackageStorage; -import io.sentry.SentryLevel; -import io.sentry.SentryReplayOptions; -import io.sentry.protocol.SdkVersion; -import io.sentry.util.Objects; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** Class responsible for reading values from manifest and setting them to the options */ -final class ManifestMetadataReader { - - static final String DSN = "io.sentry.dsn"; - static final String DEBUG = "io.sentry.debug"; - static final String DEBUG_LEVEL = "io.sentry.debug.level"; - static final String SAMPLE_RATE = "io.sentry.sample-rate"; - static final String ANR_ENABLE = "io.sentry.anr.enable"; - static final String ANR_REPORT_DEBUG = "io.sentry.anr.report-debug"; - static final String ANR_TIMEOUT_INTERVAL_MILLIS = "io.sentry.anr.timeout-interval-millis"; - static final String ANR_ATTACH_THREAD_DUMPS = "io.sentry.anr.attach-thread-dumps"; - - static final String TOMBSTONE_ENABLE = "io.sentry.tombstone.enable"; - - static final String AUTO_INIT = "io.sentry.auto-init"; - static final String NDK_ENABLE = "io.sentry.ndk.enable"; - static final String NDK_SCOPE_SYNC_ENABLE = "io.sentry.ndk.scope-sync.enable"; - static final String NDK_SDK_NAME = "io.sentry.ndk.sdk-name"; - static final String RELEASE = "io.sentry.release"; - static final String ENVIRONMENT = "io.sentry.environment"; - static final String SDK_NAME = "io.sentry.sdk.name"; - static final String SDK_VERSION = "io.sentry.sdk.version"; - - static final String AUTO_SESSION_TRACKING_ENABLE = "io.sentry.auto-session-tracking.enable"; - static final String SESSION_TRACKING_TIMEOUT_INTERVAL_MILLIS = - "io.sentry.session-tracking.timeout-interval-millis"; - - static final String BREADCRUMBS_ACTIVITY_LIFECYCLE_ENABLE = - "io.sentry.breadcrumbs.activity-lifecycle"; - static final String BREADCRUMBS_APP_LIFECYCLE_ENABLE = "io.sentry.breadcrumbs.app-lifecycle"; - static final String BREADCRUMBS_SYSTEM_EVENTS_ENABLE = "io.sentry.breadcrumbs.system-events"; - static final String BREADCRUMBS_NETWORK_EVENTS_ENABLE = "io.sentry.breadcrumbs.network-events"; - static final String BREADCRUMBS_APP_COMPONENTS_ENABLE = "io.sentry.breadcrumbs.app-components"; - static final String BREADCRUMBS_USER_INTERACTION_ENABLE = - "io.sentry.breadcrumbs.user-interaction"; - - static final String UNCAUGHT_EXCEPTION_HANDLER_ENABLE = - "io.sentry.uncaught-exception-handler.enable"; - - static final String TRACES_SAMPLE_RATE = "io.sentry.traces.sample-rate"; - static final String TRACES_ACTIVITY_ENABLE = "io.sentry.traces.activity.enable"; - static final String TRACES_ACTIVITY_AUTO_FINISH_ENABLE = - "io.sentry.traces.activity.auto-finish.enable"; - static final String TRACES_UI_ENABLE = "io.sentry.traces.user-interaction.enable"; - - static final String TTFD_ENABLE = "io.sentry.traces.time-to-full-display.enable"; - - static final String PROFILES_SAMPLE_RATE = "io.sentry.traces.profiling.sample-rate"; - - static final String PROFILE_SESSION_SAMPLE_RATE = - "io.sentry.traces.profiling.session-sample-rate"; - - static final String PROFILE_LIFECYCLE = "io.sentry.traces.profiling.lifecycle"; - - static final String PROFILER_START_ON_APP_START = "io.sentry.traces.profiling.start-on-app-start"; - - @ApiStatus.Experimental static final String TRACE_SAMPLING = "io.sentry.traces.trace-sampling"; - static final String TRACE_PROPAGATION_TARGETS = "io.sentry.traces.trace-propagation-targets"; - - static final String ATTACH_THREADS = "io.sentry.attach-threads"; - static final String PROGUARD_UUID = "io.sentry.proguard-uuid"; - static final String IDLE_TIMEOUT = "io.sentry.traces.idle-timeout"; - - static final String ATTACH_SCREENSHOT = "io.sentry.attach-screenshot"; - static final String ATTACH_VIEW_HIERARCHY = "io.sentry.attach-view-hierarchy"; - static final String CLIENT_REPORTS_ENABLE = "io.sentry.send-client-reports"; - static final String COLLECT_ADDITIONAL_CONTEXT = "io.sentry.additional-context"; - static final String COLLECT_EXTERNAL_STORAGE_CONTEXT = "io.sentry.external-storage-context"; - - static final String SEND_DEFAULT_PII = "io.sentry.send-default-pii"; - - static final String PERFORM_FRAMES_TRACKING = "io.sentry.traces.frames-tracking"; - - static final String SENTRY_GRADLE_PLUGIN_INTEGRATIONS = "io.sentry.gradle-plugin-integrations"; - - static final String ENABLE_ROOT_CHECK = "io.sentry.enable-root-check"; - - static final String ENABLE_SENTRY = "io.sentry.enabled"; - - static final String SEND_MODULES = "io.sentry.send-modules"; - - static final String ENABLE_PERFORMANCE_V2 = "io.sentry.performance-v2.enable"; - - static final String ENABLE_APP_START_PROFILING = "io.sentry.profiling.enable-app-start"; - - static final String ENABLE_SCOPE_PERSISTENCE = "io.sentry.enable-scope-persistence"; - - static final String REPLAYS_SESSION_SAMPLE_RATE = "io.sentry.session-replay.session-sample-rate"; - - static final String REPLAYS_ERROR_SAMPLE_RATE = "io.sentry.session-replay.on-error-sample-rate"; - - static final String REPLAYS_MASK_ALL_TEXT = "io.sentry.session-replay.mask-all-text"; - - static final String REPLAYS_MASK_ALL_IMAGES = "io.sentry.session-replay.mask-all-images"; - - static final String REPLAYS_DEBUG = "io.sentry.session-replay.debug"; - static final String REPLAYS_SCREENSHOT_STRATEGY = "io.sentry.session-replay.screenshot-strategy"; - - static final String REPLAYS_NETWORK_DETAIL_ALLOW_URLS = - "io.sentry.session-replay.network-detail-allow-urls"; - - static final String REPLAYS_NETWORK_DETAIL_DENY_URLS = - "io.sentry.session-replay.network-detail-deny-urls"; - - static final String REPLAYS_NETWORK_CAPTURE_BODIES = - "io.sentry.session-replay.network-capture-bodies"; - - static final String REPLAYS_NETWORK_REQUEST_HEADERS = - "io.sentry.session-replay.network-request-headers"; - - static final String REPLAYS_NETWORK_RESPONSE_HEADERS = - "io.sentry.session-replay.network-response-headers"; - - static final String FORCE_INIT = "io.sentry.force-init"; - - static final String MAX_BREADCRUMBS = "io.sentry.max-breadcrumbs"; - - static final String IGNORED_ERRORS = "io.sentry.ignored-errors"; - - static final String IN_APP_INCLUDES = "io.sentry.in-app-includes"; - - static final String IN_APP_EXCLUDES = "io.sentry.in-app-excludes"; - - static final String ENABLE_LOGS = "io.sentry.logs.enabled"; - - static final String ENABLE_METRICS = "io.sentry.metrics.enabled"; - - static final String ENABLE_AUTO_TRACE_ID_GENERATION = - "io.sentry.traces.enable-auto-id-generation"; - - static final String DEADLINE_TIMEOUT = "io.sentry.traces.deadline-timeout"; - - static final String FEEDBACK_NAME_REQUIRED = "io.sentry.feedback.is-name-required"; - - static final String FEEDBACK_SHOW_NAME = "io.sentry.feedback.show-name"; - - static final String FEEDBACK_EMAIL_REQUIRED = "io.sentry.feedback.is-email-required"; - - static final String FEEDBACK_SHOW_EMAIL = "io.sentry.feedback.show-email"; - - static final String FEEDBACK_USE_SENTRY_USER = "io.sentry.feedback.use-sentry-user"; - - static final String FEEDBACK_SHOW_BRANDING = "io.sentry.feedback.show-branding"; - - static final String SPOTLIGHT_ENABLE = "io.sentry.spotlight.enable"; - - static final String SPOTLIGHT_CONNECTION_URL = "io.sentry.spotlight.url"; - - /** ManifestMetadataReader ctor */ - private ManifestMetadataReader() {} - - /** - * Reads configurations from Manifest and sets it to the options - * - * @param context the application context - * @param options the SentryAndroidOptions - */ - @SuppressWarnings("deprecation") - static void applyMetadata( - final @NotNull Context context, - final @NotNull SentryAndroidOptions options, - final @NotNull BuildInfoProvider buildInfoProvider) { - Objects.requireNonNull(context, "The application context is required."); - Objects.requireNonNull(options, "The options object is required."); - - try { - final Bundle metadata = getMetadata(context, options.getLogger(), buildInfoProvider); - final ILogger logger = options.getLogger(); - - if (metadata != null) { - options.setDebug(readBool(metadata, logger, DEBUG, options.isDebug())); - - if (options.isDebug()) { - final @Nullable String level = - readString( - metadata, - logger, - DEBUG_LEVEL, - options.getDiagnosticLevel().name().toLowerCase(Locale.ROOT)); - if (level != null) { - options.setDiagnosticLevel(SentryLevel.valueOf(level.toUpperCase(Locale.ROOT))); - } - } - - options.setAnrEnabled(readBool(metadata, logger, ANR_ENABLE, options.isAnrEnabled())); - options.setTombstoneEnabled( - readBool(metadata, logger, TOMBSTONE_ENABLE, options.isTombstoneEnabled())); - - // use enableAutoSessionTracking as fallback - options.setEnableAutoSessionTracking( - readBool( - metadata, - logger, - AUTO_SESSION_TRACKING_ENABLE, - options.isEnableAutoSessionTracking())); - - if (options.getSampleRate() == null) { - final double sampleRate = readDouble(metadata, logger, SAMPLE_RATE); - if (sampleRate != -1) { - options.setSampleRate(sampleRate); - } - } - - options.setAnrReportInDebug( - readBool(metadata, logger, ANR_REPORT_DEBUG, options.isAnrReportInDebug())); - - options.setAnrTimeoutIntervalMillis( - readLong( - metadata, - logger, - ANR_TIMEOUT_INTERVAL_MILLIS, - options.getAnrTimeoutIntervalMillis())); - - options.setAttachAnrThreadDump( - readBool(metadata, logger, ANR_ATTACH_THREAD_DUMPS, options.isAttachAnrThreadDump())); - - final @Nullable String dsn = readString(metadata, logger, DSN, options.getDsn()); - final boolean enabled = readBool(metadata, logger, ENABLE_SENTRY, options.isEnabled()); - - if (!enabled || (dsn != null && dsn.isEmpty())) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Sentry enabled flag set to false or DSN is empty: disabling sentry-android"); - } else if (dsn == null) { - options - .getLogger() - .log(SentryLevel.FATAL, "DSN is required. Use empty string to disable SDK."); - } - - options.setEnabled(enabled); - options.setDsn(dsn); - - options.setEnableNdk(readBool(metadata, logger, NDK_ENABLE, options.isEnableNdk())); - - options.setEnableScopeSync( - readBool(metadata, logger, NDK_SCOPE_SYNC_ENABLE, options.isEnableScopeSync())); - - final @Nullable String nativeSdkName = - readString(metadata, logger, NDK_SDK_NAME, options.getNativeSdkName()); - if (nativeSdkName != null) { - options.setNativeSdkName(nativeSdkName); - } - - options.setRelease(readString(metadata, logger, RELEASE, options.getRelease())); - - options.setEnvironment(readString(metadata, logger, ENVIRONMENT, options.getEnvironment())); - - options.setSessionTrackingIntervalMillis( - readLong( - metadata, - logger, - SESSION_TRACKING_TIMEOUT_INTERVAL_MILLIS, - options.getSessionTrackingIntervalMillis())); - - options.setMaxBreadcrumbs( - (int) readLong(metadata, logger, MAX_BREADCRUMBS, options.getMaxBreadcrumbs())); - - options.setEnableActivityLifecycleBreadcrumbs( - readBool( - metadata, - logger, - BREADCRUMBS_ACTIVITY_LIFECYCLE_ENABLE, - options.isEnableActivityLifecycleBreadcrumbs())); - - options.setEnableAppLifecycleBreadcrumbs( - readBool( - metadata, - logger, - BREADCRUMBS_APP_LIFECYCLE_ENABLE, - options.isEnableAppLifecycleBreadcrumbs())); - - options.setEnableSystemEventBreadcrumbs( - readBool( - metadata, - logger, - BREADCRUMBS_SYSTEM_EVENTS_ENABLE, - options.isEnableSystemEventBreadcrumbs())); - - options.setEnableAppComponentBreadcrumbs( - readBool( - metadata, - logger, - BREADCRUMBS_APP_COMPONENTS_ENABLE, - options.isEnableAppComponentBreadcrumbs())); - - options.setEnableUserInteractionBreadcrumbs( - readBool( - metadata, - logger, - BREADCRUMBS_USER_INTERACTION_ENABLE, - options.isEnableUserInteractionBreadcrumbs())); - - options.setEnableNetworkEventBreadcrumbs( - readBool( - metadata, - logger, - BREADCRUMBS_NETWORK_EVENTS_ENABLE, - options.isEnableNetworkEventBreadcrumbs())); - - options.setEnableUncaughtExceptionHandler( - readBool( - metadata, - logger, - UNCAUGHT_EXCEPTION_HANDLER_ENABLE, - options.isEnableUncaughtExceptionHandler())); - - options.setAttachThreads( - readBool(metadata, logger, ATTACH_THREADS, options.isAttachThreads())); - - options.setAttachScreenshot( - readBool(metadata, logger, ATTACH_SCREENSHOT, options.isAttachScreenshot())); - - options.setAttachViewHierarchy( - readBool(metadata, logger, ATTACH_VIEW_HIERARCHY, options.isAttachViewHierarchy())); - - options.setSendClientReports( - readBool(metadata, logger, CLIENT_REPORTS_ENABLE, options.isSendClientReports())); - - final boolean isAutoInitEnabled = readBool(metadata, logger, AUTO_INIT, true); - if (isAutoInitEnabled) { - options.setInitPriority(InitPriority.LOW); - } - - options.setForceInit(readBool(metadata, logger, FORCE_INIT, options.isForceInit())); - - options.setCollectAdditionalContext( - readBool( - metadata, - logger, - COLLECT_ADDITIONAL_CONTEXT, - options.isCollectAdditionalContext())); - - options.setCollectExternalStorageContext( - readBool( - metadata, - logger, - COLLECT_EXTERNAL_STORAGE_CONTEXT, - options.isCollectExternalStorageContext())); - - if (options.getTracesSampleRate() == null) { - final double tracesSampleRate = readDouble(metadata, logger, TRACES_SAMPLE_RATE); - if (tracesSampleRate != -1) { - options.setTracesSampleRate(tracesSampleRate); - } - } - - options.setTraceSampling( - readBool(metadata, logger, TRACE_SAMPLING, options.isTraceSampling())); - - options.setEnableAutoActivityLifecycleTracing( - readBool( - metadata, - logger, - TRACES_ACTIVITY_ENABLE, - options.isEnableAutoActivityLifecycleTracing())); - - options.setEnableActivityLifecycleTracingAutoFinish( - readBool( - metadata, - logger, - TRACES_ACTIVITY_AUTO_FINISH_ENABLE, - options.isEnableActivityLifecycleTracingAutoFinish())); - - if (options.getProfilesSampleRate() == null) { - final double profilesSampleRate = readDouble(metadata, logger, PROFILES_SAMPLE_RATE); - if (profilesSampleRate != -1) { - options.setProfilesSampleRate(profilesSampleRate); - } - } - - if (options.getProfileSessionSampleRate() == null) { - final double profileSessionSampleRate = - readDouble(metadata, logger, PROFILE_SESSION_SAMPLE_RATE); - if (profileSessionSampleRate != -1) { - options.setProfileSessionSampleRate(profileSessionSampleRate); - } - } - - final @Nullable String profileLifecycle = - readString( - metadata, - logger, - PROFILE_LIFECYCLE, - options.getProfileLifecycle().name().toLowerCase(Locale.ROOT)); - if (profileLifecycle != null) { - options.setProfileLifecycle( - ProfileLifecycle.valueOf(profileLifecycle.toUpperCase(Locale.ROOT))); - } - - options.setStartProfilerOnAppStart( - readBool( - metadata, - logger, - PROFILER_START_ON_APP_START, - options.isStartProfilerOnAppStart())); - - options.setEnableUserInteractionTracing( - readBool(metadata, logger, TRACES_UI_ENABLE, options.isEnableUserInteractionTracing())); - - options.setEnableTimeToFullDisplayTracing( - readBool(metadata, logger, TTFD_ENABLE, options.isEnableTimeToFullDisplayTracing())); - - final long idleTimeout = readLong(metadata, logger, IDLE_TIMEOUT, -1); - if (idleTimeout != -1) { - options.setIdleTimeout(idleTimeout); - } - - @Nullable - List tracePropagationTargets = - readList(metadata, logger, TRACE_PROPAGATION_TARGETS); - - if (metadata.containsKey(TRACE_PROPAGATION_TARGETS) && tracePropagationTargets == null) { - options.setTracePropagationTargets(Collections.emptyList()); - } else if (tracePropagationTargets != null) { - options.setTracePropagationTargets(tracePropagationTargets); - } - - options.setEnableFramesTracking(readBool(metadata, logger, PERFORM_FRAMES_TRACKING, true)); - - options.setProguardUuid( - readString(metadata, logger, PROGUARD_UUID, options.getProguardUuid())); - - SdkVersion sdkInfo = options.getSdkVersion(); - if (sdkInfo == null) { - // Is already set by the Options constructor, let's use an empty default otherwise. - sdkInfo = new SdkVersion("", ""); - } - sdkInfo.setName(readStringNotNull(metadata, logger, SDK_NAME, sdkInfo.getName())); - sdkInfo.setVersion(readStringNotNull(metadata, logger, SDK_VERSION, sdkInfo.getVersion())); - options.setSdkVersion(sdkInfo); - - options.setSendDefaultPii( - readBool(metadata, logger, SEND_DEFAULT_PII, options.isSendDefaultPii())); - - // sdkInfo.addIntegration(); - - @Nullable - List integrationsFromGradlePlugin = - readList(metadata, logger, SENTRY_GRADLE_PLUGIN_INTEGRATIONS); - if (integrationsFromGradlePlugin != null) { - for (String integration : integrationsFromGradlePlugin) { - SentryIntegrationPackageStorage.getInstance().addIntegration(integration); - } - } - - options.setEnableRootCheck( - readBool(metadata, logger, ENABLE_ROOT_CHECK, options.isEnableRootCheck())); - - options.setSendModules(readBool(metadata, logger, SEND_MODULES, options.isSendModules())); - - options.setEnablePerformanceV2( - readBool(metadata, logger, ENABLE_PERFORMANCE_V2, options.isEnablePerformanceV2())); - - options.setEnableAppStartProfiling( - readBool( - metadata, logger, ENABLE_APP_START_PROFILING, options.isEnableAppStartProfiling())); - - options.setEnableScopePersistence( - readBool( - metadata, logger, ENABLE_SCOPE_PERSISTENCE, options.isEnableScopePersistence())); - - options.setEnableAutoTraceIdGeneration( - readBool( - metadata, - logger, - ENABLE_AUTO_TRACE_ID_GENERATION, - options.isEnableAutoTraceIdGeneration())); - - options.setDeadlineTimeout( - readLong(metadata, logger, DEADLINE_TIMEOUT, options.getDeadlineTimeout())); - - if (options.getSessionReplay().getSessionSampleRate() == null) { - final double sessionSampleRate = - readDouble(metadata, logger, REPLAYS_SESSION_SAMPLE_RATE); - if (sessionSampleRate != -1) { - options.getSessionReplay().setSessionSampleRate(sessionSampleRate); - } - } - - if (options.getSessionReplay().getOnErrorSampleRate() == null) { - final double onErrorSampleRate = readDouble(metadata, logger, REPLAYS_ERROR_SAMPLE_RATE); - if (onErrorSampleRate != -1) { - options.getSessionReplay().setOnErrorSampleRate(onErrorSampleRate); - } - } - - options - .getSessionReplay() - .setMaskAllText(readBool(metadata, logger, REPLAYS_MASK_ALL_TEXT, true)); - - options - .getSessionReplay() - .setMaskAllImages(readBool(metadata, logger, REPLAYS_MASK_ALL_IMAGES, true)); - - options.getSessionReplay().setDebug(readBool(metadata, logger, REPLAYS_DEBUG, false)); - - final @Nullable String screenshotStrategyRaw = - readString(metadata, logger, REPLAYS_SCREENSHOT_STRATEGY, null); - if (screenshotStrategyRaw != null) { - if ("canvas".equals(screenshotStrategyRaw.toLowerCase(Locale.ROOT))) { - options.getSessionReplay().setScreenshotStrategy(ScreenshotStrategyType.CANVAS); - } else { - // always default to PIXEL_COPY - options.getSessionReplay().setScreenshotStrategy(ScreenshotStrategyType.PIXEL_COPY); - } - } - - // Network Details Configuration - if (options.getSessionReplay().getNetworkDetailAllowUrls().isEmpty()) { - final @Nullable List allowUrls = - readList(metadata, logger, REPLAYS_NETWORK_DETAIL_ALLOW_URLS); - if (allowUrls != null && !allowUrls.isEmpty()) { - final List filteredUrls = new ArrayList<>(); - for (String url : allowUrls) { - final String trimmedUrl = url.trim(); - if (!trimmedUrl.isEmpty()) { - filteredUrls.add(trimmedUrl); - } - } - if (!filteredUrls.isEmpty()) { - options.getSessionReplay().setNetworkDetailAllowUrls(filteredUrls); - } - } - } - - if (options.getSessionReplay().getNetworkDetailDenyUrls().isEmpty()) { - final @Nullable List denyUrls = - readList(metadata, logger, REPLAYS_NETWORK_DETAIL_DENY_URLS); - if (denyUrls != null && !denyUrls.isEmpty()) { - final List filteredUrls = new ArrayList<>(); - for (String url : denyUrls) { - final String trimmedUrl = url.trim(); - if (!trimmedUrl.isEmpty()) { - filteredUrls.add(trimmedUrl); - } - } - if (!filteredUrls.isEmpty()) { - options.getSessionReplay().setNetworkDetailDenyUrls(filteredUrls); - } - } - } - - options - .getSessionReplay() - .setNetworkCaptureBodies( - readBool( - metadata, - logger, - REPLAYS_NETWORK_CAPTURE_BODIES, - options.getSessionReplay().isNetworkCaptureBodies() /* defaultValue */)); - - if (options.getSessionReplay().getNetworkRequestHeaders().size() - == SentryReplayOptions.getNetworkDetailsDefaultHeaders().size()) { // Only has defaults - final @Nullable List requestHeaders = - readList(metadata, logger, REPLAYS_NETWORK_REQUEST_HEADERS); - if (requestHeaders != null) { - final List filteredHeaders = new ArrayList<>(); - for (String header : requestHeaders) { - final String trimmedHeader = header.trim(); - if (!trimmedHeader.isEmpty()) { - filteredHeaders.add(trimmedHeader); - } - } - if (!filteredHeaders.isEmpty()) { - options.getSessionReplay().setNetworkRequestHeaders(filteredHeaders); - } - } - } - - if (options.getSessionReplay().getNetworkResponseHeaders().size() - == SentryReplayOptions.getNetworkDetailsDefaultHeaders().size()) { // Only has defaults - final @Nullable List responseHeaders = - readList(metadata, logger, REPLAYS_NETWORK_RESPONSE_HEADERS); - if (responseHeaders != null && !responseHeaders.isEmpty()) { - final List filteredHeaders = new ArrayList<>(); - for (String header : responseHeaders) { - final String trimmedHeader = header.trim(); - if (!trimmedHeader.isEmpty()) { - filteredHeaders.add(trimmedHeader); - } - } - if (!filteredHeaders.isEmpty()) { - options.getSessionReplay().setNetworkResponseHeaders(filteredHeaders); - } - } - } - - options.setIgnoredErrors(readList(metadata, logger, IGNORED_ERRORS)); - - final @Nullable List includes = readList(metadata, logger, IN_APP_INCLUDES); - if (includes != null && !includes.isEmpty()) { - for (final @NotNull String include : includes) { - options.addInAppInclude(include); - } - } - - final @Nullable List excludes = readList(metadata, logger, IN_APP_EXCLUDES); - if (excludes != null && !excludes.isEmpty()) { - for (final @NotNull String exclude : excludes) { - options.addInAppExclude(exclude); - } - } - - options - .getLogs() - .setEnabled(readBool(metadata, logger, ENABLE_LOGS, options.getLogs().isEnabled())); - - options - .getMetrics() - .setEnabled( - readBool(metadata, logger, ENABLE_METRICS, options.getMetrics().isEnabled())); - - final @NotNull SentryFeedbackOptions feedbackOptions = options.getFeedbackOptions(); - feedbackOptions.setNameRequired( - readBool(metadata, logger, FEEDBACK_NAME_REQUIRED, feedbackOptions.isNameRequired())); - feedbackOptions.setShowName( - readBool(metadata, logger, FEEDBACK_SHOW_NAME, feedbackOptions.isShowName())); - feedbackOptions.setEmailRequired( - readBool(metadata, logger, FEEDBACK_EMAIL_REQUIRED, feedbackOptions.isEmailRequired())); - feedbackOptions.setShowEmail( - readBool(metadata, logger, FEEDBACK_SHOW_EMAIL, feedbackOptions.isShowEmail())); - feedbackOptions.setUseSentryUser( - readBool( - metadata, logger, FEEDBACK_USE_SENTRY_USER, feedbackOptions.isUseSentryUser())); - feedbackOptions.setShowBranding( - readBool(metadata, logger, FEEDBACK_SHOW_BRANDING, feedbackOptions.isShowBranding())); - - options.setEnableSpotlight( - readBool(metadata, logger, SPOTLIGHT_ENABLE, options.isEnableSpotlight())); - - final @Nullable String spotlightUrl = - readString(metadata, logger, SPOTLIGHT_CONNECTION_URL, null); - if (spotlightUrl != null) { - options.setSpotlightConnectionUrl(spotlightUrl); - } - } - options - .getLogger() - .log(SentryLevel.INFO, "Retrieving configuration from AndroidManifest.xml"); - } catch (Throwable e) { - options - .getLogger() - .log( - SentryLevel.ERROR, "Failed to read configuration from android manifest metadata.", e); - } - } - - private static boolean readBool( - final @NotNull Bundle metadata, - final @NotNull ILogger logger, - final @NotNull String key, - final boolean defaultValue) { - final boolean value = metadata.getBoolean(key, defaultValue); - logger.log(SentryLevel.DEBUG, key + " read: " + value); - return value; - } - - private static @Nullable String readString( - final @NotNull Bundle metadata, - final @NotNull ILogger logger, - final @NotNull String key, - final @Nullable String defaultValue) { - final String value = metadata.getString(key, defaultValue); - logger.log(SentryLevel.DEBUG, key + " read: " + value); - return value; - } - - private static @NotNull String readStringNotNull( - final @NotNull Bundle metadata, - final @NotNull ILogger logger, - final @NotNull String key, - final @NotNull String defaultValue) { - final String value = metadata.getString(key, defaultValue); - logger.log(SentryLevel.DEBUG, key + " read: " + value); - return value; - } - - private static @Nullable List readList( - final @NotNull Bundle metadata, final @NotNull ILogger logger, final @NotNull String key) { - final String value = metadata.getString(key); - logger.log(SentryLevel.DEBUG, key + " read: " + value); - if (value != null) { - return Arrays.asList(value.split(",", -1)); - } else { - return null; - } - } - - private static double readDouble( - final @NotNull Bundle metadata, final @NotNull ILogger logger, final @NotNull String key) { - // manifest meta-data only reads float - double value = ((Float) metadata.getFloat(key, -1)).doubleValue(); - if (value == -1) { - value = ((Integer) metadata.getInt(key, -1)).doubleValue(); - } - logger.log(SentryLevel.DEBUG, key + " read: " + value); - return value; - } - - private static long readLong( - final @NotNull Bundle metadata, - final @NotNull ILogger logger, - final @NotNull String key, - final long defaultValue) { - // manifest meta-data only reads int if the value is not big enough - final long value = metadata.getInt(key, (int) defaultValue); - logger.log(SentryLevel.DEBUG, key + " read: " + value); - return value; - } - - /** - * Checks if auto init is enabled or disabled - * - * @param context the application context - * @param logger the Logger interface - * @return true if auto init is enabled or false otherwise - */ - static boolean isAutoInit(final @NotNull Context context, final @NotNull ILogger logger) { - Objects.requireNonNull(context, "The application context is required."); - - boolean autoInit = true; - try { - final Bundle metadata = getMetadata(context, logger, null); - if (metadata != null) { - autoInit = readBool(metadata, logger, AUTO_INIT, true); - } - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Failed to read auto-init from android manifest metadata.", e); - } - return autoInit; - } - - /** - * Returns the Bundle attached from the given Context - * - * @param context the application context - * @return the Bundle attached to the PackageManager - */ - private static @Nullable Bundle getMetadata( - final @NotNull Context context, - final @NotNull ILogger logger, - final @Nullable BuildInfoProvider buildInfoProvider) { - final ApplicationInfo app = - ContextUtils.getApplicationInfo( - context, buildInfoProvider != null ? buildInfoProvider : new BuildInfoProvider(logger)); - return app != null ? app.metaData : null; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/NativeEventCollector.java b/sentry-android-core/src/main/java/io/sentry/android/core/NativeEventCollector.java deleted file mode 100644 index 2cf5acd05fa..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/NativeEventCollector.java +++ /dev/null @@ -1,536 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.cache.EnvelopeCache.PREFIX_CURRENT_SESSION_FILE; -import static io.sentry.cache.EnvelopeCache.PREFIX_PREVIOUS_SESSION_FILE; -import static io.sentry.cache.EnvelopeCache.STARTUP_CRASH_MARKER_FILE; -import static java.nio.charset.StandardCharsets.UTF_8; - -import io.sentry.JsonObjectReader; -import io.sentry.SentryEnvelope; -import io.sentry.SentryEnvelopeItem; -import io.sentry.SentryEvent; -import io.sentry.SentryItemType; -import io.sentry.SentryLevel; -import io.sentry.vendor.gson.stream.JsonToken; -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.EOFException; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Collects native crash events from the outbox directory. These events can be correlated with - * tombstone events from ApplicationExitInfo to avoid sending duplicate crash reports. - */ -@ApiStatus.Internal -public final class NativeEventCollector { - - private static final String NATIVE_PLATFORM = "native"; - - private static final long TIMESTAMP_TOLERANCE_MS = 5000; - - private final @NotNull SentryAndroidOptions options; - - /** Lightweight metadata collected during scan phase. */ - private final @NotNull List nativeEnvelopes = new ArrayList<>(); - - private boolean collected = false; - - public NativeEventCollector(final @NotNull SentryAndroidOptions options) { - this.options = options; - } - - /** Lightweight metadata for matching phase - only file reference and timestamp. */ - static final class NativeEnvelopeMetadata { - private final @NotNull File file; - private final long timestampMs; - - NativeEnvelopeMetadata(final @NotNull File file, final long timestampMs) { - this.file = file; - this.timestampMs = timestampMs; - } - - @NotNull - File getFile() { - return file; - } - - long getTimestampMs() { - return timestampMs; - } - } - - /** Holds a native event along with its source file for later deletion. */ - public static final class NativeEventData { - private final @NotNull SentryEvent event; - private final @NotNull File file; - private final @NotNull SentryEnvelope envelope; - - NativeEventData( - final @NotNull SentryEvent event, - final @NotNull File file, - final @NotNull SentryEnvelope envelope) { - this.event = event; - this.file = file; - this.envelope = envelope; - } - - public @NotNull SentryEvent getEvent() { - return event; - } - - public @NotNull File getFile() { - return file; - } - - public @NotNull SentryEnvelope getEnvelope() { - return envelope; - } - } - - /** - * Scans the outbox directory and collects all native crash events. This method should be called - * once before processing tombstones. Subsequent calls are no-ops. - */ - public void collect() { - if (collected) { - return; - } - collected = true; - - final @Nullable String outboxPath = options.getOutboxPath(); - if (outboxPath == null) { - options - .getLogger() - .log(SentryLevel.DEBUG, "Outbox path is null, skipping native event collection."); - return; - } - - final File outboxDir = new File(outboxPath); - final File[] files = outboxDir.listFiles(); - if (files == null) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Outbox path is not a directory or an I/O error occurred: %s", - outboxPath); - return; - } - if (files.length == 0) { - options.getLogger().log(SentryLevel.DEBUG, "No envelope files found in outbox."); - return; - } - - options - .getLogger() - .log(SentryLevel.DEBUG, "Scanning %d files in outbox for native events.", files.length); - - for (final File file : files) { - if (!file.isFile() || !isRelevantFileName(file.getName())) { - continue; - } - - final @Nullable NativeEnvelopeMetadata metadata = extractNativeEnvelopeMetadata(file); - if (metadata != null) { - nativeEnvelopes.add(metadata); - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Found native event in outbox: %s (timestamp: %d)", - file.getName(), - metadata.getTimestampMs()); - } - } - - options - .getLogger() - .log(SentryLevel.DEBUG, "Collected %d native events from outbox.", nativeEnvelopes.size()); - } - - /** - * Finds a native event that matches the given tombstone timestamp. If a match is found, it is - * removed from the internal list so it won't be matched again. - * - *

This method will lazily collect native events from the outbox on first call. - * - * @param tombstoneTimestampMs the timestamp from ApplicationExitInfo - * @return the matching native event data, or null if no match found - */ - public @Nullable NativeEventData findAndRemoveMatchingNativeEvent( - final long tombstoneTimestampMs) { - - // Lazily collect on first use (runs on executor thread, not main thread) - collect(); - - for (final NativeEnvelopeMetadata metadata : nativeEnvelopes) { - final long timeDiff = Math.abs(tombstoneTimestampMs - metadata.getTimestampMs()); - if (timeDiff <= TIMESTAMP_TOLERANCE_MS) { - options - .getLogger() - .log(SentryLevel.DEBUG, "Matched native event by timestamp (diff: %d ms)", timeDiff); - nativeEnvelopes.remove(metadata); - // Only load full event data when we have a match - return loadFullNativeEventData(metadata.getFile()); - } - } - - return null; - } - - /** - * Deletes a native event file from the outbox. - * - * @param nativeEventData the native event data containing the file reference - * @return true if the file was deleted successfully - */ - public boolean deleteNativeEventFile(final @NotNull NativeEventData nativeEventData) { - final File file = nativeEventData.getFile(); - try { - if (file.delete()) { - options - .getLogger() - .log(SentryLevel.DEBUG, "Deleted native event file from outbox: %s", file.getName()); - return true; - } else { - options - .getLogger() - .log( - SentryLevel.WARNING, - "Failed to delete native event file: %s", - file.getAbsolutePath()); - return false; - } - } catch (Throwable e) { - options - .getLogger() - .log( - SentryLevel.ERROR, e, "Error deleting native event file: %s", file.getAbsolutePath()); - return false; - } - } - - /** - * Extracts only lightweight metadata (timestamp) from an envelope file using streaming parsing. - * This avoids loading the entire envelope and deserializing the full event. - */ - private @Nullable NativeEnvelopeMetadata extractNativeEnvelopeMetadata(final @NotNull File file) { - // we use the backend envelope size limit as a bound for the read loop - final long maxEnvelopeSize = 200 * 1024 * 1024; - long bytesProcessed = 0; - - try (final InputStream stream = new BufferedInputStream(new FileInputStream(file))) { - // Skip envelope header line - final int headerBytes = skipLine(stream); - if (headerBytes < 0) { - return null; - } - bytesProcessed += headerBytes; - - while (bytesProcessed < maxEnvelopeSize) { - final @Nullable String itemHeaderLine = readLine(stream); - if (itemHeaderLine == null || itemHeaderLine.isEmpty()) { - // We reached the end of the envelope - break; - } - bytesProcessed += itemHeaderLine.length() + 1; // +1 for newline - - final @Nullable ItemHeaderInfo headerInfo = parseItemHeader(itemHeaderLine); - if (headerInfo == null) { - break; - } - - if ("event".equals(headerInfo.type)) { - final @Nullable NativeEnvelopeMetadata metadata = - extractMetadataFromEventPayload(stream, headerInfo.length, file); - if (metadata != null) { - return metadata; - } - } else { - skipBytes(stream, headerInfo.length); - } - bytesProcessed += headerInfo.length; - - // Skip the newline after payload (if present) - final int next = stream.read(); - if (next == -1) { - break; - } - bytesProcessed++; - if (next != '\n') { - // Not a newline, we're at the next item header. Can't unread easily, - // but this shouldn't happen with well-formed envelopes - break; - } - } - } catch (Throwable e) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - e, - "Error extracting metadata from envelope file: %s", - file.getAbsolutePath()); - } - return null; - } - - /** - * Extracts platform and timestamp from an event payload using streaming JSON parsing. Only reads - * the fields we need and exits early once found. Uses a bounded stream to track position within - * the payload and skip any unread bytes on close, avoiding allocation of the full payload. - */ - private @Nullable NativeEnvelopeMetadata extractMetadataFromEventPayload( - final @NotNull InputStream stream, final int payloadLength, final @NotNull File file) { - - NativeEnvelopeMetadata result = null; - - try (final BoundedInputStream boundedStream = new BoundedInputStream(stream, payloadLength); - final Reader reader = new InputStreamReader(boundedStream, UTF_8)) { - final JsonObjectReader jsonReader = new JsonObjectReader(reader); - - String platform = null; - Date timestamp = null; - - jsonReader.beginObject(); - while (jsonReader.peek() == JsonToken.NAME) { - final String name = jsonReader.nextName(); - switch (name) { - case "platform": - platform = jsonReader.nextStringOrNull(); - break; - case "timestamp": - timestamp = jsonReader.nextDateOrNull(options.getLogger()); - break; - default: - jsonReader.skipValue(); - break; - } - if (platform != null && timestamp != null) { - break; - } - } - - if (NATIVE_PLATFORM.equals(platform) && timestamp != null) { - result = new NativeEnvelopeMetadata(file, timestamp.getTime()); - } - } catch (Throwable e) { - options - .getLogger() - .log(SentryLevel.DEBUG, e, "Error parsing event JSON from: %s", file.getName()); - } - - return result; - } - - /** Loads the full envelope and event data from a file. Used only when a match is found. */ - private @Nullable NativeEventData loadFullNativeEventData(final @NotNull File file) { - try (final InputStream stream = new BufferedInputStream(new FileInputStream(file))) { - final SentryEnvelope envelope = options.getEnvelopeReader().read(stream); - if (envelope == null) { - return null; - } - - for (final SentryEnvelopeItem item : envelope.getItems()) { - if (!SentryItemType.Event.equals(item.getHeader().getType())) { - continue; - } - - try (final Reader eventReader = - new BufferedReader( - new InputStreamReader(new ByteArrayInputStream(item.getData()), UTF_8))) { - final SentryEvent event = - options.getSerializer().deserialize(eventReader, SentryEvent.class); - if (event != null && NATIVE_PLATFORM.equals(event.getPlatform())) { - return new NativeEventData(event, file, envelope); - } - } - } - } catch (Throwable e) { - options - .getLogger() - .log(SentryLevel.DEBUG, e, "Error loading envelope file: %s", file.getAbsolutePath()); - } - return null; - } - - /** Minimal item header info needed for streaming. */ - private static final class ItemHeaderInfo { - final @Nullable String type; - final int length; - - ItemHeaderInfo(final @Nullable String type, final int length) { - this.type = type; - this.length = length; - } - } - - /** Parses item header JSON to extract only type and length fields. */ - private @Nullable ItemHeaderInfo parseItemHeader(final @NotNull String headerLine) { - try (final Reader reader = - new InputStreamReader(new ByteArrayInputStream(headerLine.getBytes(UTF_8)), UTF_8)) { - final JsonObjectReader jsonReader = new JsonObjectReader(reader); - - String type = null; - int length = -1; - - jsonReader.beginObject(); - while (jsonReader.peek() == JsonToken.NAME) { - final String name = jsonReader.nextName(); - switch (name) { - case "type": - type = jsonReader.nextStringOrNull(); - break; - case "length": - length = jsonReader.nextInt(); - break; - default: - jsonReader.skipValue(); - break; - } - // Early exit if we have both - if (type != null && length >= 0) { - break; - } - } - - if (length >= 0) { - return new ItemHeaderInfo(type, length); - } - } catch (Throwable e) { - options.getLogger().log(SentryLevel.DEBUG, e, "Error parsing item header"); - } - return null; - } - - /** Reads a line from the stream (up to and including newline). Returns null on EOF. */ - private @Nullable String readLine(final @NotNull InputStream stream) throws IOException { - final StringBuilder sb = new StringBuilder(); - int b; - while ((b = stream.read()) != -1) { - if (b == '\n') { - return sb.toString(); - } - sb.append((char) b); - } - return sb.length() > 0 ? sb.toString() : null; - } - - /** - * Skips a line in the stream (up to and including newline). Returns bytes skipped, or -1 on EOF. - */ - private int skipLine(final @NotNull InputStream stream) throws IOException { - int count = 0; - int b; - while ((b = stream.read()) != -1) { - count++; - if (b == '\n') { - return count; - } - } - return count > 0 ? count : -1; - } - - /** Skips exactly n bytes from the stream. */ - private static void skipBytes(final @NotNull InputStream stream, final long count) - throws IOException { - long remaining = count; - while (remaining > 0) { - final long skipped = stream.skip(remaining); - if (skipped == 0) { - // skip() returned 0, try reading instead - if (stream.read() == -1) { - throw new EOFException("Unexpected end of stream while skipping bytes"); - } - remaining--; - } else { - remaining -= skipped; - } - } - } - - private boolean isRelevantFileName(final @Nullable String fileName) { - return fileName != null - && !fileName.startsWith(PREFIX_CURRENT_SESSION_FILE) - && !fileName.startsWith(PREFIX_PREVIOUS_SESSION_FILE) - && !fileName.startsWith(STARTUP_CRASH_MARKER_FILE); - } - - /** - * An InputStream wrapper that tracks reads within a bounded section of the stream. This allows - * callers to read/parse only what they need (e.g., extract a few JSON fields), then skip the - * remainder of the section on close to position the stream at the next envelope item. Does not - * close the underlying stream. - */ - private static final class BoundedInputStream extends InputStream { - private final @NotNull InputStream inner; - private long remaining; - - BoundedInputStream(final @NotNull InputStream inner, final int limit) { - this.inner = inner; - this.remaining = limit; - } - - @Override - public int read() throws IOException { - if (remaining <= 0) { - return -1; - } - final int result = inner.read(); - if (result != -1) { - remaining--; - } - return result; - } - - @Override - public int read(final byte[] b, final int off, final int len) throws IOException { - if (remaining <= 0) { - return -1; - } - final int toRead = Math.min(len, (int) remaining); - final int result = inner.read(b, off, toRead); - if (result > 0) { - remaining -= result; - } - return result; - } - - @Override - public long skip(final long n) throws IOException { - final long toSkip = Math.min(n, remaining); - final long skipped = inner.skip(toSkip); - remaining -= skipped; - return skipped; - } - - @Override - public int available() throws IOException { - return Math.min(inner.available(), (int) remaining); - } - - @Override - public void close() throws IOException { - // Skip any remaining bytes to advance the underlying stream position, - // but don't close the underlying stream, because we might have other - // envelope items to read. - skipBytes(inner, remaining); - - // Reset remaining to 0 to handle multiple close() calls (e.g., from - // try-with-resources when wrapped by InputStreamReader). - remaining = 0; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/NdkHandlerStrategy.java b/sentry-android-core/src/main/java/io/sentry/android/core/NdkHandlerStrategy.java deleted file mode 100644 index 74b446658d3..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/NdkHandlerStrategy.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.sentry.android.core; - -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -public enum NdkHandlerStrategy { - SENTRY_HANDLER_STRATEGY_DEFAULT(0), - SENTRY_HANDLER_STRATEGY_CHAIN_AT_START(1); - - private final int value; - - NdkHandlerStrategy(final int value) { - this.value = value; - } - - public int getValue() { - return value; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/NdkIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/NdkIntegration.java deleted file mode 100644 index 8353a32d65d..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/NdkIntegration.java +++ /dev/null @@ -1,104 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import io.sentry.IScopes; -import io.sentry.Integration; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.util.Objects; -import java.io.Closeable; -import java.io.IOException; -import java.lang.reflect.Method; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -/** Enables the NDK error reporting for Android */ -public final class NdkIntegration implements Integration, Closeable { - - public static final String SENTRY_NDK_CLASS_NAME = "io.sentry.android.ndk.SentryNdk"; - - private final @Nullable Class sentryNdkClass; - - private @Nullable SentryAndroidOptions options; - - public NdkIntegration(final @Nullable Class sentryNdkClass) { - this.sentryNdkClass = sentryNdkClass; - } - - @Override - public final void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - Objects.requireNonNull(scopes, "Scopes are required"); - this.options = - Objects.requireNonNull( - (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, - "SentryAndroidOptions is required"); - - final boolean enabled = this.options.isEnableNdk(); - this.options.getLogger().log(SentryLevel.DEBUG, "NdkIntegration enabled: %s", enabled); - - // Note: `scopes` isn't used here because the NDK integration writes files to disk which are - // picked - // up by another integration (EnvelopeFileObserverIntegration). - if (enabled && sentryNdkClass != null) { - final String cachedDir = this.options.getCacheDirPath(); - if (cachedDir == null) { - this.options.getLogger().log(SentryLevel.ERROR, "No cache dir path is defined in options."); - disableNdkIntegration(this.options); - return; - } - - try { - final Method method = sentryNdkClass.getMethod("init", SentryAndroidOptions.class); - final Object[] args = new Object[1]; - args[0] = this.options; - method.invoke(null, args); - - this.options.getLogger().log(SentryLevel.DEBUG, "NdkIntegration installed."); - addIntegrationToSdkVersion("Ndk"); - } catch (NoSuchMethodException e) { - disableNdkIntegration(this.options); - this.options - .getLogger() - .log(SentryLevel.ERROR, "Failed to invoke the SentryNdk.init method.", e); - } catch (Throwable e) { - disableNdkIntegration(this.options); - this.options.getLogger().log(SentryLevel.ERROR, "Failed to initialize SentryNdk.", e); - } - } else { - disableNdkIntegration(this.options); - } - } - - private void disableNdkIntegration(final @NotNull SentryAndroidOptions options) { - options.setEnableNdk(false); - options.setEnableScopeSync(false); - } - - @TestOnly - @Nullable - Class getSentryNdkClass() { - return sentryNdkClass; - } - - @Override - public void close() throws IOException { - if (options != null && options.isEnableNdk() && sentryNdkClass != null) { - try { - final Method method = sentryNdkClass.getMethod("close"); - method.invoke(null, new Object[0]); - - options.getLogger().log(SentryLevel.DEBUG, "NdkIntegration removed."); - } catch (NoSuchMethodException e) { - options - .getLogger() - .log(SentryLevel.ERROR, "Failed to invoke the SentryNdk.close method.", e); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Failed to close SentryNdk.", e); - } finally { - disableNdkIntegration(this.options); - } - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/NetworkBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/NetworkBreadcrumbsIntegration.java deleted file mode 100644 index c7f3182b97d..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/NetworkBreadcrumbsIntegration.java +++ /dev/null @@ -1,253 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.os.Build; -import androidx.annotation.NonNull; -import io.sentry.Breadcrumb; -import io.sentry.DateUtils; -import io.sentry.Hint; -import io.sentry.IScopes; -import io.sentry.ISentryLifecycleToken; -import io.sentry.Integration; -import io.sentry.SentryDateProvider; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.TypeCheckHint; -import io.sentry.android.core.internal.util.AndroidConnectionStatusProvider; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.Objects; -import java.io.Closeable; -import java.io.IOException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -public final class NetworkBreadcrumbsIntegration implements Integration, Closeable { - - private final @NotNull Context context; - private final @NotNull BuildInfoProvider buildInfoProvider; - private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - private @Nullable SentryOptions options; - - @TestOnly @Nullable volatile NetworkBreadcrumbsNetworkCallback networkCallback; - - public NetworkBreadcrumbsIntegration( - final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) { - this.context = - Objects.requireNonNull(ContextUtils.getApplicationContext(context), "Context is required"); - this.buildInfoProvider = - Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required"); - } - - @Override - public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - Objects.requireNonNull(scopes, "Scopes are required"); - SentryAndroidOptions androidOptions = - Objects.requireNonNull( - (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, - "SentryAndroidOptions is required"); - - this.options = options; - - options - .getLogger() - .log( - SentryLevel.DEBUG, - "NetworkBreadcrumbsIntegration enabled: %s", - androidOptions.isEnableNetworkEventBreadcrumbs()); - - if (androidOptions.isEnableNetworkEventBreadcrumbs()) { - - // The specific error is logged in the ConnectivityChecker method - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.N) { - options.getLogger().log(SentryLevel.DEBUG, "NetworkCallbacks need Android N+."); - return; - } - - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - networkCallback = - new NetworkBreadcrumbsNetworkCallback( - scopes, buildInfoProvider, options.getDateProvider()); - - final boolean registered = - AndroidConnectionStatusProvider.addNetworkCallback( - context, options.getLogger(), buildInfoProvider, networkCallback); - if (registered) { - options.getLogger().log(SentryLevel.DEBUG, "NetworkBreadcrumbsIntegration installed."); - addIntegrationToSdkVersion("NetworkBreadcrumbs"); - } else { - options - .getLogger() - .log(SentryLevel.DEBUG, "NetworkBreadcrumbsIntegration not installed."); - // The specific error is logged by AndroidConnectionStatusProvider - } - } - } - } - - @Override - public void close() throws IOException { - final @Nullable ConnectivityManager.NetworkCallback callbackRef; - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - callbackRef = networkCallback; - networkCallback = null; - } - - if (callbackRef != null) { - AndroidConnectionStatusProvider.removeNetworkCallback(callbackRef); - } - } - - static final class NetworkBreadcrumbsNetworkCallback extends ConnectivityManager.NetworkCallback { - final @NotNull IScopes scopes; - final @NotNull BuildInfoProvider buildInfoProvider; - - @Nullable NetworkCapabilities lastCapabilities = null; - long lastCapabilityNanos = 0; - final @NotNull SentryDateProvider dateProvider; - - NetworkBreadcrumbsNetworkCallback( - final @NotNull IScopes scopes, - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull SentryDateProvider dateProvider) { - this.scopes = Objects.requireNonNull(scopes, "Scopes are required"); - this.buildInfoProvider = - Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required"); - this.dateProvider = Objects.requireNonNull(dateProvider, "SentryDateProvider is required"); - } - - @Override - public void onAvailable(final @NonNull Network network) { - final Breadcrumb breadcrumb = createBreadcrumb("NETWORK_AVAILABLE"); - scopes.addBreadcrumb(breadcrumb); - lastCapabilities = null; - } - - @Override - public void onCapabilitiesChanged( - final @NonNull Network network, final @NonNull NetworkCapabilities networkCapabilities) { - final long nowNanos = dateProvider.now().nanoTimestamp(); - final @Nullable NetworkBreadcrumbConnectionDetail connectionDetail = - getNewConnectionDetails( - lastCapabilities, networkCapabilities, lastCapabilityNanos, nowNanos); - if (connectionDetail == null) { - return; - } - lastCapabilities = networkCapabilities; - lastCapabilityNanos = nowNanos; - final Breadcrumb breadcrumb = createBreadcrumb("NETWORK_CAPABILITIES_CHANGED"); - breadcrumb.setData("download_bandwidth", connectionDetail.downBandwidth); - breadcrumb.setData("upload_bandwidth", connectionDetail.upBandwidth); - breadcrumb.setData("vpn_active", connectionDetail.isVpn); - breadcrumb.setData("network_type", connectionDetail.type); - if (connectionDetail.signalStrength != 0) { - breadcrumb.setData("signal_strength", connectionDetail.signalStrength); - } - Hint hint = new Hint(); - hint.set(TypeCheckHint.ANDROID_NETWORK_CAPABILITIES, connectionDetail); - scopes.addBreadcrumb(breadcrumb, hint); - } - - @Override - public void onLost(final @NonNull Network network) { - final Breadcrumb breadcrumb = createBreadcrumb("NETWORK_LOST"); - scopes.addBreadcrumb(breadcrumb); - lastCapabilities = null; - } - - private Breadcrumb createBreadcrumb(String action) { - final Breadcrumb breadcrumb = new Breadcrumb(); - breadcrumb.setType("system"); - breadcrumb.setCategory("network.event"); - breadcrumb.setData("action", action); - breadcrumb.setLevel(SentryLevel.INFO); - return breadcrumb; - } - - private @Nullable NetworkBreadcrumbConnectionDetail getNewConnectionDetails( - final @Nullable NetworkCapabilities oldCapabilities, - final @NotNull NetworkCapabilities newCapabilities, - final long oldCapabilityNanos, - final long newCapabilityNanos) { - if (oldCapabilities == null) { - return new NetworkBreadcrumbConnectionDetail( - newCapabilities, buildInfoProvider, newCapabilityNanos); - } - NetworkBreadcrumbConnectionDetail oldConnectionDetails = - new NetworkBreadcrumbConnectionDetail( - oldCapabilities, buildInfoProvider, oldCapabilityNanos); - NetworkBreadcrumbConnectionDetail newConnectionDetails = - new NetworkBreadcrumbConnectionDetail( - newCapabilities, buildInfoProvider, newCapabilityNanos); - - // We compare the details and if they are similar we return null, so that we don't spam the - // user with lots of breadcrumbs for e.g. an increase of signal strength of 1 point - if (oldConnectionDetails.isSimilar(newConnectionDetails)) { - return null; - } - return newConnectionDetails; - } - } - - static class NetworkBreadcrumbConnectionDetail { - final int downBandwidth, upBandwidth, signalStrength; - private long timestampNanos; - final boolean isVpn; - final @NotNull String type; - - @SuppressLint({"NewApi"}) - NetworkBreadcrumbConnectionDetail( - final @NotNull NetworkCapabilities networkCapabilities, - final @NotNull BuildInfoProvider buildInfoProvider, - final long capabilityNanos) { - Objects.requireNonNull(networkCapabilities, "NetworkCapabilities is required"); - Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required"); - this.downBandwidth = networkCapabilities.getLinkDownstreamBandwidthKbps(); - this.upBandwidth = networkCapabilities.getLinkUpstreamBandwidthKbps(); - int strength = - buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.Q - ? networkCapabilities.getSignalStrength() - : 0; - // If the system reports a signalStrength of Integer.MIN_VALUE, we adjust it to be 0 - this.signalStrength = strength > -100 ? strength : 0; - this.isVpn = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN); - String connectionType = - AndroidConnectionStatusProvider.getConnectionType(networkCapabilities); - this.type = connectionType != null ? connectionType : ""; - this.timestampNanos = capabilityNanos; - } - - /** - * Compares this connection detail to another one. - * - * @param other The other NetworkBreadcrumbConnectionDetail to compare - * @return true if the details are similar enough, false otherwise - */ - boolean isSimilar(final @NotNull NetworkBreadcrumbConnectionDetail other) { - int signalDiff = Math.abs(signalStrength - other.signalStrength); - int downBandwidthDiff = Math.abs(downBandwidth - other.downBandwidth); - int upBandwidthDiff = Math.abs(upBandwidth - other.upBandwidth); - // Signal and bandwidth will be reported at most once every 5 seconds. - // This means that if the new connection detail come less than 5 seconds after the previous - // one, we'll report the signal and bandwidth as similar, regardless of their real value. - boolean isTimestampSimilar = - DateUtils.nanosToMillis(Math.abs(timestampNanos - other.timestampNanos)) < 5000; - boolean isSignalSimilar = isTimestampSimilar || signalDiff <= 5; - boolean isDownBandwidthSimilar = - isTimestampSimilar || downBandwidthDiff <= Math.max(1000, Math.abs(downBandwidth) * 0.1); - boolean isUpBandwidthSimilar = - isTimestampSimilar || upBandwidthDiff <= Math.max(1000, Math.abs(upBandwidth) * 0.1); - return isVpn == other.isVpn - && type.equals(other.type) - && isSignalSimilar - && isDownBandwidthSimilar - && isUpBandwidthSimilar; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/NoOpDebugImagesLoader.java b/sentry-android-core/src/main/java/io/sentry/android/core/NoOpDebugImagesLoader.java deleted file mode 100644 index 193b7342193..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/NoOpDebugImagesLoader.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.sentry.android.core; - -import io.sentry.protocol.DebugImage; -import java.util.List; -import java.util.Set; -import org.jetbrains.annotations.Nullable; - -final class NoOpDebugImagesLoader implements IDebugImagesLoader { - - private static final NoOpDebugImagesLoader instance = new NoOpDebugImagesLoader(); - - private NoOpDebugImagesLoader() {} - - public static NoOpDebugImagesLoader getInstance() { - return instance; - } - - @Override - public @Nullable List loadDebugImages() { - return null; - } - - @Override - public @Nullable Set loadDebugImagesForAddresses(Set addresses) { - return null; - } - - @Override - public void clearDebugImages() {} -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java deleted file mode 100644 index f7b51cce620..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java +++ /dev/null @@ -1,312 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.android.core.ActivityLifecycleIntegration.APP_START_COLD; -import static io.sentry.android.core.ActivityLifecycleIntegration.APP_START_WARM; -import static io.sentry.android.core.ActivityLifecycleIntegration.UI_LOAD_OP; - -import io.sentry.EventProcessor; -import io.sentry.Hint; -import io.sentry.ISentryLifecycleToken; -import io.sentry.MeasurementUnit; -import io.sentry.SentryEvent; -import io.sentry.SpanContext; -import io.sentry.SpanDataConvention; -import io.sentry.SpanId; -import io.sentry.SpanStatus; -import io.sentry.android.core.internal.util.AndroidThreadChecker; -import io.sentry.android.core.performance.AppStartMetrics; -import io.sentry.android.core.performance.TimeSpan; -import io.sentry.protocol.App; -import io.sentry.protocol.MeasurementValue; -import io.sentry.protocol.SentryId; -import io.sentry.protocol.SentrySpan; -import io.sentry.protocol.SentryTransaction; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.Objects; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** Event Processor responsible for adding Android metrics to transactions */ -final class PerformanceAndroidEventProcessor implements EventProcessor { - - private static final String APP_METRICS_ORIGIN = "auto.ui"; - - private static final String APP_METRICS_CONTENT_PROVIDER_OP = "contentprovider.load"; - private static final String APP_METRICS_ACTIVITIES_OP = "activity.load"; - private static final String APP_METRICS_APPLICATION_OP = "application.load"; - private static final String APP_METRICS_PROCESS_INIT_OP = "process.load"; - private static final long MAX_PROCESS_INIT_APP_START_DIFF_MS = 10000; - - private boolean sentStartMeasurement = false; - - private final @NotNull ActivityFramesTracker activityFramesTracker; - private final @NotNull SentryAndroidOptions options; - private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - - PerformanceAndroidEventProcessor( - final @NotNull SentryAndroidOptions options, - final @NotNull ActivityFramesTracker activityFramesTracker) { - this.options = Objects.requireNonNull(options, "SentryAndroidOptions is required"); - this.activityFramesTracker = - Objects.requireNonNull(activityFramesTracker, "ActivityFramesTracker is required"); - } - - /** - * Returns the event itself - * - * @param event the SentryEvent the SentryEvent - * @param hint the Hint the Hint - * @return returns the event itself - */ - @Override - @Nullable - public SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) { - // that's only necessary because on newer versions of Unity, if not overriding this method, it's - // throwing 'java.lang.AbstractMethodError: abstract method' and the reason is probably - // compilation mismatch. - return event; - } - - @SuppressWarnings("NullAway") - @Override - public @NotNull SentryTransaction process( - @NotNull SentryTransaction transaction, @NotNull Hint hint) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (!options.isTracingEnabled()) { - return transaction; - } - - final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance(); - // the app start measurement is only sent once and only if the transaction has - // the app.start span, which is automatically created by the SDK. - if (hasAppStartSpan(transaction)) { - if (appStartMetrics.shouldSendStartMeasurements()) { - final @NotNull TimeSpan appStartTimeSpan = - appStartMetrics.getAppStartTimeSpanWithFallback(options); - final long appStartUpDurationMs = appStartTimeSpan.getDurationMs(); - - // if appStartUpDurationMs is 0, metrics are not ready to be sent - if (appStartUpDurationMs != 0) { - final MeasurementValue value = - new MeasurementValue( - (float) appStartUpDurationMs, MeasurementUnit.Duration.MILLISECOND.apiName()); - - final String appStartKey = - appStartMetrics.getAppStartType() == AppStartMetrics.AppStartType.COLD - ? MeasurementValue.KEY_APP_START_COLD - : MeasurementValue.KEY_APP_START_WARM; - - transaction.getMeasurements().put(appStartKey, value); - - attachAppStartSpans(appStartMetrics, transaction); - appStartMetrics.onAppStartSpansSent(); - } - } - - @Nullable App appContext = transaction.getContexts().getApp(); - if (appContext == null) { - appContext = new App(); - transaction.getContexts().setApp(appContext); - } - final String appStartType = - appStartMetrics.getAppStartType() == AppStartMetrics.AppStartType.COLD - ? "cold" - : "warm"; - appContext.setStartType(appStartType); - } - - setContributingFlags(transaction); - - final SentryId eventId = transaction.getEventId(); - final SpanContext spanContext = transaction.getContexts().getTrace(); - - // only add slow/frozen frames to transactions created by ActivityLifecycleIntegration - // which have the operation UI_LOAD_OP. If a user-defined (or hybrid SDK) transaction - // users it, we'll also add the metrics if available - if (eventId != null - && spanContext != null - && spanContext.getOperation().contentEquals(UI_LOAD_OP)) { - final Map framesMetrics = - activityFramesTracker.takeMetrics(eventId); - if (framesMetrics != null) { - transaction.getMeasurements().putAll(framesMetrics); - } - } - - return transaction; - } - } - - private void setContributingFlags(SentryTransaction transaction) { - - @Nullable SentrySpan ttidSpan = null; - @Nullable SentrySpan ttfdSpan = null; - for (final @NotNull SentrySpan span : transaction.getSpans()) { - if (ActivityLifecycleIntegration.TTID_OP.equals(span.getOp())) { - ttidSpan = span; - } else if (ActivityLifecycleIntegration.TTFD_OP.equals(span.getOp())) { - ttfdSpan = span; - } - // once both are found we can early exit - if (ttidSpan != null && ttfdSpan != null) { - break; - } - } - - if (ttidSpan == null && ttfdSpan == null) { - return; - } - - for (final @NotNull SentrySpan span : transaction.getSpans()) { - // as ttid and ttfd spans are artificially created, we don't want to set the flags on them - if (span == ttidSpan || span == ttfdSpan) { - continue; - } - - // let's assume main thread, unless it's set differently - boolean spanOnMainThread = true; - final @Nullable Map spanData = span.getData(); - if (spanData != null) { - final @Nullable Object threadName = spanData.get(SpanDataConvention.THREAD_NAME); - spanOnMainThread = threadName == null || "main".equals(threadName); - } - - // for ttid, only main thread spans are relevant - final boolean withinTtid = - (ttidSpan != null) - && isTimestampWithinSpan(span.getStartTimestamp(), ttidSpan) - && spanOnMainThread; - - final boolean withinTtfd = - (ttfdSpan != null) && isTimestampWithinSpan(span.getStartTimestamp(), ttfdSpan); - - if (withinTtid || withinTtfd) { - @Nullable Map data = span.getData(); - if (data == null) { - data = new ConcurrentHashMap<>(); - span.setData(data); - } - if (withinTtid) { - data.put(SpanDataConvention.CONTRIBUTES_TTID, true); - } - if (withinTtfd) { - data.put(SpanDataConvention.CONTRIBUTES_TTFD, true); - } - } - } - } - - private static boolean isTimestampWithinSpan( - final double timestamp, final @NotNull SentrySpan target) { - return timestamp >= target.getStartTimestamp() - && (target.getTimestamp() == null || timestamp <= target.getTimestamp()); - } - - private boolean hasAppStartSpan(final @NotNull SentryTransaction txn) { - final @NotNull List spans = txn.getSpans(); - for (final @NotNull SentrySpan span : spans) { - if (span.getOp().contentEquals(APP_START_COLD) - || span.getOp().contentEquals(APP_START_WARM)) { - return true; - } - } - - final @Nullable SpanContext context = txn.getContexts().getTrace(); - return context != null - && (context.getOperation().equals(APP_START_COLD) - || context.getOperation().equals(APP_START_WARM)); - } - - private void attachAppStartSpans( - final @NotNull AppStartMetrics appStartMetrics, final @NotNull SentryTransaction txn) { - - // We include process init, content providers and application.onCreate spans only on cold start - if (appStartMetrics.getAppStartType() != AppStartMetrics.AppStartType.COLD) { - return; - } - - final @Nullable SpanContext traceContext = txn.getContexts().getTrace(); - if (traceContext == null) { - return; - } - final @NotNull SentryId traceId = traceContext.getTraceId(); - - // determine the app.start.cold span, where all other spans will be attached to - @Nullable SpanId parentSpanId = null; - final @NotNull List spans = txn.getSpans(); - for (final @NotNull SentrySpan span : spans) { - if (span.getOp().contentEquals(APP_START_COLD)) { - parentSpanId = span.getSpanId(); - break; - } - } - - // Process init - final @NotNull TimeSpan processInitTimeSpan = appStartMetrics.createProcessInitSpan(); - if (processInitTimeSpan.hasStarted() - && Math.abs(processInitTimeSpan.getDurationMs()) <= MAX_PROCESS_INIT_APP_START_DIFF_MS) { - txn.getSpans() - .add( - timeSpanToSentrySpan( - processInitTimeSpan, parentSpanId, traceId, APP_METRICS_PROCESS_INIT_OP)); - } - - // Content Providers - final @NotNull List contentProviderOnCreates = - appStartMetrics.getContentProviderOnCreateTimeSpans(); - if (!contentProviderOnCreates.isEmpty()) { - for (final @NotNull TimeSpan contentProvider : contentProviderOnCreates) { - txn.getSpans() - .add( - timeSpanToSentrySpan( - contentProvider, parentSpanId, traceId, APP_METRICS_CONTENT_PROVIDER_OP)); - } - } - - // Application.onCreate - final @NotNull TimeSpan appOnCreate = appStartMetrics.getApplicationOnCreateTimeSpan(); - if (appOnCreate.hasStopped()) { - txn.getSpans() - .add( - timeSpanToSentrySpan(appOnCreate, parentSpanId, traceId, APP_METRICS_APPLICATION_OP)); - } - } - - @NotNull - private static SentrySpan timeSpanToSentrySpan( - final @NotNull TimeSpan span, - final @Nullable SpanId parentSpanId, - final @NotNull SentryId traceId, - final @NotNull String operation) { - - final Map defaultSpanData = new HashMap<>(2); - defaultSpanData.put(SpanDataConvention.THREAD_ID, AndroidThreadChecker.mainThreadSystemId); - defaultSpanData.put(SpanDataConvention.THREAD_NAME, "main"); - - defaultSpanData.put(SpanDataConvention.CONTRIBUTES_TTID, true); - defaultSpanData.put(SpanDataConvention.CONTRIBUTES_TTFD, true); - - return new SentrySpan( - span.getStartTimestampSecs(), - span.getProjectedStopTimestampSecs(), - traceId, - new SpanId(), - parentSpanId, - operation, - span.getDescription(), - SpanStatus.OK, - APP_METRICS_ORIGIN, - new ConcurrentHashMap<>(), - new ConcurrentHashMap<>(), - defaultSpanData); - } - - @Override - public @Nullable Long getOrder() { - return 9000L; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java deleted file mode 100644 index 16e96979454..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java +++ /dev/null @@ -1,113 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.TypeCheckHint.ANDROID_ACTIVITY; -import static io.sentry.android.core.internal.util.ScreenshotUtils.captureScreenshot; -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import android.app.Activity; -import android.graphics.Bitmap; -import io.sentry.Attachment; -import io.sentry.EventProcessor; -import io.sentry.Hint; -import io.sentry.SentryEvent; -import io.sentry.SentryLevel; -import io.sentry.android.core.internal.util.AndroidCurrentDateProvider; -import io.sentry.android.core.internal.util.Debouncer; -import io.sentry.android.core.internal.util.ScreenshotUtils; -import io.sentry.protocol.SentryTransaction; -import io.sentry.util.HintUtils; -import io.sentry.util.Objects; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * ScreenshotEventProcessor responsible for taking a screenshot of the screen when an error is - * captured. - */ -@ApiStatus.Internal -public final class ScreenshotEventProcessor implements EventProcessor { - - private final @NotNull SentryAndroidOptions options; - private final @NotNull BuildInfoProvider buildInfoProvider; - - private final @NotNull Debouncer debouncer; - private static final long DEBOUNCE_WAIT_TIME_MS = 2000; - private static final int DEBOUNCE_MAX_EXECUTIONS = 3; - - public ScreenshotEventProcessor( - final @NotNull SentryAndroidOptions options, - final @NotNull BuildInfoProvider buildInfoProvider) { - this.options = Objects.requireNonNull(options, "SentryAndroidOptions is required"); - this.buildInfoProvider = - Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required"); - this.debouncer = - new Debouncer( - AndroidCurrentDateProvider.getInstance(), - DEBOUNCE_WAIT_TIME_MS, - DEBOUNCE_MAX_EXECUTIONS); - - if (options.isAttachScreenshot()) { - addIntegrationToSdkVersion("Screenshot"); - } - } - - @Override - public @NotNull SentryTransaction process( - @NotNull SentryTransaction transaction, @NotNull Hint hint) { - // that's only necessary because on newer versions of Unity, if not overriding this method, it's - // throwing 'java.lang.AbstractMethodError: abstract method' and the reason is probably - // compilation mismatch - return transaction; - } - - @Override - public @NotNull SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) { - if (!event.isErrored()) { - return event; - } - if (!options.isAttachScreenshot()) { - this.options.getLogger().log(SentryLevel.DEBUG, "attachScreenshot is disabled."); - - return event; - } - final @Nullable Activity activity = CurrentActivityHolder.getInstance().getActivity(); - if (activity == null || HintUtils.isFromHybridSdk(hint)) { - return event; - } - - // skip capturing in case of debouncing (=too many frequent capture requests) - // the BeforeCaptureCallback may overrules the debouncing decision - final boolean shouldDebounce = debouncer.checkForDebounce(); - final @Nullable SentryAndroidOptions.BeforeCaptureCallback beforeCaptureCallback = - options.getBeforeScreenshotCaptureCallback(); - if (beforeCaptureCallback != null) { - if (!beforeCaptureCallback.execute(event, hint, shouldDebounce)) { - return event; - } - } else if (shouldDebounce) { - return event; - } - - final Bitmap screenshot = - captureScreenshot( - activity, options.getThreadChecker(), options.getLogger(), buildInfoProvider); - if (screenshot == null) { - return event; - } - - hint.setScreenshot( - Attachment.fromByteProvider( - () -> ScreenshotUtils.compressBitmapToPng(screenshot, options.getLogger()), - "screenshot.png", - "image/png", - false)); - hint.set(ANDROID_ACTIVITY, activity); - return event; - } - - @Override - public @Nullable Long getOrder() { - return 10000L; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SendCachedEnvelopeIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/SendCachedEnvelopeIntegration.java deleted file mode 100644 index 8eea2d71047..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SendCachedEnvelopeIntegration.java +++ /dev/null @@ -1,176 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import io.sentry.DataCategory; -import io.sentry.IConnectionStatusProvider; -import io.sentry.IScopes; -import io.sentry.ISentryLifecycleToken; -import io.sentry.Integration; -import io.sentry.SendCachedEnvelopeFireAndForgetIntegration; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.transport.RateLimiter; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.LazyEvaluator; -import io.sentry.util.Objects; -import java.io.Closeable; -import java.io.IOException; -import java.util.concurrent.Future; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -final class SendCachedEnvelopeIntegration - implements Integration, IConnectionStatusProvider.IConnectionStatusObserver, Closeable { - - private final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory - factory; - private final @NotNull LazyEvaluator startupCrashMarkerEvaluator; - private final AtomicBoolean startupCrashHandled = new AtomicBoolean(false); - private @Nullable IConnectionStatusProvider connectionStatusProvider; - private @Nullable IScopes scopes; - private @Nullable SentryAndroidOptions options; - private @Nullable SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForget sender; - private final AtomicBoolean isInitialized = new AtomicBoolean(false); - private final AtomicBoolean isClosed = new AtomicBoolean(false); - private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - - public SendCachedEnvelopeIntegration( - final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory factory, - final @NotNull LazyEvaluator startupCrashMarkerEvaluator) { - this.factory = Objects.requireNonNull(factory, "SendFireAndForgetFactory is required"); - this.startupCrashMarkerEvaluator = startupCrashMarkerEvaluator; - } - - @Override - public void register(@NotNull IScopes scopes, @NotNull SentryOptions options) { - this.scopes = Objects.requireNonNull(scopes, "Scopes are required"); - this.options = - Objects.requireNonNull( - (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, - "SentryAndroidOptions is required"); - - final String cachedDir = options.getCacheDirPath(); - if (!factory.hasValidPath(cachedDir, options.getLogger())) { - options.getLogger().log(SentryLevel.ERROR, "No cache dir path is defined in options."); - return; - } - addIntegrationToSdkVersion("SendCachedEnvelope"); - - sendCachedEnvelopes(scopes, this.options); - } - - @Override - public void close() throws IOException { - isClosed.set(true); - if (connectionStatusProvider != null) { - connectionStatusProvider.removeConnectionStatusObserver(this); - } - } - - @Override - public void onConnectionStatusChanged( - final @NotNull IConnectionStatusProvider.ConnectionStatus status) { - if (scopes != null - && options != null - && status != IConnectionStatusProvider.ConnectionStatus.DISCONNECTED) { - sendCachedEnvelopes(scopes, options); - } - } - - @SuppressWarnings({"NullAway"}) - private void sendCachedEnvelopes( - final @NotNull IScopes scopes, final @NotNull SentryAndroidOptions options) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - final Future future = - options - .getExecutorService() - .submit( - () -> { - try { - if (isClosed.get()) { - options - .getLogger() - .log( - SentryLevel.INFO, - "SendCachedEnvelopeIntegration, not trying to send after closing."); - return; - } - - if (!isInitialized.getAndSet(true)) { - connectionStatusProvider = options.getConnectionStatusProvider(); - connectionStatusProvider.addConnectionStatusObserver(this); - - sender = factory.create(scopes, options); - } - - if (connectionStatusProvider != null - && connectionStatusProvider.getConnectionStatus() - == IConnectionStatusProvider.ConnectionStatus.DISCONNECTED) { - options - .getLogger() - .log(SentryLevel.INFO, "SendCachedEnvelopeIntegration, no connection."); - return; - } - - // in case there's rate limiting active, skip processing - final @Nullable RateLimiter rateLimiter = scopes.getRateLimiter(); - if (rateLimiter != null - && rateLimiter.isActiveForCategory(DataCategory.All)) { - options - .getLogger() - .log( - SentryLevel.INFO, - "SendCachedEnvelopeIntegration, rate limiting active."); - return; - } - - if (sender == null) { - options - .getLogger() - .log( - SentryLevel.ERROR, - "SendCachedEnvelopeIntegration factory is null."); - return; - } - - sender.send(); - } catch (Throwable e) { - options - .getLogger() - .log(SentryLevel.ERROR, "Failed trying to send cached events.", e); - } - }); - - // startupCrashMarkerEvaluator remains true on subsequent runs, let's ensure we only block on - // the very first execution (=app start) - if (startupCrashMarkerEvaluator.getValue() - && startupCrashHandled.compareAndSet(false, true)) { - options.getLogger().log(SentryLevel.DEBUG, "Startup Crash marker exists, blocking flush."); - try { - future.get(options.getStartupCrashFlushTimeoutMillis(), TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - options - .getLogger() - .log(SentryLevel.DEBUG, "Synchronous send timed out, continuing in the background."); - } - } - options.getLogger().log(SentryLevel.DEBUG, "SendCachedEnvelopeIntegration installed."); - } catch (RejectedExecutionException e) { - options - .getLogger() - .log( - SentryLevel.ERROR, - "Failed to call the executor. Cached events will not be sent. Did you call Sentry.close()?", - e); - } catch (Throwable e) { - options - .getLogger() - .log(SentryLevel.ERROR, "Failed to call the executor. Cached events will not be sent", e); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java deleted file mode 100644 index 0d249f73790..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java +++ /dev/null @@ -1,279 +0,0 @@ -package io.sentry.android.core; - -import android.annotation.SuppressLint; -import android.app.Application; -import android.content.Context; -import android.os.Process; -import android.os.SystemClock; -import io.sentry.ILogger; -import io.sentry.IScopes; -import io.sentry.ISentryLifecycleToken; -import io.sentry.Integration; -import io.sentry.OptionsContainer; -import io.sentry.Sentry; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.Session; -import io.sentry.android.core.performance.AppStartMetrics; -import io.sentry.android.core.performance.TimeSpan; -import io.sentry.android.fragment.FragmentLifecycleIntegration; -import io.sentry.android.timber.SentryTimberIntegration; -import io.sentry.util.AutoClosableReentrantLock; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** Sentry initialization class */ -public final class SentryAndroid { - - // SystemClock.uptimeMillis() isn't affected by phone provider or clock changes. - private static final long sdkInitMillis = SystemClock.uptimeMillis(); - - static final String SENTRY_FRAGMENT_INTEGRATION_CLASS_NAME = - "io.sentry.android.fragment.FragmentLifecycleIntegration"; - - static final String SENTRY_TIMBER_INTEGRATION_CLASS_NAME = - "io.sentry.android.timber.SentryTimberIntegration"; - - static final String SENTRY_REPLAY_INTEGRATION_CLASS_NAME = - "io.sentry.android.replay.ReplayIntegration"; - - static final String SENTRY_DISTRIBUTION_INTEGRATION_CLASS_NAME = - "io.sentry.android.distribution.DistributionIntegration"; - - private static final String TIMBER_CLASS_NAME = "timber.log.Timber"; - private static final String FRAGMENT_CLASS_NAME = - "androidx.fragment.app.FragmentManager$FragmentLifecycleCallbacks"; - - protected static final @NotNull AutoClosableReentrantLock staticLock = - new AutoClosableReentrantLock(); - - private SentryAndroid() {} - - /** - * Sentry initialization method if auto-init is disabled - * - * @param context Application. context - */ - public static void init(@NotNull final Context context) { - init(context, new AndroidLogger()); - } - - /** - * Sentry initialization with a custom logger - * - * @param context Application. context - * @param logger your custom logger that implements ILogger - */ - public static void init(@NotNull final Context context, @NotNull ILogger logger) { - init(context, logger, options -> {}); - } - - /** - * Sentry initialization with a configuration handler that may override the default options - * - * @param context Application. context - * @param configuration Sentry.OptionsConfiguration configuration handler - */ - public static void init( - @NotNull final Context context, - @NotNull Sentry.OptionsConfiguration configuration) { - init(context, new AndroidLogger(), configuration); - } - - /** - * Sentry initialization with a configuration handler and custom logger - * - * @param context Application. context - * @param logger your custom logger that implements ILogger - * @param configuration Sentry.OptionsConfiguration configuration handler - */ - @SuppressLint("NewApi") - public static void init( - @NotNull final Context context, - @NotNull ILogger logger, - @NotNull Sentry.OptionsConfiguration configuration) { - try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { - Sentry.init( - OptionsContainer.create(SentryAndroidOptions.class), - options -> { - final io.sentry.util.LoadClass classLoader = new io.sentry.util.LoadClass(); - final boolean isTimberUpstreamAvailable = - classLoader.isClassAvailable(TIMBER_CLASS_NAME, options); - final boolean isFragmentUpstreamAvailable = - classLoader.isClassAvailable(FRAGMENT_CLASS_NAME, options); - final boolean isFragmentAvailable = - (isFragmentUpstreamAvailable - && classLoader.isClassAvailable( - SENTRY_FRAGMENT_INTEGRATION_CLASS_NAME, options)); - final boolean isTimberAvailable = - (isTimberUpstreamAvailable - && classLoader.isClassAvailable(SENTRY_TIMBER_INTEGRATION_CLASS_NAME, options)); - final boolean isReplayAvailable = - classLoader.isClassAvailable(SENTRY_REPLAY_INTEGRATION_CLASS_NAME, options); - final boolean isDistributionAvailable = - classLoader.isClassAvailable(SENTRY_DISTRIBUTION_INTEGRATION_CLASS_NAME, options); - - final BuildInfoProvider buildInfoProvider = new BuildInfoProvider(logger); - final io.sentry.util.LoadClass loadClass = new io.sentry.util.LoadClass(); - final ActivityFramesTracker activityFramesTracker = - new ActivityFramesTracker(loadClass, options); - - AndroidOptionsInitializer.loadDefaultAndMetadataOptions( - options, context, logger, buildInfoProvider); - - // We install the default integrations before the option configuration, so that the user - // can remove any of them. Integrations will not evaluate the options immediately, but - // will use them later, after being configured. - AndroidOptionsInitializer.installDefaultIntegrations( - context, - options, - buildInfoProvider, - loadClass, - activityFramesTracker, - isFragmentAvailable, - isTimberAvailable, - isReplayAvailable, - isDistributionAvailable); - - try { - configuration.configure(options); - } catch (Throwable t) { - // let it slip, but log it - options - .getLogger() - .log( - SentryLevel.ERROR, - "Error in the 'OptionsConfiguration.configure' callback.", - t); - } - - // if SentryPerformanceProvider was disabled or removed, - // we set the app start / sdk init time here instead - final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance(); - if (options.isEnablePerformanceV2() - && buildInfoProvider.getSdkInfoVersion() >= android.os.Build.VERSION_CODES.N) { - final @NotNull TimeSpan appStartTimeSpan = appStartMetrics.getAppStartTimeSpan(); - if (appStartTimeSpan.hasNotStarted()) { - appStartTimeSpan.setStartedAt(Process.getStartUptimeMillis()); - } - } - if (context.getApplicationContext() instanceof Application) { - appStartMetrics.registerLifecycleCallbacks( - (Application) context.getApplicationContext()); - } - final @NotNull TimeSpan sdkInitTimeSpan = appStartMetrics.getSdkInitTimeSpan(); - if (sdkInitTimeSpan.hasNotStarted()) { - sdkInitTimeSpan.setStartedAt(sdkInitMillis); - } - - AndroidOptionsInitializer.initializeIntegrationsAndProcessors( - options, - context, - buildInfoProvider, - loadClass, - activityFramesTracker, - isReplayAvailable); - - deduplicateIntegrations(options, isFragmentAvailable, isTimberAvailable); - }, - true); - - final @NotNull IScopes scopes = Sentry.getCurrentScopes(); - if (ContextUtils.isForegroundImportance()) { - if (scopes.getOptions().isEnableAutoSessionTracking()) { - // The LifecycleWatcher of AppLifecycleIntegration may already started a session - // so only start a session if it's not already started - // This e.g. happens on React Native, or e.g. on deferred SDK init - final AtomicBoolean sessionStarted = new AtomicBoolean(false); - scopes.configureScope( - scope -> { - final @Nullable Session currentSession = scope.getSession(); - if (currentSession != null && currentSession.getStarted() != null) { - sessionStarted.set(true); - } - }); - if (!sessionStarted.get()) { - scopes.startSession(); - } - } - scopes.getOptions().getReplayController().start(); - } - } catch (IllegalAccessException e) { - logger.log(SentryLevel.FATAL, "Fatal error during SentryAndroid.init(...)", e); - - // This is awful. Should we have this all on the interface and let the caller deal with these? - // They mean bug in the SDK. - throw new RuntimeException("Failed to initialize Sentry's SDK", e); - } catch (InstantiationException e) { - logger.log(SentryLevel.FATAL, "Fatal error during SentryAndroid.init(...)", e); - - throw new RuntimeException("Failed to initialize Sentry's SDK", e); - } catch (NoSuchMethodException e) { - logger.log(SentryLevel.FATAL, "Fatal error during SentryAndroid.init(...)", e); - - throw new RuntimeException("Failed to initialize Sentry's SDK", e); - } catch (InvocationTargetException e) { - logger.log(SentryLevel.FATAL, "Fatal error during SentryAndroid.init(...)", e); - - throw new RuntimeException("Failed to initialize Sentry's SDK", e); - } - } - - /** - * Deduplicate potentially duplicated Fragment and Timber integrations, which can be added - * automatically by our SDK as well as by the user. The user's ones (provided last in the - * options.integrations list) win over ours. - * - * @param options SentryOptions to retrieve integrations from - */ - private static void deduplicateIntegrations( - final @NotNull SentryOptions options, - final boolean isFragmentAvailable, - final boolean isTimberAvailable) { - - final List timberIntegrations = new ArrayList<>(); - final List fragmentIntegrations = new ArrayList<>(); - final List systemEventsIntegrations = new ArrayList<>(); - - for (final Integration integration : options.getIntegrations()) { - if (isFragmentAvailable) { - if (integration instanceof FragmentLifecycleIntegration) { - fragmentIntegrations.add(integration); - } - } - if (isTimberAvailable) { - if (integration instanceof SentryTimberIntegration) { - timberIntegrations.add(integration); - } - } - if (integration instanceof SystemEventsBreadcrumbsIntegration) { - systemEventsIntegrations.add(integration); - } - } - - if (fragmentIntegrations.size() > 1) { - for (int i = 0; i < fragmentIntegrations.size() - 1; i++) { - final Integration integration = fragmentIntegrations.get(i); - options.getIntegrations().remove(integration); - } - } - - if (timberIntegrations.size() > 1) { - for (int i = 0; i < timberIntegrations.size() - 1; i++) { - final Integration integration = timberIntegrations.get(i); - options.getIntegrations().remove(integration); - } - } - - if (systemEventsIntegrations.size() > 1) { - for (int i = 0; i < systemEventsIntegrations.size() - 1; i++) { - final Integration integration = systemEventsIntegrations.get(i); - options.getIntegrations().remove(integration); - } - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidDateProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidDateProvider.java deleted file mode 100644 index a6137744f48..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidDateProvider.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.sentry.android.core; - -import io.sentry.SentryDate; -import io.sentry.SentryDateProvider; -import io.sentry.SentryNanotimeDateProvider; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -/** - * This {@link SentryDateProvider} will use a combination of {@link java.util.Date} and - * System.nanoTime() on Android. During testing we discovered that {@link java.time.Instant} only - * offers ms precision if the build has desugaring enabled. Event without desugaring only API 33 - * offered higher precision. More info can be found here: - * https://github.com/getsentry/sentry-java/pull/2451 - */ -@ApiStatus.Internal -public final class SentryAndroidDateProvider implements SentryDateProvider { - - private @NotNull SentryDateProvider dateProvider = new SentryNanotimeDateProvider(); - - @Override - public SentryDate now() { - return dateProvider.now(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java deleted file mode 100644 index d106f63e75b..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java +++ /dev/null @@ -1,704 +0,0 @@ -package io.sentry.android.core; - -import android.app.Activity; -import android.app.ActivityManager; -import android.app.ApplicationExitInfo; -import io.sentry.Hint; -import io.sentry.IScope; -import io.sentry.ISpan; -import io.sentry.Sentry; -import io.sentry.SentryEvent; -import io.sentry.SentryFeedbackOptions; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.SpanStatus; -import io.sentry.android.core.internal.util.RootChecker; -import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; -import io.sentry.protocol.Mechanism; -import io.sentry.protocol.SdkVersion; -import io.sentry.protocol.SentryId; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -/** Sentry SDK options for Android */ -public final class SentryAndroidOptions extends SentryOptions { - - /** - * Enable or disable ANR (Application Not Responding) Default is enabled Used by AnrIntegration - */ - private boolean anrEnabled = true; - - /** ANR Timeout interval in Millis Default is 5000 = 5s Used by AnrIntegration */ - private long anrTimeoutIntervalMillis = 5000; - - /** Enable or disable ANR on Debug mode Default is disabled Used by AnrIntegration */ - private boolean anrReportInDebug = false; - - /** - * Enable or disable automatic breadcrumbs for Activity lifecycle. Using - * Application.ActivityLifecycleCallbacks - */ - private boolean enableActivityLifecycleBreadcrumbs = true; - - /** Enable or disable automatic breadcrumbs for App's lifecycle Using ProcessLifecycleOwner */ - private boolean enableAppLifecycleBreadcrumbs = true; - - /** Enable or disable automatic breadcrumbs for SystemEvents Registering a BroadcastReceiver */ - private boolean enableSystemEventBreadcrumbs = true; - - /** Enable or disable automatic breadcrumbs for App Components Using ComponentCallbacks */ - private boolean enableAppComponentBreadcrumbs = true; - - /** Enable or disable automatic breadcrumbs for Network Events Using NetworkCallback */ - private boolean enableNetworkEventBreadcrumbs = true; - - /** - * Enables the Auto instrumentation for Activity lifecycle tracing. - * - *

    - *
  • It also requires setting any of {@link SentryOptions#getTracesSampleRate()} or {@link - * SentryOptions#getTracesSampler()}. - *
- * - *
    - * It starts a transaction before each Activity's onCreate method is called - * (onActivityPreCreated). - *
  • The transaction's name is the Activity's name, e.g. MainActivity. - *
  • The transaction's operation is navigation. - *
- * - *
    - * It finishes the transaction after each activity's onResume method is called - * (onActivityPostResumed), this depends on {@link - * SentryAndroidOptions#enableActivityLifecycleTracingAutoFinish}. - *
  • If {@link SentryAndroidOptions#enableActivityLifecycleTracingAutoFinish} is disabled, you - * may finish the transaction manually. - *
  • If the transaction is not finished either automatically or manually, we finish it - * automatically when each Activity's onDestroy method is called (onActivityDestroyed). - *
  • If the previous transaction is not finished when a new Activity is being shown, we finish - * it automatically before the new Activity's onCreate is called (onActivityPreCreated). - *
  • The transaction status will be {@link SpanStatus#OK} if none is set. - *
- * - *

The transaction is automatically bound to the {@link IScope}, but only if there's no - * transaction already bound to the Scope. - */ - private boolean enableAutoActivityLifecycleTracing = true; - - /** - * Enables the Auto instrumentation for Activity lifecycle tracing, but specifically when to - * finish the transaction, read {@link SentryAndroidOptions#enableAutoActivityLifecycleTracing}. - * - *

If you require a specific lifecycle to finish a transaction or even after the Activity is - * fully rendered but still waiting for an IO operation, you could call {@link ISpan#finish()} - * yourself on {@link Sentry#getSpan()}, be sure that you've finished all of your manually created - * Spans. - */ - private boolean enableActivityLifecycleTracingAutoFinish = true; - - /** Interface that loads the debug images list */ - private @NotNull IDebugImagesLoader debugImagesLoader = NoOpDebugImagesLoader.getInstance(); - - /** - * Enables or disables the attach screenshot feature when an error happened. Use {@link - * SentryAndroidOptions#setBeforeScreenshotCaptureCallback(BeforeCaptureCallback)} ()} to control - * when a screenshot should be captured. - */ - private boolean attachScreenshot; - - /** - * Enables or disables the attach view hierarchy feature when an error happened. Use {@link - * SentryAndroidOptions#setBeforeViewHierarchyCaptureCallback(BeforeCaptureCallback)} ()} to - * control when a view hierarchy should be captured. - */ - private boolean attachViewHierarchy; - - /** - * Enables or disables collecting of device information which requires Inter-Process Communication - * (IPC) - */ - private boolean collectAdditionalContext = true; - - /** Enables or disables collecting of external storage context. */ - private boolean collectExternalStorageContext = false; - - /** - * Controls how many seconds to wait for sending events in case there were Startup Crashes in the - * previous run. Sentry SDKs normally send events from a background queue, but in the case of - * Startup Crashes, it blocks the execution of the {@link Sentry#init()} function for the amount - * of startupCrashFlushTimeoutMillis to make sure the events make it to Sentry. - * - *

When the timeout is reached, the execution will continue on background. - * - *

Default is 5000 = 5s. - */ - private long startupCrashFlushTimeoutMillis = 5000; // 5s - - /** - * Controls the threshold after the application startup time, within which a crash should happen - * to be considered a Startup Crash. - * - *

Startup Crashes are sent on {@link Sentry#init()} in a blocking way, controlled by {@link - * SentryAndroidOptions#startupCrashFlushTimeoutMillis}. - * - *

Default is 2000 = 2s. - */ - private final long startupCrashDurationThresholdMillis = 2000; // 2s - - private boolean enableFramesTracking = true; - - private @Nullable String nativeSdkName = null; - - /** - * Controls whether to enable the {@link RootChecker}, which can potentially make apps to be - * flagged by some app stores as harmful. - */ - private boolean enableRootCheck = true; - - private @Nullable BeforeCaptureCallback beforeScreenshotCaptureCallback; - - private @Nullable BeforeCaptureCallback beforeViewHierarchyCaptureCallback; - - /** Turns NDK on or off. Default is enabled. */ - private boolean enableNdk = true; - - @NotNull - private NdkHandlerStrategy ndkHandlerStrategy = - NdkHandlerStrategy.SENTRY_HANDLER_STRATEGY_DEFAULT; - - /** - * Enable the Java to NDK Scope sync. The default value for sentry-java is disabled and enabled - * for sentry-android. - */ - private boolean enableScopeSync = true; - - /** - * Whether to enable automatic trace ID generation. This is mainly used by the Hybrid SDKs to - * control the trace ID generation from the outside. - */ - private boolean enableAutoTraceIdGeneration = true; - - /** Enable or disable intent extras reporting for system event breadcrumbs. Default is false. */ - private boolean enableSystemEventBreadcrumbsExtras = false; - - public interface BeforeCaptureCallback { - - /** - * A callback which can be used to suppress capturing of screenshots or view hierarchies. This - * gives more fine grained control when capturing should be performed. E.g. - only capture - * screenshots for fatal events - overrule any debouncing for important events
- * As capturing can be resource-intensive, the debounce parameter should be respected if - * possible. - * - *

-     *  if (debounce) {
-     *    return false;
-     *  } else {
-     *    // check event and hint
-     *  }
-     *  
- * - * @param event the event - * @param hint the hints - * @param debounce true if capturing is marked for being debounced - * @return true if capturing should be performed, false otherwise - */ - boolean execute(@NotNull SentryEvent event, @NotNull Hint hint, boolean debounce); - } - - /** - * Controls whether to report historical ANRs (v2) from the {@link ApplicationExitInfo} system - * API. When enabled, reports all of the ANRs available in the {@link - * ActivityManager#getHistoricalProcessExitReasons(String, int, int)} list, as opposed to - * reporting only the latest one. - * - *

These events do not affect ANR rate nor are they enriched with additional information from - * {@link IScope} like breadcrumbs. The events are reported with 'HistoricalAppExitInfo' {@link - * Mechanism}. - */ - private boolean reportHistoricalAnrs = false; - - /** - * Controls whether to report historical Tombstones from the {@link ApplicationExitInfo} system - * API. When enabled, reports all of the Tombstones available in the {@link - * ActivityManager#getHistoricalProcessExitReasons(String, int, int)} list, as opposed to - * reporting only the latest one. - * - *

These events do not affect crash rate nor are they enriched with additional information from - * {@link IScope} like breadcrumbs. - */ - private boolean reportHistoricalTombstones = false; - - /** - * Controls whether to send ANR (v2) thread dump as an attachment with plain text. The thread dump - * is being attached from {@link ApplicationExitInfo#getTraceInputStream()}, if available. - */ - private boolean attachAnrThreadDump = false; - - private boolean enablePerformanceV2 = true; - - private @Nullable SentryFrameMetricsCollector frameMetricsCollector; - - private boolean enableTombstone = false; - - public SentryAndroidOptions() { - setSentryClientName(BuildConfig.SENTRY_ANDROID_SDK_NAME + "/" + BuildConfig.VERSION_NAME); - setSdkVersion(createSdkVersion()); - setAttachServerName(false); - } - - private @NotNull SdkVersion createSdkVersion() { - SdkVersion sdkVersion = getSdkVersion(); - - final String name = BuildConfig.SENTRY_ANDROID_SDK_NAME; - final String version = BuildConfig.VERSION_NAME; - sdkVersion = SdkVersion.updateSdkVersion(sdkVersion, name, version); - - sdkVersion.addPackage("maven:io.sentry:sentry-android-core", version); - - return sdkVersion; - } - - /** - * Checks if ANR (Application Not Responding) is enabled or disabled Default is enabled - * - * @return true if enabled or false otherwise - */ - public boolean isAnrEnabled() { - return anrEnabled; - } - - /** - * Sets ANR (Application Not Responding) to enabled or disabled Default is enabled - * - * @param anrEnabled true for enabled and false for disabled - */ - public void setAnrEnabled(boolean anrEnabled) { - this.anrEnabled = anrEnabled; - } - - /** - * Returns the ANR timeout internal in Millis Default is 5000 = 5s - * - * @return the timeout in millis - */ - public long getAnrTimeoutIntervalMillis() { - return anrTimeoutIntervalMillis; - } - - /** - * Sets the ANR timeout internal in Millis Default is 5000 = 5s - * - * @param anrTimeoutIntervalMillis the timeout internal in Millis - */ - public void setAnrTimeoutIntervalMillis(long anrTimeoutIntervalMillis) { - this.anrTimeoutIntervalMillis = anrTimeoutIntervalMillis; - } - - /** - * Checks if ANR (Application Not Responding) is enabled or disabled on Debug mode Default is - * disabled - * - * @return true if enabled or false otherwise - */ - public boolean isAnrReportInDebug() { - return anrReportInDebug; - } - - /** - * Sets ANR (Application Not Responding) to enabled or disabled on Debug mode Default is disabled - * - * @param anrReportInDebug true for enabled and false for disabled - */ - public void setAnrReportInDebug(boolean anrReportInDebug) { - this.anrReportInDebug = anrReportInDebug; - } - - /** - * Sets Tombstone reporting (ApplicationExitInfo.REASON_CRASH_NATIVE) to enabled or disabled. - * - * @param enableTombstone true for enabled and false for disabled - */ - public void setTombstoneEnabled(boolean enableTombstone) { - this.enableTombstone = enableTombstone; - } - - /** - * Checks if Tombstone reporting (ApplicationExitInfo.REASON_CRASH_NATIVE) is enabled or disabled - * Default is disabled - * - * @return true if enabled or false otherwise - */ - public boolean isTombstoneEnabled() { - return enableTombstone; - } - - public boolean isEnableActivityLifecycleBreadcrumbs() { - return enableActivityLifecycleBreadcrumbs; - } - - public void setEnableActivityLifecycleBreadcrumbs(boolean enableActivityLifecycleBreadcrumbs) { - this.enableActivityLifecycleBreadcrumbs = enableActivityLifecycleBreadcrumbs; - } - - public boolean isEnableAppLifecycleBreadcrumbs() { - return enableAppLifecycleBreadcrumbs; - } - - public void setEnableAppLifecycleBreadcrumbs(boolean enableAppLifecycleBreadcrumbs) { - this.enableAppLifecycleBreadcrumbs = enableAppLifecycleBreadcrumbs; - } - - public boolean isEnableSystemEventBreadcrumbs() { - return enableSystemEventBreadcrumbs; - } - - public void setEnableSystemEventBreadcrumbs(boolean enableSystemEventBreadcrumbs) { - this.enableSystemEventBreadcrumbs = enableSystemEventBreadcrumbs; - } - - public boolean isEnableAppComponentBreadcrumbs() { - return enableAppComponentBreadcrumbs; - } - - public void setEnableAppComponentBreadcrumbs(boolean enableAppComponentBreadcrumbs) { - this.enableAppComponentBreadcrumbs = enableAppComponentBreadcrumbs; - } - - public boolean isEnableNetworkEventBreadcrumbs() { - return enableNetworkEventBreadcrumbs; - } - - public void setEnableNetworkEventBreadcrumbs(boolean enableNetworkEventBreadcrumbs) { - this.enableNetworkEventBreadcrumbs = enableNetworkEventBreadcrumbs; - } - - /** - * Enable or disable all the automatic breadcrumbs - * - * @param enable true if enable or false otherwise - */ - public void enableAllAutoBreadcrumbs(boolean enable) { - enableActivityLifecycleBreadcrumbs = enable; - enableAppComponentBreadcrumbs = enable; - enableSystemEventBreadcrumbs = enable; - enableAppLifecycleBreadcrumbs = enable; - enableNetworkEventBreadcrumbs = enable; - setEnableUserInteractionBreadcrumbs(enable); - } - - /** - * Returns the Debug image loader - * - * @return the image loader - */ - public @NotNull IDebugImagesLoader getDebugImagesLoader() { - return debugImagesLoader; - } - - /** - * Sets the image loader - * - * @param debugImagesLoader the image loader - */ - public void setDebugImagesLoader(final @NotNull IDebugImagesLoader debugImagesLoader) { - this.debugImagesLoader = - debugImagesLoader != null ? debugImagesLoader : NoOpDebugImagesLoader.getInstance(); - } - - public boolean isEnableAutoActivityLifecycleTracing() { - return enableAutoActivityLifecycleTracing; - } - - public void setEnableAutoActivityLifecycleTracing(boolean enableAutoActivityLifecycleTracing) { - this.enableAutoActivityLifecycleTracing = enableAutoActivityLifecycleTracing; - } - - public boolean isEnableActivityLifecycleTracingAutoFinish() { - return enableActivityLifecycleTracingAutoFinish; - } - - public void setEnableActivityLifecycleTracingAutoFinish( - boolean enableActivityLifecycleTracingAutoFinish) { - this.enableActivityLifecycleTracingAutoFinish = enableActivityLifecycleTracingAutoFinish; - } - - public boolean isAttachScreenshot() { - return attachScreenshot; - } - - public void setAttachScreenshot(boolean attachScreenshot) { - this.attachScreenshot = attachScreenshot; - } - - public boolean isAttachViewHierarchy() { - return attachViewHierarchy; - } - - public void setAttachViewHierarchy(boolean attachViewHierarchy) { - this.attachViewHierarchy = attachViewHierarchy; - } - - public boolean isCollectAdditionalContext() { - return collectAdditionalContext; - } - - public void setCollectAdditionalContext(boolean collectAdditionalContext) { - this.collectAdditionalContext = collectAdditionalContext; - } - - public boolean isCollectExternalStorageContext() { - return collectExternalStorageContext; - } - - public void setCollectExternalStorageContext(final boolean collectExternalStorageContext) { - this.collectExternalStorageContext = collectExternalStorageContext; - } - - public boolean isEnableFramesTracking() { - return enableFramesTracking; - } - - /** - * Enable or disable Frames Tracking, which is used to report slow and frozen frames. - * - * @param enableFramesTracking true if frames tracking should be enabled, false otherwise. - */ - public void setEnableFramesTracking(boolean enableFramesTracking) { - this.enableFramesTracking = enableFramesTracking; - } - - /** - * Returns the Startup Crash flush timeout in Millis - * - * @return the timeout in Millis - */ - @ApiStatus.Internal - long getStartupCrashFlushTimeoutMillis() { - return startupCrashFlushTimeoutMillis; - } - - /** - * Sets the Startup Crash flush timeout in Millis - * - * @param startupCrashFlushTimeoutMillis the timeout in Millis - */ - @TestOnly - void setStartupCrashFlushTimeoutMillis(long startupCrashFlushTimeoutMillis) { - this.startupCrashFlushTimeoutMillis = startupCrashFlushTimeoutMillis; - } - - /** - * Returns the Startup Crash duration threshold in Millis - * - * @return the threshold in Millis - */ - @ApiStatus.Internal - public long getStartupCrashDurationThresholdMillis() { - return startupCrashDurationThresholdMillis; - } - - /** - * Sets the sdk name for the sentry-native ndk module. The value is used for the event->sdk - * attribute and the sentry_client auth header. - * - * @param nativeSdkName the native sdk name - */ - @ApiStatus.Internal - public void setNativeSdkName(final @Nullable String nativeSdkName) { - this.nativeSdkName = nativeSdkName; - } - - @ApiStatus.Internal - public void setNativeHandlerStrategy(final @NotNull NdkHandlerStrategy ndkHandlerStrategy) { - this.ndkHandlerStrategy = ndkHandlerStrategy; - } - - @ApiStatus.Internal - public int getNdkHandlerStrategy() { - return ndkHandlerStrategy.getValue(); - } - - /** - * Returns the sdk name for the sentry native ndk module. - * - * @return the custom SDK name if set, otherwise null - */ - @ApiStatus.Internal - public @Nullable String getNativeSdkName() { - return nativeSdkName; - } - - public boolean isEnableRootCheck() { - return enableRootCheck; - } - - public void setEnableRootCheck(final boolean enableRootCheck) { - this.enableRootCheck = enableRootCheck; - } - - public @Nullable BeforeCaptureCallback getBeforeScreenshotCaptureCallback() { - return beforeScreenshotCaptureCallback; - } - - /** - * Sets a callback which is executed before capturing screenshots. Only relevant if - * attachScreenshot is set to true. - * - * @param beforeScreenshotCaptureCallback the callback to execute - */ - public void setBeforeScreenshotCaptureCallback( - final @NotNull BeforeCaptureCallback beforeScreenshotCaptureCallback) { - this.beforeScreenshotCaptureCallback = beforeScreenshotCaptureCallback; - } - - public @Nullable BeforeCaptureCallback getBeforeViewHierarchyCaptureCallback() { - return beforeViewHierarchyCaptureCallback; - } - - /** - * Sets a callback which is executed before capturing view hierarchies. Only relevant if - * attachViewHierarchy is set to true. - * - * @param beforeViewHierarchyCaptureCallback the callback to execute - */ - public void setBeforeViewHierarchyCaptureCallback( - final @NotNull BeforeCaptureCallback beforeViewHierarchyCaptureCallback) { - this.beforeViewHierarchyCaptureCallback = beforeViewHierarchyCaptureCallback; - } - - /** - * Check if NDK is ON or OFF Default is ON - * - * @return true if ON or false otherwise - */ - public boolean isEnableNdk() { - return enableNdk; - } - - /** - * Sets NDK to ON or OFF - * - * @param enableNdk true if ON or false otherwise - */ - public void setEnableNdk(boolean enableNdk) { - this.enableNdk = enableNdk; - } - - /** - * Returns if the Java to NDK Scope sync is enabled - * - * @return true if enabled or false otherwise - */ - public boolean isEnableScopeSync() { - return enableScopeSync; - } - - /** - * Enables or not the Java to NDK Scope sync - * - * @param enableScopeSync true if enabled or false otherwise - */ - public void setEnableScopeSync(boolean enableScopeSync) { - this.enableScopeSync = enableScopeSync; - } - - public boolean isReportHistoricalAnrs() { - return reportHistoricalAnrs; - } - - public void setReportHistoricalAnrs(final boolean reportHistoricalAnrs) { - this.reportHistoricalAnrs = reportHistoricalAnrs; - } - - public boolean isReportHistoricalTombstones() { - return reportHistoricalTombstones; - } - - public void setReportHistoricalTombstones(final boolean reportHistoricalTombstones) { - this.reportHistoricalTombstones = reportHistoricalTombstones; - } - - public boolean isAttachAnrThreadDump() { - return attachAnrThreadDump; - } - - public void setAttachAnrThreadDump(final boolean attachAnrThreadDump) { - this.attachAnrThreadDump = attachAnrThreadDump; - } - - /** - * @return true if performance-v2 is enabled. See {@link #setEnablePerformanceV2(boolean)} for - * more details. - */ - public boolean isEnablePerformanceV2() { - return enablePerformanceV2; - } - - /** - * Enables or disables the Performance V2 SDK features. - * - *

With this change - Cold app start spans will provide more accurate timings - Cold app start - * spans will be enriched with detailed ContentProvider, Application and Activity startup times - * - * @param enablePerformanceV2 true if enabled or false otherwise - */ - public void setEnablePerformanceV2(final boolean enablePerformanceV2) { - this.enablePerformanceV2 = enablePerformanceV2; - } - - @ApiStatus.Internal - public @Nullable SentryFrameMetricsCollector getFrameMetricsCollector() { - return frameMetricsCollector; - } - - @ApiStatus.Internal - public void setFrameMetricsCollector( - final @Nullable SentryFrameMetricsCollector frameMetricsCollector) { - this.frameMetricsCollector = frameMetricsCollector; - } - - public boolean isEnableAutoTraceIdGeneration() { - return enableAutoTraceIdGeneration; - } - - public void setEnableAutoTraceIdGeneration(final boolean enableAutoTraceIdGeneration) { - this.enableAutoTraceIdGeneration = enableAutoTraceIdGeneration; - } - - public boolean isEnableSystemEventBreadcrumbsExtras() { - return enableSystemEventBreadcrumbsExtras; - } - - public void setEnableSystemEventBreadcrumbsExtras( - final boolean enableSystemEventBreadcrumbsExtras) { - this.enableSystemEventBreadcrumbsExtras = enableSystemEventBreadcrumbsExtras; - } - - static class AndroidUserFeedbackIDialogHandler implements SentryFeedbackOptions.IDialogHandler { - @Override - public void showDialog( - final @Nullable SentryId associatedEventId, - final @Nullable SentryFeedbackOptions.OptionsConfigurator configurator) { - final @Nullable Activity activity = CurrentActivityHolder.getInstance().getActivity(); - if (activity == null) { - Sentry.getCurrentScopes() - .getOptions() - .getLogger() - .log( - SentryLevel.ERROR, - "Cannot show user feedback dialog, no activity is available. " - + "Make sure to call SentryAndroid.init() in your Application.onCreate() method."); - return; - } - - new SentryUserFeedbackDialog.Builder(activity) - .associatedEventId(associatedEventId) - .configurator(configurator) - .create() - .show(); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryFrameMetrics.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryFrameMetrics.java deleted file mode 100644 index cf2241757ca..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryFrameMetrics.java +++ /dev/null @@ -1,118 +0,0 @@ -package io.sentry.android.core; - -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -final class SentryFrameMetrics { - - private int slowFrameCount; - private int frozenFrameCount; - - private long slowFrameDelayNanos; - private long frozenFrameDelayNanos; - - private long totalDurationNanos; - - public SentryFrameMetrics() {} - - public SentryFrameMetrics( - final int slowFrameCount, - final long slowFrameDelayNanos, - final int frozenFrameCount, - final long frozenFrameDelayNanos, - final long totalDurationNanos) { - this.slowFrameCount = slowFrameCount; - this.slowFrameDelayNanos = slowFrameDelayNanos; - - this.frozenFrameCount = frozenFrameCount; - this.frozenFrameDelayNanos = frozenFrameDelayNanos; - this.totalDurationNanos = totalDurationNanos; - } - - public void addFrame( - final long durationNanos, - final long delayNanos, - final boolean isSlow, - final boolean isFrozen) { - totalDurationNanos += durationNanos; - if (isFrozen) { - frozenFrameDelayNanos += delayNanos; - frozenFrameCount += 1; - } else if (isSlow) { - slowFrameDelayNanos += delayNanos; - slowFrameCount += 1; - } - } - - public int getSlowFrameCount() { - return slowFrameCount; - } - - public int getFrozenFrameCount() { - return frozenFrameCount; - } - - public long getSlowFrameDelayNanos() { - return slowFrameDelayNanos; - } - - public long getFrozenFrameDelayNanos() { - return frozenFrameDelayNanos; - } - - /** Returns the sum of the slow and frozen frames. */ - public int getSlowFrozenFrameCount() { - return slowFrameCount + frozenFrameCount; - } - - public long getTotalDurationNanos() { - return totalDurationNanos; - } - - public void clear() { - slowFrameCount = 0; - slowFrameDelayNanos = 0; - - frozenFrameCount = 0; - frozenFrameDelayNanos = 0; - - totalDurationNanos = 0; - } - - @NotNull - public SentryFrameMetrics duplicate() { - return new SentryFrameMetrics( - slowFrameCount, - slowFrameDelayNanos, - frozenFrameCount, - frozenFrameDelayNanos, - totalDurationNanos); - } - - /** - * @param other the other frame metrics to compare to, usually the older one - * @return the difference between two frame metrics (this minus other) - */ - @NotNull - public SentryFrameMetrics diffTo(final @NotNull SentryFrameMetrics other) { - return new SentryFrameMetrics( - slowFrameCount - other.slowFrameCount, - slowFrameDelayNanos - other.slowFrameDelayNanos, - frozenFrameCount - other.frozenFrameCount, - frozenFrameDelayNanos - other.frozenFrameDelayNanos, - totalDurationNanos - other.totalDurationNanos); - } - - /** - * @return true if the frame metrics contain valid data, meaning all values are greater or equal - * to 0 - */ - public boolean containsValidData() { - return slowFrameCount >= 0 - && slowFrameDelayNanos >= 0 - && frozenFrameCount >= 0 - && frozenFrameDelayNanos >= 0 - && totalDurationNanos >= 0; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryInitProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryInitProvider.java deleted file mode 100644 index 749eda07efc..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryInitProvider.java +++ /dev/null @@ -1,52 +0,0 @@ -package io.sentry.android.core; - -import android.content.Context; -import android.content.pm.ProviderInfo; -import android.net.Uri; -import io.sentry.Sentry; -import io.sentry.SentryIntegrationPackageStorage; -import io.sentry.SentryLevel; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public final class SentryInitProvider extends EmptySecureContentProvider { - - @Override - public boolean onCreate() { - AndroidLogger logger = new AndroidLogger(); - final Context context = getContext(); - if (context == null) { - logger.log(SentryLevel.FATAL, "App. Context from ContentProvider is null"); - return false; - } - - if (ManifestMetadataReader.isAutoInit(context, logger) - && !ContextUtils.appIsLibraryForComposePreview(context)) { - SentryAndroid.init(context, logger); - SentryIntegrationPackageStorage.getInstance().addIntegration("AutoInit"); - } - return true; - } - - @Override - public void shutdown() { - Sentry.close(); - } - - @Override - public void attachInfo(@NotNull Context context, @NotNull ProviderInfo info) { - // applicationId is expected to be prepended. See AndroidManifest.xml - if (SentryInitProvider.class.getName().equals(info.authority)) { - throw new IllegalStateException( - "An applicationId is required to fulfill the manifest placeholder."); - } - super.attachInfo(context, info); - } - - @Override - public @Nullable String getType(@NotNull Uri uri) { - return null; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java deleted file mode 100644 index 1e649c1783f..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java +++ /dev/null @@ -1,153 +0,0 @@ -package io.sentry.android.core; - -import android.util.Log; -import io.sentry.Breadcrumb; -import io.sentry.ScopesAdapter; -import io.sentry.Sentry; -import io.sentry.SentryLevel; -import io.sentry.SentryLogLevel; -import io.sentry.logger.SentryLogParameters; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * This class replaces {@link android.util.Log} with its own implementations which creates a {@link - * io.sentry.Breadcrumb} for each log. It only replaces log functions that meet a minimum level set - * by the user on the Sentry Android Gradle Plugin. - */ -@ApiStatus.Internal -public final class SentryLogcatAdapter { - - private static void addAsBreadcrumb( - @Nullable String tag, @NotNull SentryLevel level, @Nullable String msg) { - addAsBreadcrumb(tag, level, msg, null); - } - - private static void addAsBreadcrumb( - @Nullable String tag, @NotNull SentryLevel level, @Nullable Throwable tr) { - addAsBreadcrumb(tag, level, null, tr); - } - - private static void addAsBreadcrumb( - @Nullable final String tag, - @NotNull final SentryLevel level, - @Nullable final String msg, - @Nullable final Throwable tr) { - Breadcrumb breadcrumb = new Breadcrumb(); - breadcrumb.setCategory("Logcat"); - breadcrumb.setMessage(msg); - breadcrumb.setLevel(level); - if (tag != null) { - breadcrumb.setData("tag", tag); - } - if (tr != null && tr.getMessage() != null) { - breadcrumb.setData("throwable", tr.getMessage()); - } - Sentry.addBreadcrumb(breadcrumb); - } - - private static void addAsLog( - @NotNull final SentryLogLevel level, - @Nullable final String msg, - @Nullable final Throwable tr) { - final @NotNull ScopesAdapter scopes = ScopesAdapter.getInstance(); - // Check if logs are enabled before doing expensive operations - if (!scopes.getOptions().getLogs().isEnabled()) { - return; - } - final @Nullable String trMessage = tr != null ? tr.getMessage() : null; - final @NotNull SentryLogParameters params = new SentryLogParameters(); - params.setOrigin("auto.log.logcat"); - - if (tr == null || trMessage == null) { - scopes.logger().log(level, params, msg); - } else { - scopes.logger().log(level, params, msg != null ? (msg + "\n" + trMessage) : trMessage); - } - } - - public static int v(@Nullable String tag, @Nullable String msg) { - addAsBreadcrumb(tag, SentryLevel.DEBUG, msg); - addAsLog(SentryLogLevel.TRACE, msg, null); - return Log.v(tag, msg); - } - - public static int v(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) { - addAsBreadcrumb(tag, SentryLevel.DEBUG, msg, tr); - addAsLog(SentryLogLevel.TRACE, msg, tr); - return Log.v(tag, msg, tr); - } - - public static int d(@Nullable String tag, @Nullable String msg) { - addAsBreadcrumb(tag, SentryLevel.DEBUG, msg); - addAsLog(SentryLogLevel.DEBUG, msg, null); - return Log.d(tag, msg); - } - - public static int d(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) { - addAsBreadcrumb(tag, SentryLevel.DEBUG, msg, tr); - addAsLog(SentryLogLevel.DEBUG, msg, tr); - return Log.d(tag, msg, tr); - } - - public static int i(@Nullable String tag, @Nullable String msg) { - addAsBreadcrumb(tag, SentryLevel.INFO, msg); - addAsLog(SentryLogLevel.INFO, msg, null); - return Log.i(tag, msg); - } - - public static int i(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) { - addAsBreadcrumb(tag, SentryLevel.INFO, msg, tr); - addAsLog(SentryLogLevel.INFO, msg, tr); - return Log.i(tag, msg, tr); - } - - public static int w(@Nullable String tag, @Nullable String msg) { - addAsBreadcrumb(tag, SentryLevel.WARNING, msg); - addAsLog(SentryLogLevel.WARN, msg, null); - return Log.w(tag, msg); - } - - public static int w(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) { - addAsBreadcrumb(tag, SentryLevel.WARNING, msg, tr); - addAsLog(SentryLogLevel.WARN, msg, tr); - return Log.w(tag, msg, tr); - } - - public static int w(@Nullable String tag, @Nullable Throwable tr) { - addAsBreadcrumb(tag, SentryLevel.WARNING, tr); - addAsLog(SentryLogLevel.WARN, null, tr); - return Log.w(tag, tr); - } - - public static int e(@Nullable String tag, @Nullable String msg) { - addAsBreadcrumb(tag, SentryLevel.ERROR, msg); - addAsLog(SentryLogLevel.ERROR, msg, null); - return Log.e(tag, msg); - } - - public static int e(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) { - addAsBreadcrumb(tag, SentryLevel.ERROR, msg, tr); - addAsLog(SentryLogLevel.ERROR, msg, tr); - return Log.e(tag, msg, tr); - } - - public static int wtf(@Nullable String tag, @Nullable String msg) { - addAsBreadcrumb(tag, SentryLevel.ERROR, msg); - addAsLog(SentryLogLevel.FATAL, msg, null); - return Log.wtf(tag, msg); - } - - public static int wtf(@Nullable String tag, @Nullable Throwable tr) { - addAsBreadcrumb(tag, SentryLevel.ERROR, tr); - addAsLog(SentryLogLevel.FATAL, null, tr); - return Log.wtf(tag, tr); - } - - public static int wtf(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) { - addAsBreadcrumb(tag, SentryLevel.ERROR, msg, tr); - addAsLog(SentryLogLevel.FATAL, msg, tr); - return Log.wtf(tag, msg, tr); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java deleted file mode 100644 index d687670f9b6..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java +++ /dev/null @@ -1,248 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.Sentry.APP_START_PROFILING_CONFIG_FILE_NAME; - -import android.annotation.SuppressLint; -import android.app.Application; -import android.content.Context; -import android.content.pm.ProviderInfo; -import android.net.Uri; -import android.os.Process; -import android.os.SystemClock; -import io.sentry.IContinuousProfiler; -import io.sentry.ILogger; -import io.sentry.ISentryLifecycleToken; -import io.sentry.ITransactionProfiler; -import io.sentry.JsonSerializer; -import io.sentry.SentryAppStartProfilingOptions; -import io.sentry.SentryExecutorService; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.TracesSampler; -import io.sentry.TracesSamplingDecision; -import io.sentry.android.core.internal.util.AndroidRuntimeManager; -import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; -import io.sentry.android.core.performance.AppStartMetrics; -import io.sentry.android.core.performance.TimeSpan; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.runtime.IRuntimeManager; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStreamReader; -import java.io.Reader; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -@ApiStatus.Internal -public final class SentryPerformanceProvider extends EmptySecureContentProvider { - - // static to rely on Class load - // SystemClock.uptimeMillis() isn't affected by phone provider or clock changes. - private static final long sdkInitMillis = SystemClock.uptimeMillis(); - - private @Nullable Application app; - - private final @NotNull ILogger logger; - private final @NotNull BuildInfoProvider buildInfoProvider; - private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - - @TestOnly - SentryPerformanceProvider( - final @NotNull ILogger logger, final @NotNull BuildInfoProvider buildInfoProvider) { - this.logger = logger; - this.buildInfoProvider = buildInfoProvider; - } - - public SentryPerformanceProvider() { - logger = new AndroidLogger(); - buildInfoProvider = new BuildInfoProvider(logger); - } - - @Override - public boolean onCreate() { - final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance(); - onAppLaunched(getContext(), appStartMetrics); - launchAppStartProfiler(appStartMetrics); - return true; - } - - @Override - public void attachInfo(Context context, ProviderInfo info) { - // applicationId is expected to be prepended. See AndroidManifest.xml - if (SentryPerformanceProvider.class.getName().equals(info.authority)) { - throw new IllegalStateException( - "An applicationId is required to fulfill the manifest placeholder."); - } - super.attachInfo(context, info); - } - - @Nullable - @Override - public String getType(@NotNull Uri uri) { - return null; - } - - @Override - public void shutdown() { - try (final @NotNull ISentryLifecycleToken ignored = AppStartMetrics.staticLock.acquire()) { - final @Nullable ITransactionProfiler appStartProfiler = - AppStartMetrics.getInstance().getAppStartProfiler(); - if (appStartProfiler != null) { - appStartProfiler.close(); - } - final @Nullable IContinuousProfiler appStartContinuousProfiler = - AppStartMetrics.getInstance().getAppStartContinuousProfiler(); - if (appStartContinuousProfiler != null) { - appStartContinuousProfiler.close(true); - } - } - } - - private void launchAppStartProfiler(final @NotNull AppStartMetrics appStartMetrics) { - final @Nullable Context context = getContext(); - - if (context == null) { - logger.log(SentryLevel.FATAL, "App. Context from ContentProvider is null"); - return; - } - - final @NotNull IRuntimeManager runtimeManager = new AndroidRuntimeManager(); - final @NotNull File cacheDir = - runtimeManager.runWithRelaxedPolicy(() -> AndroidOptionsInitializer.getCacheDir(context)); - final @NotNull File configFile = new File(cacheDir, APP_START_PROFILING_CONFIG_FILE_NAME); - - // No config exists: app start profiling is not enabled - if (!configFile.exists() || !configFile.canRead()) { - return; - } - - try (final @NotNull Reader reader = - new BufferedReader(new InputStreamReader(new FileInputStream(configFile)))) { - final @Nullable SentryAppStartProfilingOptions profilingOptions = - new JsonSerializer(SentryOptions.empty()) - .deserialize(reader, SentryAppStartProfilingOptions.class); - - if (profilingOptions == null) { - logger.log( - SentryLevel.WARNING, - "Unable to deserialize the SentryAppStartProfilingOptions. App start profiling will not start."); - return; - } - - if (profilingOptions.isContinuousProfilingEnabled() - && profilingOptions.isStartProfilerOnAppStart()) { - createAndStartContinuousProfiler(context, profilingOptions, appStartMetrics); - return; - } - - if (!profilingOptions.isProfilingEnabled()) { - logger.log( - SentryLevel.INFO, "Profiling is not enabled. App start profiling will not start."); - return; - } - - if (profilingOptions.isEnableAppStartProfiling()) { - createAndStartTransactionProfiler(context, profilingOptions, appStartMetrics); - } - } catch (FileNotFoundException e) { - logger.log(SentryLevel.ERROR, "App start profiling config file not found. ", e); - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Error reading app start profiling config file. ", e); - } - } - - private void createAndStartContinuousProfiler( - final @NotNull Context context, - final @NotNull SentryAppStartProfilingOptions profilingOptions, - final @NotNull AppStartMetrics appStartMetrics) { - - if (!profilingOptions.isContinuousProfileSampled()) { - logger.log(SentryLevel.DEBUG, "App start profiling was not sampled. It will not start."); - return; - } - - final @NotNull SentryExecutorService startupExecutorService = new SentryExecutorService(); - final @NotNull IContinuousProfiler appStartContinuousProfiler = - new AndroidContinuousProfiler( - buildInfoProvider, - new SentryFrameMetricsCollector( - context.getApplicationContext(), logger, buildInfoProvider), - logger, - profilingOptions.getProfilingTracesDirPath(), - profilingOptions.getProfilingTracesHz(), - () -> startupExecutorService); - appStartMetrics.setAppStartProfiler(null); - appStartMetrics.setAppStartContinuousProfiler(appStartContinuousProfiler); - logger.log(SentryLevel.DEBUG, "App start continuous profiling started."); - SentryOptions sentryOptions = SentryOptions.empty(); - // Let's fake a sampler to accept the sampling decision that was calculated on last run - sentryOptions.setProfileSessionSampleRate( - profilingOptions.isContinuousProfileSampled() ? 1.0 : 0.0); - appStartContinuousProfiler.startProfiler( - profilingOptions.getProfileLifecycle(), new TracesSampler(sentryOptions)); - } - - private void createAndStartTransactionProfiler( - final @NotNull Context context, - final @NotNull SentryAppStartProfilingOptions profilingOptions, - final @NotNull AppStartMetrics appStartMetrics) { - final @NotNull TracesSamplingDecision appStartSamplingDecision = - new TracesSamplingDecision( - profilingOptions.isTraceSampled(), - profilingOptions.getTraceSampleRate(), - profilingOptions.isProfileSampled(), - profilingOptions.getProfileSampleRate()); - // We store any sampling decision, so we can respect it when the first transaction starts - appStartMetrics.setAppStartSamplingDecision(appStartSamplingDecision); - - if (!(appStartSamplingDecision.getProfileSampled() && appStartSamplingDecision.getSampled())) { - logger.log(SentryLevel.DEBUG, "App start profiling was not sampled. It will not start."); - return; - } - - final @NotNull SentryExecutorService executorService = new SentryExecutorService(); - final @NotNull ITransactionProfiler appStartProfiler = - new AndroidTransactionProfiler( - context, - buildInfoProvider, - new SentryFrameMetricsCollector(context, logger, buildInfoProvider), - logger, - profilingOptions.getProfilingTracesDirPath(), - profilingOptions.isProfilingEnabled(), - profilingOptions.getProfilingTracesHz(), - () -> executorService); - appStartMetrics.setAppStartContinuousProfiler(null); - appStartMetrics.setAppStartProfiler(appStartProfiler); - logger.log(SentryLevel.DEBUG, "App start profiling started."); - appStartProfiler.start(); - } - - @SuppressLint("NewApi") - private void onAppLaunched( - final @Nullable Context context, final @NotNull AppStartMetrics appStartMetrics) { - - // sdk-init uses static field init as start time - final @NotNull TimeSpan sdkInitTimeSpan = appStartMetrics.getSdkInitTimeSpan(); - sdkInitTimeSpan.setStartedAt(sdkInitMillis); - - // performance v2: Uses Process.getStartUptimeMillis() - // requires API level 24+ - if (buildInfoProvider.getSdkInfoVersion() >= android.os.Build.VERSION_CODES.N) { - final @NotNull TimeSpan appStartTimespan = appStartMetrics.getAppStartTimeSpan(); - appStartTimespan.setStartedAt(Process.getStartUptimeMillis()); - } - - if (context instanceof Application) { - app = (Application) context; - } - if (app == null) { - return; - } - - appStartMetrics.registerLifecycleCallbacks(app); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryUserFeedbackButton.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryUserFeedbackButton.java deleted file mode 100644 index eedafd8f001..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryUserFeedbackButton.java +++ /dev/null @@ -1,122 +0,0 @@ -package io.sentry.android.core; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.TypedArray; -import android.os.Build; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.widget.Button; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class SentryUserFeedbackButton extends Button { - - private @Nullable OnClickListener delegate; - - public SentryUserFeedbackButton(Context context) { - super(context); - init(context, null, 0, 0); - } - - public SentryUserFeedbackButton(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 0, 0); - } - - public SentryUserFeedbackButton(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr, 0); - } - - public SentryUserFeedbackButton( - Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - init(context, attrs, defStyleAttr, defStyleRes); - } - - @SuppressLint("SetTextI18n") - @SuppressWarnings("deprecation") - private void init( - final @NotNull Context context, - final @Nullable AttributeSet attrs, - final int defStyleAttr, - final int defStyleRes) { - try (final @NotNull TypedArray typedArray = - context.obtainStyledAttributes( - attrs, R.styleable.SentryUserFeedbackButton, defStyleAttr, defStyleRes)) { - final float dimensionScale = context.getResources().getDisplayMetrics().density; - final float drawablePadding = - typedArray.getDimension(R.styleable.SentryUserFeedbackButton_android_drawablePadding, -1); - final int drawableStart = - typedArray.getResourceId(R.styleable.SentryUserFeedbackButton_android_drawableStart, -1); - final boolean textAllCaps = - typedArray.getBoolean(R.styleable.SentryUserFeedbackButton_android_textAllCaps, false); - final int background = - typedArray.getResourceId(R.styleable.SentryUserFeedbackButton_android_background, -1); - final float padding = - typedArray.getDimension(R.styleable.SentryUserFeedbackButton_android_padding, -1); - final int textColor = - typedArray.getColor(R.styleable.SentryUserFeedbackButton_android_textColor, -1); - final @Nullable String text = - typedArray.getString(R.styleable.SentryUserFeedbackButton_android_text); - - // If the drawable padding is not set, set it to 4dp - if (drawablePadding == -1) { - setCompoundDrawablePadding((int) (4 * dimensionScale)); - } - - // If the drawable start is not set, set it to the default drawable - if (drawableStart == -1) { - setCompoundDrawablesRelativeWithIntrinsicBounds( - R.drawable.sentry_user_feedback_button_logo_24, 0, 0, 0); - } - - // Set the text all caps - setAllCaps(textAllCaps); - - // If the background is not set, set it to the default background - if (background == -1) { - setBackgroundResource(R.drawable.sentry_oval_button_ripple_background); - } - - // If the padding is not set, set it to 12dp - if (padding == -1) { - int defaultPadding = (int) (12 * dimensionScale); - setPadding(defaultPadding, defaultPadding, defaultPadding, defaultPadding); - } - - // If the text color is not set, set it to the default text color - if (textColor == -1) { - // We need the TypedValue to resolve the color from the theme - final @NotNull TypedValue typedValue = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.colorForeground, typedValue, true); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - setTextColor(context.getResources().getColor(typedValue.resourceId, context.getTheme())); - } else { - setTextColor(context.getResources().getColor(typedValue.resourceId)); - } - } - - // If the text is not set, set it to "Report a Bug" - if (text == null) { - setText("Report a Bug"); - } - } - - // Set the default ClickListener to open the SentryUserFeedbackDialog - setOnClickListener(delegate); - } - - @Override - public void setOnClickListener(final @Nullable OnClickListener listener) { - delegate = listener; - super.setOnClickListener( - v -> { - new SentryUserFeedbackDialog.Builder(getContext()).create().show(); - if (delegate != null) { - delegate.onClick(v); - } - }); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryUserFeedbackDialog.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryUserFeedbackDialog.java deleted file mode 100644 index 542a7027a4f..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryUserFeedbackDialog.java +++ /dev/null @@ -1,380 +0,0 @@ -package io.sentry.android.core; - -import android.app.AlertDialog; -import android.content.Context; -import android.os.Bundle; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; -import io.sentry.IScopes; -import io.sentry.Sentry; -import io.sentry.SentryFeedbackOptions; -import io.sentry.SentryIntegrationPackageStorage; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.protocol.Feedback; -import io.sentry.protocol.SentryId; -import io.sentry.protocol.User; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public final class SentryUserFeedbackDialog extends AlertDialog { - - private boolean isCancelable = false; - private @Nullable SentryId currentReplayId; - private final @Nullable SentryId associatedEventId; - private @Nullable OnDismissListener delegate; - - private final @Nullable OptionsConfiguration configuration; - private final @Nullable SentryFeedbackOptions.OptionsConfigurator configurator; - - SentryUserFeedbackDialog( - final @NotNull Context context, - final int themeResId, - final @Nullable SentryId associatedEventId, - final @Nullable OptionsConfiguration configuration, - final @Nullable SentryFeedbackOptions.OptionsConfigurator configurator) { - super(context, themeResId); - this.associatedEventId = associatedEventId; - this.configuration = configuration; - this.configurator = configurator; - SentryIntegrationPackageStorage.getInstance().addIntegration("UserFeedbackWidget"); - } - - @Override - public void setCancelable(boolean cancelable) { - super.setCancelable(cancelable); - isCancelable = cancelable; - } - - @Override - @SuppressWarnings("deprecation") - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.sentry_dialog_user_feedback); - setCancelable(isCancelable); - - final @NotNull SentryFeedbackOptions feedbackOptions = - new SentryFeedbackOptions(Sentry.getCurrentScopes().getOptions().getFeedbackOptions()); - if (configuration != null) { - configuration.configure(getContext(), feedbackOptions); - } - if (configurator != null) { - configurator.configure(feedbackOptions); - } - final @NotNull TextView lblTitle = findViewById(R.id.sentry_dialog_user_feedback_title); - final @NotNull ImageView imgLogo = findViewById(R.id.sentry_dialog_user_feedback_logo); - final @NotNull TextView lblName = findViewById(R.id.sentry_dialog_user_feedback_txt_name); - final @NotNull EditText edtName = findViewById(R.id.sentry_dialog_user_feedback_edt_name); - final @NotNull TextView lblEmail = findViewById(R.id.sentry_dialog_user_feedback_txt_email); - final @NotNull EditText edtEmail = findViewById(R.id.sentry_dialog_user_feedback_edt_email); - final @NotNull TextView lblMessage = - findViewById(R.id.sentry_dialog_user_feedback_txt_description); - final @NotNull EditText edtMessage = - findViewById(R.id.sentry_dialog_user_feedback_edt_description); - final @NotNull Button btnSend = findViewById(R.id.sentry_dialog_user_feedback_btn_send); - final @NotNull Button btnCancel = findViewById(R.id.sentry_dialog_user_feedback_btn_cancel); - - if (feedbackOptions.isShowBranding()) { - imgLogo.setVisibility(View.VISIBLE); - } else { - imgLogo.setVisibility(View.GONE); - } - - // If name is required, ignore showName flag - if (!feedbackOptions.isShowName() && !feedbackOptions.isNameRequired()) { - lblName.setVisibility(View.GONE); - edtName.setVisibility(View.GONE); - } else { - lblName.setVisibility(View.VISIBLE); - edtName.setVisibility(View.VISIBLE); - lblName.setText(feedbackOptions.getNameLabel()); - edtName.setHint(feedbackOptions.getNamePlaceholder()); - if (feedbackOptions.isNameRequired()) { - lblName.append(feedbackOptions.getIsRequiredLabel()); - } - } - - // If email is required, ignore showEmail flag - if (!feedbackOptions.isShowEmail() && !feedbackOptions.isEmailRequired()) { - lblEmail.setVisibility(View.GONE); - edtEmail.setVisibility(View.GONE); - } else { - lblEmail.setVisibility(View.VISIBLE); - edtEmail.setVisibility(View.VISIBLE); - lblEmail.setText(feedbackOptions.getEmailLabel()); - edtEmail.setHint(feedbackOptions.getEmailPlaceholder()); - if (feedbackOptions.isEmailRequired()) { - lblEmail.append(feedbackOptions.getIsRequiredLabel()); - } - } - - // If Sentry user is set, and useSentryUser is true, populate the name and email - if (feedbackOptions.isUseSentryUser()) { - final @Nullable User user = Sentry.getCurrentScopes().getScope().getUser(); - if (user != null) { - edtName.setText(user.getUsername()); - edtEmail.setText(user.getEmail()); - } - } - - lblMessage.setText(feedbackOptions.getMessageLabel()); - lblMessage.append(feedbackOptions.getIsRequiredLabel()); - edtMessage.setHint(feedbackOptions.getMessagePlaceholder()); - lblTitle.setText(feedbackOptions.getFormTitle()); - - btnSend.setText(feedbackOptions.getSubmitButtonLabel()); - btnSend.setOnClickListener( - v -> { - // Gather fields and trim them - final @NotNull String name = edtName.getText().toString().trim(); - final @NotNull String email = edtEmail.getText().toString().trim(); - final @NotNull String message = edtMessage.getText().toString().trim(); - - // If a required field is missing, shows the error label - if (name.isEmpty() && feedbackOptions.isNameRequired()) { - edtName.setError(lblName.getText()); - return; - } - - if (email.isEmpty() && feedbackOptions.isEmailRequired()) { - edtEmail.setError(lblEmail.getText()); - return; - } - - if (message.isEmpty()) { - edtMessage.setError(lblMessage.getText()); - return; - } - - // Create the feedback object - final @NotNull Feedback feedback = new Feedback(message); - feedback.setName(name); - feedback.setContactEmail(email); - if (associatedEventId != null) { - feedback.setAssociatedEventId(associatedEventId); - } - if (currentReplayId != null) { - feedback.setReplayId(currentReplayId); - } - - // Capture the feedback. If the ID is empty, it means that the feedback was not sent - final @NotNull SentryId id = Sentry.captureFeedback(feedback); - if (!id.equals(SentryId.EMPTY_ID)) { - Toast.makeText( - getContext(), feedbackOptions.getSuccessMessageText(), Toast.LENGTH_SHORT) - .show(); - final @Nullable SentryFeedbackOptions.SentryFeedbackCallback onSubmitSuccess = - feedbackOptions.getOnSubmitSuccess(); - if (onSubmitSuccess != null) { - onSubmitSuccess.call(feedback); - } - } else { - final @Nullable SentryFeedbackOptions.SentryFeedbackCallback onSubmitError = - feedbackOptions.getOnSubmitError(); - if (onSubmitError != null) { - onSubmitError.call(feedback); - } - } - cancel(); - }); - - btnCancel.setText(feedbackOptions.getCancelButtonLabel()); - btnCancel.setOnClickListener(v -> cancel()); - setOnDismissListener(delegate); - } - - @Override - public void setOnDismissListener(final @Nullable OnDismissListener listener) { - delegate = listener; - // If the user set a custom onDismissListener, we ensure it doesn't override the onFormClose - final @NotNull SentryOptions options = Sentry.getCurrentScopes().getOptions(); - final @Nullable Runnable onFormClose = options.getFeedbackOptions().getOnFormClose(); - if (onFormClose != null) { - super.setOnDismissListener( - dialog -> { - onFormClose.run(); - currentReplayId = null; - if (delegate != null) { - delegate.onDismiss(dialog); - } - }); - } else { - super.setOnDismissListener(delegate); - } - } - - @Override - protected void onStart() { - super.onStart(); - final @NotNull SentryOptions options = Sentry.getCurrentScopes().getOptions(); - final @NotNull SentryFeedbackOptions feedbackOptions = options.getFeedbackOptions(); - final @Nullable Runnable onFormOpen = feedbackOptions.getOnFormOpen(); - if (onFormOpen != null) { - onFormOpen.run(); - } - options.getReplayController().captureReplay(false); - currentReplayId = options.getReplayController().getReplayId(); - } - - @Override - public void show() { - // If Sentry is disabled, don't show the dialog, but log a warning - final @NotNull IScopes scopes = Sentry.getCurrentScopes(); - final @NotNull SentryOptions options = scopes.getOptions(); - if (!scopes.isEnabled() || !options.isEnabled()) { - options - .getLogger() - .log(SentryLevel.WARNING, "Sentry is disabled. Feedback dialog won't be shown."); - return; - } - // Otherwise, show the dialog - super.show(); - } - - public static class Builder { - - @Nullable OptionsConfiguration configuration; - @Nullable SentryFeedbackOptions.OptionsConfigurator configurator; - @Nullable SentryId associatedEventId; - final @NotNull Context context; - final int themeResId; - - /** - * Creates a builder for a {@link SentryUserFeedbackDialog} that uses the default alert dialog - * theme. - * - *

The default alert dialog theme is defined by {@link android.R.attr#alertDialogTheme} - * within the parent {@code context}'s theme. - * - * @param context the parent context - */ - public Builder(final @NotNull Context context) { - this(context, 0); - } - - /** - * Creates a builder for a {@link SentryUserFeedbackDialog} that uses an explicit theme - * resource. - * - *

The specified theme resource ({@code themeResId}) is applied on top of the parent {@code - * context}'s theme. It may be specified as a style resource containing a fully-populated theme, - * such as {@link android.R.style#Theme_Material_Dialog}, to replace all attributes in the - * parent {@code context}'s theme including primary and accent colors. - * - *

To preserve attributes such as primary and accent colors, the {@code themeResId} may - * instead be specified as an overlay theme such as {@link - * android.R.style#ThemeOverlay_Material_Dialog}. This will override only the window attributes - * necessary to style the alert window as a dialog. - * - *

Alternatively, the {@code themeResId} may be specified as {@code 0} to use the parent - * {@code context}'s resolved value for {@link android.R.attr#alertDialogTheme}. - * - * @param context the parent context - * @param themeResId the resource ID of the theme against which to inflate this dialog, or - * {@code 0} to use the parent {@code context}'s default alert dialog theme - */ - public Builder(Context context, int themeResId) { - this(context, themeResId, null); - } - - /** - * Creates a builder for a {@link SentryUserFeedbackDialog} that uses the default alert dialog - * theme. The {@code configuration} can be used to configure the feedback options for this - * specific dialog. - * - *

The default alert dialog theme is defined by {@link android.R.attr#alertDialogTheme} - * within the parent {@code context}'s theme. - * - * @param context the parent context - * @param configuration the configuration for the feedback options, can be {@code null} to use - * the global feedback options. - */ - public Builder( - final @NotNull Context context, final @Nullable OptionsConfiguration configuration) { - this(context, 0, configuration); - } - - /** - * Creates a builder for a {@link SentryUserFeedbackDialog} that uses an explicit theme - * resource. The {@code configuration} can be used to configure the feedback options for this - * specific dialog. - * - *

The specified theme resource ({@code themeResId}) is applied on top of the parent {@code - * context}'s theme. It may be specified as a style resource containing a fully-populated theme, - * such as {@link android.R.style#Theme_Material_Dialog}, to replace all attributes in the - * parent {@code context}'s theme including primary and accent colors. - * - *

To preserve attributes such as primary and accent colors, the {@code themeResId} may - * instead be specified as an overlay theme such as {@link - * android.R.style#ThemeOverlay_Material_Dialog}. This will override only the window attributes - * necessary to style the alert window as a dialog. - * - *

Alternatively, the {@code themeResId} may be specified as {@code 0} to use the parent - * {@code context}'s resolved value for {@link android.R.attr#alertDialogTheme}. - * - * @param context the parent context - * @param themeResId the resource ID of the theme against which to inflate this dialog, or - * {@code 0} to use the parent {@code context}'s default alert dialog theme - * @param configuration the configuration for the feedback options, can be {@code null} to use - * the global feedback options. - */ - public Builder( - final @NotNull Context context, - final int themeResId, - final @Nullable OptionsConfiguration configuration) { - this.context = context; - this.themeResId = themeResId; - this.configuration = configuration; - } - - /** - * Sets the configuration for the feedback options. - * - * @param configurator the configuration for the feedback options, can be {@code null} to use - * the global feedback options. - */ - public Builder configurator( - final @Nullable SentryFeedbackOptions.OptionsConfigurator configurator) { - this.configurator = configurator; - return this; - } - - /** - * Sets the associated event ID for the feedback. - * - * @param associatedEventId the associated event ID for the feedback, can be {@code null} to - * avoid associating the feedback to an event. - */ - public Builder associatedEventId(final @Nullable SentryId associatedEventId) { - this.associatedEventId = associatedEventId; - return this; - } - - /** - * Builds a new {@link SentryUserFeedbackDialog} with the specified context, theme, and - * configuration. - * - * @return a new instance of {@link SentryUserFeedbackDialog} - */ - public SentryUserFeedbackDialog create() { - return new SentryUserFeedbackDialog( - context, themeResId, associatedEventId, configuration, configurator); - } - } - - /** Configuration callback for feedback options. */ - public interface OptionsConfiguration { - - /** - * configure the feedback options - * - * @param context the context of the feedback dialog - * @param options the feedback options - */ - void configure(final @NotNull Context context, final @NotNull SentryFeedbackOptions options); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java b/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java deleted file mode 100644 index a83454d29b7..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java +++ /dev/null @@ -1,365 +0,0 @@ -package io.sentry.android.core; - -import io.sentry.DateUtils; -import io.sentry.IPerformanceContinuousCollector; -import io.sentry.ISentryLifecycleToken; -import io.sentry.ISpan; -import io.sentry.ITransaction; -import io.sentry.NoOpSpan; -import io.sentry.NoOpTransaction; -import io.sentry.SentryDate; -import io.sentry.SentryNanotimeDate; -import io.sentry.SpanDataConvention; -import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; -import io.sentry.protocol.MeasurementValue; -import io.sentry.util.AutoClosableReentrantLock; -import java.util.Date; -import java.util.Iterator; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.concurrent.ConcurrentSkipListSet; -import java.util.concurrent.TimeUnit; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public class SpanFrameMetricsCollector - implements IPerformanceContinuousCollector, - SentryFrameMetricsCollector.FrameMetricsCollectorListener { - - // 30s span duration at 120fps = 3600 frames - // this is just an upper limit for frames.size, ensuring that the buffer does not - // grow indefinitely in case of a long running span - private static final int MAX_FRAMES_COUNT = 3600; - private static final long ONE_SECOND_NANOS = TimeUnit.SECONDS.toNanos(1); - private static final SentryNanotimeDate EMPTY_NANO_TIME = new SentryNanotimeDate(new Date(0), 0); - - private final boolean enabled; - protected final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - private final @NotNull SentryFrameMetricsCollector frameMetricsCollector; - - private volatile @Nullable String listenerId; - - // all running spans, sorted by span start nano time - private final @NotNull SortedSet runningSpans = - new TreeSet<>( - (o1, o2) -> { - if (o1 == o2) { - return 0; - } - int timeDiff = o1.getStartDate().compareTo(o2.getStartDate()); - if (timeDiff != 0) { - return timeDiff; - } - // TreeSet uses compareTo to check for duplicates, so ensure that - // two non-equal spans with the same start date are not considered equal - return o1.getSpanContext() - .getSpanId() - .toString() - .compareTo(o2.getSpanContext().getSpanId().toString()); - }); - - // all collected frames, sorted by frame end time - // this is a concurrent set, as the frames are added on the main thread, - // but span starts/finish may happen on any thread - // the list only holds Frames, but in order to query for a specific span NanoTimeStamp is used - private final @NotNull ConcurrentSkipListSet frames = new ConcurrentSkipListSet<>(); - - // assume 60fps until we get a value reported by the system - private long lastKnownFrameDurationNanos = 16_666_666L; - - public SpanFrameMetricsCollector( - final @NotNull SentryAndroidOptions options, - final @NotNull SentryFrameMetricsCollector frameMetricsCollector) { - this.frameMetricsCollector = frameMetricsCollector; - - enabled = options.isEnablePerformanceV2() && options.isEnableFramesTracking(); - } - - @Override - public void onSpanStarted(final @NotNull ISpan span) { - if (!enabled) { - return; - } - if (span instanceof NoOpSpan) { - return; - } - if (span instanceof NoOpTransaction) { - return; - } - - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - runningSpans.add(span); - - if (listenerId == null) { - listenerId = frameMetricsCollector.startCollection(this); - } - } - } - - @Override - public void onSpanFinished(final @NotNull ISpan span) { - if (!enabled) { - return; - } - - if (span instanceof NoOpSpan) { - return; - } - - if (span instanceof NoOpTransaction) { - return; - } - - // ignore span if onSpanStarted was never called for it - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (!runningSpans.contains(span)) { - return; - } - } - - captureFrameMetrics(span); - - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (runningSpans.isEmpty()) { - clear(); - } else { - // otherwise only remove old/irrelevant frames - final @NotNull ISpan oldestSpan = runningSpans.first(); - frames.headSet(new Frame(toNanoTime(oldestSpan.getStartDate()))).clear(); - } - } - } - - private void captureFrameMetrics(@NotNull final ISpan span) { - // TODO lock still required? - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - boolean removed = runningSpans.remove(span); - if (!removed) { - return; - } - - final @Nullable SentryDate spanFinishDate = span.getFinishDate(); - if (spanFinishDate == null) { - return; - } - - final long spanStartNanos = toNanoTime(span.getStartDate()); - final long spanEndNanos = toNanoTime(spanFinishDate); - final long spanDurationNanos = spanEndNanos - spanStartNanos; - if (spanDurationNanos <= 0) { - return; - } - - final @NotNull SentryFrameMetrics frameMetrics = new SentryFrameMetrics(); - - long frameDurationNanos = lastKnownFrameDurationNanos; - - if (!frames.isEmpty()) { - // determine relevant start in frames list - final Iterator iterator = frames.tailSet(new Frame(spanStartNanos)).iterator(); - - //noinspection WhileLoopReplaceableByForEach - while (iterator.hasNext()) { - final @NotNull Frame frame = iterator.next(); - - if (frame.startNanos > spanEndNanos) { - break; - } - - if (frame.startNanos >= spanStartNanos && frame.endNanos <= spanEndNanos) { - // if the frame is contained within the span, add it 1:1 to the span metrics - frameMetrics.addFrame( - frame.durationNanos, frame.delayNanos, frame.isSlow, frame.isFrozen); - } else if ((spanStartNanos > frame.startNanos && spanStartNanos < frame.endNanos) - || (spanEndNanos > frame.startNanos && spanEndNanos < frame.endNanos)) { - // span start or end are within frame - // calculate the intersection - final long durationBeforeSpan = Math.max(0, spanStartNanos - frame.startNanos); - final long delayBeforeSpan = - Math.max(0, durationBeforeSpan - frame.expectedDurationNanos); - final long delayWithinSpan = - Math.min(frame.delayNanos - delayBeforeSpan, spanDurationNanos); - - final long frameStart = Math.max(spanStartNanos, frame.startNanos); - final long frameEnd = Math.min(spanEndNanos, frame.endNanos); - final long frameDuration = frameEnd - frameStart; - frameMetrics.addFrame( - frameDuration, - delayWithinSpan, - SentryFrameMetricsCollector.isSlow(frameDuration, frame.expectedDurationNanos), - SentryFrameMetricsCollector.isFrozen(frameDuration)); - } - - frameDurationNanos = frame.expectedDurationNanos; - } - } - - int totalFrameCount = frameMetrics.getSlowFrozenFrameCount(); - - final long nextScheduledFrameNanos = frameMetricsCollector.getLastKnownFrameStartTimeNanos(); - // nextScheduledFrameNanos might be -1 if no frames have been scheduled for drawing yet - // e.g. can happen during early app start - if (nextScheduledFrameNanos != -1) { - totalFrameCount += - addPendingFrameDelay( - frameMetrics, frameDurationNanos, spanEndNanos, nextScheduledFrameNanos); - totalFrameCount += - interpolateFrameCount(frameMetrics, frameDurationNanos, spanDurationNanos); - } - final long frameDelayNanos = - frameMetrics.getSlowFrameDelayNanos() + frameMetrics.getFrozenFrameDelayNanos(); - final double frameDelayInSeconds = frameDelayNanos / 1e9d; - - span.setData(SpanDataConvention.FRAMES_TOTAL, totalFrameCount); - span.setData(SpanDataConvention.FRAMES_SLOW, frameMetrics.getSlowFrameCount()); - span.setData(SpanDataConvention.FRAMES_FROZEN, frameMetrics.getFrozenFrameCount()); - span.setData(SpanDataConvention.FRAMES_DELAY, frameDelayInSeconds); - - if (span instanceof ITransaction) { - span.setMeasurement(MeasurementValue.KEY_FRAMES_TOTAL, totalFrameCount); - span.setMeasurement(MeasurementValue.KEY_FRAMES_SLOW, frameMetrics.getSlowFrameCount()); - span.setMeasurement(MeasurementValue.KEY_FRAMES_FROZEN, frameMetrics.getFrozenFrameCount()); - span.setMeasurement(MeasurementValue.KEY_FRAMES_DELAY, frameDelayInSeconds); - } - } - } - - @Override - public void clear() { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (listenerId != null) { - frameMetricsCollector.stopCollection(listenerId); - listenerId = null; - } - frames.clear(); - runningSpans.clear(); - } - } - - @Override - public void onFrameMetricCollected( - long frameStartNanos, - long frameEndNanos, - long durationNanos, - long delayNanos, - boolean isSlow, - boolean isFrozen, - float refreshRate) { - - // buffer is full, skip adding new frames for now - // once a span finishes, the buffer will trimmed - if (frames.size() > MAX_FRAMES_COUNT) { - return; - } - - final long expectedFrameDurationNanos = - (long) ((double) ONE_SECOND_NANOS / (double) refreshRate); - lastKnownFrameDurationNanos = expectedFrameDurationNanos; - - if (isSlow || isFrozen) { - frames.add( - new Frame( - frameStartNanos, - frameEndNanos, - durationNanos, - delayNanos, - isSlow, - isFrozen, - expectedFrameDurationNanos)); - } - } - - private static int interpolateFrameCount( - final @NotNull SentryFrameMetrics frameMetrics, - final long frameDurationNanos, - final long spanDurationNanos) { - // if there are no content changes on Android, also no new frame metrics are provided by the - // system - // in order to match the span duration with the total frame count, - // we simply interpolate the total number of frames based on the span duration - // this way the data is more sound and we also match the output of the cocoa SDK - final long frameMetricsDurationNanos = frameMetrics.getTotalDurationNanos(); - final long nonRenderedDuration = spanDurationNanos - frameMetricsDurationNanos; - if (nonRenderedDuration > 0) { - return (int) Math.ceil((double) nonRenderedDuration / frameDurationNanos); - } - return 0; - } - - private static int addPendingFrameDelay( - @NotNull final SentryFrameMetrics frameMetrics, - final long frameDurationNanos, - final long spanEndNanos, - final long nextScheduledFrameNanos) { - final long pendingDurationNanos = Math.max(0, spanEndNanos - nextScheduledFrameNanos); - final boolean isSlow = - SentryFrameMetricsCollector.isSlow(pendingDurationNanos, frameDurationNanos); - if (isSlow) { - // add a single slow/frozen frame - final boolean isFrozen = SentryFrameMetricsCollector.isFrozen(pendingDurationNanos); - final long pendingDelayNanos = Math.max(0, pendingDurationNanos - frameDurationNanos); - frameMetrics.addFrame(pendingDurationNanos, pendingDelayNanos, true, isFrozen); - return 1; - } - return 0; - } - - /** - * Because {@link SentryNanotimeDate#nanoTimestamp()} only gives you millisecond precision, but - * diff does ¯\_(ツ)_/¯ - * - * @param date the input date - * @return a non-unix timestamp in nano precision, similar to {@link System#nanoTime()}. - */ - private static long toNanoTime(final @NotNull SentryDate date) { - // SentryNanotimeDate nanotime is based on System.nanotime(), like EMPTY_NANO_TIME, - // thus diff will simply return the System.nanotime() value of date - if (date instanceof SentryNanotimeDate) { - return date.diff(EMPTY_NANO_TIME); - } - - // e.g. SentryLongDate is unix time based - upscaled to nanos, - // we need to project it back to System.nanotime() format - long nowUnixInNanos = DateUtils.millisToNanos(System.currentTimeMillis()); - long shiftInNanos = nowUnixInNanos - date.nanoTimestamp(); - return System.nanoTime() - shiftInNanos; - } - - private static class Frame implements Comparable { - private final long startNanos; - private final long endNanos; - private final long durationNanos; - private final long delayNanos; - private final boolean isSlow; - private final boolean isFrozen; - private final long expectedDurationNanos; - - Frame(final long timestampNanos) { - this(timestampNanos, timestampNanos, 0, 0, false, false, 0); - } - - Frame( - final long startNanos, - final long endNanos, - final long durationNanos, - final long delayNanos, - final boolean isSlow, - final boolean isFrozen, - final long expectedFrameDurationNanos) { - this.startNanos = startNanos; - this.endNanos = endNanos; - this.durationNanos = durationNanos; - this.delayNanos = delayNanos; - this.isSlow = isSlow; - this.isFrozen = isFrozen; - this.expectedDurationNanos = expectedFrameDurationNanos; - } - - @Override - public int compareTo(final @NotNull Frame o) { - return Long.compare(this.endNanos, o.endNanos); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java deleted file mode 100644 index a5e56fcc3f2..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java +++ /dev/null @@ -1,443 +0,0 @@ -package io.sentry.android.core; - -import static android.content.Intent.ACTION_AIRPLANE_MODE_CHANGED; -import static android.content.Intent.ACTION_BATTERY_CHANGED; -import static android.content.Intent.ACTION_CAMERA_BUTTON; -import static android.content.Intent.ACTION_CONFIGURATION_CHANGED; -import static android.content.Intent.ACTION_DATE_CHANGED; -import static android.content.Intent.ACTION_DEVICE_STORAGE_LOW; -import static android.content.Intent.ACTION_DEVICE_STORAGE_OK; -import static android.content.Intent.ACTION_DOCK_EVENT; -import static android.content.Intent.ACTION_DREAMING_STARTED; -import static android.content.Intent.ACTION_DREAMING_STOPPED; -import static android.content.Intent.ACTION_INPUT_METHOD_CHANGED; -import static android.content.Intent.ACTION_LOCALE_CHANGED; -import static android.content.Intent.ACTION_SCREEN_OFF; -import static android.content.Intent.ACTION_SCREEN_ON; -import static android.content.Intent.ACTION_SHUTDOWN; -import static android.content.Intent.ACTION_TIMEZONE_CHANGED; -import static android.content.Intent.ACTION_TIME_CHANGED; -import static io.sentry.TypeCheckHint.ANDROID_INTENT; -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Process; -import io.sentry.Breadcrumb; -import io.sentry.Hint; -import io.sentry.IScopes; -import io.sentry.ISentryLifecycleToken; -import io.sentry.Integration; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.android.core.internal.util.AndroidCurrentDateProvider; -import io.sentry.android.core.internal.util.Debouncer; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.Objects; -import io.sentry.util.StringUtils; -import java.io.Closeable; -import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -public final class SystemEventsBreadcrumbsIntegration - implements Integration, Closeable, AppState.AppStateListener { - - private final @NotNull Context context; - - @TestOnly @Nullable volatile SystemEventsBroadcastReceiver receiver; - - private @Nullable SentryAndroidOptions options; - - private @Nullable IScopes scopes; - - private final @NotNull String[] actions; - private volatile boolean isClosed = false; - private volatile boolean isStopped = false; - private volatile IntentFilter filter = null; - private volatile HandlerThread handlerThread = null; - private final @NotNull AtomicBoolean isReceiverRegistered = new AtomicBoolean(false); - private final @NotNull AutoClosableReentrantLock receiverLock = new AutoClosableReentrantLock(); - // Track previous battery state to avoid duplicate breadcrumbs when values haven't changed - private @Nullable BatteryState previousBatteryState; - @TestOnly @Nullable Handler customHandler = null; - - public SystemEventsBreadcrumbsIntegration(final @NotNull Context context) { - this(context, getDefaultActionsInternal(), null); - } - - public SystemEventsBreadcrumbsIntegration( - final @NotNull Context context, final @NotNull Handler handler) { - this(context, getDefaultActionsInternal(), handler); - } - - SystemEventsBreadcrumbsIntegration( - final @NotNull Context context, - final @NotNull String[] actions, - final @Nullable Handler handler) { - this.context = ContextUtils.getApplicationContext(context); - this.actions = actions; - this.customHandler = handler; - } - - public SystemEventsBreadcrumbsIntegration( - final @NotNull Context context, final @NotNull List actions) { - this.context = ContextUtils.getApplicationContext(context); - this.actions = new String[actions.size()]; - actions.toArray(this.actions); - } - - @Override - public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - Objects.requireNonNull(scopes, "Scopes are required"); - this.options = - Objects.requireNonNull( - (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, - "SentryAndroidOptions is required"); - this.scopes = scopes; - - this.options - .getLogger() - .log( - SentryLevel.DEBUG, - "SystemEventsBreadcrumbsIntegration enabled: %s", - this.options.isEnableSystemEventBreadcrumbs()); - - if (this.options.isEnableSystemEventBreadcrumbs()) { - AppState.getInstance().addAppStateListener(this); - - if (ContextUtils.isForegroundImportance()) { - registerReceiver(this.scopes, this.options); - } - } - } - - private void registerReceiver( - final @NotNull IScopes scopes, final @NotNull SentryAndroidOptions options) { - - if (!options.isEnableSystemEventBreadcrumbs()) { - return; - } - - if (isClosed || isStopped || receiver != null) { - return; - } - - try { - options - .getExecutorService() - .submit( - () -> { - try (final @NotNull ISentryLifecycleToken ignored = receiverLock.acquire()) { - if (isClosed || isStopped || receiver != null) { - return; - } - - receiver = new SystemEventsBroadcastReceiver(scopes, options); - if (filter == null) { - filter = new IntentFilter(); - for (String item : actions) { - filter.addAction(item); - } - } - if (customHandler == null && handlerThread == null) { - handlerThread = - new HandlerThread( - "SystemEventsReceiver", Process.THREAD_PRIORITY_BACKGROUND); - handlerThread.start(); - } - try { - // registerReceiver can throw SecurityException but it's not documented in the - // official docs - - // onReceive will be called on this handler thread - @NotNull Handler handler; - if (customHandler != null) { - handler = customHandler; - } else { - handler = new Handler(handlerThread.getLooper()); - } - ContextUtils.registerReceiver(context, options, receiver, filter, handler); - if (!isReceiverRegistered.getAndSet(true)) { - options - .getLogger() - .log(SentryLevel.DEBUG, "SystemEventsBreadcrumbsIntegration installed."); - addIntegrationToSdkVersion("SystemEventsBreadcrumbs"); - } - } catch (Throwable e) { - options.setEnableSystemEventBreadcrumbs(false); - options - .getLogger() - .log( - SentryLevel.ERROR, - "Failed to initialize SystemEventsBreadcrumbsIntegration.", - e); - } - } - }); - } catch (Throwable e) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "Failed to start SystemEventsBreadcrumbsIntegration on executor thread."); - } - } - - @SuppressWarnings("Convert2MethodRef") // older AGP versions do not support method references - private void scheduleUnregisterReceiver() { - if (options == null) { - return; - } - - try { - options.getExecutorService().submit(() -> unregisterReceiver()); - } catch (RejectedExecutionException e) { - unregisterReceiver(); - } - } - - private void unregisterReceiver() { - final @Nullable SystemEventsBroadcastReceiver receiverRef; - try (final @NotNull ISentryLifecycleToken ignored = receiverLock.acquire()) { - isStopped = true; - receiverRef = receiver; - receiver = null; - } - - if (receiverRef != null) { - context.unregisterReceiver(receiverRef); - } - } - - @Override - public void close() throws IOException { - try (final @NotNull ISentryLifecycleToken ignored = receiverLock.acquire()) { - isClosed = true; - filter = null; - if (handlerThread != null) { - handlerThread.quit(); - } - handlerThread = null; - } - - AppState.getInstance().removeAppStateListener(this); - scheduleUnregisterReceiver(); - - if (options != null) { - options.getLogger().log(SentryLevel.DEBUG, "SystemEventsBreadcrumbsIntegration removed."); - } - } - - public static @NotNull List getDefaultActions() { - return Arrays.asList(getDefaultActionsInternal()); - } - - @SuppressWarnings("deprecation") - private static @NotNull String[] getDefaultActionsInternal() { - final String[] actions = new String[19]; - actions[0] = ACTION_SHUTDOWN; - actions[1] = ACTION_AIRPLANE_MODE_CHANGED; - actions[2] = ACTION_BATTERY_CHANGED; - actions[3] = ACTION_CAMERA_BUTTON; - actions[4] = ACTION_CONFIGURATION_CHANGED; - actions[5] = ACTION_DATE_CHANGED; - actions[6] = ACTION_DEVICE_STORAGE_LOW; - actions[7] = ACTION_DEVICE_STORAGE_OK; - actions[8] = ACTION_DOCK_EVENT; - actions[9] = ACTION_DREAMING_STARTED; - actions[10] = ACTION_DREAMING_STOPPED; - actions[11] = ACTION_INPUT_METHOD_CHANGED; - actions[12] = ACTION_LOCALE_CHANGED; - actions[13] = ACTION_SCREEN_OFF; - actions[14] = ACTION_SCREEN_ON; - actions[15] = ACTION_TIMEZONE_CHANGED; - actions[16] = ACTION_TIME_CHANGED; - actions[17] = "android.os.action.DEVICE_IDLE_MODE_CHANGED"; - actions[18] = "android.os.action.POWER_SAVE_MODE_CHANGED"; - return actions; - } - - @Override - public void onForeground() { - if (scopes == null || options == null) { - return; - } - - isStopped = false; - - registerReceiver(scopes, options); - } - - @Override - public void onBackground() { - scheduleUnregisterReceiver(); - } - - final class SystemEventsBroadcastReceiver extends BroadcastReceiver { - - private static final long DEBOUNCE_WAIT_TIME_MS = 60 * 1000; - private final @NotNull IScopes scopes; - private final @NotNull SentryAndroidOptions options; - private final @NotNull Debouncer batteryChangedDebouncer = - new Debouncer(AndroidCurrentDateProvider.getInstance(), DEBOUNCE_WAIT_TIME_MS, 0); - - SystemEventsBroadcastReceiver( - final @NotNull IScopes scopes, final @NotNull SentryAndroidOptions options) { - this.scopes = scopes; - this.options = options; - } - - @Override - public void onReceive(final Context context, final @NotNull Intent intent) { - final @Nullable String action = intent.getAction(); - final boolean isBatteryChanged = ACTION_BATTERY_CHANGED.equals(action); - - @Nullable BatteryState batteryState = null; - if (isBatteryChanged) { - if (batteryChangedDebouncer.checkForDebounce()) { - // aligning with iOS which only captures battery status changes every minute at maximum - return; - } - - // For battery changes, check if the actual values have changed - final @Nullable Float batteryLevel = DeviceInfoUtil.getBatteryLevel(intent, options); - final @Nullable Integer currentBatteryLevel = - batteryLevel != null ? batteryLevel.intValue() : null; - final @Nullable Boolean currentChargingState = DeviceInfoUtil.isCharging(intent, options); - batteryState = new BatteryState(currentBatteryLevel, currentChargingState); - - // Only create breadcrumb if battery state has actually changed - if (batteryState.equals(previousBatteryState)) { - return; - } - - previousBatteryState = batteryState; - } - - final BatteryState state = batteryState; - final long now = System.currentTimeMillis(); - final Breadcrumb breadcrumb = createBreadcrumb(now, intent, action, state); - final Hint hint = new Hint(); - hint.set(ANDROID_INTENT, intent); - scopes.addBreadcrumb(breadcrumb, hint); - } - - // in theory this should be ThreadLocal, but we won't have more than 1 thread accessing it, - // so we save some memory here and CPU cycles. 64 is because all intent actions we subscribe for - // are less than 64 chars. We also don't care about encoding as those are always UTF. - private final char[] buf = new char[64]; - - @TestOnly - @Nullable - String getStringAfterDotFast(final @Nullable String str) { - if (str == null) { - return null; - } - - final int len = str.length(); - int bufIndex = buf.length; - - // the idea here is to iterate from the end of the string and copy the characters to a - // pre-allocated buffer in reverse order. When we find a dot, we create a new string - // from the buffer. This way we use a fixed size buffer and do a bare minimum of iterations. - for (int i = len - 1; i >= 0; i--) { - final char c = str.charAt(i); - if (c == '.') { - return new String(buf, bufIndex, buf.length - bufIndex); - } - if (bufIndex == 0) { - // Overflow — fallback to safe version - return StringUtils.getStringAfterDot(str); - } - buf[--bufIndex] = c; - } - - // No dot found — return original - return str; - } - - private @NotNull Breadcrumb createBreadcrumb( - final long timeMs, - final @NotNull Intent intent, - final @Nullable String action, - final @Nullable BatteryState batteryState) { - final Breadcrumb breadcrumb = new Breadcrumb(timeMs); - breadcrumb.setType("system"); - breadcrumb.setCategory("device.event"); - final String shortAction = getStringAfterDotFast(action); - if (shortAction != null) { - breadcrumb.setData("action", shortAction); - } - - if (batteryState != null) { - if (batteryState.level != null) { - breadcrumb.setData("level", batteryState.level); - } - if (batteryState.charging != null) { - breadcrumb.setData("charging", batteryState.charging); - } - } else if (options.isEnableSystemEventBreadcrumbsExtras()) { - final Bundle extras = intent.getExtras(); - if (extras != null && !extras.isEmpty()) { - final Map newExtras = new HashMap<>(extras.size()); - for (String item : extras.keySet()) { - try { - @SuppressWarnings("deprecation") - Object value = extras.get(item); - if (value != null) { - newExtras.put(item, value.toString()); - } - } catch (Throwable exception) { - options - .getLogger() - .log( - SentryLevel.ERROR, - exception, - "%s key of the %s action threw an error.", - item, - action); - } - } - breadcrumb.setData("extras", newExtras); - } - } - breadcrumb.setLevel(SentryLevel.INFO); - return breadcrumb; - } - } - - static final class BatteryState { - private final @Nullable Integer level; - private final @Nullable Boolean charging; - - BatteryState(final @Nullable Integer level, final @Nullable Boolean charging) { - this.level = level; - this.charging = charging; - } - - @Override - public boolean equals(final @Nullable Object other) { - if (!(other instanceof BatteryState)) return false; - BatteryState that = (BatteryState) other; - return Objects.equals(level, that.level) && Objects.equals(charging, that.charging); - } - - @Override - public int hashCode() { - return Objects.hash(level, charging); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/TombstoneIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/TombstoneIntegration.java deleted file mode 100644 index f2b87742544..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/TombstoneIntegration.java +++ /dev/null @@ -1,345 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import android.app.ApplicationExitInfo; -import android.content.Context; -import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import io.sentry.Attachment; -import io.sentry.DateUtils; -import io.sentry.Hint; -import io.sentry.ILogger; -import io.sentry.IScopes; -import io.sentry.Integration; -import io.sentry.SentryEnvelope; -import io.sentry.SentryEnvelopeItem; -import io.sentry.SentryEvent; -import io.sentry.SentryItemType; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.android.core.ApplicationExitInfoHistoryDispatcher.ApplicationExitInfoPolicy; -import io.sentry.android.core.NativeEventCollector.NativeEventData; -import io.sentry.android.core.cache.AndroidEnvelopeCache; -import io.sentry.android.core.internal.tombstone.NativeExceptionMechanism; -import io.sentry.android.core.internal.tombstone.TombstoneParser; -import io.sentry.hints.Backfillable; -import io.sentry.hints.BlockingFlushHint; -import io.sentry.hints.NativeCrashExit; -import io.sentry.protocol.DebugMeta; -import io.sentry.protocol.Mechanism; -import io.sentry.protocol.SentryException; -import io.sentry.protocol.SentryId; -import io.sentry.protocol.SentryThread; -import io.sentry.transport.CurrentDateProvider; -import io.sentry.transport.ICurrentDateProvider; -import io.sentry.util.HintUtils; -import io.sentry.util.Objects; -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.util.List; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public class TombstoneIntegration implements Integration, Closeable { - private final @NotNull Context context; - private final @NotNull ICurrentDateProvider dateProvider; - private @Nullable SentryAndroidOptions options; - - public TombstoneIntegration(final @NotNull Context context) { - // using CurrentDateProvider instead of AndroidCurrentDateProvider as AppExitInfo uses - // System.currentTimeMillis - this(context, CurrentDateProvider.getInstance()); - } - - TombstoneIntegration( - final @NotNull Context context, final @NotNull ICurrentDateProvider dateProvider) { - this.context = ContextUtils.getApplicationContext(context); - this.dateProvider = dateProvider; - } - - @Override - public void register(@NotNull IScopes scopes, @NotNull SentryOptions options) { - this.options = - Objects.requireNonNull( - (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, - "SentryAndroidOptions is required"); - - this.options - .getLogger() - .log( - SentryLevel.DEBUG, - "TombstoneIntegration enabled: %s", - this.options.isTombstoneEnabled()); - - if (this.options.isTombstoneEnabled()) { - if (this.options.getCacheDirPath() == null) { - this.options - .getLogger() - .log(SentryLevel.INFO, "Cache dir is not set, unable to process Tombstones"); - return; - } - - try { - options - .getExecutorService() - .submit( - new ApplicationExitInfoHistoryDispatcher( - context, - scopes, - this.options, - dateProvider, - new TombstonePolicy(this.options, this.context))); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.DEBUG, "Failed to start tombstone processor.", e); - } - options.getLogger().log(SentryLevel.DEBUG, "TombstoneIntegration installed."); - addIntegrationToSdkVersion("Tombstone"); - } - } - - @Override - public void close() throws IOException { - if (options != null) { - options.getLogger().log(SentryLevel.DEBUG, "TombstoneIntegration removed."); - } - } - - @ApiStatus.Internal - public static class TombstonePolicy implements ApplicationExitInfoPolicy { - - private final @NotNull SentryAndroidOptions options; - private final @NotNull NativeEventCollector nativeEventCollector; - @NotNull private final Context context; - - public TombstonePolicy(final @NotNull SentryAndroidOptions options, @NotNull Context context) { - this.options = options; - this.nativeEventCollector = new NativeEventCollector(options); - this.context = context; - } - - @Override - public @NotNull String getLabel() { - return "Tombstone"; - } - - @RequiresApi(api = Build.VERSION_CODES.R) - @Override - public int getTargetReason() { - return ApplicationExitInfo.REASON_CRASH_NATIVE; - } - - @Override - public boolean shouldReportHistorical() { - return options.isReportHistoricalTombstones(); - } - - @Override - public @Nullable Long getLastReportedTimestamp() { - return AndroidEnvelopeCache.lastReportedTombstone(options); - } - - @RequiresApi(api = Build.VERSION_CODES.R) - @Override - public @Nullable ApplicationExitInfoHistoryDispatcher.Report buildReport( - final @NotNull ApplicationExitInfo exitInfo, final boolean enrich) { - SentryEvent event; - try { - final InputStream tombstoneInputStream = exitInfo.getTraceInputStream(); - if (tombstoneInputStream == null) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "No tombstone InputStream available for ApplicationExitInfo from %s", - DateTimeFormatter.ISO_INSTANT.format( - Instant.ofEpochMilli(exitInfo.getTimestamp()))); - return null; - } - - try (final TombstoneParser parser = - new TombstoneParser( - tombstoneInputStream, - this.options.getInAppIncludes(), - this.options.getInAppExcludes(), - this.context.getApplicationInfo().nativeLibraryDir)) { - event = parser.parse(); - } - } catch (Throwable e) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "Failed to parse tombstone from %s: %s", - DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(exitInfo.getTimestamp())), - e.getMessage()); - return null; - } - - final long tombstoneTimestamp = exitInfo.getTimestamp(); - event.setTimestamp(DateUtils.getDateTime(tombstoneTimestamp)); - - final TombstoneHint tombstoneHint = - new TombstoneHint( - options.getFlushTimeoutMillis(), options.getLogger(), tombstoneTimestamp, enrich); - final Hint hint = HintUtils.createWithTypeCheckHint(tombstoneHint); - - try { - final @Nullable SentryEvent mergedEvent = - mergeWithMatchingNativeEvents(tombstoneTimestamp, event, hint); - if (mergedEvent != null) { - event = mergedEvent; - } - } catch (Throwable e) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "Failed to merge native event with tombstone, continuing without merge: %s", - e.getMessage()); - } - - return new ApplicationExitInfoHistoryDispatcher.Report(event, hint, tombstoneHint); - } - - /** - * Attempts to find a matching native SDK event for the tombstone and merge them. - * - * @return The merged native event (with tombstone data applied) if a match was found and - * merged, or null if no matching event was found or merge failed. - */ - private @Nullable SentryEvent mergeWithMatchingNativeEvents( - long tombstoneTimestamp, SentryEvent tombstoneEvent, Hint hint) { - // Try to find and remove matching native event from outbox - final @Nullable NativeEventData matchingNativeEvent = - nativeEventCollector.findAndRemoveMatchingNativeEvent(tombstoneTimestamp); - - if (matchingNativeEvent == null) { - options.getLogger().log(SentryLevel.DEBUG, "No matching native event found for tombstone."); - return null; - } - - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Found matching native event for tombstone, removing from outbox: %s", - matchingNativeEvent.getFile().getName()); - - // Delete from outbox so OutboxSender doesn't send it - boolean deletionSuccess = nativeEventCollector.deleteNativeEventFile(matchingNativeEvent); - - if (deletionSuccess) { - final SentryEvent nativeEvent = matchingNativeEvent.getEvent(); - mergeNativeCrashes(nativeEvent, tombstoneEvent); - addNativeAttachmentsToTombstoneHint(matchingNativeEvent, hint); - return nativeEvent; - } - return null; - } - - private void addNativeAttachmentsToTombstoneHint( - @NonNull NativeEventData matchingNativeEvent, Hint hint) { - @NotNull SentryEnvelope nativeEnvelope = matchingNativeEvent.getEnvelope(); - for (SentryEnvelopeItem item : nativeEnvelope.getItems()) { - try { - @Nullable String attachmentFileName = item.getHeader().getFileName(); - if (item.getHeader().getType() != SentryItemType.Attachment - || attachmentFileName == null) { - continue; - } - hint.addAttachment( - new Attachment( - item.getData(), - attachmentFileName, - item.getHeader().getContentType(), - item.getHeader().getAttachmentType(), - false)); - } catch (Throwable e) { - options - .getLogger() - .log(SentryLevel.DEBUG, "Failed to process envelope item: %s", e.getMessage()); - } - } - } - - private void mergeNativeCrashes( - final @NotNull SentryEvent nativeEvent, final @NotNull SentryEvent tombstoneEvent) { - // we take the event data verbatim from the Native SDK and only apply tombstone data where we - // are sure that it will improve the outcome: - // * context from the Native SDK will be closer to what users want than any backfilling - // * the Native SDK only tracks the crashing thread (vs. tombstone dumps all) - // * even for the crashing we expect a much better stack-trace (+ symbolication) - // * tombstone adds additional exception meta-data to signal handler content - // * we add debug-meta for consistency since the Native SDK caches memory maps early - @Nullable List tombstoneExceptions = tombstoneEvent.getExceptions(); - @Nullable DebugMeta tombstoneDebugMeta = tombstoneEvent.getDebugMeta(); - @Nullable List tombstoneThreads = tombstoneEvent.getThreads(); - if (tombstoneExceptions != null - && !tombstoneExceptions.isEmpty() - && tombstoneDebugMeta != null - && tombstoneThreads != null) { - // native crashes don't nest, we always expect one level. - SentryException exception = tombstoneExceptions.get(0); - @Nullable Mechanism mechanism = exception.getMechanism(); - if (mechanism != null) { - mechanism.setType(NativeExceptionMechanism.TOMBSTONE_MERGED.getValue()); - } - - // Don't overwrite existing messages in the native event - if (nativeEvent.getMessage() == null - || nativeEvent.getMessage().getMessage() == null - || nativeEvent.getMessage().getMessage().isEmpty()) { - nativeEvent.setMessage(tombstoneEvent.getMessage()); - } - - nativeEvent.setExceptions(tombstoneExceptions); - nativeEvent.setDebugMeta(tombstoneDebugMeta); - nativeEvent.setThreads(tombstoneThreads); - } - } - } - - @ApiStatus.Internal - public static final class TombstoneHint extends BlockingFlushHint - implements Backfillable, NativeCrashExit { - - private final long tombstoneTimestamp; - private final boolean shouldEnrich; - - public TombstoneHint( - long flushTimeoutMillis, - @NotNull ILogger logger, - long tombstoneTimestamp, - boolean shouldEnrich) { - super(flushTimeoutMillis, logger); - this.tombstoneTimestamp = tombstoneTimestamp; - this.shouldEnrich = shouldEnrich; - } - - @NotNull - @Override - public Long timestamp() { - return tombstoneTimestamp; - } - - @Override - public boolean shouldEnrich() { - return shouldEnrich; - } - - @Override - public boolean isFlushable(@Nullable SentryId eventId) { - return true; - } - - @Override - public void setFlushable(@NotNull SentryId eventId) {} - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java deleted file mode 100644 index 9f47fc8666c..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java +++ /dev/null @@ -1,164 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import android.app.Activity; -import android.app.Application; -import android.os.Bundle; -import android.view.Window; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleOwner; -import io.sentry.IScopes; -import io.sentry.Integration; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.android.core.internal.gestures.NoOpWindowCallback; -import io.sentry.android.core.internal.gestures.SentryGestureListener; -import io.sentry.android.core.internal.gestures.SentryWindowCallback; -import io.sentry.util.Objects; -import java.io.Closeable; -import java.io.IOException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public final class UserInteractionIntegration - implements Integration, Closeable, Application.ActivityLifecycleCallbacks { - - private final @NotNull Application application; - private @Nullable IScopes scopes; - private @Nullable SentryAndroidOptions options; - - private final boolean isAndroidXAvailable; - private final boolean isAndroidxLifecycleAvailable; - - public UserInteractionIntegration( - final @NotNull Application application, final @NotNull io.sentry.util.LoadClass classLoader) { - this.application = Objects.requireNonNull(application, "Application is required"); - isAndroidXAvailable = - classLoader.isClassAvailable("androidx.core.view.GestureDetectorCompat", options); - isAndroidxLifecycleAvailable = - classLoader.isClassAvailable("androidx.lifecycle.Lifecycle", options); - } - - private void startTracking(final @NotNull Activity activity) { - final Window window = activity.getWindow(); - if (window == null) { - if (options != null) { - options.getLogger().log(SentryLevel.INFO, "Window was null in startTracking"); - } - return; - } - - if (scopes != null && options != null) { - Window.Callback delegate = window.getCallback(); - if (delegate == null) { - delegate = new NoOpWindowCallback(); - } - - if (delegate instanceof SentryWindowCallback) { - // already instrumented - return; - } - - final SentryGestureListener gestureListener = - new SentryGestureListener(activity, scopes, options); - window.setCallback(new SentryWindowCallback(delegate, activity, gestureListener, options)); - } - } - - private void stopTracking(final @NotNull Activity activity) { - final Window window = activity.getWindow(); - if (window == null) { - if (options != null) { - options.getLogger().log(SentryLevel.INFO, "Window was null in stopTracking"); - } - return; - } - - final Window.Callback current = window.getCallback(); - if (current instanceof SentryWindowCallback) { - ((SentryWindowCallback) current).stopTracking(); - if (((SentryWindowCallback) current).getDelegate() instanceof NoOpWindowCallback) { - window.setCallback(null); - } else { - window.setCallback(((SentryWindowCallback) current).getDelegate()); - } - } - } - - @Override - public void onActivityCreated(@NotNull Activity activity, @Nullable Bundle bundle) {} - - @Override - public void onActivityStarted(@NotNull Activity activity) {} - - @Override - public void onActivityResumed(@NotNull Activity activity) { - startTracking(activity); - } - - @Override - public void onActivityPaused(@NotNull Activity activity) { - stopTracking(activity); - } - - @Override - public void onActivityStopped(@NotNull Activity activity) {} - - @Override - public void onActivitySaveInstanceState(@NotNull Activity activity, @NotNull Bundle bundle) {} - - @Override - public void onActivityDestroyed(@NotNull Activity activity) {} - - @Override - public void register(@NotNull IScopes scopes, @NotNull SentryOptions options) { - this.options = - Objects.requireNonNull( - (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, - "SentryAndroidOptions is required"); - - this.scopes = Objects.requireNonNull(scopes, "Scopes are required"); - - final boolean integrationEnabled = - this.options.isEnableUserInteractionBreadcrumbs() - || this.options.isEnableUserInteractionTracing(); - this.options - .getLogger() - .log(SentryLevel.DEBUG, "UserInteractionIntegration enabled: %s", integrationEnabled); - - if (integrationEnabled) { - if (isAndroidXAvailable) { - application.registerActivityLifecycleCallbacks(this); - this.options.getLogger().log(SentryLevel.DEBUG, "UserInteractionIntegration installed."); - addIntegrationToSdkVersion("UserInteraction"); - - // In case of a deferred init, we hook into any resumed activity - if (isAndroidxLifecycleAvailable) { - final @Nullable Activity activity = CurrentActivityHolder.getInstance().getActivity(); - if (activity instanceof LifecycleOwner) { - if (((LifecycleOwner) activity).getLifecycle().getCurrentState() - == Lifecycle.State.RESUMED) { - startTracking(activity); - } - } - } - } else { - options - .getLogger() - .log( - SentryLevel.INFO, - "androidx.core is not available, UserInteractionIntegration won't be installed"); - } - } - } - - @Override - public void close() throws IOException { - application.unregisterActivityLifecycleCallbacks(this); - - if (options != null) { - options.getLogger().log(SentryLevel.DEBUG, "UserInteractionIntegration removed."); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java deleted file mode 100644 index c32b05892f9..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java +++ /dev/null @@ -1,292 +0,0 @@ -package io.sentry.android.core; - -import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; - -import android.app.Activity; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import io.sentry.Attachment; -import io.sentry.EventProcessor; -import io.sentry.Hint; -import io.sentry.ILogger; -import io.sentry.ISerializer; -import io.sentry.SentryEvent; -import io.sentry.SentryLevel; -import io.sentry.android.core.internal.gestures.ViewUtils; -import io.sentry.android.core.internal.util.AndroidCurrentDateProvider; -import io.sentry.android.core.internal.util.AndroidThreadChecker; -import io.sentry.android.core.internal.util.ClassUtil; -import io.sentry.android.core.internal.util.Debouncer; -import io.sentry.internal.viewhierarchy.ViewHierarchyExporter; -import io.sentry.protocol.SentryTransaction; -import io.sentry.protocol.ViewHierarchy; -import io.sentry.protocol.ViewHierarchyNode; -import io.sentry.util.HintUtils; -import io.sentry.util.JsonSerializationUtils; -import io.sentry.util.Objects; -import io.sentry.util.thread.IThreadChecker; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** ViewHierarchyEventProcessor responsible for taking a snapshot of the current view hierarchy. */ -@ApiStatus.Internal -public final class ViewHierarchyEventProcessor implements EventProcessor { - - private final @NotNull SentryAndroidOptions options; - private final @NotNull Debouncer debouncer; - - private static final long CAPTURE_TIMEOUT_MS = 1000; - private static final long DEBOUNCE_WAIT_TIME_MS = 2000; - private static final int DEBOUNCE_MAX_EXECUTIONS = 3; - - public ViewHierarchyEventProcessor(final @NotNull SentryAndroidOptions options) { - this.options = Objects.requireNonNull(options, "SentryAndroidOptions is required"); - this.debouncer = - new Debouncer( - AndroidCurrentDateProvider.getInstance(), - DEBOUNCE_WAIT_TIME_MS, - DEBOUNCE_MAX_EXECUTIONS); - - if (options.isAttachViewHierarchy()) { - addIntegrationToSdkVersion("ViewHierarchy"); - } - } - - @Override - public @NotNull SentryTransaction process( - @NotNull SentryTransaction transaction, @NotNull Hint hint) { - // that's only necessary because on newer versions of Unity, if not overriding this method, it's - // throwing 'java.lang.AbstractMethodError: abstract method' and the reason is probably - // compilation mismatch - return transaction; - } - - @Override - public @NotNull SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) { - if (!event.isErrored()) { - return event; - } - - if (!options.isAttachViewHierarchy()) { - options.getLogger().log(SentryLevel.DEBUG, "attachViewHierarchy is disabled."); - return event; - } - - if (HintUtils.isFromHybridSdk(hint)) { - return event; - } - - // skip capturing in case of debouncing (=too many frequent capture requests) - // the BeforeCaptureCallback may overrules the debouncing decision - final boolean shouldDebounce = debouncer.checkForDebounce(); - final @Nullable SentryAndroidOptions.BeforeCaptureCallback beforeCaptureCallback = - options.getBeforeViewHierarchyCaptureCallback(); - if (beforeCaptureCallback != null) { - if (!beforeCaptureCallback.execute(event, hint, shouldDebounce)) { - return event; - } - } else if (shouldDebounce) { - return event; - } - - final @Nullable Activity activity = CurrentActivityHolder.getInstance().getActivity(); - final @Nullable ViewHierarchy viewHierarchy = - snapshotViewHierarchy( - activity, - options.getViewHierarchyExporters(), - options.getThreadChecker(), - options.getLogger()); - - if (viewHierarchy != null) { - hint.setViewHierarchy(Attachment.fromViewHierarchy(viewHierarchy)); - } - - return event; - } - - public static byte[] snapshotViewHierarchyAsData( - @Nullable Activity activity, - @NotNull IThreadChecker threadChecker, - @NotNull ISerializer serializer, - @NotNull ILogger logger) { - - @Nullable - ViewHierarchy viewHierarchy = - snapshotViewHierarchy(activity, new ArrayList<>(0), threadChecker, logger); - - if (viewHierarchy == null) { - logger.log(SentryLevel.ERROR, "Could not get ViewHierarchy."); - return null; - } - - final @Nullable byte[] bytes = - JsonSerializationUtils.bytesFrom(serializer, logger, viewHierarchy); - if (bytes == null) { - logger.log(SentryLevel.ERROR, "Could not serialize ViewHierarchy."); - return null; - } - if (bytes.length < 1) { - logger.log(SentryLevel.ERROR, "Got empty bytes array after serializing ViewHierarchy."); - return null; - } - - return bytes; - } - - @Nullable - public static ViewHierarchy snapshotViewHierarchy( - final @Nullable Activity activity, final @NotNull ILogger logger) { - return snapshotViewHierarchy( - activity, new ArrayList<>(0), AndroidThreadChecker.getInstance(), logger); - } - - @Nullable - public static ViewHierarchy snapshotViewHierarchy( - final @Nullable Activity activity, - final @NotNull List exporters, - final @NotNull IThreadChecker threadChecker, - final @NotNull ILogger logger) { - - if (activity == null) { - logger.log(SentryLevel.INFO, "Missing activity for view hierarchy snapshot."); - return null; - } - - final @Nullable Window window = activity.getWindow(); - if (window == null) { - logger.log(SentryLevel.INFO, "Missing window for view hierarchy snapshot."); - return null; - } - - final @Nullable View decorView = window.peekDecorView(); - if (decorView == null) { - logger.log(SentryLevel.INFO, "Missing decor view for view hierarchy snapshot."); - return null; - } - - try { - if (threadChecker.isMainThread()) { - return snapshotViewHierarchy(decorView, exporters); - } else { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference viewHierarchy = new AtomicReference<>(null); - activity.runOnUiThread( - () -> { - try { - viewHierarchy.set(snapshotViewHierarchy(decorView, exporters)); - latch.countDown(); - } catch (Throwable t) { - logger.log(SentryLevel.ERROR, "Failed to process view hierarchy.", t); - } - }); - if (latch.await(CAPTURE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { - return viewHierarchy.get(); - } - } - } catch (Throwable t) { - logger.log(SentryLevel.ERROR, "Failed to process view hierarchy.", t); - } - return null; - } - - @NotNull - public static ViewHierarchy snapshotViewHierarchy(final @NotNull View view) { - return snapshotViewHierarchy(view, new ArrayList<>(0)); - } - - @NotNull - public static ViewHierarchy snapshotViewHierarchy( - final @NotNull View view, final @NotNull List exporters) { - final List windows = new ArrayList<>(1); - final ViewHierarchy viewHierarchy = new ViewHierarchy("android_view_system", windows); - - final @NotNull ViewHierarchyNode node = viewToNode(view); - windows.add(node); - addChildren(view, node, exporters); - - return viewHierarchy; - } - - private static void addChildren( - final @NotNull View view, - final @NotNull ViewHierarchyNode parentNode, - final @NotNull List exporters) { - if (!(view instanceof ViewGroup)) { - return; - } - - // In case any external exporter recognizes it's own widget (e.g. AndroidComposeView) - // we can immediately return - for (ViewHierarchyExporter exporter : exporters) { - if (exporter.export(parentNode, view)) { - return; - } - } - - final @NotNull ViewGroup viewGroup = ((ViewGroup) view); - final int childCount = viewGroup.getChildCount(); - if (childCount == 0) { - return; - } - - final @NotNull List childNodes = new ArrayList<>(childCount); - for (int i = 0; i < childCount; i++) { - final @Nullable View child = viewGroup.getChildAt(i); - if (child != null) { - final @NotNull ViewHierarchyNode childNode = viewToNode(child); - childNodes.add(childNode); - addChildren(child, childNode, exporters); - } - } - parentNode.setChildren(childNodes); - } - - @NotNull - private static ViewHierarchyNode viewToNode(@NotNull final View view) { - @NotNull final ViewHierarchyNode node = new ViewHierarchyNode(); - - @Nullable String className = ClassUtil.getClassName(view); - node.setType(className); - - try { - final String identifier = ViewUtils.getResourceId(view); - node.setIdentifier(identifier); - } catch (Throwable e) { - // ignored - } - node.setX((double) view.getX()); - node.setY((double) view.getY()); - node.setWidth((double) view.getWidth()); - node.setHeight((double) view.getHeight()); - node.setAlpha((double) view.getAlpha()); - - switch (view.getVisibility()) { - case View.VISIBLE: - node.setVisibility("visible"); - break; - case View.INVISIBLE: - node.setVisibility("invisible"); - break; - case View.GONE: - node.setVisibility("gone"); - break; - default: - // ignored - break; - } - - return node; - } - - @Override - public @Nullable Long getOrder() { - return 11000L; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java b/sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java deleted file mode 100644 index 5aad7ef1b26..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java +++ /dev/null @@ -1,269 +0,0 @@ -package io.sentry.android.core.cache; - -import static io.sentry.SentryLevel.DEBUG; -import static io.sentry.SentryLevel.ERROR; - -import io.sentry.Hint; -import io.sentry.SentryEnvelope; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.UncaughtExceptionHandlerIntegration; -import io.sentry.android.core.AnrV2Integration; -import io.sentry.android.core.SentryAndroidOptions; -import io.sentry.android.core.TombstoneIntegration; -import io.sentry.android.core.internal.util.AndroidCurrentDateProvider; -import io.sentry.android.core.performance.AppStartMetrics; -import io.sentry.android.core.performance.TimeSpan; -import io.sentry.cache.EnvelopeCache; -import io.sentry.transport.ICurrentDateProvider; -import io.sentry.util.FileUtils; -import io.sentry.util.HintUtils; -import io.sentry.util.Objects; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.OutputStream; -import java.util.Arrays; -import java.util.List; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -@ApiStatus.Internal -public final class AndroidEnvelopeCache extends EnvelopeCache { - - public static final String LAST_ANR_REPORT = "last_anr_report"; - public static final String LAST_TOMBSTONE_REPORT = "last_tombstone_report"; - - private final @NotNull ICurrentDateProvider currentDateProvider; - - public AndroidEnvelopeCache(final @NotNull SentryAndroidOptions options) { - this(options, AndroidCurrentDateProvider.getInstance()); - } - - AndroidEnvelopeCache( - final @NotNull SentryAndroidOptions options, - final @NotNull ICurrentDateProvider currentDateProvider) { - super( - options, - Objects.requireNonNull(options.getCacheDirPath(), "cacheDirPath must not be null"), - options.getMaxCacheItems()); - this.currentDateProvider = currentDateProvider; - } - - @SuppressWarnings("deprecation") - @Override - public void store(@NotNull SentryEnvelope envelope, @NotNull Hint hint) { - storeInternalAndroid(envelope, hint); - } - - @Override - public boolean storeEnvelope(@NotNull SentryEnvelope envelope, @NotNull Hint hint) { - return storeInternalAndroid(envelope, hint); - } - - private boolean storeInternalAndroid(@NotNull SentryEnvelope envelope, @NotNull Hint hint) { - final boolean didStore = super.storeEnvelope(envelope, hint); - - final SentryAndroidOptions options = (SentryAndroidOptions) this.options; - final TimeSpan sdkInitTimeSpan = AppStartMetrics.getInstance().getSdkInitTimeSpan(); - - if (HintUtils.hasType(hint, UncaughtExceptionHandlerIntegration.UncaughtExceptionHint.class) - && sdkInitTimeSpan.hasStarted()) { - long timeSinceSdkInit = - currentDateProvider.getCurrentTimeMillis() - sdkInitTimeSpan.getStartUptimeMs(); - if (timeSinceSdkInit <= options.getStartupCrashDurationThresholdMillis()) { - options - .getLogger() - .log( - DEBUG, - "Startup Crash detected %d milliseconds after SDK init. Writing a startup crash marker file to disk.", - timeSinceSdkInit); - writeStartupCrashMarkerFile(); - } - } - - for (TimestampMarkerHandler handler : TIMESTAMP_MARKER_HANDLERS) { - handler.handle(this, hint, options); - } - - return didStore; - } - - @TestOnly - public @NotNull File getDirectory() { - return directory; - } - - private void writeStartupCrashMarkerFile() { - // we use outbox path always, as it's the one that will also contain markers if hybrid sdks - // decide to write it, which will trigger the blocking init - final String outboxPath = options.getOutboxPath(); - if (outboxPath == null) { - options - .getLogger() - .log(DEBUG, "Outbox path is null, the startup crash marker file will not be written"); - return; - } - final File crashMarkerFile = new File(outboxPath, STARTUP_CRASH_MARKER_FILE); - try { - crashMarkerFile.createNewFile(); - } catch (Throwable e) { - options.getLogger().log(ERROR, "Error writing the startup crash marker file to the disk", e); - } - } - - public static boolean hasStartupCrashMarker(final @NotNull SentryOptions options) { - final String outboxPath = options.getOutboxPath(); - if (outboxPath == null) { - options - .getLogger() - .log(DEBUG, "Outbox path is null, the startup crash marker file does not exist"); - return false; - } - - final File crashMarkerFile = new File(outboxPath, STARTUP_CRASH_MARKER_FILE); - try { - final boolean exists = - options.getRuntimeManager().runWithRelaxedPolicy(() -> crashMarkerFile.exists()); - if (exists) { - if (!options.getRuntimeManager().runWithRelaxedPolicy(() -> crashMarkerFile.delete())) { - options - .getLogger() - .log( - ERROR, - "Failed to delete the startup crash marker file. %s.", - crashMarkerFile.getAbsolutePath()); - } - } - return exists; - } catch (Throwable e) { - options - .getLogger() - .log(ERROR, "Error reading/deleting the startup crash marker file on the disk", e); - } - return false; - } - - private static @Nullable Long lastReportedMarker( - final @NotNull SentryOptions options, - @NotNull String reportFilename, - @NotNull String markerLabel) { - final String cacheDirPath = - Objects.requireNonNull( - options.getCacheDirPath(), - "Cache dir path should be set for getting " + markerLabel + "s reported"); - - final File lastMarker = new File(cacheDirPath, reportFilename); - try { - final String content = FileUtils.readText(lastMarker); - // we wrapped into try-catch already - //noinspection ConstantConditions - return (content == null || content.equals("null")) ? null : Long.parseLong(content.trim()); - } catch (Throwable e) { - if (e instanceof FileNotFoundException) { - options - .getLogger() - .log( - DEBUG, - "Last " + markerLabel + " marker does not exist. %s.", - lastMarker.getAbsolutePath()); - } else { - options.getLogger().log(ERROR, "Error reading last " + markerLabel + " marker", e); - } - } - return null; - } - - private void writeLastReportedMarker( - final @Nullable Long timestamp, - @NotNull String reportFilename, - @NotNull String markerCategory) { - final String cacheDirPath = options.getCacheDirPath(); - if (cacheDirPath == null) { - options - .getLogger() - .log( - DEBUG, - "Cache dir path is null, the " + markerCategory + " marker will not be written"); - return; - } - - final File anrMarker = new File(cacheDirPath, reportFilename); - try (final OutputStream outputStream = new FileOutputStream(anrMarker)) { - outputStream.write(String.valueOf(timestamp).getBytes(UTF_8)); - outputStream.flush(); - } catch (Throwable e) { - options - .getLogger() - .log(ERROR, "Error writing the " + markerCategory + " marker to the disk", e); - } - } - - public static @Nullable Long lastReportedAnr(final @NotNull SentryOptions options) { - return lastReportedMarker(options, LAST_ANR_REPORT, LAST_ANR_MARKER_LABEL); - } - - public static @Nullable Long lastReportedTombstone(final @NotNull SentryOptions options) { - return lastReportedMarker(options, LAST_TOMBSTONE_REPORT, LAST_TOMBSTONE_MARKER_LABEL); - } - - private static final class TimestampMarkerHandler { - interface TimestampExtractor { - @NotNull - Long extract(T value); - } - - private final @NotNull Class type; - private final @NotNull String label; - private final @NotNull String reportFilename; - private final @NotNull TimestampExtractor timestampProvider; - - TimestampMarkerHandler( - final @NotNull Class type, - final @NotNull String label, - final @NotNull String reportFilename, - final @NotNull TimestampExtractor timestampProvider) { - this.type = type; - this.label = label; - this.reportFilename = reportFilename; - this.timestampProvider = timestampProvider; - } - - void handle( - final @NotNull AndroidEnvelopeCache cache, - final @NotNull Hint hint, - final @NotNull SentryAndroidOptions options) { - HintUtils.runIfHasType( - hint, - type, - (typedHint) -> { - final @NotNull Long timestamp = timestampProvider.extract(typedHint); - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Writing last reported %s marker with timestamp %d", - label, - timestamp); - cache.writeLastReportedMarker(timestamp, reportFilename, label); - }); - } - } - - public static final String LAST_TOMBSTONE_MARKER_LABEL = "Tombstone"; - public static final String LAST_ANR_MARKER_LABEL = "ANR"; - private static final List> TIMESTAMP_MARKER_HANDLERS = - Arrays.asList( - new TimestampMarkerHandler<>( - AnrV2Integration.AnrV2Hint.class, - LAST_ANR_MARKER_LABEL, - LAST_ANR_REPORT, - anrV2Hint -> anrV2Hint.timestamp()), - new TimestampMarkerHandler<>( - TombstoneIntegration.TombstoneHint.class, - LAST_TOMBSTONE_MARKER_LABEL, - LAST_TOMBSTONE_REPORT, - tombstoneHint -> tombstoneHint.timestamp())); -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/debugmeta/AssetsDebugMetaLoader.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/debugmeta/AssetsDebugMetaLoader.java deleted file mode 100644 index d0dd4981a3c..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/debugmeta/AssetsDebugMetaLoader.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.sentry.android.core.internal.debugmeta; - -import static io.sentry.util.DebugMetaPropertiesApplier.DEBUG_META_PROPERTIES_FILENAME; - -import android.content.Context; -import android.content.res.AssetManager; -import io.sentry.ILogger; -import io.sentry.SentryLevel; -import io.sentry.android.core.ContextUtils; -import io.sentry.internal.debugmeta.IDebugMetaLoader; -import java.io.BufferedInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.List; -import java.util.Properties; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public final class AssetsDebugMetaLoader implements IDebugMetaLoader { - private final @NotNull Context context; - private final @NotNull ILogger logger; - - public AssetsDebugMetaLoader(final @NotNull Context context, final @NotNull ILogger logger) { - this.context = ContextUtils.getApplicationContext(context); - this.logger = logger; - } - - @Override - public @Nullable List loadDebugMeta() { - final AssetManager assets = context.getAssets(); - // one may have thousands of asset files and looking up this list might slow down the SDK init. - // quite a bit, for this reason, we try to open the file directly and take care of errors - // like FileNotFoundException - try (final InputStream is = - new BufferedInputStream(assets.open(DEBUG_META_PROPERTIES_FILENAME))) { - final Properties properties = new Properties(); - properties.load(is); - return Collections.singletonList(properties); - } catch (FileNotFoundException e) { - logger.log(SentryLevel.INFO, "%s file was not found.", DEBUG_META_PROPERTIES_FILENAME); - } catch (IOException e) { - logger.log(SentryLevel.ERROR, "Error getting Proguard UUIDs.", e); - } catch (RuntimeException e) { - logger.log(SentryLevel.ERROR, e, "%s file is malformed.", DEBUG_META_PROPERTIES_FILENAME); - } - - return null; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java deleted file mode 100644 index c85fb80dc35..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java +++ /dev/null @@ -1,73 +0,0 @@ -package io.sentry.android.core.internal.gestures; - -import android.content.res.Resources; -import android.view.View; -import android.widget.AbsListView; -import android.widget.ScrollView; -import androidx.core.view.ScrollingView; -import io.sentry.android.core.internal.util.ClassUtil; -import io.sentry.internal.gestures.GestureTargetLocator; -import io.sentry.internal.gestures.UiElement; -import io.sentry.util.LazyEvaluator; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public final class AndroidViewGestureTargetLocator implements GestureTargetLocator { - - private static final String ORIGIN = "old_view_system"; - - private final @NotNull LazyEvaluator isAndroidXAvailable; - - public AndroidViewGestureTargetLocator( - final @NotNull LazyEvaluator isAndroidXAvailable) { - this.isAndroidXAvailable = isAndroidXAvailable; - } - - @Override - public @Nullable UiElement locate( - @Nullable Object root, float x, float y, UiElement.Type targetType) { - if (!(root instanceof View)) { - return null; - } - final View view = (View) root; - if (targetType == UiElement.Type.CLICKABLE && isViewTappable(view)) { - return createUiElement(view); - } else if (targetType == UiElement.Type.SCROLLABLE - && isViewScrollable(view, isAndroidXAvailable.getValue())) { - return createUiElement(view); - } - return null; - } - - private UiElement createUiElement(final @NotNull View targetView) { - try { - final String resourceName = ViewUtils.getResourceId(targetView); - @Nullable String className = ClassUtil.getClassName(targetView); - return new UiElement(targetView, className, resourceName, null, ORIGIN); - } catch (Resources.NotFoundException ignored) { - return null; - } - } - - private static boolean isViewTappable(final @NotNull View view) { - return view.isClickable() && view.getVisibility() == View.VISIBLE; - } - - private static boolean isViewScrollable( - final @NotNull View view, final boolean isAndroidXAvailable) { - return (isJetpackScrollingView(view, isAndroidXAvailable) - || AbsListView.class.isAssignableFrom(view.getClass()) - || ScrollView.class.isAssignableFrom(view.getClass())) - && view.getVisibility() == View.VISIBLE; - } - - private static boolean isJetpackScrollingView( - final @NotNull View view, final boolean isAndroidXAvailable) { - if (!isAndroidXAvailable) { - return false; - } - return ScrollingView.class.isAssignableFrom(view.getClass()); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/NoOpWindowCallback.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/NoOpWindowCallback.java deleted file mode 100644 index db50d855e63..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/NoOpWindowCallback.java +++ /dev/null @@ -1,120 +0,0 @@ -package io.sentry.android.core.internal.gestures; - -import android.view.ActionMode; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.SearchEvent; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityEvent; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -public final class NoOpWindowCallback implements Window.Callback { - @Override - public boolean dispatchKeyEvent(KeyEvent keyEvent) { - return false; - } - - @Override - public boolean dispatchKeyShortcutEvent(KeyEvent keyEvent) { - return false; - } - - @Override - public boolean dispatchTouchEvent(MotionEvent motionEvent) { - return false; - } - - @Override - public boolean dispatchTrackballEvent(MotionEvent motionEvent) { - return false; - } - - @Override - public boolean dispatchGenericMotionEvent(MotionEvent motionEvent) { - return false; - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent accessibilityEvent) { - return false; - } - - @Nullable - @Override - public View onCreatePanelView(int i) { - return null; - } - - @Override - public boolean onCreatePanelMenu(int i, @NonNull Menu menu) { - return false; - } - - @Override - public boolean onPreparePanel(int i, @Nullable View view, @NonNull Menu menu) { - return false; - } - - @Override - public boolean onMenuOpened(int i, @NonNull Menu menu) { - return false; - } - - @Override - public boolean onMenuItemSelected(int i, @NonNull MenuItem menuItem) { - return false; - } - - @Override - public void onWindowAttributesChanged(WindowManager.LayoutParams layoutParams) {} - - @Override - public void onContentChanged() {} - - @Override - public void onWindowFocusChanged(boolean b) {} - - @Override - public void onAttachedToWindow() {} - - @Override - public void onDetachedFromWindow() {} - - @Override - public void onPanelClosed(int i, @NonNull Menu menu) {} - - @Override - public boolean onSearchRequested() { - return false; - } - - @Override - public boolean onSearchRequested(SearchEvent searchEvent) { - return false; - } - - @Nullable - @Override - public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { - return null; - } - - @Nullable - @Override - public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int i) { - return null; - } - - @Override - public void onActionModeStarted(ActionMode actionMode) {} - - @Override - public void onActionModeFinished(ActionMode actionMode) {} -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java deleted file mode 100644 index cd89db72f53..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java +++ /dev/null @@ -1,428 +0,0 @@ -package io.sentry.android.core.internal.gestures; - -import static io.sentry.TypeCheckHint.ANDROID_MOTION_EVENT; -import static io.sentry.TypeCheckHint.ANDROID_VIEW; - -import android.app.Activity; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.View; -import android.view.Window; -import io.sentry.Breadcrumb; -import io.sentry.Hint; -import io.sentry.IScope; -import io.sentry.IScopes; -import io.sentry.ITransaction; -import io.sentry.SentryLevel; -import io.sentry.SpanStatus; -import io.sentry.TransactionContext; -import io.sentry.TransactionOptions; -import io.sentry.android.core.SentryAndroidOptions; -import io.sentry.internal.gestures.UiElement; -import io.sentry.protocol.TransactionNameSource; -import io.sentry.util.TracingUtils; -import java.lang.ref.WeakReference; -import java.util.Collections; -import java.util.Map; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.VisibleForTesting; - -@ApiStatus.Internal -public final class SentryGestureListener implements GestureDetector.OnGestureListener { - - private enum GestureType { - Click, - Scroll, - Swipe, - Unknown - } - - static final String UI_ACTION = "ui.action"; - private static final String TRACE_ORIGIN = "auto.ui.gesture_listener"; - - private final @NotNull WeakReference activityRef; - private final @NotNull IScopes scopes; - private final @NotNull SentryAndroidOptions options; - - private @Nullable UiElement activeUiElement = null; - private @Nullable ITransaction activeTransaction = null; - private @NotNull GestureType activeEventType = GestureType.Unknown; - - private final ScrollState scrollState = new ScrollState(); - - public SentryGestureListener( - final @NotNull Activity currentActivity, - final @NotNull IScopes scopes, - final @NotNull SentryAndroidOptions options) { - this.activityRef = new WeakReference<>(currentActivity); - this.scopes = scopes; - this.options = options; - } - - public void onUp(final @NotNull MotionEvent motionEvent) { - final View decorView = ensureWindowDecorView("onUp"); - final UiElement scrollTarget = scrollState.target; - if (decorView == null || scrollTarget == null) { - return; - } - - if (scrollState.type == GestureType.Unknown) { - options - .getLogger() - .log(SentryLevel.DEBUG, "Unable to define scroll type. No breadcrumb captured."); - return; - } - - final String direction = scrollState.calculateDirection(motionEvent); - addBreadcrumb( - scrollTarget, - scrollState.type, - Collections.singletonMap("direction", direction), - motionEvent); - startTracing(scrollTarget, scrollState.type); - scrollState.reset(); - } - - @Override - public boolean onDown(final @Nullable MotionEvent motionEvent) { - if (motionEvent == null) { - return false; - } - scrollState.reset(); - scrollState.startX = motionEvent.getX(); - scrollState.startY = motionEvent.getY(); - return false; - } - - @Override - public boolean onSingleTapUp(final @Nullable MotionEvent motionEvent) { - final View decorView = ensureWindowDecorView("onSingleTapUp"); - if (decorView == null || motionEvent == null) { - return false; - } - - final @Nullable UiElement target = - ViewUtils.findTarget( - options, decorView, motionEvent.getX(), motionEvent.getY(), UiElement.Type.CLICKABLE); - - if (target == null) { - options - .getLogger() - .log(SentryLevel.DEBUG, "Unable to find click target. No breadcrumb captured."); - return false; - } - - addBreadcrumb(target, GestureType.Click, Collections.emptyMap(), motionEvent); - startTracing(target, GestureType.Click); - return false; - } - - @Override - public boolean onScroll( - final @Nullable MotionEvent firstEvent, - final @Nullable MotionEvent currentEvent, - final float distX, - final float distY) { - final View decorView = ensureWindowDecorView("onScroll"); - if (decorView == null || firstEvent == null) { - return false; - } - - if (scrollState.type == GestureType.Unknown) { - final @Nullable UiElement target = - ViewUtils.findTarget( - options, decorView, firstEvent.getX(), firstEvent.getY(), UiElement.Type.SCROLLABLE); - - if (target == null) { - options - .getLogger() - .log(SentryLevel.DEBUG, "Unable to find scroll target. No breadcrumb captured."); - scrollState.type = GestureType.Scroll; - return false; - } else { - options - .getLogger() - .log(SentryLevel.DEBUG, "Scroll target found: " + target.getIdentifier()); - } - - scrollState.setTarget(target); - scrollState.type = GestureType.Scroll; - } - return false; - } - - @Override - public boolean onFling( - final @Nullable MotionEvent motionEvent, - final @Nullable MotionEvent motionEvent1, - final float v, - final float v1) { - scrollState.type = GestureType.Swipe; - return false; - } - - @Override - public void onShowPress(MotionEvent motionEvent) {} - - @Override - public void onLongPress(MotionEvent motionEvent) {} - - // region utils - private void addBreadcrumb( - final @NotNull UiElement target, - final @NotNull GestureType eventType, - final @NotNull Map additionalData, - final @NotNull MotionEvent motionEvent) { - - if (!options.isEnableUserInteractionBreadcrumbs()) { - return; - } - - final String type = getGestureType(eventType); - - final Hint hint = new Hint(); - hint.set(ANDROID_MOTION_EVENT, motionEvent); - hint.set(ANDROID_VIEW, target.getView()); - - scopes.addBreadcrumb( - Breadcrumb.userInteraction( - type, target.getResourceName(), target.getClassName(), target.getTag(), additionalData), - hint); - } - - private void startTracing(final @NotNull UiElement target, final @NotNull GestureType eventType) { - - final boolean isNewGestureSameAsActive = - (eventType == activeEventType && target.equals(activeUiElement)); - final boolean isClickGesture = eventType == GestureType.Click; - // we always want to start new transaction/traces for clicks, for swipe/scroll only if the - // target changed - final boolean isNewInteraction = isClickGesture || !isNewGestureSameAsActive; - - if (!(options.isTracingEnabled() && options.isEnableUserInteractionTracing())) { - if (isNewInteraction) { - if (options.isEnableAutoTraceIdGeneration()) { - TracingUtils.startNewTrace(scopes); - } - activeUiElement = target; - activeEventType = eventType; - } - return; - } - - final Activity activity = activityRef.get(); - if (activity == null) { - options.getLogger().log(SentryLevel.DEBUG, "Activity is null, no transaction captured."); - return; - } - - final @Nullable String viewIdentifier = target.getIdentifier(); - - if (activeTransaction != null) { - if (!isNewInteraction && !activeTransaction.isFinished()) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "The view with id: " - + viewIdentifier - + " already has an ongoing transaction assigned. Rescheduling finish"); - - final Long idleTimeout = options.getIdleTimeout(); - if (idleTimeout != null) { - // reschedule the finish task for the idle transaction, so it keeps running for the same - // view - activeTransaction.scheduleFinish(); - } - return; - } else { - // as we allow a single UI transaction running on the bound Scope, we finish the previous - // one, if it's a new view - stopTracing(SpanStatus.OK); - } - } - - // we can only bind to the scope if there's no running transaction - final String name = getActivityName(activity) + "." + viewIdentifier; - final String op = UI_ACTION + "." + getGestureType(eventType); - - final TransactionOptions transactionOptions = new TransactionOptions(); - transactionOptions.setWaitForChildren(true); - - // Set deadline timeout based on configured option - final long deadlineTimeoutMillis = options.getDeadlineTimeout(); - // No deadline when zero or negative value is set - transactionOptions.setDeadlineTimeout( - deadlineTimeoutMillis <= 0 ? null : deadlineTimeoutMillis); - - transactionOptions.setIdleTimeout(options.getIdleTimeout()); - transactionOptions.setTrimEnd(true); - transactionOptions.setOrigin(TRACE_ORIGIN + "." + target.getOrigin()); - - final ITransaction transaction = - scopes.startTransaction( - new TransactionContext(name, TransactionNameSource.COMPONENT, op), transactionOptions); - - scopes.configureScope( - scope -> { - applyScope(scope, transaction); - }); - - activeTransaction = transaction; - activeUiElement = target; - activeEventType = eventType; - } - - void stopTracing(final @NotNull SpanStatus status) { - if (activeTransaction != null) { - final SpanStatus currentStatus = activeTransaction.getStatus(); - // status might be set by other integrations, let's not overwrite it - if (currentStatus == null) { - activeTransaction.finish(status); - } else { - activeTransaction.finish(); - } - } - scopes.configureScope( - scope -> { - // avoid method refs on Android due to some issues with older AGP setups - // noinspection Convert2MethodRef - clearScope(scope); - }); - activeTransaction = null; - if (activeUiElement != null) { - activeUiElement = null; - } - activeEventType = GestureType.Unknown; - } - - @VisibleForTesting - void clearScope(final @NotNull IScope scope) { - scope.withTransaction( - transaction -> { - if (transaction == activeTransaction) { - scope.clearTransaction(); - } - }); - } - - @VisibleForTesting - void applyScope(final @NotNull IScope scope, final @NotNull ITransaction transaction) { - scope.withTransaction( - scopeTransaction -> { - // we'd not like to overwrite existent transactions bound to the Scope manually - if (scopeTransaction == null) { - scope.setTransaction(transaction); - } else { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Transaction '%s' won't be bound to the Scope since there's one already in there.", - transaction.getName()); - } - }); - } - - private @NotNull String getActivityName(final @NotNull Activity activity) { - return activity.getClass().getSimpleName(); - } - - private @Nullable View ensureWindowDecorView(final @NotNull String caller) { - final Activity activity = activityRef.get(); - if (activity == null) { - options - .getLogger() - .log(SentryLevel.DEBUG, "Activity is null in " + caller + ". No breadcrumb captured."); - return null; - } - - final Window window = activity.getWindow(); - if (window == null) { - options - .getLogger() - .log(SentryLevel.DEBUG, "Window is null in " + caller + ". No breadcrumb captured."); - return null; - } - - final View decorView = window.getDecorView(); - if (decorView == null) { - options - .getLogger() - .log(SentryLevel.DEBUG, "DecorView is null in " + caller + ". No breadcrumb captured."); - return null; - } - return decorView; - } - - @NotNull - private static String getGestureType(final @NotNull GestureType eventType) { - final @NotNull String type; - switch (eventType) { - case Click: - type = "click"; - break; - case Scroll: - type = "scroll"; - break; - case Swipe: - type = "swipe"; - break; - default: - case Unknown: - type = "unknown"; - break; - } - return type; - } - - // endregion - - // region scroll logic - private static final class ScrollState { - private @NotNull GestureType type = GestureType.Unknown; - private @Nullable UiElement target; - private float startX = 0f; - private float startY = 0f; - - private void setTarget(final @NotNull UiElement target) { - this.target = target; - } - - /** - * Calculates the direction of the scroll/swipe based on startX and startY and a given event - * - * @param endEvent - the event which notifies when the scroll/swipe ended - * @return String, one of (left|right|up|down) - */ - private @NotNull String calculateDirection(MotionEvent endEvent) { - final float diffX = endEvent.getX() - startX; - final float diffY = endEvent.getY() - startY; - final String direction; - if (Math.abs(diffX) > Math.abs(diffY)) { - if (diffX > 0f) { - direction = "right"; - } else { - direction = "left"; - } - } else { - if (diffY > 0) { - direction = "down"; - } else { - direction = "up"; - } - } - return direction; - } - - private void reset() { - target = null; - type = GestureType.Unknown; - startX = 0f; - startY = 0f; - } - } - // endregion -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryWindowCallback.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryWindowCallback.java deleted file mode 100644 index edb9c9f9daa..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryWindowCallback.java +++ /dev/null @@ -1,91 +0,0 @@ -package io.sentry.android.core.internal.gestures; - -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.view.MotionEvent; -import android.view.Window; -import androidx.core.view.GestureDetectorCompat; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.SpanStatus; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public final class SentryWindowCallback extends WindowCallbackAdapter { - - private final @NotNull Window.Callback delegate; - private final @NotNull SentryGestureListener gestureListener; - private final @NotNull GestureDetectorCompat gestureDetector; - private final @Nullable SentryOptions options; - private final @NotNull MotionEventObtainer motionEventObtainer; - - public SentryWindowCallback( - final @NotNull Window.Callback delegate, - final @NotNull Context context, - final @NotNull SentryGestureListener gestureListener, - final @Nullable SentryOptions options) { - this( - delegate, - new GestureDetectorCompat(context, gestureListener, new Handler(Looper.getMainLooper())), - gestureListener, - options, - new MotionEventObtainer() {}); - } - - SentryWindowCallback( - final @NotNull Window.Callback delegate, - final @NotNull GestureDetectorCompat gestureDetector, - final @NotNull SentryGestureListener gestureListener, - final @Nullable SentryOptions options, - final @NotNull MotionEventObtainer motionEventObtainer) { - super(delegate); - this.delegate = delegate; - this.gestureListener = gestureListener; - this.options = options; - this.gestureDetector = gestureDetector; - this.motionEventObtainer = motionEventObtainer; - } - - @Override - public boolean dispatchTouchEvent(final @Nullable MotionEvent motionEvent) { - if (motionEvent != null) { - final MotionEvent copy = motionEventObtainer.obtain(motionEvent); - try { - handleTouchEvent(copy); - } catch (Throwable e) { - if (options != null) { - options.getLogger().log(SentryLevel.ERROR, "Error dispatching touch event", e); - } - } finally { - copy.recycle(); - } - } - return super.dispatchTouchEvent(motionEvent); - } - - private void handleTouchEvent(final @NotNull MotionEvent motionEvent) { - gestureDetector.onTouchEvent(motionEvent); - int action = motionEvent.getActionMasked(); - if (action == MotionEvent.ACTION_UP) { - gestureListener.onUp(motionEvent); - } - } - - public void stopTracking() { - gestureListener.stopTracing(SpanStatus.CANCELLED); - } - - public @NotNull Window.Callback getDelegate() { - return delegate; - } - - interface MotionEventObtainer { - @NotNull - default MotionEvent obtain(@NotNull MotionEvent origin) { - return MotionEvent.obtain(origin); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/ViewUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/ViewUtils.java deleted file mode 100644 index 501a05a5007..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/ViewUtils.java +++ /dev/null @@ -1,138 +0,0 @@ -package io.sentry.android.core.internal.gestures; - -import android.content.res.Resources; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import io.sentry.android.core.SentryAndroidOptions; -import io.sentry.internal.gestures.GestureTargetLocator; -import io.sentry.internal.gestures.UiElement; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public final class ViewUtils { - - private static final int[] coordinates = new int[2]; - - /** - * Verifies if the given touch coordinates are within the bounds of the given view. - * - * @param view the view to check if the touch coordinates are within its bounds - * @param x - the x coordinate of a {@link MotionEvent} - * @param y - the y coordinate of {@link MotionEvent} - * @return true if the touch coordinates are within the bounds of the view, false otherwise - */ - private static boolean touchWithinBounds( - final @Nullable View view, final float x, final float y) { - if (view == null) { - return false; - } - - view.getLocationOnScreen(coordinates); - int vx = coordinates[0]; - int vy = coordinates[1]; - - int w = view.getWidth(); - int h = view.getHeight(); - - return !(x < vx || x > vx + w || y < vy || y > vy + h); - } - - /** - * Finds a target view, that has been selected/clicked by the given coordinates x and y and the - * given {@code viewTargetSelector}. - * - * @param decorView - the root view of this window - * @param x - the x coordinate of a {@link MotionEvent} - * @param y - the y coordinate of {@link MotionEvent} - * @param targetType - the type of target to find - * @return the {@link View} that contains the touch coordinates and complements the {@code - * viewTargetSelector} - */ - static @Nullable UiElement findTarget( - final @NotNull SentryAndroidOptions options, - final @NotNull View decorView, - final float x, - final float y, - final UiElement.Type targetType) { - - final List locators = options.getGestureTargetLocators(); - final Queue queue = new LinkedList<>(); - queue.add(decorView); - - @Nullable UiElement target = null; - while (queue.size() > 0) { - final View view = queue.poll(); - - if (!touchWithinBounds(view, x, y)) { - // if the touch is not hitting the view, skip traversal of its children - continue; - } - - if (view instanceof ViewGroup) { - final ViewGroup viewGroup = (ViewGroup) view; - for (int i = 0; i < viewGroup.getChildCount(); i++) { - queue.add(viewGroup.getChildAt(i)); - } - } - - for (int i = 0; i < locators.size(); i++) { - final GestureTargetLocator locator = locators.get(i); - final @Nullable UiElement newTarget = locator.locate(view, x, y, targetType); - if (newTarget != null) { - if (targetType == UiElement.Type.CLICKABLE) { - target = newTarget; - } else if (targetType == UiElement.Type.SCROLLABLE) { - return newTarget; - } - } - } - } - return target; - } - - /** - * Retrieves the human-readable view id based on {@code view.getContext().getResources()}, falls - * back to a hexadecimal id representation in case the view id is not available in the resources. - * - * @param view - the view that the id is being retrieved for. - * @return human-readable view id - */ - static String getResourceIdWithFallback(final @NotNull View view) { - final int viewId = view.getId(); - try { - return getResourceId(view); - } catch (Resources.NotFoundException e) { - // fall back to hex representation of the id - return "0x" + Integer.toString(viewId, 16); - } - } - - /** - * Retrieves the human-readable view id based on {@code view.getContext().getResources()}. - * - * @param view - the view whose id is being retrieved - * @return human-readable view id - * @throws Resources.NotFoundException in case the view id was not found - */ - public static String getResourceId(final @NotNull View view) throws Resources.NotFoundException { - final int viewId = view.getId(); - if (viewId == View.NO_ID || isViewIdGenerated(viewId)) { - throw new Resources.NotFoundException(); - } - final Resources resources = view.getContext().getResources(); - if (resources != null) { - return resources.getResourceEntryName(viewId); - } - return ""; - } - - private static boolean isViewIdGenerated(int id) { - return (id & 0xFF000000) == 0 && (id & 0x00FFFFFF) != 0; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/WindowCallbackAdapter.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/WindowCallbackAdapter.java deleted file mode 100644 index c6a3e627115..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/WindowCallbackAdapter.java +++ /dev/null @@ -1,146 +0,0 @@ -package io.sentry.android.core.internal.gestures; - -import android.annotation.SuppressLint; -import android.view.ActionMode; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.SearchEvent; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityEvent; -import com.jakewharton.nopen.annotation.Open; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@Open -public class WindowCallbackAdapter implements Window.Callback { - - private final @NotNull Window.Callback delegate; - - public WindowCallbackAdapter(final Window.@NotNull Callback delegate) { - this.delegate = delegate; - } - - @Override - public boolean dispatchKeyEvent(KeyEvent keyEvent) { - return delegate.dispatchKeyEvent(keyEvent); - } - - @Override - public boolean dispatchKeyShortcutEvent(KeyEvent keyEvent) { - return delegate.dispatchKeyShortcutEvent(keyEvent); - } - - @Override - public boolean dispatchTouchEvent(@Nullable MotionEvent motionEvent) { - return delegate.dispatchTouchEvent(motionEvent); - } - - @Override - public boolean dispatchTrackballEvent(MotionEvent motionEvent) { - return delegate.dispatchTrackballEvent(motionEvent); - } - - @Override - public boolean dispatchGenericMotionEvent(MotionEvent motionEvent) { - return delegate.dispatchGenericMotionEvent(motionEvent); - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent accessibilityEvent) { - return delegate.dispatchPopulateAccessibilityEvent(accessibilityEvent); - } - - @Nullable - @Override - public View onCreatePanelView(int i) { - return delegate.onCreatePanelView(i); - } - - @Override - public boolean onCreatePanelMenu(int i, @NotNull Menu menu) { - return delegate.onCreatePanelMenu(i, menu); - } - - @Override - public boolean onPreparePanel(int i, @Nullable View view, @NotNull Menu menu) { - return delegate.onPreparePanel(i, view, menu); - } - - @Override - public boolean onMenuOpened(int i, @NotNull Menu menu) { - return delegate.onMenuOpened(i, menu); - } - - @Override - public boolean onMenuItemSelected(int i, @NotNull MenuItem menuItem) { - return delegate.onMenuItemSelected(i, menuItem); - } - - @Override - public void onWindowAttributesChanged(WindowManager.LayoutParams layoutParams) { - delegate.onWindowAttributesChanged(layoutParams); - } - - @Override - public void onContentChanged() { - delegate.onContentChanged(); - } - - @Override - public void onWindowFocusChanged(boolean b) { - delegate.onWindowFocusChanged(b); - } - - @Override - public void onAttachedToWindow() { - delegate.onAttachedToWindow(); - } - - @Override - public void onDetachedFromWindow() { - delegate.onDetachedFromWindow(); - } - - @Override - public void onPanelClosed(int i, @NotNull Menu menu) { - delegate.onPanelClosed(i, menu); - } - - @Override - public boolean onSearchRequested() { - return delegate.onSearchRequested(); - } - - @SuppressLint("NewApi") - @Override - public boolean onSearchRequested(SearchEvent searchEvent) { - return delegate.onSearchRequested(searchEvent); - } - - @Nullable - @Override - public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { - return delegate.onWindowStartingActionMode(callback); - } - - @SuppressLint("NewApi") - @Nullable - @Override - public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int i) { - return delegate.onWindowStartingActionMode(callback, i); - } - - @Override - public void onActionModeStarted(ActionMode actionMode) { - delegate.onActionModeStarted(actionMode); - } - - @Override - public void onActionModeFinished(ActionMode actionMode) { - delegate.onActionModeFinished(actionMode); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/modules/AssetsModulesLoader.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/modules/AssetsModulesLoader.java deleted file mode 100644 index 05bb75a60f4..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/modules/AssetsModulesLoader.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.sentry.android.core.internal.modules; - -import android.content.Context; -import io.sentry.ILogger; -import io.sentry.SentryLevel; -import io.sentry.android.core.ContextUtils; -import io.sentry.internal.modules.ModulesLoader; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; -import java.util.TreeMap; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public final class AssetsModulesLoader extends ModulesLoader { - - private final @NotNull Context context; - - public AssetsModulesLoader(final @NotNull Context context, final @NotNull ILogger logger) { - super(logger); - this.context = ContextUtils.getApplicationContext(context); - - // pre-load modules on a bg thread to avoid doing so on the main thread in case of a crash/error - //noinspection Convert2MethodRef - new Thread(() -> getOrLoadModules()).start(); - } - - @Override - protected Map loadModules() { - final Map modules = new TreeMap<>(); - - try (final InputStream stream = context.getAssets().open(EXTERNAL_MODULES_FILENAME)) { - return parseStream(stream); - } catch (FileNotFoundException e) { - logger.log(SentryLevel.INFO, "%s file was not found.", EXTERNAL_MODULES_FILENAME); - } catch (IOException e) { - logger.log(SentryLevel.ERROR, "Error extracting modules.", e); - } - return modules; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/Line.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/Line.java deleted file mode 100644 index 452c481ddc3..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/Line.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Adapted from https://cs.android.com/android/platform/superproject/+/master:development/tools/bugreport/src/com/android/bugreport/util/Line.java - * - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.sentry.android.core.internal.threaddump; - -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public final class Line { - public int lineno; - public @NotNull String text; - - public Line(final int lineno, final @NotNull String text) { - this.lineno = lineno; - this.text = text; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/Lines.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/Lines.java deleted file mode 100644 index 5c314e1b23f..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/Lines.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Adapted from https://cs.android.com/android/platform/superproject/+/master:development/tools/bugreport/src/com/android/bugreport/util/Lines.java - * - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.sentry.android.core.internal.threaddump; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.ArrayList; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * A stream of parsed lines. Can be rewound, and sub-regions cloned for recursive descent parsing. - */ -@ApiStatus.Internal -public final class Lines { - private final @NotNull ArrayList mList; - private final int mMin; - private final int mMax; - - /** The read position inside the list. */ - public int pos; - - /** Read the whole file into a Lines object. */ - public static Lines readLines(final @NotNull File file) throws IOException { - try (BufferedReader reader = new BufferedReader(new FileReader(file))) { - return Lines.readLines(reader); - } - } - - /** Read the whole file into a Lines object. */ - public static Lines readLines(final @NotNull BufferedReader in) throws IOException { - final ArrayList list = new ArrayList<>(); - - int lineno = 0; - String text; - while ((text = in.readLine()) != null) { - lineno++; - list.add(new Line(lineno, text)); - } - - return new Lines(list); - } - - /** Construct with a list of lines. */ - public Lines(final @NotNull ArrayList list) { - this.mList = list; - mMin = 0; - mMax = mList.size(); - } - - /** If there are more lines to read within the current range. */ - public boolean hasNext() { - return pos < mMax; - } - - /** - * Return the next line, or null if there are no more lines to read. Also returns null in the - * error condition where pos is before the beginning. - */ - @Nullable - public Line next() { - if (pos >= mMin && pos < mMax) { - return this.mList.get(pos++); - } else { - return null; - } - } - - /** Move the read position back by one line. */ - public void rewind() { - pos--; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/ThreadDumpParser.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/ThreadDumpParser.java deleted file mode 100644 index 5f70e39f8b8..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/ThreadDumpParser.java +++ /dev/null @@ -1,412 +0,0 @@ -/* - * Adapted from https://cs.android.com/android/platform/superproject/+/master:development/tools/bugreport/src/com/android/bugreport/stacks/ThreadSnapshotParser.java - * - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.sentry.android.core.internal.threaddump; - -import io.sentry.SentryLevel; -import io.sentry.SentryLockReason; -import io.sentry.SentryOptions; -import io.sentry.SentryStackTraceFactory; -import io.sentry.android.core.internal.util.NativeEventUtils; -import io.sentry.protocol.DebugImage; -import io.sentry.protocol.SentryStackFrame; -import io.sentry.protocol.SentryStackTrace; -import io.sentry.protocol.SentryThread; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class ThreadDumpParser { - private static final Pattern BEGIN_MANAGED_THREAD_RE = - Pattern.compile("\"(.*)\" (.*) ?prio=(\\d+)\\s+tid=(\\d+)\\s*(.*)"); - - private static final Pattern BEGIN_UNMANAGED_NATIVE_THREAD_RE = - Pattern.compile("\"(.*)\" (.*) ?sysTid=(\\d+)"); - - // For reference, see native_stack_dump.cc and tombstone_proto_to_text.cpp in Android sources - // Groups - // 0:entire regex - // 1:index - // 2:pc - // 3:mapinfo - // 4:filename - // 5:mapoffset - // 6:function - // 7:fnoffset - // 8:buildid - private static final Pattern NATIVE_RE = - Pattern.compile( - // " native: #12 pc 0xabcd1234" - " *(?:native: )?#(\\d+) \\S+ ([0-9a-fA-F]+)" - // The map info includes a filename and an optional offset into the file - + ("\\s+(" - // "/path/to/file.ext", - + "(.*?)" - // optional " (deleted)" suffix (deleted files) needed here to bias regex - // correctly - + "(?:\\s+\\(deleted\\))?" - // " (offset 0xabcd1234)", if the mapping is not into the beginning of the file - + "(?:\\s+\\(offset (.*?)\\))?" - + ")") - // Optional function - + ("(?:\\s+\\((?:" - + "\\?\\?\\?" // " (???) marks a missing function, so don't capture it in a group - + "|(.*?)(?:\\+(\\d+))?" // " (func+1234)", offset is - // optional - + ")\\))?") - // Optional " (BuildId: abcd1234abcd1234abcd1234abcd1234abcd1234)" - + "(?:\\s+\\(BuildId: (.*?)\\))?"); - - private static final Pattern JAVA_RE = - Pattern.compile(" *at (?:(.+)\\.)?([^.]+)\\.([^.]+)\\((.*):([\\d-]+)\\)"); - private static final Pattern JNI_RE = - Pattern.compile(" *at (?:(.+)\\.)?([^.]+)\\.([^.]+)\\(Native method\\)"); - private static final Pattern LOCKED_RE = - Pattern.compile(" *- locked \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)"); - private static final Pattern SLEEPING_ON_RE = - Pattern.compile(" *- sleeping on \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)"); - private static final Pattern WAITING_ON_RE = - Pattern.compile(" *- waiting on \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)"); - private static final Pattern WAITING_TO_LOCK_RE = - Pattern.compile( - " *- waiting to lock \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)"); - private static final Pattern WAITING_TO_LOCK_HELD_RE = - Pattern.compile( - " *- waiting to lock \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)" - + "(?: held by thread (\\d+))"); - private static final Pattern WAITING_TO_LOCK_UNKNOWN_RE = - Pattern.compile(" *- waiting to lock an unknown object"); - private static final Pattern BLANK_RE = Pattern.compile("\\s+"); - - private final @NotNull SentryOptions options; - - private final boolean isBackground; - - private final @NotNull SentryStackTraceFactory stackTraceFactory; - - private final @NotNull Map debugImages; - - private final @NotNull List threads; - - public ThreadDumpParser(final @NotNull SentryOptions options, final boolean isBackground) { - this.options = options; - this.isBackground = isBackground; - this.stackTraceFactory = new SentryStackTraceFactory(options); - this.debugImages = new HashMap<>(); - this.threads = new ArrayList<>(); - } - - @NotNull - public List getDebugImages() { - return new ArrayList<>(debugImages.values()); - } - - @NotNull - public List getThreads() { - return threads; - } - - public void parse(final @NotNull Lines lines) { - - final Matcher beginManagedThreadRe = BEGIN_MANAGED_THREAD_RE.matcher(""); - final Matcher beginUnmanagedNativeThreadRe = BEGIN_UNMANAGED_NATIVE_THREAD_RE.matcher(""); - - while (lines.hasNext()) { - final Line line = lines.next(); - if (line == null) { - options.getLogger().log(SentryLevel.WARNING, "Internal error while parsing thread dump."); - return; - } - final String text = line.text; - // we only handle managed threads, as unmanaged/not attached do not have the thread id and - // our protocol does not support this case - if (matches(beginManagedThreadRe, text) || matches(beginUnmanagedNativeThreadRe, text)) { - lines.rewind(); - - final SentryThread thread = parseThread(lines); - if (thread != null) { - threads.add(thread); - } - } - } - } - - private SentryThread parseThread(final @NotNull Lines lines) { - final SentryThread sentryThread = new SentryThread(); - - final Matcher beginManagedThreadRe = BEGIN_MANAGED_THREAD_RE.matcher(""); - final Matcher beginUnmanagedNativeThreadRe = BEGIN_UNMANAGED_NATIVE_THREAD_RE.matcher(""); - - // thread attributes - if (!lines.hasNext()) { - return null; - } - final Line line = lines.next(); - if (line == null) { - options.getLogger().log(SentryLevel.WARNING, "Internal error while parsing thread dump."); - return null; - } - if (matches(beginManagedThreadRe, line.text)) { - final Long tid = getLong(beginManagedThreadRe, 4, null); - if (tid == null) { - options.getLogger().log(SentryLevel.DEBUG, "No thread id in the dump, skipping thread."); - // tid is required by our protocol - return null; - } - sentryThread.setId(tid); - sentryThread.setName(beginManagedThreadRe.group(1)); - final String state = beginManagedThreadRe.group(5); - // sanitizing thread that have more details after their actual state, e.g. - // "Native (still starting up)" <- we just need "Native" here - if (state != null) { - if (state.contains(" ")) { - sentryThread.setState(state.substring(0, state.indexOf(' '))); - } else { - sentryThread.setState(state); - } - } - } else if (matches(beginUnmanagedNativeThreadRe, line.text)) { - final Long sysTid = getLong(beginUnmanagedNativeThreadRe, 3, null); - if (sysTid == null) { - options.getLogger().log(SentryLevel.DEBUG, "No thread id in the dump, skipping thread."); - // tid is required by our protocol - return null; - } - sentryThread.setId(sysTid); - sentryThread.setName(beginUnmanagedNativeThreadRe.group(1)); - } - - final String threadName = sentryThread.getName(); - if (threadName != null) { - final boolean isMain = threadName.equals("main"); - sentryThread.setMain(isMain); - // since it's an ANR, the crashed thread will always be main - sentryThread.setCrashed(isMain); - sentryThread.setCurrent(isMain && !isBackground); - } - - // thread stacktrace - final SentryStackTrace stackTrace = parseStacktrace(lines, sentryThread); - sentryThread.setStacktrace(stackTrace); - return sentryThread; - } - - @NotNull - private SentryStackTrace parseStacktrace( - final @NotNull Lines lines, final @NotNull SentryThread thread) { - final List frames = new ArrayList<>(); - SentryStackFrame lastJavaFrame = null; - - final Matcher nativeRe = NATIVE_RE.matcher(""); - final Matcher javaRe = JAVA_RE.matcher(""); - final Matcher jniRe = JNI_RE.matcher(""); - final Matcher lockedRe = LOCKED_RE.matcher(""); - final Matcher waitingOnRe = WAITING_ON_RE.matcher(""); - final Matcher sleepingOnRe = SLEEPING_ON_RE.matcher(""); - final Matcher waitingToLockHeldRe = WAITING_TO_LOCK_HELD_RE.matcher(""); - final Matcher waitingToLockRe = WAITING_TO_LOCK_RE.matcher(""); - final Matcher waitingToLockUnknownRe = WAITING_TO_LOCK_UNKNOWN_RE.matcher(""); - final Matcher blankRe = BLANK_RE.matcher(""); - - while (lines.hasNext()) { - final Line line = lines.next(); - if (line == null) { - options.getLogger().log(SentryLevel.WARNING, "Internal error while parsing thread dump."); - break; - } - final String text = line.text; - if (matches(javaRe, text)) { - final SentryStackFrame frame = new SentryStackFrame(); - final String packageName = javaRe.group(1); - final String className = javaRe.group(2); - final String module = String.format("%s.%s", packageName, className); - frame.setModule(module); - frame.setFunction(javaRe.group(3)); - frame.setFilename(javaRe.group(4)); - frame.setLineno(getUInteger(javaRe, 5, null)); - frame.setInApp(stackTraceFactory.isInApp(module)); - frames.add(frame); - lastJavaFrame = frame; - } else if (matches(nativeRe, text)) { - final SentryStackFrame frame = new SentryStackFrame(); - frame.setPackage(nativeRe.group(3)); - frame.setFunction(nativeRe.group(6)); - frame.setLineno(getInteger(nativeRe, 7, null)); - frame.setInstructionAddr("0x" + nativeRe.group(2)); - frame.setPlatform("native"); - - final String buildId = nativeRe.group(8); - final String debugId = buildId == null ? null : NativeEventUtils.buildIdToDebugId(buildId); - if (debugId != null) { - if (!debugImages.containsKey(debugId)) { - final DebugImage debugImage = new DebugImage(); - debugImage.setDebugId(debugId); - debugImage.setType("elf"); - debugImage.setCodeFile(nativeRe.group(4)); - debugImage.setCodeId(buildId); - debugImages.put(debugId, debugImage); - } - // The addresses in the thread dump are relative to the image - frame.setAddrMode("rel:" + debugId); - } - - frames.add(frame); - lastJavaFrame = null; - } else if (matches(jniRe, text)) { - final SentryStackFrame frame = new SentryStackFrame(); - final String packageName = jniRe.group(1); - final String className = jniRe.group(2); - final String module = String.format("%s.%s", packageName, className); - frame.setModule(module); - frame.setFunction(jniRe.group(3)); - frame.setInApp(stackTraceFactory.isInApp(module)); - frame.setNative(true); - frames.add(frame); - lastJavaFrame = frame; - } else if (matches(lockedRe, text)) { - if (lastJavaFrame != null) { - final SentryLockReason lock = new SentryLockReason(); - lock.setType(SentryLockReason.LOCKED); - lock.setAddress(lockedRe.group(1)); - lock.setPackageName(lockedRe.group(2)); - lock.setClassName(lockedRe.group(3)); - lastJavaFrame.setLock(lock); - combineThreadLocks(thread, lock); - } - } else if (matches(waitingOnRe, text)) { - if (lastJavaFrame != null) { - final SentryLockReason lock = new SentryLockReason(); - lock.setType(SentryLockReason.WAITING); - lock.setAddress(waitingOnRe.group(1)); - lock.setPackageName(waitingOnRe.group(2)); - lock.setClassName(waitingOnRe.group(3)); - lastJavaFrame.setLock(lock); - combineThreadLocks(thread, lock); - } - } else if (matches(sleepingOnRe, text)) { - if (lastJavaFrame != null) { - final SentryLockReason lock = new SentryLockReason(); - lock.setType(SentryLockReason.SLEEPING); - lock.setAddress(sleepingOnRe.group(1)); - lock.setPackageName(sleepingOnRe.group(2)); - lock.setClassName(sleepingOnRe.group(3)); - lastJavaFrame.setLock(lock); - combineThreadLocks(thread, lock); - } - } else if (matches(waitingToLockHeldRe, text)) { - if (lastJavaFrame != null) { - final SentryLockReason lock = new SentryLockReason(); - lock.setType(SentryLockReason.BLOCKED); - lock.setAddress(waitingToLockHeldRe.group(1)); - lock.setPackageName(waitingToLockHeldRe.group(2)); - lock.setClassName(waitingToLockHeldRe.group(3)); - lock.setThreadId(getLong(waitingToLockHeldRe, 4, null)); - lastJavaFrame.setLock(lock); - combineThreadLocks(thread, lock); - } - } else if (matches(waitingToLockRe, text)) { - if (lastJavaFrame != null) { - final SentryLockReason lock = new SentryLockReason(); - lock.setType(SentryLockReason.BLOCKED); - lock.setAddress(waitingToLockRe.group(1)); - lock.setPackageName(waitingToLockRe.group(2)); - lock.setClassName(waitingToLockRe.group(3)); - lastJavaFrame.setLock(lock); - combineThreadLocks(thread, lock); - } - } else if (matches(waitingToLockUnknownRe, text)) { - if (lastJavaFrame != null) { - final SentryLockReason lock = new SentryLockReason(); - lock.setType(SentryLockReason.BLOCKED); - lastJavaFrame.setLock(lock); - combineThreadLocks(thread, lock); - } - } else if (text.length() == 0 || matches(blankRe, text)) { - break; - } - } - - // Sentry expects frames to be in reverse order - Collections.reverse(frames); - final SentryStackTrace stackTrace = new SentryStackTrace(frames); - // it's a thread dump - stackTrace.setSnapshot(true); - return stackTrace; - } - - private boolean matches(final @NotNull Matcher matcher, final @NotNull String text) { - matcher.reset(text); - return matcher.matches(); - } - - private void combineThreadLocks( - final @NotNull SentryThread thread, final @NotNull SentryLockReason lockReason) { - Map heldLocks = thread.getHeldLocks(); - if (heldLocks == null) { - heldLocks = new HashMap<>(); - } - final SentryLockReason prev = heldLocks.get(lockReason.getAddress()); - if (prev != null) { - // higher type prevails as we are tagging thread with the most severe lock reason - prev.setType(Math.max(prev.getType(), lockReason.getType())); - } else { - heldLocks.put(lockReason.getAddress(), new SentryLockReason(lockReason)); - } - thread.setHeldLocks(heldLocks); - } - - @Nullable - private Long getLong( - final @NotNull Matcher matcher, final int group, final @Nullable Long defaultValue) { - final String str = matcher.group(group); - if (str == null || str.length() == 0) { - return defaultValue; - } else { - return Long.parseLong(str); - } - } - - @Nullable - private Integer getInteger( - final @NotNull Matcher matcher, final int groupIndex, final @Nullable Integer defaultValue) { - final String str = matcher.group(groupIndex); - if (str == null || str.length() == 0) { - return defaultValue; - } else { - return Integer.parseInt(str); - } - } - - @Nullable - private Integer getUInteger( - final @NotNull Matcher matcher, final int group, final @Nullable Integer defaultValue) { - final String str = matcher.group(group); - if (str == null || str.length() == 0) { - return defaultValue; - } else { - final Integer parsed = Integer.parseInt(str); - return parsed >= 0 ? parsed : defaultValue; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/NativeExceptionMechanism.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/NativeExceptionMechanism.java deleted file mode 100644 index 2dad712b193..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/NativeExceptionMechanism.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.sentry.android.core.internal.tombstone; - -import androidx.annotation.NonNull; - -/** Mechanism types for native crashes. */ -public enum NativeExceptionMechanism { - TOMBSTONE("Tombstone"), - SIGNAL_HANDLER("signalhandler"), - TOMBSTONE_MERGED("TombstoneMerged"); - - private final @NonNull String value; - - NativeExceptionMechanism(@NonNull final String value) { - this.value = value; - } - - @NonNull - public String getValue() { - return value; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/TombstoneParser.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/TombstoneParser.java deleted file mode 100644 index 3235b566556..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/TombstoneParser.java +++ /dev/null @@ -1,338 +0,0 @@ -package io.sentry.android.core.internal.tombstone; - -import androidx.annotation.NonNull; -import io.sentry.SentryEvent; -import io.sentry.SentryLevel; -import io.sentry.SentryStackTraceFactory; -import io.sentry.android.core.internal.util.NativeEventUtils; -import io.sentry.protocol.DebugImage; -import io.sentry.protocol.DebugMeta; -import io.sentry.protocol.Mechanism; -import io.sentry.protocol.Message; -import io.sentry.protocol.SentryException; -import io.sentry.protocol.SentryStackFrame; -import io.sentry.protocol.SentryStackTrace; -import io.sentry.protocol.SentryThread; -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class TombstoneParser implements Closeable { - - private final InputStream tombstoneStream; - @NotNull private final List inAppIncludes; - @NotNull private final List inAppExcludes; - @Nullable private final String nativeLibraryDir; - private final Map excTypeValueMap = new HashMap<>(); - - private static String formatHex(long value) { - return String.format("0x%x", value); - } - - public TombstoneParser( - @NonNull final InputStream tombstoneStream, - @NotNull List inAppIncludes, - @NotNull List inAppExcludes, - @Nullable String nativeLibraryDir) { - this.tombstoneStream = tombstoneStream; - this.inAppIncludes = inAppIncludes; - this.inAppExcludes = inAppExcludes; - this.nativeLibraryDir = nativeLibraryDir; - - // keep the current signal type -> value mapping for compatibility - excTypeValueMap.put("SIGILL", "IllegalInstruction"); - excTypeValueMap.put("SIGTRAP", "Trap"); - excTypeValueMap.put("SIGABRT", "Abort"); - excTypeValueMap.put("SIGBUS", "BusError"); - excTypeValueMap.put("SIGFPE", "FloatingPointException"); - excTypeValueMap.put("SIGSEGV", "Segfault"); - } - - @NonNull - public SentryEvent parse() throws IOException { - @NonNull - final TombstoneProtos.Tombstone tombstone = - TombstoneProtos.Tombstone.parseFrom(tombstoneStream); - - final SentryEvent event = new SentryEvent(); - event.setLevel(SentryLevel.FATAL); - - // must use the "native" platform because otherwise the stack-trace wouldn't be correctly parsed - event.setPlatform("native"); - - event.setMessage(constructMessage(tombstone)); - event.setDebugMeta(createDebugMeta(tombstone)); - event.setExceptions(createException(tombstone)); - event.setThreads( - createThreads(tombstone, Objects.requireNonNull(event.getExceptions()).get(0))); - - return event; - } - - @NonNull - private List createThreads( - @NonNull final TombstoneProtos.Tombstone tombstone, @NonNull final SentryException exc) { - final List threads = new ArrayList<>(); - for (Map.Entry threadEntry : - tombstone.getThreadsMap().entrySet()) { - final TombstoneProtos.Thread threadEntryValue = threadEntry.getValue(); - - final SentryThread thread = new SentryThread(); - thread.setId(Long.valueOf(threadEntry.getKey())); - thread.setName(threadEntryValue.getName()); - - final SentryStackTrace stacktrace = createStackTrace(threadEntryValue); - thread.setStacktrace(stacktrace); - if (tombstone.getTid() == threadEntryValue.getId()) { - thread.setCrashed(true); - // even though we refer to the thread_id from the exception, - // the backend currently requires a stack-trace in exception - exc.setStacktrace(stacktrace); - } - threads.add(thread); - } - - return threads; - } - - @NonNull - private SentryStackTrace createStackTrace(@NonNull final TombstoneProtos.Thread thread) { - final List frames = new ArrayList<>(); - - for (TombstoneProtos.BacktraceFrame frame : thread.getCurrentBacktraceList()) { - if (frame.getFileName().endsWith("libart.so")) { - // We ignore all ART frames for time being because they aren't actionable for app developers - continue; - } - if (frame.getFileName().startsWith(" registers = new HashMap<>(); - for (TombstoneProtos.Register register : thread.getRegistersList()) { - registers.put(register.getName(), formatHex(register.getU64())); - } - stacktrace.setRegisters(registers); - - return stacktrace; - } - - @NonNull - private List createException(@NonNull TombstoneProtos.Tombstone tombstone) { - final SentryException exception = new SentryException(); - - if (tombstone.hasSignalInfo()) { - final TombstoneProtos.Signal signalInfo = tombstone.getSignalInfo(); - exception.setType(signalInfo.getName()); - exception.setValue(excTypeValueMap.get(signalInfo.getName())); - exception.setMechanism(createMechanismFromSignalInfo(signalInfo)); - } - - exception.setThreadId((long) tombstone.getTid()); - final List exceptions = new ArrayList<>(1); - exceptions.add(exception); - - return exceptions; - } - - @NonNull - private static Mechanism createMechanismFromSignalInfo( - @NonNull final TombstoneProtos.Signal signalInfo) { - - final Mechanism mechanism = new Mechanism(); - mechanism.setType(NativeExceptionMechanism.TOMBSTONE.getValue()); - mechanism.setHandled(false); - mechanism.setSynthetic(true); - - final Map meta = new HashMap<>(); - meta.put("number", signalInfo.getNumber()); - meta.put("name", signalInfo.getName()); - meta.put("code", signalInfo.getCode()); - meta.put("code_name", signalInfo.getCodeName()); - mechanism.setMeta(meta); - - return mechanism; - } - - @NonNull - private Message constructMessage(@NonNull final TombstoneProtos.Tombstone tombstone) { - final Message message = new Message(); - final TombstoneProtos.Signal signalInfo = tombstone.getSignalInfo(); - - // reproduce the message `debuggerd` would use to dump the stack trace in logcat - String command = String.join(" ", tombstone.getCommandLineList()); - if (tombstone.hasSignalInfo()) { - String abortMessage = tombstone.getAbortMessage(); - message.setFormatted( - String.format( - Locale.ROOT, - "%sFatal signal %s (%d), %s (%d), pid = %d (%s)", - !abortMessage.isEmpty() ? abortMessage + ": " : "", - signalInfo.getName(), - signalInfo.getNumber(), - signalInfo.getCodeName(), - signalInfo.getCode(), - tombstone.getPid(), - command)); - } else { - message.setFormatted( - String.format(Locale.ROOT, "Fatal exit pid = %d (%s)", tombstone.getPid(), command)); - } - - return message; - } - - /** - * Helper class to accumulate memory mappings into a single module. Modules in the Sentry sense - * are the entire readable memory map for a file, not just the executable segment. This is - * important to maintain the file-offset contract of map entries, which is necessary to resolve - * runtime instruction addresses in the files uploaded for symbolication. - */ - private static class ModuleAccumulator { - String mappingName; - String buildId; - long beginAddress; - long endAddress; - - ModuleAccumulator(TombstoneProtos.MemoryMapping mapping) { - this.mappingName = mapping.getMappingName(); - this.buildId = mapping.getBuildId(); - this.beginAddress = mapping.getBeginAddress(); - this.endAddress = mapping.getEndAddress(); - } - - void extendTo(long newEndAddress) { - this.endAddress = newEndAddress; - } - - DebugImage toDebugImage() { - if (buildId.isEmpty()) { - return null; - } - final DebugImage image = new DebugImage(); - image.setCodeId(buildId); - image.setCodeFile(mappingName); - - final String debugId = NativeEventUtils.buildIdToDebugId(buildId); - image.setDebugId(debugId != null ? debugId : buildId); - - image.setImageAddr(formatHex(beginAddress)); - image.setImageSize(endAddress - beginAddress); - image.setType("elf"); - - return image; - } - } - - private DebugMeta createDebugMeta(@NonNull final TombstoneProtos.Tombstone tombstone) { - final List images = new ArrayList<>(); - - // Coalesce memory mappings into modules similar to how sentry-native does it. - // A module consists of all readable mappings for the same file, starting from - // the first mapping that has a valid ELF header (indicated by offset 0 with build_id). - // In sentry-native, is_valid_elf_header() reads the ELF magic bytes from memory, - // which is only present at the start of the file (offset 0). We use offset == 0 - // combined with non-empty build_id as a proxy for this check. - ModuleAccumulator currentModule = null; - - for (TombstoneProtos.MemoryMapping mapping : tombstone.getMemoryMappingsList()) { - // Skip mappings that are not readable - if (!mapping.getRead()) { - continue; - } - - // Skip mappings with empty name or in /dev/ - final String mappingName = mapping.getMappingName(); - if (mappingName.isEmpty() || mappingName.startsWith("/dev/")) { - continue; - } - - final boolean hasBuildId = !mapping.getBuildId().isEmpty(); - final boolean isFileStart = mapping.getOffset() == 0; - - if (hasBuildId && isFileStart) { - // Check for duplicated mappings: On Android, the same ELF can have multiple - // mappings at offset 0 with different permissions (r--p, r-xp, r--p). - // If it's the same file as the current module, just extend it. - if (currentModule != null && mappingName.equals(currentModule.mappingName)) { - currentModule.extendTo(mapping.getEndAddress()); - continue; - } - - // Flush the previous module (different file) - if (currentModule != null) { - final DebugImage image = currentModule.toDebugImage(); - if (image != null) { - images.add(image); - } - } - - // Start a new module - currentModule = new ModuleAccumulator(mapping); - } else if (currentModule != null && mappingName.equals(currentModule.mappingName)) { - // Extend the current module with this mapping (same file, continuation) - currentModule.extendTo(mapping.getEndAddress()); - } - } - - // Flush the last module - if (currentModule != null) { - final DebugImage image = currentModule.toDebugImage(); - if (image != null) { - images.add(image); - } - } - - final DebugMeta debugMeta = new DebugMeta(); - debugMeta.setImages(images); - - return debugMeta; - } - - @Override - public void close() throws IOException { - tombstoneStream.close(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidConnectionStatusProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidConnectionStatusProvider.java deleted file mode 100644 index 3f05beeceb2..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidConnectionStatusProvider.java +++ /dev/null @@ -1,831 +0,0 @@ -package io.sentry.android.core.internal.util; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.ConnectivityManager.NetworkCallback; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.os.Build; -import android.os.Handler; -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import io.sentry.IConnectionStatusProvider; -import io.sentry.ILogger; -import io.sentry.ISentryLifecycleToken; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.android.core.AppState; -import io.sentry.android.core.BuildInfoProvider; -import io.sentry.android.core.ContextUtils; -import io.sentry.transport.ICurrentDateProvider; -import io.sentry.util.AutoClosableReentrantLock; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -/** - * Note: ConnectivityManager sometimes throws SecurityExceptions on Android 11. Hence all relevant - * calls are guarded with try/catch. see https://issuetracker.google.com/issues/175055271 for more - * details - */ -@ApiStatus.Internal -public final class AndroidConnectionStatusProvider - implements IConnectionStatusProvider, AppState.AppStateListener { - - private final @NotNull Context context; - private final @NotNull SentryOptions options; - private final @NotNull BuildInfoProvider buildInfoProvider; - private final @NotNull ICurrentDateProvider timeProvider; - private final @NotNull List connectionStatusObservers; - private final @Nullable Handler handler; - private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - private volatile @Nullable NetworkCallback networkCallback; - - private static final @NotNull AutoClosableReentrantLock connectivityManagerLock = - new AutoClosableReentrantLock(); - private static volatile @Nullable ConnectivityManager connectivityManager; - - private static final @NotNull AutoClosableReentrantLock childCallbacksLock = - new AutoClosableReentrantLock(); - private static final @NotNull List childCallbacks = new ArrayList<>(); - - private static final int[] transports = { - NetworkCapabilities.TRANSPORT_WIFI, - NetworkCapabilities.TRANSPORT_CELLULAR, - NetworkCapabilities.TRANSPORT_ETHERNET, - NetworkCapabilities.TRANSPORT_BLUETOOTH - }; - - private static final int[] capabilities = new int[2]; - - private volatile @Nullable NetworkCapabilities cachedNetworkCapabilities; - private volatile @Nullable Network currentNetwork; - private volatile long lastCacheUpdateTime = 0; - private static final long CACHE_TTL_MS = 2 * 60 * 1000L; // 2 minutes - private final @NotNull AtomicBoolean isConnected = new AtomicBoolean(false); - - public AndroidConnectionStatusProvider( - @NotNull Context context, - @NotNull SentryOptions options, - @NotNull BuildInfoProvider buildInfoProvider, - @NotNull ICurrentDateProvider timeProvider) { - this(context, options, buildInfoProvider, timeProvider, null); - } - - @SuppressLint("InlinedApi") - public AndroidConnectionStatusProvider( - @NotNull Context context, - @NotNull SentryOptions options, - @NotNull BuildInfoProvider buildInfoProvider, - @NotNull ICurrentDateProvider timeProvider, - @Nullable Handler handler) { - this.context = ContextUtils.getApplicationContext(context); - this.options = options; - this.buildInfoProvider = buildInfoProvider; - this.timeProvider = timeProvider; - this.handler = handler; - this.connectionStatusObservers = new ArrayList<>(); - - capabilities[0] = NetworkCapabilities.NET_CAPABILITY_INTERNET; - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.M) { - capabilities[1] = NetworkCapabilities.NET_CAPABILITY_VALIDATED; - } - - // Register network callback immediately for caching - //noinspection Convert2MethodRef - submitSafe(() -> ensureNetworkCallbackRegistered()); - - AppState.getInstance().addAppStateListener(this); - } - - /** - * Enhanced network connectivity check for Android 15. Checks for NET_CAPABILITY_INTERNET, - * NET_CAPABILITY_VALIDATED, and proper transport types. - * https://medium.com/@doronkakuli/adapting-your-network-connectivity-checks-for-android-15-a-practical-guide-2b1850619294 - */ - @SuppressLint("InlinedApi") - private boolean isNetworkEffectivelyConnected( - final @Nullable NetworkCapabilities networkCapabilities) { - if (networkCapabilities == null) { - return false; - } - - // Check for general internet capability AND validated status - boolean hasInternetAndValidated = - networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.M) { - hasInternetAndValidated = - hasInternetAndValidated - && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); - } - - if (!hasInternetAndValidated) { - return false; - } - - // Additionally, ensure it's a recognized transport type for general internet access - for (final int transport : transports) { - if (networkCapabilities.hasTransport(transport)) { - return true; - } - } - return false; - } - - /** Get connection status from cached NetworkCapabilities or fallback to legacy method. */ - private @NotNull ConnectionStatus getConnectionStatusFromCache() { - if (cachedNetworkCapabilities != null) { - return isNetworkEffectivelyConnected(cachedNetworkCapabilities) - ? ConnectionStatus.CONNECTED - : ConnectionStatus.DISCONNECTED; - } - - // Fallback to legacy method when NetworkCapabilities not available - final ConnectivityManager connectivityManager = - getConnectivityManager(context, options.getLogger()); - if (connectivityManager != null) { - return getConnectionStatus(context, connectivityManager, options.getLogger()); - } - - return ConnectionStatus.UNKNOWN; - } - - /** Get connection type from cached NetworkCapabilities or fallback to legacy method. */ - private @Nullable String getConnectionTypeFromCache() { - final NetworkCapabilities capabilities = cachedNetworkCapabilities; - if (capabilities != null) { - return getConnectionType(capabilities); - } - - // Fallback to legacy method when NetworkCapabilities not available - return getConnectionType(context, options.getLogger(), buildInfoProvider); - } - - private void ensureNetworkCallbackRegistered() { - if (!ContextUtils.isForegroundImportance()) { - return; - } - - if (networkCallback != null) { - return; // Already registered - } - - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (networkCallback != null) { - return; - } - - final @NotNull NetworkCallback callback = - new NetworkCallback() { - @Override - public void onAvailable(final @NotNull Network network) { - currentNetwork = network; - - // have to only dispatch this on first registration + when the connection got - // re-established - // otherwise it would've been dispatched on every foreground - if (!isConnected.getAndSet(true)) { - try (final @NotNull ISentryLifecycleToken ignored = childCallbacksLock.acquire()) { - for (final @NotNull NetworkCallback cb : childCallbacks) { - cb.onAvailable(network); - } - } - } - } - - @RequiresApi(Build.VERSION_CODES.O) - @Override - public void onUnavailable() { - clearCacheAndNotifyObservers(); - - try (final @NotNull ISentryLifecycleToken ignored = childCallbacksLock.acquire()) { - for (final @NotNull NetworkCallback cb : childCallbacks) { - cb.onUnavailable(); - } - } - } - - @Override - public void onLost(final @NotNull Network network) { - if (!network.equals(currentNetwork)) { - return; - } - clearCacheAndNotifyObservers(); - - try (final @NotNull ISentryLifecycleToken ignored = childCallbacksLock.acquire()) { - for (final @NotNull NetworkCallback cb : childCallbacks) { - cb.onLost(network); - } - } - } - - private void clearCacheAndNotifyObservers() { - isConnected.set(false); - // Clear cached capabilities and network reference atomically - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - cachedNetworkCapabilities = null; - currentNetwork = null; - lastCacheUpdateTime = timeProvider.getCurrentTimeMillis(); - - options - .getLogger() - .log(SentryLevel.DEBUG, "Cache cleared - network lost/unavailable"); - - // Notify all observers with DISCONNECTED status directly - // No need to query ConnectivityManager - we know the network is gone - for (final @NotNull IConnectionStatusObserver observer : - connectionStatusObservers) { - observer.onConnectionStatusChanged(ConnectionStatus.DISCONNECTED); - } - } - } - - @Override - public void onCapabilitiesChanged( - @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) { - if (!network.equals(currentNetwork)) { - return; - } - updateCacheAndNotifyObservers(network, networkCapabilities); - - try (final @NotNull ISentryLifecycleToken ignored = childCallbacksLock.acquire()) { - for (final @NotNull NetworkCallback cb : childCallbacks) { - cb.onCapabilitiesChanged(network, networkCapabilities); - } - } - } - - private void updateCacheAndNotifyObservers( - @Nullable Network network, @Nullable NetworkCapabilities networkCapabilities) { - // Check if this change is meaningful before notifying observers - final boolean shouldUpdate = isSignificantChange(networkCapabilities); - - // Only notify observers if something meaningful changed - if (shouldUpdate) { - updateCache(networkCapabilities); - - final @NotNull ConnectionStatus status = getConnectionStatusFromCache(); - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - for (final @NotNull IConnectionStatusObserver observer : - connectionStatusObservers) { - observer.onConnectionStatusChanged(status); - } - } - } - } - - /** - * Check if NetworkCapabilities change is significant for our observers. Only notify for - * changes that affect connectivity status or connection type. - */ - private boolean isSignificantChange(@Nullable NetworkCapabilities newCapabilities) { - final NetworkCapabilities oldCapabilities = cachedNetworkCapabilities; - - // Always significant if transitioning between null and non-null - if ((oldCapabilities == null) != (newCapabilities == null)) { - return true; - } - - // If both null, no change - if (oldCapabilities == null && newCapabilities == null) { - return false; - } - - // Check significant capability changes - if (hasSignificantCapabilityChanges(oldCapabilities, newCapabilities)) { - return true; - } - - // Check significant transport changes - if (hasSignificantTransportChanges(oldCapabilities, newCapabilities)) { - return true; - } - - return false; - } - - /** Check if capabilities that affect connectivity status changed. */ - private boolean hasSignificantCapabilityChanges( - @NotNull NetworkCapabilities old, @NotNull NetworkCapabilities new_) { - // Check capabilities we care about for connectivity determination - for (int capability : capabilities) { - if (capability != 0 - && old.hasCapability(capability) != new_.hasCapability(capability)) { - return true; - } - } - - return false; - } - - /** Check if transport types that affect connection type changed. */ - private boolean hasSignificantTransportChanges( - @NotNull NetworkCapabilities old, @NotNull NetworkCapabilities new_) { - // Check transports we care about for connection type determination - for (int transport : transports) { - if (old.hasTransport(transport) != new_.hasTransport(transport)) { - return true; - } - } - - return false; - } - }; - - if (registerNetworkCallback( - context, options.getLogger(), buildInfoProvider, handler, callback)) { - networkCallback = callback; - options.getLogger().log(SentryLevel.DEBUG, "Network callback registered successfully"); - } else { - options.getLogger().log(SentryLevel.WARNING, "Failed to register network callback"); - } - } - } - - @SuppressLint({"NewApi", "MissingPermission"}) - private void updateCache(@Nullable NetworkCapabilities networkCapabilities) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - try { - if (networkCapabilities != null) { - cachedNetworkCapabilities = networkCapabilities; - } else { - if (!Permissions.hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE)) { - options - .getLogger() - .log( - SentryLevel.INFO, - "No permission (ACCESS_NETWORK_STATE) to check network status."); - cachedNetworkCapabilities = null; - lastCacheUpdateTime = timeProvider.getCurrentTimeMillis(); - return; - } - - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.M) { - cachedNetworkCapabilities = null; - lastCacheUpdateTime = timeProvider.getCurrentTimeMillis(); - return; - } - - // Fallback: query current active network - final ConnectivityManager connectivityManager = - getConnectivityManager(context, options.getLogger()); - if (connectivityManager != null) { - final Network activeNetwork = connectivityManager.getActiveNetwork(); - - cachedNetworkCapabilities = - activeNetwork != null - ? connectivityManager.getNetworkCapabilities(activeNetwork) - : null; - } else { - cachedNetworkCapabilities = - null; // Clear cached capabilities if connectivity manager is null - } - } - lastCacheUpdateTime = timeProvider.getCurrentTimeMillis(); - - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Cache updated - Status: " - + getConnectionStatusFromCache() - + ", Type: " - + getConnectionTypeFromCache()); - } catch (Throwable t) { - options.getLogger().log(SentryLevel.WARNING, "Failed to update connection status cache", t); - cachedNetworkCapabilities = null; - lastCacheUpdateTime = timeProvider.getCurrentTimeMillis(); - } - } - } - - private boolean isCacheValid() { - return (timeProvider.getCurrentTimeMillis() - lastCacheUpdateTime) < CACHE_TTL_MS; - } - - @Override - public @NotNull ConnectionStatus getConnectionStatus() { - if (!isCacheValid()) { - updateCache(null); - } - return getConnectionStatusFromCache(); - } - - @Override - public @Nullable String getConnectionType() { - if (!isCacheValid()) { - updateCache(null); - } - return getConnectionTypeFromCache(); - } - - @Override - public boolean addConnectionStatusObserver(final @NotNull IConnectionStatusObserver observer) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - connectionStatusObservers.add(observer); - } - ensureNetworkCallbackRegistered(); - - // Network callback is already registered during initialization - return networkCallback != null; - } - - @Override - public void removeConnectionStatusObserver(final @NotNull IConnectionStatusObserver observer) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - connectionStatusObservers.remove(observer); - // Keep the callback registered for caching even if no observers - } - } - - private void unregisterNetworkCallback(final boolean clearObservers) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (clearObservers) { - connectionStatusObservers.clear(); - } - - final @Nullable NetworkCallback callbackRef = networkCallback; - networkCallback = null; - - if (callbackRef != null) { - unregisterNetworkCallback(context, options.getLogger(), callbackRef); - } - // Clear cached state - cachedNetworkCapabilities = null; - currentNetwork = null; - lastCacheUpdateTime = 0; - } - options.getLogger().log(SentryLevel.DEBUG, "Network callback unregistered"); - } - - /** Clean up resources - should be called when the provider is no longer needed */ - @Override - public void close() { - submitSafe( - () -> { - unregisterNetworkCallback(/* clearObservers= */ true); - try (final @NotNull ISentryLifecycleToken ignored = childCallbacksLock.acquire()) { - childCallbacks.clear(); - } - try (final @NotNull ISentryLifecycleToken ignored = connectivityManagerLock.acquire()) { - connectivityManager = null; - } - AppState.getInstance().removeAppStateListener(this); - }); - } - - @Override - public void onForeground() { - if (networkCallback != null) { - return; - } - - submitSafe( - () -> { - // proactively update cache and notify observers on foreground to ensure connectivity - // state is not stale - updateCache(null); - - final @NotNull ConnectionStatus status = getConnectionStatusFromCache(); - if (status == ConnectionStatus.DISCONNECTED) { - // onLost is not called retroactively when we registerNetworkCallback (as opposed to - // onAvailable), so we have to do it manually for the DISCONNECTED case - isConnected.set(false); - try (final @NotNull ISentryLifecycleToken ignored = childCallbacksLock.acquire()) { - for (final @NotNull NetworkCallback cb : childCallbacks) { - //noinspection DataFlowIssue - cb.onLost(null); - } - } - } - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - for (final @NotNull IConnectionStatusObserver observer : connectionStatusObservers) { - observer.onConnectionStatusChanged(status); - } - } - - // this will ONLY do the necessary parts like registerNetworkCallback and onAvailable, but - // it won't updateCache and notify observes because we just did it above and the cached - // capabilities will be the same - ensureNetworkCallbackRegistered(); - }); - } - - @Override - public void onBackground() { - if (networkCallback == null) { - return; - } - - submitSafe( - () -> { - //noinspection Convert2MethodRef - unregisterNetworkCallback(/* clearObservers= */ false); - }); - } - - /** - * Get the cached NetworkCapabilities for advanced use cases. Returns null if cache is stale or no - * capabilities are available. - * - * @return cached NetworkCapabilities or null - */ - @TestOnly - @Nullable - public NetworkCapabilities getCachedNetworkCapabilities() { - return cachedNetworkCapabilities; - } - - /** - * Return the Connection status - * - * @param context the Context - * @param connectivityManager the ConnectivityManager - * @param logger the Logger - * @return true if connected or no permission to check, false otherwise - */ - @SuppressWarnings({"deprecation", "MissingPermission"}) - private static @NotNull IConnectionStatusProvider.ConnectionStatus getConnectionStatus( - final @NotNull Context context, - final @NotNull ConnectivityManager connectivityManager, - final @NotNull ILogger logger) { - if (!Permissions.hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE)) { - logger.log(SentryLevel.INFO, "No permission (ACCESS_NETWORK_STATE) to check network status."); - return ConnectionStatus.NO_PERMISSION; - } - - try { - final android.net.NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); - if (activeNetworkInfo == null) { - logger.log(SentryLevel.INFO, "NetworkInfo is null, there's no active network."); - return ConnectionStatus.DISCONNECTED; - } - return activeNetworkInfo.isConnected() - ? ConnectionStatus.CONNECTED - : ConnectionStatus.DISCONNECTED; - } catch (Throwable t) { - logger.log(SentryLevel.WARNING, "Could not retrieve Connection Status", t); - return ConnectionStatus.UNKNOWN; - } - } - - /** - * Check the connection type of the active network - * - * @param context the App. context - * @param logger the logger from options - * @param buildInfoProvider the BuildInfoProvider provider - * @return the connection type wifi, ethernet, cellular or null - */ - @SuppressLint({"ObsoleteSdkInt", "MissingPermission", "NewApi"}) - public static @Nullable String getConnectionType( - final @NotNull Context context, - final @NotNull ILogger logger, - final @NotNull BuildInfoProvider buildInfoProvider) { - final ConnectivityManager connectivityManager = getConnectivityManager(context, logger); - if (connectivityManager == null) { - return null; - } - if (!Permissions.hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE)) { - logger.log(SentryLevel.INFO, "No permission (ACCESS_NETWORK_STATE) to check network status."); - return null; - } - - try { - boolean ethernet = false; - boolean wifi = false; - boolean cellular = false; - - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.M) { - - final Network activeNetwork = connectivityManager.getActiveNetwork(); - if (activeNetwork == null) { - logger.log(SentryLevel.INFO, "Network is null and cannot check network status"); - return null; - } - final NetworkCapabilities networkCapabilities = - connectivityManager.getNetworkCapabilities(activeNetwork); - if (networkCapabilities == null) { - logger.log(SentryLevel.INFO, "NetworkCapabilities is null and cannot check network type"); - return null; - } - if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { - ethernet = true; - } - if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { - wifi = true; - } - if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { - cellular = true; - } - } else { - // ideally using connectivityManager.getAllNetworks(), but its >= Android L only - - // for some reason linting didn't allow a single @SuppressWarnings("deprecation") on method - // signature - @SuppressWarnings("deprecation") - final android.net.NetworkInfo activeNetworkInfo = - connectivityManager.getActiveNetworkInfo(); - - if (activeNetworkInfo == null) { - logger.log(SentryLevel.INFO, "NetworkInfo is null, there's no active network."); - return null; - } - - @SuppressWarnings("deprecation") - final int type = activeNetworkInfo.getType(); - - @SuppressWarnings("deprecation") - final int TYPE_ETHERNET = ConnectivityManager.TYPE_ETHERNET; - - @SuppressWarnings("deprecation") - final int TYPE_WIFI = ConnectivityManager.TYPE_WIFI; - - @SuppressWarnings("deprecation") - final int TYPE_MOBILE = ConnectivityManager.TYPE_MOBILE; - - switch (type) { - case TYPE_ETHERNET: - ethernet = true; - break; - case TYPE_WIFI: - wifi = true; - break; - case TYPE_MOBILE: - cellular = true; - break; - } - } - - // TODO: change the protocol to be a list of transports as a device may have the capability of - // multiple - if (ethernet) { - return "ethernet"; - } - if (wifi) { - return "wifi"; - } - if (cellular) { - return "cellular"; - } - } catch (Throwable exception) { - logger.log(SentryLevel.ERROR, "Failed to retrieve network info", exception); - } - - return null; - } - - /** - * Check the connection type of the active network - * - * @param networkCapabilities the NetworkCapabilities to check the transport type - * @return the connection type wifi, ethernet, cellular or null - */ - public static @Nullable String getConnectionType( - final @NotNull NetworkCapabilities networkCapabilities) { - // TODO: change the protocol to be a list of transports as a device may have the capability of - // multiple - - if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { - return "ethernet"; - } - if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { - return "wifi"; - } - if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { - return "cellular"; - } - - return null; - } - - private static @Nullable ConnectivityManager getConnectivityManager( - final @NotNull Context context, final @NotNull ILogger logger) { - if (connectivityManager != null) { - return connectivityManager; - } - - try (final @NotNull ISentryLifecycleToken ignored = connectivityManagerLock.acquire()) { - if (connectivityManager != null) { - return connectivityManager; // Double-checked locking - } - - connectivityManager = - (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivityManager == null) { - logger.log(SentryLevel.INFO, "ConnectivityManager is null and cannot check network status"); - } - return connectivityManager; - } - } - - public static boolean addNetworkCallback( - final @NotNull Context context, - final @NotNull ILogger logger, - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull NetworkCallback networkCallback) { - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.N) { - logger.log(SentryLevel.DEBUG, "NetworkCallbacks need Android N+."); - return false; - } - - if (!Permissions.hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE)) { - logger.log(SentryLevel.INFO, "No permission (ACCESS_NETWORK_STATE) to check network status."); - return false; - } - - try (final @NotNull ISentryLifecycleToken ignored = childCallbacksLock.acquire()) { - childCallbacks.add(networkCallback); - } - return true; - } - - public static void removeNetworkCallback(final @NotNull NetworkCallback networkCallback) { - try (final @NotNull ISentryLifecycleToken ignored = childCallbacksLock.acquire()) { - childCallbacks.remove(networkCallback); - } - } - - @SuppressLint({"MissingPermission", "NewApi"}) - static boolean registerNetworkCallback( - final @NotNull Context context, - final @NotNull ILogger logger, - final @NotNull BuildInfoProvider buildInfoProvider, - final @Nullable Handler handler, - final @NotNull NetworkCallback networkCallback) { - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.N) { - logger.log(SentryLevel.DEBUG, "NetworkCallbacks need Android N+."); - return false; - } - final ConnectivityManager connectivityManager = getConnectivityManager(context, logger); - if (connectivityManager == null) { - return false; - } - if (!Permissions.hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE)) { - logger.log(SentryLevel.INFO, "No permission (ACCESS_NETWORK_STATE) to check network status."); - return false; - } - try { - if (handler != null) { - connectivityManager.registerDefaultNetworkCallback(networkCallback, handler); - } else { - connectivityManager.registerDefaultNetworkCallback(networkCallback); - } - } catch (Throwable t) { - logger.log(SentryLevel.WARNING, "registerDefaultNetworkCallback failed", t); - return false; - } - return true; - } - - @SuppressLint("NewApi") - static void unregisterNetworkCallback( - final @NotNull Context context, - final @NotNull ILogger logger, - final @NotNull NetworkCallback networkCallback) { - - final ConnectivityManager connectivityManager = getConnectivityManager(context, logger); - if (connectivityManager == null) { - return; - } - try { - connectivityManager.unregisterNetworkCallback(networkCallback); - } catch (Throwable t) { - logger.log(SentryLevel.WARNING, "unregisterNetworkCallback failed", t); - } - } - - @TestOnly - @NotNull - public List getStatusObservers() { - return connectionStatusObservers; - } - - @TestOnly - @Nullable - public NetworkCallback getNetworkCallback() { - return networkCallback; - } - - @TestOnly - @NotNull - public static List getChildCallbacks() { - return childCallbacks; - } - - private void submitSafe(@NotNull Runnable r) { - try { - options.getExecutorService().submit(r); - } catch (Throwable e) { - options - .getLogger() - .log(SentryLevel.ERROR, "AndroidConnectionStatusProvider submit failed", e); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidCurrentDateProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidCurrentDateProvider.java deleted file mode 100644 index cdf66c319a2..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidCurrentDateProvider.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.sentry.android.core.internal.util; - -import android.os.SystemClock; -import io.sentry.transport.ICurrentDateProvider; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -public final class AndroidCurrentDateProvider implements ICurrentDateProvider { - - private static final ICurrentDateProvider instance = new AndroidCurrentDateProvider(); - - public static ICurrentDateProvider getInstance() { - return instance; - } - - private AndroidCurrentDateProvider() {} - - @Override - public long getCurrentTimeMillis() { - return SystemClock.uptimeMillis(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidRuntimeManager.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidRuntimeManager.java deleted file mode 100644 index bed8d63ec11..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidRuntimeManager.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.sentry.android.core.internal.util; - -import android.os.StrictMode; -import io.sentry.util.runtime.IRuntimeManager; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public final class AndroidRuntimeManager implements IRuntimeManager { - @Override - public T runWithRelaxedPolicy(final @NotNull IRuntimeManagerCallback toRun) { - final @NotNull StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); - final @NotNull StrictMode.VmPolicy oldVmPolicy = StrictMode.getVmPolicy(); - StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX); - StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX); - try { - return toRun.run(); - } finally { - StrictMode.setThreadPolicy(oldPolicy); - StrictMode.setVmPolicy(oldVmPolicy); - } - } - - @Override - public void runWithRelaxedPolicy(final @NotNull Runnable toRun) { - runWithRelaxedPolicy( - () -> { - toRun.run(); - return null; - }); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidThreadChecker.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidThreadChecker.java deleted file mode 100644 index 7228c849cf7..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidThreadChecker.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.sentry.android.core.internal.util; - -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.os.Process; -import io.sentry.protocol.SentryThread; -import io.sentry.util.thread.IThreadChecker; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -/** Class that checks if a given thread is the Android Main/UI thread */ -@ApiStatus.Internal -public final class AndroidThreadChecker implements IThreadChecker { - - private static final AndroidThreadChecker instance = new AndroidThreadChecker(); - public static volatile long mainThreadSystemId = Process.myTid(); - - public static AndroidThreadChecker getInstance() { - return instance; - } - - private AndroidThreadChecker() { - // The first time this class is loaded, we make sure to set the correct mainThreadId - new Handler(Looper.getMainLooper()).post(() -> mainThreadSystemId = Process.myTid()); - } - - /** - * Gets the thread ID in a way that's compatible across Android versions. - * - *

Uses {@link Thread#threadId()} on Android 16 (API 36) and above, and falls back to {@link - * Thread#getId()} on older versions. - * - * @param thread the thread to get the ID for - * @return the thread ID - */ - @SuppressWarnings("deprecation") - public static long getThreadId(final @NotNull Thread thread) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) { - return thread.threadId(); - } else { - return thread.getId(); - } - } - - @Override - public boolean isMainThread(final long threadId) { - return getThreadId(Looper.getMainLooper().getThread()) == threadId; - } - - @Override - public boolean isMainThread(final @NotNull Thread thread) { - return isMainThread(getThreadId(thread)); - } - - @Override - public boolean isMainThread() { - return isMainThread(Thread.currentThread()); - } - - @Override - public @NotNull String getCurrentThreadName() { - return isMainThread() ? "main" : Thread.currentThread().getName(); - } - - @Override - public boolean isMainThread(final @NotNull SentryThread sentryThread) { - final Long threadId = sentryThread.getId(); - return threadId != null && isMainThread(threadId); - } - - @Override - public long currentThreadSystemId() { - return Process.myTid(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/BreadcrumbFactory.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/BreadcrumbFactory.java deleted file mode 100644 index 04cabc9430e..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/BreadcrumbFactory.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.sentry.android.core.internal.util; - -import io.sentry.Breadcrumb; -import io.sentry.SentryLevel; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public class BreadcrumbFactory { - - public static @NotNull Breadcrumb forSession(@NotNull String state) { - final Breadcrumb breadcrumb = new Breadcrumb(); - breadcrumb.setType("session"); - breadcrumb.setData("state", state); - breadcrumb.setCategory("app.lifecycle"); - breadcrumb.setLevel(SentryLevel.INFO); - return breadcrumb; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ClassUtil.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ClassUtil.java deleted file mode 100644 index 8b730e267ae..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ClassUtil.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.sentry.android.core.internal.util; - -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public class ClassUtil { - - public static @Nullable String getClassName(final @Nullable Object object) { - if (object == null) { - return null; - } - final @Nullable String canonicalName = object.getClass().getCanonicalName(); - if (canonicalName != null) { - return canonicalName; - } - return object.getClass().getSimpleName(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ContentProviderSecurityChecker.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ContentProviderSecurityChecker.java deleted file mode 100644 index 9aa21507f73..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ContentProviderSecurityChecker.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.sentry.android.core.internal.util; - -import android.annotation.SuppressLint; -import android.content.ContentProvider; -import android.os.Build; -import io.sentry.NoOpLogger; -import io.sentry.android.core.BuildInfoProvider; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public final class ContentProviderSecurityChecker { - - private final @NotNull BuildInfoProvider buildInfoProvider; - - public ContentProviderSecurityChecker() { - this(new BuildInfoProvider(NoOpLogger.getInstance())); - } - - public ContentProviderSecurityChecker(final @NotNull BuildInfoProvider buildInfoProvider) { - this.buildInfoProvider = buildInfoProvider; - } - - /** - * Protects against "Privilege Escalation via Content Provider" (CVE-2018-9492). - * - *

Throws a SecurityException if the security check is breached. - * - *

See https://www.cvedetails.com/cve/CVE-2018-9492/ and - * https://github.com/getsentry/sentry-java/issues/2460 - * - *

Call this function in the {@link ContentProvider}'s implementations of the abstract - * functions; query, insert, update, and delete. - * - *

This should be invoked regardless of whether there is data to read/write or not. The attack - * is not contained to the specific provider but rather the entire system. - * - *

This blocks the attacker by only allowing the app itself (not other apps) to interact with - * the ContentProvider. If the ContentProvider needs to be able to interact with other trusted - * apps, then this function or class should be refactored to accommodate that. - * - *

The vulnerability is specific to un-patched versions of Android 8 and 9 (API 26 to 28). - * Therefore, this security check is limited to those versions to mitigate risk of regression. - */ - @SuppressLint("NewApi") - public void checkPrivilegeEscalation(@NotNull ContentProvider contentProvider) { - final int sdkVersion = buildInfoProvider.getSdkInfoVersion(); - if (sdkVersion >= Build.VERSION_CODES.O && sdkVersion <= Build.VERSION_CODES.P) { - - String callingPackage = contentProvider.getCallingPackage(); - String appPackage = contentProvider.getContext().getPackageName(); - if (callingPackage != null && callingPackage.equals(appPackage)) { - return; - } - throw new SecurityException("Provider does not allow for granting of Uri permissions"); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java deleted file mode 100644 index ae94f410dab..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java +++ /dev/null @@ -1,90 +0,0 @@ -package io.sentry.android.core.internal.util; - -import io.sentry.ISentryLifecycleToken; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.FileUtils; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.TestOnly; -import org.jetbrains.annotations.VisibleForTesting; - -@ApiStatus.Internal -public final class CpuInfoUtils { - - private static final CpuInfoUtils instance = new CpuInfoUtils(); - private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); - - public static CpuInfoUtils getInstance() { - return instance; - } - - private CpuInfoUtils() {} - - private static final @NotNull String SYSTEM_CPU_PATH = "/sys/devices/system/cpu"; - - @VisibleForTesting - static final @NotNull String CPUINFO_MAX_FREQ_PATH = "cpufreq/cpuinfo_max_freq"; - - /** Cached max frequencies to avoid reading files multiple times */ - private final @NotNull List cpuMaxFrequenciesMhz = new ArrayList<>(); - - /** - * Read the max frequency of each core of the cpu and returns it in Mhz - * - * @return A list with the frequency of each core of the cpu in Mhz - */ - public @NotNull List readMaxFrequencies() { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - if (!cpuMaxFrequenciesMhz.isEmpty()) { - return cpuMaxFrequenciesMhz; - } - File[] cpuDirs = new File(getSystemCpuPath()).listFiles(); - if (cpuDirs == null) { - return new ArrayList<>(); - } - - for (File cpuDir : cpuDirs) { - if (!cpuDir.getName().matches("cpu[0-9]+")) continue; - File cpuMaxFreqFile = new File(cpuDir, CPUINFO_MAX_FREQ_PATH); - - long khz; - try { - String content = FileUtils.readText(cpuMaxFreqFile); - if (content == null) continue; - khz = Long.parseLong(content.trim()); - } catch (NumberFormatException e) { - continue; - } catch (IOException e) { - continue; - } - cpuMaxFrequenciesMhz.add((int) (khz / 1000)); - } - return cpuMaxFrequenciesMhz; - } - } - - @VisibleForTesting - @NotNull - String getSystemCpuPath() { - return SYSTEM_CPU_PATH; - } - - @TestOnly - public void setCpuMaxFrequencies(List frequencies) { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - cpuMaxFrequenciesMhz.clear(); - cpuMaxFrequenciesMhz.addAll(frequencies); - } - } - - @TestOnly - public void clear() { - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { - cpuMaxFrequenciesMhz.clear(); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/Debouncer.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/Debouncer.java deleted file mode 100644 index 8808947c0cc..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/Debouncer.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.sentry.android.core.internal.util; - -import io.sentry.transport.ICurrentDateProvider; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -/** A simple time-based debouncing mechanism */ -@ApiStatus.Internal -public class Debouncer { - - private final long waitTimeMs; - private final @NotNull ICurrentDateProvider timeProvider; - private final @NotNull AtomicInteger executions = new AtomicInteger(0); - private final int maxExecutions; - - private final @NotNull AtomicLong lastExecutionTime = new AtomicLong(0); - - public Debouncer( - final @NotNull ICurrentDateProvider timeProvider, - final long waitTimeMs, - final int maxExecutions) { - this.timeProvider = timeProvider; - this.waitTimeMs = waitTimeMs; - this.maxExecutions = maxExecutions <= 0 ? 1 : maxExecutions; - } - - /** - * @return true if the execution should be debounced due to maxExecutions executions being made - * within waitTimeMs, otherwise false. - */ - public boolean checkForDebounce() { - final long now = timeProvider.getCurrentTimeMillis(); - if (lastExecutionTime.get() == 0 || (lastExecutionTime.get() + waitTimeMs) <= now) { - executions.set(0); - lastExecutionTime.set(now); - return false; - } - if (executions.incrementAndGet() < maxExecutions) { - return false; - } - executions.set(0); - return true; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/DeviceOrientations.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/DeviceOrientations.java deleted file mode 100644 index 7ecd8487105..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/DeviceOrientations.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.sentry.android.core.internal.util; - -import android.content.res.Configuration; -import io.sentry.protocol.Device; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public final class DeviceOrientations { - private DeviceOrientations() {} - - /** - * Get the device's current screen orientation. - * - * @return the device's current screen orientation, or null if unknown - */ - @SuppressWarnings("deprecation") - public static @Nullable Device.DeviceOrientation getOrientation(int orientation) { - switch (orientation) { - case Configuration.ORIENTATION_LANDSCAPE: - return Device.DeviceOrientation.LANDSCAPE; - case Configuration.ORIENTATION_PORTRAIT: - return Device.DeviceOrientation.PORTRAIT; - case Configuration.ORIENTATION_SQUARE: - case Configuration.ORIENTATION_UNDEFINED: - default: - return null; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/FirstDrawDoneListener.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/FirstDrawDoneListener.java deleted file mode 100644 index f2612b4aa84..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/FirstDrawDoneListener.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * https://github.com/firebase/firebase-android-sdk/blob/master/firebase-perf/src/main/java/com/google/firebase/perf/util/FirstDrawDoneListener.java - * - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.sentry.android.core.internal.util; - -import android.app.Activity; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.view.View; -import android.view.ViewTreeObserver; -import android.view.Window; -import androidx.annotation.Nullable; -import io.sentry.android.core.BuildInfoProvider; -import io.sentry.android.core.internal.gestures.NoOpWindowCallback; -import io.sentry.android.core.performance.WindowContentChangedCallback; -import java.util.concurrent.atomic.AtomicReference; -import org.jetbrains.annotations.NotNull; - -/** - * OnDrawListener that unregisters itself and invokes callback when the next draw is done. This API - * 16+ implementation is an approximation of the initial-display-time defined by Android Vitals. - */ -public class FirstDrawDoneListener implements ViewTreeObserver.OnDrawListener { - private final @NotNull Handler mainThreadHandler = new Handler(Looper.getMainLooper()); - private final @NotNull AtomicReference viewReference; - private final @NotNull Runnable callback; - - public static void registerForNextDraw( - final @NotNull Activity activity, - final @NotNull Runnable drawDoneCallback, - final @NotNull BuildInfoProvider buildInfoProvider) { - - @Nullable Window window = activity.getWindow(); - if (window != null) { - @Nullable View decorView = window.peekDecorView(); - if (decorView != null) { - registerForNextDraw(decorView, drawDoneCallback, buildInfoProvider); - } else { - final @Nullable Window.Callback oldCallback = window.getCallback(); - window.setCallback( - new WindowContentChangedCallback( - oldCallback != null ? oldCallback : new NoOpWindowCallback(), - () -> { - @Nullable View newDecorView = window.peekDecorView(); - if (newDecorView != null) { - // let's set the old callback again, so we don't intercept anymore - window.setCallback(oldCallback); - registerForNextDraw(newDecorView, drawDoneCallback, buildInfoProvider); - } - })); - } - } - } - - /** Registers a post-draw callback for the next draw of a view. */ - public static void registerForNextDraw( - final @NotNull View view, - final @NotNull Runnable drawDoneCallback, - final @NotNull BuildInfoProvider buildInfoProvider) { - final FirstDrawDoneListener listener = new FirstDrawDoneListener(view, drawDoneCallback); - // Handle bug prior to API 26 where OnDrawListener from the floating ViewTreeObserver is not - // merged into the real ViewTreeObserver. - // https://android.googlesource.com/platform/frameworks/base/+/9f8ec54244a5e0343b9748db3329733f259604f3 - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.O - && !isAliveAndAttached(view)) { - view.addOnAttachStateChangeListener( - new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View view) { - view.getViewTreeObserver().addOnDrawListener(listener); - view.removeOnAttachStateChangeListener(this); - } - - @Override - public void onViewDetachedFromWindow(View view) { - view.removeOnAttachStateChangeListener(this); - } - }); - } else { - view.getViewTreeObserver().addOnDrawListener(listener); - } - } - - private FirstDrawDoneListener(final @NotNull View view, final @NotNull Runnable callback) { - this.viewReference = new AtomicReference<>(view); - this.callback = callback; - } - - @Override - public void onDraw() { - // Set viewReference to null so any onDraw past the first is a no-op - final View view = viewReference.getAndSet(null); - if (view == null) { - return; - } - // OnDrawListeners cannot be removed within onDraw, so we remove it with a - // GlobalLayoutListener - view.getViewTreeObserver() - .addOnGlobalLayoutListener(() -> view.getViewTreeObserver().removeOnDrawListener(this)); - mainThreadHandler.postAtFrontOfQueue(callback); - } - - /** - * Helper to avoid bug - * prior to API 26, where the floating ViewTreeObserver's OnDrawListeners are not merged into - * the real ViewTreeObserver during attach. - * - * @return true if the View is already attached and the ViewTreeObserver is not a floating - * placeholder. - */ - private static boolean isAliveAndAttached(final @NotNull View view) { - return view.getViewTreeObserver().isAlive() && view.isAttachedToWindow(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/NativeEventUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/NativeEventUtils.java deleted file mode 100644 index f8bd70cb6c3..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/NativeEventUtils.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.sentry.android.core.internal.util; - -import java.math.BigInteger; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class NativeEventUtils { - @Nullable - public static String buildIdToDebugId(final @NotNull String buildId) { - try { - // Abuse BigInteger as a hex string parser. Extra byte needed to handle leading zeros. - final ByteBuffer buf = ByteBuffer.wrap(new BigInteger("10" + buildId, 16).toByteArray()); - buf.get(); - return String.format( - "%08x-%04x-%04x-%04x-%04x%08x", - buf.order(ByteOrder.LITTLE_ENDIAN).getInt(), - buf.getShort(), - buf.getShort(), - buf.order(ByteOrder.BIG_ENDIAN).getShort(), - buf.getShort(), - buf.getInt()); - } catch (NumberFormatException | BufferUnderflowException e) { - return null; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/Permissions.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/Permissions.java deleted file mode 100644 index 57e846897ec..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/Permissions.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.sentry.android.core.internal.util; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Process; -import io.sentry.util.Objects; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public final class Permissions { - - private Permissions() {} - - public static boolean hasPermission( - final @NotNull Context context, final @NotNull String permission) { - Objects.requireNonNull(context, "The application context is required."); - - return context.checkPermission(permission, Process.myPid(), Process.myUid()) - == PackageManager.PERMISSION_GRANTED; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/RootChecker.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/RootChecker.java deleted file mode 100644 index 2823f278a7d..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/RootChecker.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Root detection implementation adapted from Ravencoin Android: - * https://github.com/Menwitz/ravencoin-android/blob/7b68378c046e2fd0d6f30cea59cbd87fcb6db12d/app/src/main/java/com/ravencoin/tools/security/RootHelper.java - * - * RavenWallet - *

- * Created by Mihail Gutan on 5/19/16. - * Copyright (c) 2016 breadwallet LLC - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package io.sentry.android.core.internal.util; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Build; -import io.sentry.ILogger; -import io.sentry.SentryLevel; -import io.sentry.android.core.BuildInfoProvider; -import io.sentry.util.Objects; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.Charset; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public final class RootChecker { - - /** the UTF-8 Charset */ - @SuppressWarnings("CharsetObjectCanBeUsed") - private static final Charset UTF_8 = Charset.forName("UTF-8"); - - private final @NotNull Context context; - private final @NotNull BuildInfoProvider buildInfoProvider; - private final @NotNull ILogger logger; - - private final @NotNull String[] rootFiles; - - private final @NotNull String[] rootPackages; - - private final @NotNull Runtime runtime; - - public RootChecker( - final @NotNull Context context, - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull ILogger logger) { - this( - context, - buildInfoProvider, - logger, - new String[] { - "/sbin/su", - "/data/local/xbin/su", - "/system/bin/su", - "/system/xbin/su", - "/data/local/bin/su", - "/system/app/Superuser.apk", - "/system/sd/xbin/su", - "/system/bin/failsafe/su", - "/data/local/su", - "/su/bin/su", - "/su/bin", - "/system/xbin/daemonsu" - }, - new String[] { - "com.devadvance.rootcloak", - "com.devadvance.rootcloakplus", - "com.koushikdutta.superuser", - "com.thirdparty.superuser", - "eu.chainfire.supersu", // SuperSU - "com.noshufou.android.su" // superuser - }, - Runtime.getRuntime()); - } - - RootChecker( - final @NotNull Context context, - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull ILogger logger, - final @NotNull String[] rootFiles, - final @NotNull String[] rootPackages, - final @NotNull Runtime runtime) { - this.context = Objects.requireNonNull(context, "The application context is required."); - this.buildInfoProvider = - Objects.requireNonNull(buildInfoProvider, "The BuildInfoProvider is required."); - this.logger = Objects.requireNonNull(logger, "The Logger is required."); - this.rootFiles = Objects.requireNonNull(rootFiles, "The root Files are required."); - this.rootPackages = Objects.requireNonNull(rootPackages, "The root packages are required."); - this.runtime = Objects.requireNonNull(runtime, "The Runtime is required."); - } - - /** - * Check if the device is rooted or not - * - * @return whether the device is rooted or not - */ - public boolean isDeviceRooted() { - return checkRootA() || checkRootB() || checkRootC() || checkRootPackages(logger); - } - - /** - * Android Roms from Google are build with release-key tags. If test-keys are present, this can - * mean that the Android build on the device is either a developer build or an unofficial Google - * build. - * - * @return whether if it contains test keys or not - */ - private boolean checkRootA() { - final String buildTags = buildInfoProvider.getBuildTags(); - return buildTags != null && buildTags.contains("test-keys"); - } - - /** - * Often the rooted device have the following files . This method will check whether the device is - * having these files or not - * - * @return whether if the root files exist or not - */ - private boolean checkRootB() { - for (final String path : rootFiles) { - try { - if (new File(path).exists()) { - return true; - } - } catch (RuntimeException e) { - logger.log( - SentryLevel.ERROR, e, "Error when trying to check if root file %s exists.", path); - } - } - return false; - } - - /** - * this will check if SU(Super User) exist or not - * - * @return whether su exists or not - */ - private boolean checkRootC() { - Process p = null; - final String[] su = {"/system/xbin/which", "su"}; - - try { - p = runtime.exec(su); - - try (final BufferedReader reader = - new BufferedReader(new InputStreamReader(p.getInputStream(), UTF_8))) { - return reader.readLine() != null; - } - } catch (IOException e) { - logger.log(SentryLevel.DEBUG, "SU isn't found on this Device."); - } catch (Throwable e) { - logger.log(SentryLevel.DEBUG, "Error when trying to check if SU exists.", e); - } finally { - if (p != null) { - p.destroy(); - } - } - return false; - } - - /** - * some application hide the root status of the android device. This will check for those files - * - * @return whether the root packages exist or not - */ - @SuppressLint("NewApi") - @SuppressWarnings("deprecation") - private boolean checkRootPackages(final @NotNull ILogger logger) { - BuildInfoProvider buildInfoProvider = new BuildInfoProvider(logger); - final PackageManager pm = context.getPackageManager(); - if (pm != null) { - for (final String pkg : rootPackages) { - try { - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.TIRAMISU) { - pm.getPackageInfo(pkg, PackageManager.PackageInfoFlags.of(0)); - } else { - pm.getPackageInfo(pkg, 0); - } - return true; - } catch (PackageManager.NameNotFoundException ignored) { - // fine, package doesn't exist. - } - } - } - return false; - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ScreenshotUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ScreenshotUtils.java deleted file mode 100644 index db2b12122a5..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ScreenshotUtils.java +++ /dev/null @@ -1,202 +0,0 @@ -package io.sentry.android.core.internal.util; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.os.Build; -import android.os.Handler; -import android.os.HandlerThread; -import android.view.PixelCopy; -import android.view.View; -import android.view.Window; -import androidx.annotation.Nullable; -import io.sentry.ILogger; -import io.sentry.SentryLevel; -import io.sentry.android.core.BuildInfoProvider; -import io.sentry.util.thread.IThreadChecker; -import java.io.ByteArrayOutputStream; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public class ScreenshotUtils { - - private static final long CAPTURE_TIMEOUT_MS = 1000; - - // Used by Hybrid SDKs - /** - * @noinspection unused - */ - public static @Nullable byte[] takeScreenshot( - final @NotNull Activity activity, - final @NotNull ILogger logger, - final @NotNull BuildInfoProvider buildInfoProvider) { - return takeScreenshot(activity, AndroidThreadChecker.getInstance(), logger, buildInfoProvider); - } - - // Used by Hybrid SDKs - @SuppressLint("NewApi") - public static @Nullable byte[] takeScreenshot( - final @NotNull Activity activity, - final @NotNull IThreadChecker threadChecker, - final @NotNull ILogger logger, - final @NotNull BuildInfoProvider buildInfoProvider) { - - final @Nullable Bitmap screenshot = - captureScreenshot(activity, threadChecker, logger, buildInfoProvider); - return compressBitmapToPng(screenshot, logger); - } - - public static @Nullable Bitmap captureScreenshot( - final @NotNull Activity activity, - final @NotNull ILogger logger, - final @NotNull BuildInfoProvider buildInfoProvider) { - return captureScreenshot( - activity, AndroidThreadChecker.getInstance(), logger, buildInfoProvider); - } - - @SuppressLint("NewApi") - public static @Nullable Bitmap captureScreenshot( - final @NotNull Activity activity, - final @NotNull IThreadChecker threadChecker, - final @NotNull ILogger logger, - final @NotNull BuildInfoProvider buildInfoProvider) { - // We are keeping BuildInfoProvider param for compatibility, as it's being used by - // cross-platform SDKs - - if (!isActivityValid(activity)) { - logger.log(SentryLevel.DEBUG, "Activity isn't valid, not taking screenshot."); - return null; - } - - final @Nullable Window window = activity.getWindow(); - if (window == null) { - logger.log(SentryLevel.DEBUG, "Activity window is null, not taking screenshot."); - return null; - } - - final @Nullable View decorView = window.peekDecorView(); - if (decorView == null) { - logger.log(SentryLevel.DEBUG, "DecorView is null, not taking screenshot."); - return null; - } - - final @Nullable View view = decorView.getRootView(); - if (view == null) { - logger.log(SentryLevel.DEBUG, "Root view is null, not taking screenshot."); - return null; - } - - if (view.getWidth() <= 0 || view.getHeight() <= 0) { - logger.log(SentryLevel.DEBUG, "View's width and height is zeroed, not taking screenshot."); - return null; - } - - try { - // ARGB_8888 -> This configuration is very flexible and offers the best quality - final Bitmap bitmap = - Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); - - final @NotNull CountDownLatch latch = new CountDownLatch(1); - - // Use Pixel Copy API on new devices, fallback to canvas rendering on older ones - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.O) { - - final HandlerThread thread = new HandlerThread("SentryScreenshot"); - thread.start(); - - boolean success = false; - try { - final Handler handler = new Handler(thread.getLooper()); - final AtomicBoolean copyResultSuccess = new AtomicBoolean(false); - - PixelCopy.request( - window, - bitmap, - copyResult -> { - copyResultSuccess.set(copyResult == PixelCopy.SUCCESS); - latch.countDown(); - }, - handler); - - success = - latch.await(CAPTURE_TIMEOUT_MS, TimeUnit.MILLISECONDS) && copyResultSuccess.get(); - } catch (Throwable e) { - // ignored - logger.log(SentryLevel.ERROR, "Taking screenshot using PixelCopy failed.", e); - } finally { - thread.quit(); - } - - if (!success) { - return null; - } - } else { - final Canvas canvas = new Canvas(bitmap); - if (threadChecker.isMainThread()) { - view.draw(canvas); - latch.countDown(); - } else { - activity.runOnUiThread( - () -> { - try { - view.draw(canvas); - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Taking screenshot failed (view.draw).", e); - } finally { - latch.countDown(); - } - }); - } - - if (!latch.await(CAPTURE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { - return null; - } - } - return bitmap; - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Taking screenshot failed.", e); - } - return null; - } - - /** - * Compresses the supplied Bitmap to a PNG byte array. After compression, the Bitmap will be - * recycled. - * - * @param bitmap The bitmap to compress - * @param logger the logger - * @return the Bitmap in PNG format, or null if the bitmap was null, recycled or compressing faile - */ - public static @Nullable byte[] compressBitmapToPng( - final @Nullable Bitmap bitmap, final @NotNull ILogger logger) { - if (bitmap == null || bitmap.isRecycled()) { - return null; - } - try (final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { - // 0 meaning compress for small size, 100 meaning compress for max quality. - // Some formats, like PNG which is lossless, will ignore the quality setting. - bitmap.compress(Bitmap.CompressFormat.PNG, 0, byteArrayOutputStream); - bitmap.recycle(); - - if (byteArrayOutputStream.size() <= 0) { - logger.log(SentryLevel.DEBUG, "Screenshot is 0 bytes, not attaching the image."); - return null; - } - - // screenshot png is around ~100-150 kb - return byteArrayOutputStream.toByteArray(); - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Compressing bitmap failed.", e); - } - return null; - } - - private static boolean isActivityValid(final @NotNull Activity activity) { - return !activity.isFinishing() && !activity.isDestroyed(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java deleted file mode 100644 index 55342c0e4c0..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java +++ /dev/null @@ -1,407 +0,0 @@ -package io.sentry.android.core.internal.util; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.Application; -import android.content.Context; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.view.Choreographer; -import android.view.FrameMetrics; -import android.view.Window; -import androidx.annotation.RequiresApi; -import io.sentry.ILogger; -import io.sentry.SentryLevel; -import io.sentry.SentryOptions; -import io.sentry.SentryUUID; -import io.sentry.android.core.BuildInfoProvider; -import io.sentry.android.core.ContextUtils; -import io.sentry.util.Objects; -import java.lang.ref.WeakReference; -import java.lang.reflect.Field; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.TimeUnit; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public final class SentryFrameMetricsCollector implements Application.ActivityLifecycleCallbacks { - private static final long oneSecondInNanos = TimeUnit.SECONDS.toNanos(1); - private static final long frozenFrameThresholdNanos = TimeUnit.MILLISECONDS.toNanos(700); - - private final @NotNull BuildInfoProvider buildInfoProvider; - private final @NotNull Set trackedWindows = new CopyOnWriteArraySet<>(); - - private final @NotNull ILogger logger; - private @Nullable Handler handler; - private @Nullable WeakReference currentWindow; - private final @NotNull Map listenerMap = - new ConcurrentHashMap<>(); - private boolean isAvailable = false; - private final WindowFrameMetricsManager windowFrameMetricsManager; - - private @Nullable Window.OnFrameMetricsAvailableListener frameMetricsAvailableListener; - private @Nullable Choreographer choreographer; - private @Nullable Field choreographerLastFrameTimeField; - private long lastFrameStartNanos = 0; - private long lastFrameEndNanos = 0; - - @SuppressLint("NewApi") - public SentryFrameMetricsCollector( - final @NotNull Context context, - final @NotNull SentryOptions options, - final @NotNull BuildInfoProvider buildInfoProvider) { - this(context, options, buildInfoProvider, new WindowFrameMetricsManager() {}); - } - - @SuppressLint("NewApi") - public SentryFrameMetricsCollector( - final @NotNull Context context, - final @NotNull ILogger logger, - final @NotNull BuildInfoProvider buildInfoProvider) { - this(context, logger, buildInfoProvider, new WindowFrameMetricsManager() {}); - } - - @SuppressLint({"NewApi", "DiscouragedPrivateApi"}) - public SentryFrameMetricsCollector( - final @NotNull Context context, - final @NotNull SentryOptions options, - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull WindowFrameMetricsManager windowFrameMetricsManager) { - this(context, options.getLogger(), buildInfoProvider, windowFrameMetricsManager); - } - - @SuppressWarnings("deprecation") - @SuppressLint({"NewApi", "PrivateApi"}) - public SentryFrameMetricsCollector( - final @NotNull Context context, - final @NotNull ILogger logger, - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull WindowFrameMetricsManager windowFrameMetricsManager) { - final @NotNull Context appContext = - Objects.requireNonNull( - ContextUtils.getApplicationContext(context), "The context is required"); - this.logger = Objects.requireNonNull(logger, "Logger is required"); - this.buildInfoProvider = - Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required"); - this.windowFrameMetricsManager = - Objects.requireNonNull(windowFrameMetricsManager, "WindowFrameMetricsManager is required"); - - // registerActivityLifecycleCallbacks is only available if Context is an AppContext - if (!(appContext instanceof Application)) { - return; - } - // FrameMetrics api is only available since sdk version N - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.N) { - return; - } - isAvailable = true; - - HandlerThread handlerThread = - new HandlerThread("io.sentry.android.core.internal.util.SentryFrameMetricsCollector"); - handlerThread.setUncaughtExceptionHandler( - (thread, e) -> logger.log(SentryLevel.ERROR, "Error during frames measurements.", e)); - handlerThread.start(); - handler = new Handler(handlerThread.getLooper()); - - // We have to register the lifecycle callback, even if no profile is started, otherwise when we - // start a profile, we wouldn't have the current activity and couldn't get the frameMetrics. - ((Application) appContext).registerActivityLifecycleCallbacks(this); - - // Most considerations regarding timestamps of frames are inspired from JankStats library: - // https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:metrics/metrics-performance/src/main/java/androidx/metrics/performance/JankStatsApi24Impl.kt - - // The Choreographer instance must be accessed on the main thread - new Handler(Looper.getMainLooper()) - .post( - () -> { - try { - choreographer = Choreographer.getInstance(); - } catch (Throwable e) { - logger.log( - SentryLevel.ERROR, - "Error retrieving Choreographer instance. Slow and frozen frames will not be reported.", - e); - } - }); - // Let's get the last frame timestamp from the choreographer private field - try { - choreographerLastFrameTimeField = Choreographer.class.getDeclaredField("mLastFrameTimeNanos"); - choreographerLastFrameTimeField.setAccessible(true); - } catch (NoSuchFieldException e) { - logger.log( - SentryLevel.ERROR, "Unable to get the frame timestamp from the choreographer: ", e); - } - - frameMetricsAvailableListener = - (window, frameMetrics, dropCountSinceLastInvocation) -> { - final long now = System.nanoTime(); - final float refreshRate = - buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.R - ? window.getContext().getDisplay().getRefreshRate() - : window.getWindowManager().getDefaultDisplay().getRefreshRate(); - - final long expectedFrameDuration = (long) (oneSecondInNanos / refreshRate); - - final long cpuDuration = getFrameCpuDuration(frameMetrics); - - // if totalDurationNanos is smaller than expectedFrameTimeNanos, - // it means that the frame was drawn within it's time budget, thus 0 delay - final long delayNanos = Math.max(0, cpuDuration - expectedFrameDuration); - - long startTime = getFrameStartTimestamp(frameMetrics); - // If we couldn't get the timestamp through reflection, we use current time - if (startTime < 0) { - startTime = now - cpuDuration; - } - // Let's "adjust" the start time of a frame to be after the end of the previous frame - startTime = Math.max(startTime, lastFrameEndNanos); - // Let's avoid emitting duplicates (start time equals to last start time) - if (startTime == lastFrameStartNanos) { - return; - } - lastFrameStartNanos = startTime; - lastFrameEndNanos = startTime + cpuDuration; - - // Most frames take just a few nanoseconds longer than the optimal calculated - // duration. - // Therefore we subtract one, because otherwise almost all frames would be slow. - final boolean isSlow = - isSlow(cpuDuration, (long) ((float) oneSecondInNanos / (refreshRate - 1.0f))); - final boolean isFrozen = isSlow && isFrozen(cpuDuration); - - for (FrameMetricsCollectorListener l : listenerMap.values()) { - l.onFrameMetricCollected( - startTime, - lastFrameEndNanos, - cpuDuration, - delayNanos, - isSlow, - isFrozen, - refreshRate); - } - }; - } - - public static boolean isFrozen(long frameDuration) { - return frameDuration > frozenFrameThresholdNanos; - } - - public static boolean isSlow(long frameDuration, final long expectedFrameDuration) { - return frameDuration > expectedFrameDuration; - } - - /** - * Return the internal timestamp in the choreographer of the last frame start timestamp through - * reflection. On Android O the value is read from the frameMetrics itself. - */ - @SuppressLint("NewApi") - private long getFrameStartTimestamp(final @NotNull FrameMetrics frameMetrics) { - - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.O) { - return frameMetrics.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP); - } - - return getLastKnownFrameStartTimeNanos(); - } - - /** - * Return time spent on the main thread (cpu) for frame creation. It doesn't consider time spent - * on the render thread (gpu). - */ - @RequiresApi(api = Build.VERSION_CODES.N) - private long getFrameCpuDuration(final @NotNull FrameMetrics frameMetrics) { - // Inspired by JankStats - // https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:metrics/metrics-performance/src/main/java/androidx/metrics/performance/JankStatsApi24Impl.kt;l=74-79;drc=1de6215c6bd9e887e3d94556e9ac55cfb7b8c797 - return frameMetrics.getMetric(FrameMetrics.UNKNOWN_DELAY_DURATION) - + frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION) - + frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION) - + frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION) - + frameMetrics.getMetric(FrameMetrics.DRAW_DURATION) - + frameMetrics.getMetric(FrameMetrics.SYNC_DURATION); - } - - // addOnFrameMetricsAvailableListener internally calls Activity.getWindow().getDecorView(), - // which cannot be called before setContentView. That's why we call it in onActivityStarted() - @Override - public void onActivityCreated(@NotNull Activity activity, @Nullable Bundle savedInstanceState) {} - - @Override - public void onActivityStarted(@NotNull Activity activity) { - setCurrentWindow(activity.getWindow()); - } - - @Override - public void onActivityResumed(@NotNull Activity activity) {} - - @Override - public void onActivityPaused(@NotNull Activity activity) {} - - @Override - public void onActivityStopped(@NotNull Activity activity) { - stopTrackingWindow(activity.getWindow()); - if (currentWindow != null && currentWindow.get() == activity.getWindow()) { - currentWindow = null; - } - } - - @Override - public void onActivitySaveInstanceState(@NotNull Activity activity, @NotNull Bundle outState) {} - - @Override - public void onActivityDestroyed(@NotNull Activity activity) {} - - public @Nullable String startCollection(final @NotNull FrameMetricsCollectorListener listener) { - if (!isAvailable) { - return null; - } - final String uid = SentryUUID.generateSentryId(); - listenerMap.put(uid, listener); - trackCurrentWindow(); - return uid; - } - - public void stopCollection(final @Nullable String listenerId) { - if (!isAvailable) { - return; - } - if (listenerId != null) { - listenerMap.remove(listenerId); - } - Window window = currentWindow != null ? currentWindow.get() : null; - if (window != null && listenerMap.isEmpty()) { - stopTrackingWindow(window); - } - } - - @SuppressLint("NewApi") - private void stopTrackingWindow(final @NotNull Window window) { - new Handler(Looper.getMainLooper()) - .post( - () -> { - try { - // Re-check if we should still remove the listener for this window - // in case trackCurrentWindow was called in the meantime - if (trackedWindows.remove(window)) { - windowFrameMetricsManager.removeOnFrameMetricsAvailableListener( - window, frameMetricsAvailableListener); - } - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Failed to remove frameMetricsAvailableListener", e); - } - }); - } - - private void setCurrentWindow(final @NotNull Window window) { - if (currentWindow != null && currentWindow.get() == window) { - return; - } - currentWindow = new WeakReference<>(window); - trackCurrentWindow(); - } - - @SuppressLint("NewApi") - private void trackCurrentWindow() { - @Nullable Window window = currentWindow != null ? currentWindow.get() : null; - if (window == null || !isAvailable) { - return; - } - - if (listenerMap.isEmpty()) { - return; - } - - if (handler != null) { - // Ensure the addOnFrameMetricsAvailableListener is called on the main thread - new Handler(Looper.getMainLooper()) - .post( - () -> { - if (trackedWindows.add(window)) { - try { - windowFrameMetricsManager.addOnFrameMetricsAvailableListener( - window, frameMetricsAvailableListener, handler); - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Failed to add frameMetricsAvailableListener", e); - } - } - }); - } - } - - /** - * @return the last known time a frame was started, according to the Choreographer - */ - public long getLastKnownFrameStartTimeNanos() { - // Let's read the choreographer private field to get start timestamp of the frame, which - // uses System.nanoTime() under the hood - if (choreographer != null && choreographerLastFrameTimeField != null) { - try { - Long choreographerFrameStartTime = - (Long) choreographerLastFrameTimeField.get(choreographer); - if (choreographerFrameStartTime != null) { - return choreographerFrameStartTime; - } - } catch (IllegalAccessException ignored) { - } - } - return -1; - } - - @ApiStatus.Internal - public interface FrameMetricsCollectorListener { - /** - * Called when a frame is collected. - * - * @param frameStartNanos Start timestamp of a frame in nanoseconds relative to - * System.nanotime(). - * @param frameEndNanos End timestamp of a frame in nanoseconds relative to System.nanotime(). - * @param durationNanos Duration in nanoseconds of the time spent from the cpu on the main - * thread to create the frame. - * @param delayNanos the frame delay, in nanoseconds. - * @param isSlow True if the frame is considered slow, rendering taking longer than the - * refresh-rate based budget, false otherwise. - * @param isFrozen True if the frame is considered frozen, rendering taking longer than 700ms, - * false otherwise. - * @param refreshRate the last known refresh rate when the frame was rendered. - */ - void onFrameMetricCollected( - final long frameStartNanos, - final long frameEndNanos, - final long durationNanos, - final long delayNanos, - final boolean isSlow, - final boolean isFrozen, - final float refreshRate); - } - - @ApiStatus.Internal - public interface WindowFrameMetricsManager { - @RequiresApi(api = Build.VERSION_CODES.N) - default void addOnFrameMetricsAvailableListener( - final @NotNull Window window, - final @Nullable Window.OnFrameMetricsAvailableListener frameMetricsAvailableListener, - final @Nullable Handler handler) { - if (frameMetricsAvailableListener == null) { - return; - } - window.addOnFrameMetricsAvailableListener(frameMetricsAvailableListener, handler); - } - - @RequiresApi(api = Build.VERSION_CODES.N) - default void removeOnFrameMetricsAvailableListener( - final @NotNull Window window, - final @Nullable Window.OnFrameMetricsAvailableListener frameMetricsAvailableListener) { - if (frameMetricsAvailableListener == null) { - return; - } - window.removeOnFrameMetricsAvailableListener(frameMetricsAvailableListener); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleCallbacksAdapter.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleCallbacksAdapter.java deleted file mode 100644 index c7a449d1ef8..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleCallbacksAdapter.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.sentry.android.core.performance; - -import android.app.Activity; -import android.app.Application; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -public class ActivityLifecycleCallbacksAdapter implements Application.ActivityLifecycleCallbacks { - - @Override - public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {} - - @Override - public void onActivityStarted(@NonNull Activity activity) {} - - @Override - public void onActivityResumed(@NonNull Activity activity) {} - - @Override - public void onActivityPaused(@NonNull Activity activity) {} - - @Override - public void onActivityStopped(@NonNull Activity activity) {} - - @Override - public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {} - - @Override - public void onActivityDestroyed(@NonNull Activity activity) {} -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelper.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelper.java deleted file mode 100644 index accb56db0dc..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelper.java +++ /dev/null @@ -1,140 +0,0 @@ -package io.sentry.android.core.performance; - -import android.os.Looper; -import android.os.SystemClock; -import io.sentry.ISpan; -import io.sentry.Instrumenter; -import io.sentry.SentryDate; -import io.sentry.SpanDataConvention; -import io.sentry.SpanStatus; -import io.sentry.android.core.AndroidDateUtils; -import io.sentry.android.core.internal.util.AndroidThreadChecker; -import java.util.concurrent.TimeUnit; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@ApiStatus.Internal -public class ActivityLifecycleSpanHelper { - private static final String APP_METRICS_ACTIVITIES_OP = "activity.load"; - - private final @NotNull String activityName; - - private @Nullable SentryDate onCreateStartTimestamp = null; - private @Nullable SentryDate onStartStartTimestamp = null; - private @Nullable ISpan onCreateSpan = null; - private @Nullable ISpan onStartSpan = null; - - public ActivityLifecycleSpanHelper(final @NotNull String activityName) { - this.activityName = activityName; - } - - public void setOnCreateStartTimestamp(final @NotNull SentryDate onCreateStartTimestamp) { - this.onCreateStartTimestamp = onCreateStartTimestamp; - } - - public void setOnStartStartTimestamp(final @NotNull SentryDate onStartStartTimestamp) { - this.onStartStartTimestamp = onStartStartTimestamp; - } - - public void createAndStopOnCreateSpan(final @Nullable ISpan parentSpan) { - if (onCreateStartTimestamp != null && parentSpan != null) { - onCreateSpan = - createLifecycleSpan(parentSpan, activityName + ".onCreate", onCreateStartTimestamp); - onCreateSpan.finish(); - } - } - - public void createAndStopOnStartSpan(final @Nullable ISpan parentSpan) { - if (onStartStartTimestamp != null && parentSpan != null) { - onStartSpan = - createLifecycleSpan(parentSpan, activityName + ".onStart", onStartStartTimestamp); - onStartSpan.finish(); - } - } - - public @Nullable ISpan getOnCreateSpan() { - return onCreateSpan; - } - - public @Nullable ISpan getOnStartSpan() { - return onStartSpan; - } - - public @Nullable SentryDate getOnCreateStartTimestamp() { - return onCreateStartTimestamp; - } - - public @Nullable SentryDate getOnStartStartTimestamp() { - return onStartStartTimestamp; - } - - public void saveSpanToAppStartMetrics() { - if (onCreateSpan == null || onStartSpan == null) { - return; - } - final @Nullable SentryDate onCreateFinishDate = onCreateSpan.getFinishDate(); - final @Nullable SentryDate onStartFinishDate = onStartSpan.getFinishDate(); - if (onCreateFinishDate == null || onStartFinishDate == null) { - return; - } - final long now = SystemClock.uptimeMillis(); - final @NotNull SentryDate nowDate = AndroidDateUtils.getCurrentSentryDateTime(); - final long onCreateShiftMs = - TimeUnit.NANOSECONDS.toMillis(nowDate.diff(onCreateSpan.getStartDate())); - final long onCreateStopShiftMs = - TimeUnit.NANOSECONDS.toMillis(nowDate.diff(onCreateFinishDate)); - final long onStartShiftMs = - TimeUnit.NANOSECONDS.toMillis(nowDate.diff(onStartSpan.getStartDate())); - final long onStartStopShiftMs = TimeUnit.NANOSECONDS.toMillis(nowDate.diff(onStartFinishDate)); - - ActivityLifecycleTimeSpan activityLifecycleTimeSpan = new ActivityLifecycleTimeSpan(); - activityLifecycleTimeSpan - .getOnCreate() - .setup( - onCreateSpan.getDescription(), - TimeUnit.NANOSECONDS.toMillis(onCreateSpan.getStartDate().nanoTimestamp()), - now - onCreateShiftMs, - now - onCreateStopShiftMs); - activityLifecycleTimeSpan - .getOnStart() - .setup( - onStartSpan.getDescription(), - TimeUnit.NANOSECONDS.toMillis(onStartSpan.getStartDate().nanoTimestamp()), - now - onStartShiftMs, - now - onStartStopShiftMs); - AppStartMetrics.getInstance().addActivityLifecycleTimeSpans(activityLifecycleTimeSpan); - } - - private @NotNull ISpan createLifecycleSpan( - final @NotNull ISpan parentSpan, - final @NotNull String description, - final @NotNull SentryDate startTimestamp) { - final @NotNull ISpan span = - parentSpan.startChild( - APP_METRICS_ACTIVITIES_OP, description, startTimestamp, Instrumenter.SENTRY); - setDefaultStartSpanData(span); - return span; - } - - public void clear() { - // in case the parentSpan isn't completed yet, we finish it as cancelled to avoid memory leak - if (onCreateSpan != null && !onCreateSpan.isFinished()) { - onCreateSpan.finish(SpanStatus.CANCELLED); - } - onCreateSpan = null; - if (onStartSpan != null && !onStartSpan.isFinished()) { - onStartSpan.finish(SpanStatus.CANCELLED); - } - onStartSpan = null; - } - - private void setDefaultStartSpanData(final @NotNull ISpan span) { - span.setData( - SpanDataConvention.THREAD_ID, - AndroidThreadChecker.getThreadId(Looper.getMainLooper().getThread())); - span.setData(SpanDataConvention.THREAD_NAME, "main"); - span.setData(SpanDataConvention.CONTRIBUTES_TTID, true); - span.setData(SpanDataConvention.CONTRIBUTES_TTFD, true); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleTimeSpan.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleTimeSpan.java deleted file mode 100644 index 52688a28567..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleTimeSpan.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.sentry.android.core.performance; - -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public class ActivityLifecycleTimeSpan implements Comparable { - private final @NotNull TimeSpan onCreate = new TimeSpan(); - private final @NotNull TimeSpan onStart = new TimeSpan(); - - public final @NotNull TimeSpan getOnCreate() { - return onCreate; - } - - public final @NotNull TimeSpan getOnStart() { - return onStart; - } - - @Override - public int compareTo(ActivityLifecycleTimeSpan o) { - final int onCreateDiff = - Long.compare(onCreate.getStartUptimeMs(), o.onCreate.getStartUptimeMs()); - if (onCreateDiff == 0) { - return Long.compare(onStart.getStartUptimeMs(), o.onStart.getStartUptimeMs()); - } else { - return onCreateDiff; - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java deleted file mode 100644 index 1bb95b9061a..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java +++ /dev/null @@ -1,523 +0,0 @@ -package io.sentry.android.core.performance; - -import android.app.Activity; -import android.app.ActivityManager; -import android.app.Application; -import android.app.ApplicationStartInfo; -import android.content.ContentProvider; -import android.content.Context; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.os.MessageQueue; -import android.os.SystemClock; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import io.sentry.IContinuousProfiler; -import io.sentry.ISentryLifecycleToken; -import io.sentry.ITransactionProfiler; -import io.sentry.NoOpLogger; -import io.sentry.TracesSamplingDecision; -import io.sentry.android.core.BuildInfoProvider; -import io.sentry.android.core.ContextUtils; -import io.sentry.android.core.CurrentActivityHolder; -import io.sentry.android.core.SentryAndroidOptions; -import io.sentry.android.core.internal.util.FirstDrawDoneListener; -import io.sentry.util.AutoClosableReentrantLock; -import io.sentry.util.LazyEvaluator; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.TestOnly; - -/** - * An in-memory representation for app-metrics during app start. As the SDK can't be initialized - * that early, we can't use transactions or spans directly. Thus simple TimeSpans are used and later - * transformed into SDK specific txn/span data structures. - * - *

This class is also responsible for - determining the app start type (cold, warm) - determining - * if the app was launched in foreground - */ -@ApiStatus.Internal -public class AppStartMetrics extends ActivityLifecycleCallbacksAdapter { - public enum AppStartType { - UNKNOWN, - COLD, - WARM - } - - private static long CLASS_LOADED_UPTIME_MS = SystemClock.uptimeMillis(); - - private static volatile @Nullable AppStartMetrics instance; - public static final @NotNull AutoClosableReentrantLock staticLock = - new AutoClosableReentrantLock(); - - private @NotNull AppStartType appStartType = AppStartType.UNKNOWN; - private final LazyEvaluator appLaunchedInForeground = - new LazyEvaluator<>( - new LazyEvaluator.Evaluator() { - @Override - public @NotNull Boolean evaluate() { - return ContextUtils.isForegroundImportance(); - } - }); - private volatile long firstIdle = -1; - - private final @NotNull TimeSpan appStartSpan; - private final @NotNull TimeSpan sdkInitTimeSpan; - private final @NotNull TimeSpan applicationOnCreate; - private final @NotNull Map contentProviderOnCreates; - private final @NotNull List activityLifecycles; - private @Nullable ITransactionProfiler appStartProfiler = null; - private @Nullable IContinuousProfiler appStartContinuousProfiler = null; - private @Nullable TracesSamplingDecision appStartSamplingDecision = null; - private boolean isCallbackRegistered = false; - private boolean shouldSendStartMeasurements = true; - private final AtomicInteger activeActivitiesCounter = new AtomicInteger(); - private final AtomicBoolean firstDrawDone = new AtomicBoolean(false); - - public static @NotNull AppStartMetrics getInstance() { - if (instance == null) { - try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { - if (instance == null) { - instance = new AppStartMetrics(); - } - } - } - //noinspection DataFlowIssue - return instance; - } - - public AppStartMetrics() { - appStartSpan = new TimeSpan(); - sdkInitTimeSpan = new TimeSpan(); - applicationOnCreate = new TimeSpan(); - contentProviderOnCreates = new HashMap<>(); - activityLifecycles = new ArrayList<>(); - } - - /** - * @return the app start span Uses Process.getStartUptimeMillis() as start timestamp, which - * requires API level 24+ - */ - public @NotNull TimeSpan getAppStartTimeSpan() { - return appStartSpan; - } - - /** - * @return the app start span Uses Process.getStartUptimeMillis() as start timestamp, which - * requires API level 24+ - */ - public @NotNull TimeSpan createProcessInitSpan() { - // AppStartSpan and CLASS_LOADED_UPTIME_MS can be modified at any time. - // So, we cannot cache the processInitSpan, but we need to create it when needed. - final @NotNull TimeSpan processInitSpan = new TimeSpan(); - processInitSpan.setup( - "Process Initialization", - appStartSpan.getStartTimestampMs(), - appStartSpan.getStartUptimeMs(), - CLASS_LOADED_UPTIME_MS); - return processInitSpan; - } - - /** - * @return the SDK init time span, as measured pre-performance-v2 Uses ContentProvider/Sdk init - * time as start timestamp - *

Data is filled by either {@link io.sentry.android.core.SentryPerformanceProvider} with a - * fallback to {@link io.sentry.android.core.SentryAndroid}. At least the start timestamp - * should always be set. - */ - public @NotNull TimeSpan getSdkInitTimeSpan() { - return sdkInitTimeSpan; - } - - public @NotNull TimeSpan getApplicationOnCreateTimeSpan() { - return applicationOnCreate; - } - - public void setAppStartType(final @NotNull AppStartType appStartType) { - this.appStartType = appStartType; - } - - public @NotNull AppStartType getAppStartType() { - return appStartType; - } - - public boolean isAppLaunchedInForeground() { - return appLaunchedInForeground.getValue(); - } - - @VisibleForTesting - public void setAppLaunchedInForeground(final boolean appLaunchedInForeground) { - this.appLaunchedInForeground.setValue(appLaunchedInForeground); - } - - /** - * Provides all collected content provider onCreate time spans - * - * @return A sorted list of all onCreate calls - */ - public @NotNull List getContentProviderOnCreateTimeSpans() { - final List spans = new ArrayList<>(contentProviderOnCreates.values()); - Collections.sort(spans); - return spans; - } - - public @NotNull List getActivityLifecycleTimeSpans() { - final List spans = new ArrayList<>(activityLifecycles); - Collections.sort(spans); - return spans; - } - - public void addActivityLifecycleTimeSpans(final @NotNull ActivityLifecycleTimeSpan timeSpan) { - activityLifecycles.add(timeSpan); - } - - public void onAppStartSpansSent() { - shouldSendStartMeasurements = false; - contentProviderOnCreates.clear(); - activityLifecycles.clear(); - } - - public boolean shouldSendStartMeasurements() { - return shouldSendStartMeasurements && appLaunchedInForeground.getValue(); - } - - public long getClassLoadedUptimeMs() { - return CLASS_LOADED_UPTIME_MS; - } - - /** - * @return the app start time span if it was started and perf-2 is enabled, falls back to the sdk - * init time span otherwise - */ - public @NotNull TimeSpan getAppStartTimeSpanWithFallback( - final @NotNull SentryAndroidOptions options) { - // If the app start type was never determined or app wasn't launched in foreground, - // the app start is considered invalid - if (appStartType != AppStartType.UNKNOWN && appLaunchedInForeground.getValue()) { - if (options.isEnablePerformanceV2()) { - // Only started when sdk version is >= N - final @NotNull TimeSpan appStartSpan = getAppStartTimeSpan(); - if (appStartSpan.hasStarted() - && appStartSpan.getDurationMs() <= TimeUnit.MINUTES.toMillis(1)) { - return appStartSpan; - } - } - - // fallback: use sdk init time span, as it will always have a start time set - final @NotNull TimeSpan sdkInitTimeSpan = getSdkInitTimeSpan(); - if (sdkInitTimeSpan.hasStarted() - && sdkInitTimeSpan.getDurationMs() <= TimeUnit.MINUTES.toMillis(1)) { - return sdkInitTimeSpan; - } - } - - return new TimeSpan(); - } - - @TestOnly - void setFirstIdle(final long firstIdle) { - this.firstIdle = firstIdle; - } - - @TestOnly - long getFirstIdle() { - return firstIdle; - } - - @TestOnly - public void clear() { - appStartType = AppStartType.UNKNOWN; - appStartSpan.reset(); - sdkInitTimeSpan.reset(); - applicationOnCreate.reset(); - contentProviderOnCreates.clear(); - activityLifecycles.clear(); - if (appStartProfiler != null) { - appStartProfiler.close(); - } - appStartProfiler = null; - if (appStartContinuousProfiler != null) { - appStartContinuousProfiler.close(true); - } - appStartContinuousProfiler = null; - appStartSamplingDecision = null; - appLaunchedInForeground.resetValue(); - isCallbackRegistered = false; - shouldSendStartMeasurements = true; - firstDrawDone.set(false); - activeActivitiesCounter.set(0); - firstIdle = -1; - } - - public @Nullable ITransactionProfiler getAppStartProfiler() { - return appStartProfiler; - } - - public void setAppStartProfiler(final @Nullable ITransactionProfiler appStartProfiler) { - this.appStartProfiler = appStartProfiler; - } - - public @Nullable IContinuousProfiler getAppStartContinuousProfiler() { - return appStartContinuousProfiler; - } - - public void setAppStartContinuousProfiler( - final @Nullable IContinuousProfiler appStartContinuousProfiler) { - this.appStartContinuousProfiler = appStartContinuousProfiler; - } - - public void setAppStartSamplingDecision( - final @Nullable TracesSamplingDecision appStartSamplingDecision) { - this.appStartSamplingDecision = appStartSamplingDecision; - } - - public @Nullable TracesSamplingDecision getAppStartSamplingDecision() { - return appStartSamplingDecision; - } - - @TestOnly - @ApiStatus.Internal - public void setClassLoadedUptimeMs(final long classLoadedUptimeMs) { - CLASS_LOADED_UPTIME_MS = classLoadedUptimeMs; - } - - /** - * Called by instrumentation - * - * @param application The application object where onCreate was called on - * @noinspection unused - */ - public static void onApplicationCreate(final @NotNull Application application) { - final long now = SystemClock.uptimeMillis(); - - final @NotNull AppStartMetrics instance = getInstance(); - if (instance.applicationOnCreate.hasNotStarted()) { - instance.applicationOnCreate.setStartedAt(now); - instance.registerLifecycleCallbacks(application); - } - } - - /** - * Called by instrumentation - * - * @param application The application object where onCreate was called on - * @noinspection unused - */ - public static void onApplicationPostCreate(final @NotNull Application application) { - final long now = SystemClock.uptimeMillis(); - - final @NotNull AppStartMetrics instance = getInstance(); - if (instance.applicationOnCreate.hasNotStopped()) { - instance.applicationOnCreate.setDescription(application.getClass().getName() + ".onCreate"); - instance.applicationOnCreate.setStoppedAt(now); - } - } - - /** - * Register a callback to check if an activity was started after the application was created. Must - * be called from the main thread. - * - * @param application The application object to register the callback to - */ - public void registerLifecycleCallbacks(final @NotNull Application application) { - if (isCallbackRegistered) { - return; - } - isCallbackRegistered = true; - appLaunchedInForeground.resetValue(); - application.registerActivityLifecycleCallbacks(instance); - - final @Nullable ActivityManager activityManager = - (ActivityManager) application.getSystemService(Context.ACTIVITY_SERVICE); - - if (activityManager != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { - final List historicalProcessStartReasons = - activityManager.getHistoricalProcessStartReasons(1); - if (!historicalProcessStartReasons.isEmpty()) { - final @NotNull ApplicationStartInfo info = historicalProcessStartReasons.get(0); - if (info.getStartupState() == ApplicationStartInfo.STARTUP_STATE_STARTED) { - if (info.getStartType() == ApplicationStartInfo.START_TYPE_COLD) { - appStartType = AppStartType.COLD; - } else { - appStartType = AppStartType.WARM; - } - } - } - } - - if (appStartType == AppStartType.UNKNOWN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - Looper.getMainLooper() - .getQueue() - .addIdleHandler( - new MessageQueue.IdleHandler() { - @Override - public boolean queueIdle() { - firstIdle = SystemClock.uptimeMillis(); - checkCreateTimeOnMain(); - return false; - } - }); - } else if (appStartType == AppStartType.UNKNOWN) { - // We post on the main thread a task to post a check on the main thread. On Pixel devices - // (possibly others) the first task posted on the main thread is called before the - // Activity.onCreate callback. This is a workaround for that, so that the Activity.onCreate - // callback is called before the application one. - final Handler handler = new Handler(Looper.getMainLooper()); - handler.post( - new Runnable() { - @Override - public void run() { - // not technically correct, but close enough for pre-M - firstIdle = SystemClock.uptimeMillis(); - handler.post(() -> checkCreateTimeOnMain()); - } - }); - } - } - - private void checkCreateTimeOnMain() { - // if no activity has ever been created, app was launched in background - if (activeActivitiesCounter.get() == 0) { - appLaunchedInForeground.setValue(false); - - // we stop the app start profilers, as they are useless and likely to timeout - if (appStartProfiler != null && appStartProfiler.isRunning()) { - appStartProfiler.close(); - appStartProfiler = null; - } - if (appStartContinuousProfiler != null && appStartContinuousProfiler.isRunning()) { - appStartContinuousProfiler.close(true); - appStartContinuousProfiler = null; - } - } - } - - @Override - public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { - final long activityCreatedUptimeMillis = SystemClock.uptimeMillis(); - CurrentActivityHolder.getInstance().setActivity(activity); - - // the first activity determines the app start type - if (activeActivitiesCounter.incrementAndGet() == 1 && !firstDrawDone.get()) { - final long nowUptimeMs = SystemClock.uptimeMillis(); - - // If the app (process) was launched more than 1 minute ago, consider it a warm start - final long durationSinceAppStartMillis = nowUptimeMs - appStartSpan.getStartUptimeMs(); - if (!appLaunchedInForeground.getValue() - || durationSinceAppStartMillis > TimeUnit.MINUTES.toMillis(1)) { - appStartType = AppStartType.WARM; - shouldSendStartMeasurements = true; - appStartSpan.reset(); - appStartSpan.setStartedAt(activityCreatedUptimeMillis); - CLASS_LOADED_UPTIME_MS = activityCreatedUptimeMillis; - contentProviderOnCreates.clear(); - applicationOnCreate.reset(); - } else if (appStartType == AppStartType.UNKNOWN) { - // pre API 35 handling - if (savedInstanceState != null) { - appStartType = AppStartType.WARM; - } else if (firstIdle != -1 && activityCreatedUptimeMillis > firstIdle) { - appStartType = AppStartType.WARM; - } else { - appStartType = AppStartType.COLD; - } - } - } - appLaunchedInForeground.setValue(true); - } - - @Override - public void onActivityStarted(@NonNull Activity activity) { - CurrentActivityHolder.getInstance().setActivity(activity); - - if (firstDrawDone.get()) { - return; - } - if (activity.getWindow() != null) { - FirstDrawDoneListener.registerForNextDraw( - activity, () -> onFirstFrameDrawn(), new BuildInfoProvider(NoOpLogger.getInstance())); - } else { - new Handler(Looper.getMainLooper()).post(() -> onFirstFrameDrawn()); - } - } - - @Override - public void onActivityResumed(@NonNull Activity activity) { - CurrentActivityHolder.getInstance().setActivity(activity); - } - - @Override - public void onActivityPaused(@NonNull Activity activity) { - CurrentActivityHolder.getInstance().clearActivity(activity); - } - - @Override - public void onActivityStopped(@NonNull Activity activity) { - CurrentActivityHolder.getInstance().clearActivity(activity); - } - - @Override - public void onActivityDestroyed(@NonNull Activity activity) { - CurrentActivityHolder.getInstance().clearActivity(activity); - - final int remainingActivities = activeActivitiesCounter.decrementAndGet(); - // if the app is moving into background - // as the next onActivityCreated will treat it as a new warm app start - if (remainingActivities == 0 && !activity.isChangingConfigurations()) { - appStartType = AppStartType.WARM; - appLaunchedInForeground.setValue(true); - shouldSendStartMeasurements = true; - firstDrawDone.set(false); - } - } - - /** - * Called by instrumentation - * - * @param contentProvider The content provider where onCreate was called on - * @noinspection unused - */ - public static void onContentProviderCreate(final @NotNull ContentProvider contentProvider) { - final long now = SystemClock.uptimeMillis(); - - final TimeSpan measurement = new TimeSpan(); - measurement.setStartedAt(now); - getInstance().contentProviderOnCreates.put(contentProvider, measurement); - } - - /** - * Called by instrumentation - * - * @param contentProvider The content provider where onCreate was called on - * @noinspection unused - */ - public static void onContentProviderPostCreate(final @NotNull ContentProvider contentProvider) { - final long now = SystemClock.uptimeMillis(); - - final @Nullable TimeSpan measurement = - getInstance().contentProviderOnCreates.get(contentProvider); - if (measurement != null && measurement.hasNotStopped()) { - measurement.setDescription(contentProvider.getClass().getName() + ".onCreate"); - measurement.setStoppedAt(now); - } - } - - synchronized void onFirstFrameDrawn() { - if (!firstDrawDone.getAndSet(true)) { - final @NotNull AppStartMetrics appStartMetrics = getInstance(); - appStartMetrics.getSdkInitTimeSpan().stop(); - appStartMetrics.getAppStartTimeSpan().stop(); - } - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/TimeSpan.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/TimeSpan.java deleted file mode 100644 index eb631739728..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/TimeSpan.java +++ /dev/null @@ -1,181 +0,0 @@ -package io.sentry.android.core.performance; - -import android.os.SystemClock; -import io.sentry.DateUtils; -import io.sentry.SentryDate; -import io.sentry.SentryLongDate; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -/** - * A measurement for time critical components on a macro (ms) level. Based on {@link - * SystemClock#uptimeMillis()} to ensure linear time progression (as opposed to a syncable clock). - * To provide real world unix time information, the start uptime time is stored alongside the unix - * time. The stop unix time is artificial, it gets projected based on the start time + duration of - * the time span. - */ -@ApiStatus.Internal -public class TimeSpan implements Comparable { - - private @Nullable String description; - private long startUnixTimeMs; - private long startUptimeMs; - private long stopUptimeMs; - - public void setup( - final @Nullable String description, - final long startUnixTimeMs, - final long startUptimeMs, - final long stopUptimeMs) { - this.description = description; - this.startUnixTimeMs = startUnixTimeMs; - this.startUptimeMs = startUptimeMs; - this.stopUptimeMs = stopUptimeMs; - } - - /** Start the time span */ - public void start() { - startUptimeMs = SystemClock.uptimeMillis(); - startUnixTimeMs = System.currentTimeMillis(); - } - - /** - * @param uptimeMs the uptime in ms, provided by {@link SystemClock#uptimeMillis()} - */ - public void setStartedAt(final long uptimeMs) { - // TODO maybe sanity check? - this.startUptimeMs = uptimeMs; - - final long shiftMs = SystemClock.uptimeMillis() - startUptimeMs; - startUnixTimeMs = System.currentTimeMillis() - shiftMs; - } - - /** Stops the time span */ - public void stop() { - stopUptimeMs = SystemClock.uptimeMillis(); - } - - /** - * @param uptimeMs the uptime in ms, provided by {@link SystemClock#uptimeMillis()} - */ - public void setStoppedAt(final long uptimeMs) { - // TODO maybe sanity check? - stopUptimeMs = uptimeMs; - } - - public boolean hasStarted() { - return startUptimeMs != 0; - } - - public boolean hasNotStarted() { - return startUptimeMs == 0; - } - - public boolean hasStopped() { - return stopUptimeMs != 0; - } - - public boolean hasNotStopped() { - return stopUptimeMs == 0; - } - - /** - * @return the start timestamp of this measurement, as uptime, in ms - */ - public long getStartUptimeMs() { - return startUptimeMs; - } - - /** - * @return the start timestamp of this measurement, unix time, in ms - */ - public long getStartTimestampMs() { - return startUnixTimeMs; - } - - /** - * @return the start timestamp of this measurement, unix time - */ - public @Nullable SentryDate getStartTimestamp() { - if (hasStarted()) { - return new SentryLongDate(DateUtils.millisToNanos(getStartTimestampMs())); - } - return null; - } - - /** - * @return the start timestamp of this measurement, unix time, in seconds - */ - public double getStartTimestampSecs() { - return DateUtils.millisToSeconds(startUnixTimeMs); - } - - /** - * @return the projected stop timestamp of this measurement, based on the start timestamp and the - * duration. If the time span was not started 0 is returned, if the time span was not stopped - * the start timestamp is returned. - */ - public long getProjectedStopTimestampMs() { - if (hasStarted()) { - return startUnixTimeMs + getDurationMs(); - } - return 0; - } - - /** - * @return the projected stop timestamp - * @see #getProjectedStopTimestampMs() - */ - public double getProjectedStopTimestampSecs() { - return DateUtils.millisToSeconds(getProjectedStopTimestampMs()); - } - - /** - * @return the projected stop timestamp - * @see #getProjectedStopTimestampMs() - */ - public @Nullable SentryDate getProjectedStopTimestamp() { - if (hasStopped()) { - return new SentryLongDate(DateUtils.millisToNanos(getProjectedStopTimestampMs())); - } - return null; - } - - /** - * @return the duration of this measurement, in ms, or 0 if no end time is set - */ - public long getDurationMs() { - if (hasStopped()) { - return stopUptimeMs - startUptimeMs; - } else { - return 0; - } - } - - @TestOnly - public void setStartUnixTimeMs(long startUnixTimeMs) { - this.startUnixTimeMs = startUnixTimeMs; - } - - public @Nullable String getDescription() { - return description; - } - - public void setDescription(@Nullable final String description) { - this.description = description; - } - - public void reset() { - description = null; - startUptimeMs = 0; - stopUptimeMs = 0; - startUnixTimeMs = 0; - } - - @Override - public int compareTo(@NotNull final TimeSpan o) { - return Long.compare(startUnixTimeMs, o.startUnixTimeMs); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/WindowContentChangedCallback.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/WindowContentChangedCallback.java deleted file mode 100644 index 79180a7bd5a..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/WindowContentChangedCallback.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.sentry.android.core.performance; - -import android.view.Window; -import io.sentry.android.core.internal.gestures.WindowCallbackAdapter; -import org.jetbrains.annotations.NotNull; - -public class WindowContentChangedCallback extends WindowCallbackAdapter { - - private final @NotNull Runnable callback; - - public WindowContentChangedCallback( - final @NotNull Window.Callback delegate, final @NotNull Runnable callback) { - super(delegate); - this.callback = callback; - } - - @Override - public void onContentChanged() { - super.onContentChanged(); - callback.run(); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/util/AndroidLazyEvaluator.java b/sentry-android-core/src/main/java/io/sentry/android/core/util/AndroidLazyEvaluator.java deleted file mode 100644 index beb9ff8e8ed..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/util/AndroidLazyEvaluator.java +++ /dev/null @@ -1,68 +0,0 @@ -package io.sentry.android.core.util; - -import android.content.Context; -import io.sentry.util.LazyEvaluator; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Class that evaluates a function lazily. It means the evaluator function is called only when - * getValue is called, and it's cached. Same as {@link LazyEvaluator} but accepts Context as an - * argument for {@link AndroidLazyEvaluator#getValue}. - */ -@ApiStatus.Internal -public final class AndroidLazyEvaluator { - - private volatile @Nullable T value = null; - private final @NotNull AndroidEvaluator evaluator; - - /** - * Class that evaluates a function lazily. It means the evaluator function is called only when - * getValue is called, and it's cached. - * - * @param evaluator The function to evaluate. - */ - public AndroidLazyEvaluator(final @NotNull AndroidEvaluator evaluator) { - this.evaluator = evaluator; - } - - /** - * Executes the evaluator function and caches its result, so that it's called only once, unless - * resetValue is called. - * - * @return The result of the evaluator function. - */ - public @Nullable T getValue(final @NotNull Context context) { - if (value == null) { - synchronized (this) { - if (value == null) { - value = evaluator.evaluate(context); - } - } - } - - return value; - } - - public void setValue(final @Nullable T value) { - synchronized (this) { - this.value = value; - } - } - - /** - * Resets the internal value and forces the evaluator function to be called the next time - * getValue() is called. - */ - public void resetValue() { - synchronized (this) { - this.value = null; - } - } - - public interface AndroidEvaluator { - @Nullable - T evaluate(@NotNull Context context); - } -} diff --git a/sentry-android-core/src/main/proto/io/sentry/android/core/internal/tombstone/tombstone.proto b/sentry-android-core/src/main/proto/io/sentry/android/core/internal/tombstone/tombstone.proto deleted file mode 100644 index 2f9cbe52850..00000000000 --- a/sentry-android-core/src/main/proto/io/sentry/android/core/internal/tombstone/tombstone.proto +++ /dev/null @@ -1,218 +0,0 @@ -// Added and adapted from: https://android.googlesource.com/platform/system/core/+/refs/heads/main/debuggerd/proto/tombstone.proto -// Sentry changes: -// * change the java_package -// -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Protobuf definition for Android tombstones. -// -// An app can get hold of these for any `REASON_CRASH_NATIVE` instance of -// `android.app.ApplicationExitInfo`. -// -// https://developer.android.com/reference/android/app/ApplicationExitInfo#getTraceInputStream() -// -syntax = "proto3"; -option java_package = "io.sentry.android.core.internal.tombstone"; -option java_outer_classname = "TombstoneProtos"; -// NOTE TO OEMS: -// If you add custom fields to this proto, do not use numbers in the reserved range. -// NOTE TO CONSUMERS: -// With proto3 -- unlike proto2 -- HasValue is unreliable for any field -// where the default value for that type is also a valid value for the field. -// This means, for example, that a boolean that is false or an integer that -// is zero will appear to be missing --- but because they're not actually -// marked as `optional` in this schema, consumers should just use values -// without first checking whether or not they're "present". -// https://protobuf.dev/programming-guides/proto3/#default -message CrashDetail { - bytes name = 1; - bytes data = 2; - reserved 3 to 999; -} -message StackHistoryBufferEntry { - BacktraceFrame addr = 1; - uint64 fp = 2; - uint64 tag = 3; - reserved 4 to 999; -} -message StackHistoryBuffer { - uint64 tid = 1; - repeated StackHistoryBufferEntry entries = 2; - reserved 3 to 999; -} -message Tombstone { - Architecture arch = 1; - Architecture guest_arch = 24; - string build_fingerprint = 2; - string revision = 3; - string timestamp = 4; - uint32 pid = 5; - uint32 tid = 6; - uint32 uid = 7; - string selinux_label = 8; - repeated string command_line = 9; - // Process uptime in seconds. - uint32 process_uptime = 20; - Signal signal_info = 10; - string abort_message = 14; - repeated CrashDetail crash_details = 21; - repeated Cause causes = 15; - map threads = 16; - map guest_threads = 25; - repeated MemoryMapping memory_mappings = 17; - repeated LogBuffer log_buffers = 18; - repeated FD open_fds = 19; - uint32 page_size = 22; - bool has_been_16kb_mode = 23; - StackHistoryBuffer stack_history_buffer = 26; - reserved 27 to 999; -} -enum Architecture { - ARM32 = 0; - ARM64 = 1; - X86 = 2; - X86_64 = 3; - RISCV64 = 4; - NONE = 5; - reserved 6 to 999; -} -message Signal { - int32 number = 1; - string name = 2; - int32 code = 3; - string code_name = 4; - bool has_sender = 5; - int32 sender_uid = 6; - int32 sender_pid = 7; - bool has_fault_address = 8; - uint64 fault_address = 9; - // Note, may or may not contain the dump of the actual memory contents. Currently, on arm64, we - // only include metadata, and not the contents. - MemoryDump fault_adjacent_metadata = 10; - reserved 11 to 999; -} -message HeapObject { - uint64 address = 1; - uint64 size = 2; - uint64 allocation_tid = 3; - repeated BacktraceFrame allocation_backtrace = 4; - uint64 deallocation_tid = 5; - repeated BacktraceFrame deallocation_backtrace = 6; -} -message MemoryError { - enum Tool { - GWP_ASAN = 0; - SCUDO = 1; - reserved 2 to 999; - } - Tool tool = 1; - enum Type { - UNKNOWN = 0; - USE_AFTER_FREE = 1; - DOUBLE_FREE = 2; - INVALID_FREE = 3; - BUFFER_OVERFLOW = 4; - BUFFER_UNDERFLOW = 5; - reserved 6 to 999; - } - Type type = 2; - oneof location { - HeapObject heap = 3; - } - reserved 4 to 999; -} -message Cause { - string human_readable = 1; - oneof details { - MemoryError memory_error = 2; - } - reserved 3 to 999; -} -message Register { - string name = 1; - uint64 u64 = 2; - reserved 3 to 999; -} -message Thread { - int32 id = 1; - string name = 2; - repeated Register registers = 3; - repeated string backtrace_note = 7; - repeated string unreadable_elf_files = 9; - repeated BacktraceFrame current_backtrace = 4; - repeated MemoryDump memory_dump = 5; - int64 tagged_addr_ctrl = 6; - int64 pac_enabled_keys = 8; - reserved 10 to 999; -} -message BacktraceFrame { - uint64 rel_pc = 1; - uint64 pc = 2; - uint64 sp = 3; - string function_name = 4; - uint64 function_offset = 5; - string file_name = 6; - uint64 file_map_offset = 7; - string build_id = 8; - reserved 9 to 999; -} -message ArmMTEMetadata { - // One memory tag per granule (e.g. every 16 bytes) of regular memory. - bytes memory_tags = 1; - reserved 2 to 999; -} -message MemoryDump { - string register_name = 1; - string mapping_name = 2; - uint64 begin_address = 3; - bytes memory = 4; - oneof metadata { - ArmMTEMetadata arm_mte_metadata = 6; - } - reserved 5, 7 to 999; -} -message MemoryMapping { - uint64 begin_address = 1; - uint64 end_address = 2; - uint64 offset = 3; - bool read = 4; - bool write = 5; - bool execute = 6; - string mapping_name = 7; - string build_id = 8; - uint64 load_bias = 9; - reserved 10 to 999; -} -message FD { - int32 fd = 1; - string path = 2; - string owner = 3; - uint64 tag = 4; - reserved 5 to 999; -} -message LogBuffer { - string name = 1; - repeated LogMessage logs = 2; - reserved 3 to 999; -} -message LogMessage { - string timestamp = 1; - uint32 pid = 2; - uint32 tid = 3; - uint32 priority = 4; - string tag = 5; - string message = 6; - reserved 7 to 999; -} diff --git a/sentry-android-core/src/main/res/drawable/sentry_edit_text_border.xml b/sentry-android-core/src/main/res/drawable/sentry_edit_text_border.xml deleted file mode 100644 index 5615e318573..00000000000 --- a/sentry-android-core/src/main/res/drawable/sentry_edit_text_border.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - diff --git a/sentry-android-core/src/main/res/drawable/sentry_logo_dark.xml b/sentry-android-core/src/main/res/drawable/sentry_logo_dark.xml deleted file mode 100644 index 72bae31335f..00000000000 --- a/sentry-android-core/src/main/res/drawable/sentry_logo_dark.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/sentry-android-core/src/main/res/drawable/sentry_oval_button_ripple_background.xml b/sentry-android-core/src/main/res/drawable/sentry_oval_button_ripple_background.xml deleted file mode 100644 index 10e58ce27f6..00000000000 --- a/sentry-android-core/src/main/res/drawable/sentry_oval_button_ripple_background.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/sentry-android-core/src/main/res/drawable/sentry_user_feedback_button_logo_24.xml b/sentry-android-core/src/main/res/drawable/sentry_user_feedback_button_logo_24.xml deleted file mode 100644 index 4aab5d6c37b..00000000000 --- a/sentry-android-core/src/main/res/drawable/sentry_user_feedback_button_logo_24.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/sentry-android-core/src/main/res/layout/sentry_dialog_user_feedback.xml b/sentry-android-core/src/main/res/layout/sentry_dialog_user_feedback.xml deleted file mode 100644 index e6f77b90a7f..00000000000 --- a/sentry-android-core/src/main/res/layout/sentry_dialog_user_feedback.xml +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - - - - - - - - - - -