diff --git a/.github/actions/environment/action.yml b/.github/actions/environment/action.yml index 9e451a282e..a646c021f8 100644 --- a/.github/actions/environment/action.yml +++ b/.github/actions/environment/action.yml @@ -24,6 +24,12 @@ runs: shell: bash run: sudo chmod 666 /var/run/docker.sock + # Install old deprecated libssl1 for .NET 5.0 on Linux + - name: Install libssl1 for NET 5.0 on Linux + if: ${{ runner.os == 'Linux' }} + shell: bash + run: sudo ./scripts/install-libssl1.sh + - name: Install Linux ARM 32-bit dependencies if: ${{ matrix.rid == 'linux-arm' }} shell: bash @@ -56,7 +62,7 @@ runs: uses: android-actions/setup-android@07976c6290703d34c16d382cb36445f98bb43b1f # v3.2.0 with: # Exclude "tools" because the emulator is not needed (nor available for Windows/Linux ARM64) - packages: platform-tools platforms;android-34 platforms;android-35 build-tools;36.0.0 + packages: platform-tools platforms;android-35 platforms;android-36 build-tools;36.0.0 log-accepted-android-sdk-licenses: false # Java 11 is needed by .NET Android @@ -90,6 +96,15 @@ runs: dotnet-version: | 8.0.x 9.0.304 + 10.0.100-rc.1.25451.107 + global-json-file: global.json + + # .NET 5.0 does not support ARM64 on macOS + - name: Install .NET 5.0 SDK + if: ${{ runner.os != 'macOS' || runner.arch != 'ARM64' }} + uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + with: + dotnet-version: 5.0.x - name: Install .NET Workloads shell: bash diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1bb59a1678..435e61fd07 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,15 +64,11 @@ jobs: key: sentry-native-${{ matrix.rid }}-${{ hashFiles('scripts/build-sentry-native.ps1') }}-${{ hashFiles('.git/modules/modules/sentry-native/HEAD') }} enableCrossOsArchive: true - - name: Remove unused applications - if: ${{ !matrix.container }} - uses: ./.github/actions/freediskspace - - run: scripts/build-sentry-native.ps1 if: steps.cache.outputs.cache-hit != 'true' shell: pwsh - build: + build-sentry: needs: build-sentry-native name: .NET (${{ matrix.rid }}) runs-on: ${{ matrix.os }} @@ -254,7 +250,8 @@ jobs: uses: actions/checkout@v5 with: # We only check out what is absolutely necessary to reduce a chance of local files impacting - # integration tests, e.g. Directory.Build.props, nuget.config, ... + # integration tests (nuget.config etc.)... But we need the root Directory.Build.props calculate + # the package version sparse-checkout: | integration-test .github @@ -267,11 +264,23 @@ jobs: path: src - name: Integration test + if: ${{ (matrix.rid != 'linux-musl-x64') && (matrix.rid != 'linux-musl-arm64') }} + uses: getsentry/github-workflows/sentry-cli/integration-test/@a5e409bd5bad4c295201cdcfe862b17c50b29ab7 # v2.14.1 + with: + path: integration-test + + # For the linux-musl runtimes we have to pin the ContainerBaseImage for preview or RC builds, since + # these don't conform to the normal naming conventions and don't get resolved automatically. We do + # by passing it as parameter to aot.Tests.ps1 via an environment variable + - name: Integration test (musl) + if: ${{ (matrix.rid == 'linux-musl-x64') || (matrix.rid == 'linux-musl-arm64') }} uses: getsentry/github-workflows/sentry-cli/integration-test/@v2 + env: + ContainerBaseImage: 'mcr.microsoft.com/dotnet/nightly/runtime-deps:10.0-preview-alpine3.22' with: path: integration-test - msbuild: + ms-build: needs: build-sentry-native name: MSBuild runs-on: windows-latest @@ -301,7 +310,12 @@ jobs: - name: Run MSBuild id: msbuild - run: msbuild Sentry-CI-Build-Windows.slnf -t:Restore,Build -p:Configuration=Release --nologo -v:minimal -flp:logfile=msbuild.log -p:CopyLocalLockFileAssemblies=true -bl:msbuild.binlog + run: msbuild Sentry-CI-Build-Windows.slnf -t:Restore,Build,Pack -p:Configuration=Release --nologo -v:minimal -flp:logfile=msbuild.log -p:CopyLocalLockFileAssemblies=true -bl:msbuild.binlog + + - name: Test MSBuild + uses: getsentry/github-workflows/sentry-cli/integration-test/@a5e409bd5bad4c295201cdcfe862b17c50b29ab7 # v2.14.1 + with: + path: integration-test/msbuild.Tests.ps1 - name: Upload logs if: ${{ always() }} @@ -316,7 +330,7 @@ jobs: # Unsupported Native AOT runtimes should have SentryNative auto-disabled # to avoid native library loading errors on startup. unsupported-aot: - needs: build + needs: build-sentry name: Unsupported AOT (${{ matrix.rid }}) runs-on: ${{ matrix.os }} @@ -348,7 +362,7 @@ jobs: path: src - name: Test AOT - uses: getsentry/github-workflows/sentry-cli/integration-test/@v2 + uses: getsentry/github-workflows/sentry-cli/integration-test/@a5e409bd5bad4c295201cdcfe862b17c50b29ab7 # v2.14.1 env: RuntimeIdentifier: ${{ matrix.rid }} with: @@ -384,7 +398,7 @@ jobs: uses: ./.github/actions/buildnative - name: Publish Test app (macOS) - run: dotnet publish test/Sentry.TrimTest/Sentry.TrimTest.csproj -c Release -r osx-arm64 + run: dotnet publish test/Sentry.TrimTest/Sentry.TrimTest.csproj -c Release -r osx-arm64 -p:NO_MOBILE=true - name: Publish Test app (Android) run: dotnet publish test/Sentry.MauiTrimTest/Sentry.MauiTrimTest.csproj -c Release -f net9.0-android35.0 -r android-arm64 diff --git a/.github/workflows/device-tests-android.yml b/.github/workflows/device-tests-android.yml index b97936f3e2..b234d39df2 100644 --- a/.github/workflows/device-tests-android.yml +++ b/.github/workflows/device-tests-android.yml @@ -8,6 +8,7 @@ on: pull_request: paths-ignore: - "**.md" + workflow_dispatch: jobs: build: @@ -64,6 +65,12 @@ jobs: env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_NOLOGO: 1 + # We don't need the Google APIs, but the default images are not available for 32+ + ANDROID_EMULATOR_TARGET: google_apis + ANDROID_EMULATOR_RAM_SIZE: 2048M + ANDROID_EMULATOR_ARCH: x86_64 + ANDROID_EMULATOR_DISK_SIZE: 4096M + ANDROID_EMULATOR_OPTIONS: -no-snapshot-save -no-window -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none steps: # See https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/ - name: Enable KVM group perms @@ -87,17 +94,33 @@ jobs: # Cached AVD setup per https://github.com/ReactiveCircus/android-emulator-runner/blob/main/README.md - name: Run Tests + id: first-run + continue-on-error: true timeout-minutes: 40 uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # Tag: v2.34.0 with: api-level: ${{ matrix.api-level }} - # We don't need the Google APIs, but the default images are not available for 32+ - target: google_apis + target: ${{ env.ANDROID_EMULATOR_TARGET }} force-avd-creation: false - ram-size: 2048M - arch: x86_64 - disk-size: 4096M - emulator-options: -no-snapshot-save -no-window -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + ram-size: ${{ env.ANDROID_EMULATOR_RAM_SIZE }} + arch: ${{ env.ANDROID_EMULATOR_ARCH }} + disk-size: ${{ env.ANDROID_EMULATOR_DISK_SIZE }} + emulator-options: ${{ env.ANDROID_EMULATOR_OPTIONS }} + disable-animations: false + script: pwsh scripts/device-test.ps1 android -Run -Tfm ${{ matrix.tfm }} + + - name: Retry Tests (if previous failed to run) + if: steps.first-run.outcome == 'failure' + timeout-minutes: 40 + uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # Tag: v2.34.0 + with: + api-level: ${{ matrix.api-level }} + target: ${{ env.ANDROID_EMULATOR_TARGET }} + force-avd-creation: false + ram-size: ${{ env.ANDROID_EMULATOR_RAM_SIZE }} + arch: ${{ env.ANDROID_EMULATOR_ARCH }} + disk-size: ${{ env.ANDROID_EMULATOR_DISK_SIZE }} + emulator-options: ${{ env.ANDROID_EMULATOR_OPTIONS }} disable-animations: false script: pwsh scripts/device-test.ps1 android -Run -Tfm ${{ matrix.tfm }} diff --git a/.github/workflows/device-tests-ios.yml b/.github/workflows/device-tests-ios.yml index c56226bdb6..0e118745cc 100644 --- a/.github/workflows/device-tests-ios.yml +++ b/.github/workflows/device-tests-ios.yml @@ -8,6 +8,7 @@ on: pull_request: paths-ignore: - "**.md" + workflow_dispatch: jobs: ios-tests: diff --git a/CHANGELOG.md b/CHANGELOG.md index 141222b598..0e39c2c1a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ # Changelog +## 6.0.0-preview.1 + +### Features + +- Added `EnableBackpressureHandling` option for Automatic backpressure handling. When enabled this automatically reduces the sample rate when the SDK detects events being dropped. ([#4452](https://github.com/getsentry/sentry-dotnet/pull/4452)) +- Add (experimental) _Structured Logs_ integration for `Serilog` ([#4462](https://github.com/getsentry/sentry-dotnet/pull/4462)) + +### Fixes + +- Upload linked PDBs to fix non-IL-stripped symbolication for iOS ([#4527](https://github.com/getsentry/sentry-dotnet/pull/4527)) +- In MAUI Android apps, generate and inject UUID to APK and upload ProGuard mapping to Sentry with the UUID ([#4532](https://github.com/getsentry/sentry-dotnet/pull/4532)) +- Fixed WASM0001 warning when building Blazor WebAssembly projects ([#4519](https://github.com/getsentry/sentry-dotnet/pull/4519)) + +### Dependencies + +- Bump Cocoa SDK from v8.56.0 to v8.56.2 ([#4555](https://github.com/getsentry/sentry-dotnet/pull/4555), [#4572](https://github.com/getsentry/sentry-dotnet/pull/4572)) + - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8562) + - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.56.0...8.56.2) +- Bump Native SDK from v0.11.0 to v0.11.1 ([#4557](https://github.com/getsentry/sentry-dotnet/pull/4557)) + - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0111) + - [diff](https://github.com/getsentry/sentry-native/compare/0.11.0...0.11.1) +- Bump CLI from v2.54.0 to v2.55.0 ([#4556](https://github.com/getsentry/sentry-dotnet/pull/4556)) + - [changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md#2550) + - [diff](https://github.com/getsentry/sentry-cli/compare/2.54.0...2.55.0) + +### Dependencies + +- Bump Java SDK from v8.21.1 to v8.22.0 ([#4552](https://github.com/getsentry/sentry-dotnet/pull/4552)) + - [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#8220) + - [diff](https://github.com/getsentry/sentry-java/compare/8.21.1...8.22.0) + +## Version 6.0.0 + +### BREAKING CHANGES + +- This release adds support for .NET 10 and drops support for net8.0-android, net8.0-ios, net8.0-maccatalyst and net8.0-windows10.0.19041.0 ([#4461](https://github.com/getsentry/sentry-dotnet/pull/4461)) +- Added support for v3 of the Android AssemblyStore format that is used in .NET 10 and dropped support for v1 that was used in .NET 8 ([#4583](https://github.com/getsentry/sentry-dotnet/pull/4583)) + ## 5.15.1 ### Fixes diff --git a/Directory.Build.props b/Directory.Build.props index a74738d5cd..eac7530416 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,8 @@ - 5.15.1 + 6.0.0-preview.1 + prerelease 13 true true @@ -20,11 +21,27 @@ $(NoWarn);NU1902;NU1903 + + $(NoWarn);XCODE_26_0_PREVIEW + NU1902;NU1903 + + + net10.0 + net9.0 + net8.0 + net8.0;net9.0;net10.0 + net10.0-android36.0 + net9.0-android35.0 + net9.0-ios26 + net9.0-ios18.0 + net9.0-maccatalyst26 + net9.0-maccatalyst18.0 + net9.0-windows10.0.19041.0 - + dev @@ -86,7 +103,7 @@ - 2.54.0 + 2.55.0 $(MSBuildThisFileDirectory)tools\sentry-cli\$(SentryCLIVersion)\ diff --git a/Sentry.sln b/Sentry.sln index 3730377a46..e8b04afa38 100644 --- a/Sentry.sln +++ b/Sentry.sln @@ -209,6 +209,74 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.SourceGenerators.Tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Maui.CommunityToolkit.Mvvm.Tests", "test\Sentry.Maui.CommunityToolkit.Mvvm.Tests\Sentry.Maui.CommunityToolkit.Mvvm.Tests.csproj", "{ADC91A84-6054-42EC-8241-0D717E4C7194}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{A8A97D6A-02C0-4808-9D62-DFFAB324A323}" + ProjectSection(SolutionItems) = preProject + scripts\update-cli.ps1 = scripts\update-cli.ps1 + scripts\bump-version.sh = scripts\bump-version.sh + scripts\device-test.ps1 = scripts\device-test.ps1 + scripts\dirty-check.ps1 = scripts\dirty-check.ps1 + scripts\update-java.ps1 = scripts\update-java.ps1 + scripts\bump-version.ps1 = scripts\bump-version.ps1 + scripts\parse-xunit2-xml.ps1 = scripts\parse-xunit2-xml.ps1 + scripts\build-sentry-cocoa.sh = scripts\build-sentry-cocoa.sh + scripts\update-project-xml.ps1 = scripts\update-project-xml.ps1 + scripts\build-sentry-native.ps1 = scripts\build-sentry-native.ps1 + scripts\ios-simulator-utils.ps1 = scripts\ios-simulator-utils.ps1 + scripts\commit-formatted-code.sh = scripts\commit-formatted-code.sh + scripts\accept-verifier-changes.ps1 = scripts\accept-verifier-changes.ps1 + scripts\generate-cocoa-bindings.ps1 = scripts\generate-cocoa-bindings.ps1 + scripts\generate-solution-filters.ps1 = scripts\generate-solution-filters.ps1 + scripts\generate-solution-filters-config.yaml = scripts\generate-solution-filters-config.yaml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{EC6ADE8A-E557-4848-8F03-519039830B5F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{BFF081D8-7CC0-4069-99F5-5CA0D70B56AB}" + ProjectSection(SolutionItems) = preProject + .github\workflows\build.yml = .github\workflows\build.yml + .github\workflows\alpine.yml = .github\workflows\alpine.yml + .github\workflows\danger.yml = .github\workflows\danger.yml + .github\workflows\release.yml = .github\workflows\release.yml + .github\workflows\format-code.yml = .github\workflows\format-code.yml + .github\workflows\update-deps.yml = .github\workflows\update-deps.yml + .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml + .github\workflows\vulnerabilities.yml = .github\workflows\vulnerabilities.yml + .github\workflows\device-tests-ios.yml = .github\workflows\device-tests-ios.yml + .github\workflows\device-tests-android.yml = .github\workflows\device-tests-android.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "actions", "actions", "{5D50D425-244F-4B79-B9F5-21D26DD52DC1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "environment", "environment", "{39216438-F347-427C-AB70-48DB1BA6E299}" + ProjectSection(SolutionItems) = preProject + .github\actions\environment\action.yml = .github\actions\environment\action.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "buildnative", "buildnative", "{A384A71C-A46F-49DB-B7FB-5DEEFC5E6CA3}" + ProjectSection(SolutionItems) = preProject + .github\actions\buildnative\action.yml = .github\actions\buildnative\action.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "freediskspace", "freediskspace", "{E34AA22F-B42E-4D4C-B96E-426AEBC2F367}" + ProjectSection(SolutionItems) = preProject + .github\actions\freediskspace\action.yml = .github\actions\freediskspace\action.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "install-zstd", "install-zstd", "{94A2DCA5-F298-41FB-913A-476668EF5786}" + ProjectSection(SolutionItems) = preProject + .github\actions\install-zstd\action.yml = .github\actions\install-zstd\action.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integration-test", "integration-test", "{94CCDBEF-5867-4C24-A305-0C2AE738AF42}" + ProjectSection(SolutionItems) = preProject + integration-test\common.ps1 = integration-test\common.ps1 + integration-test\aot.Tests.ps1 = integration-test\aot.Tests.ps1 + integration-test\cli.Tests.ps1 = integration-test\cli.Tests.ps1 + integration-test\runtime.Tests.ps1 = integration-test\runtime.Tests.ps1 + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net4-console", "net4-console", "{33793113-C7B5-434D-B5C1-6CA1A9587842}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1343,5 +1411,12 @@ Global {C3CDF61C-3E28-441C-A9CE-011C89D11719} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} {3A76FF7D-2F32-4EA5-8999-2FFE3C7CB893} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} {ADC91A84-6054-42EC-8241-0D717E4C7194} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} + {BFF081D8-7CC0-4069-99F5-5CA0D70B56AB} = {EC6ADE8A-E557-4848-8F03-519039830B5F} + {5D50D425-244F-4B79-B9F5-21D26DD52DC1} = {EC6ADE8A-E557-4848-8F03-519039830B5F} + {39216438-F347-427C-AB70-48DB1BA6E299} = {5D50D425-244F-4B79-B9F5-21D26DD52DC1} + {A384A71C-A46F-49DB-B7FB-5DEEFC5E6CA3} = {5D50D425-244F-4B79-B9F5-21D26DD52DC1} + {E34AA22F-B42E-4D4C-B96E-426AEBC2F367} = {5D50D425-244F-4B79-B9F5-21D26DD52DC1} + {94A2DCA5-F298-41FB-913A-476668EF5786} = {5D50D425-244F-4B79-B9F5-21D26DD52DC1} + {33793113-C7B5-434D-B5C1-6CA1A9587842} = {94CCDBEF-5867-4C24-A305-0C2AE738AF42} EndGlobalSection EndGlobal diff --git a/SentryNoMobile.slnf b/SentryNoMobile.slnf index 1592f77697..bfc8311ad7 100644 --- a/SentryNoMobile.slnf +++ b/SentryNoMobile.slnf @@ -24,7 +24,6 @@ "samples\\Sentry.Samples.GraphQL.Server\\Sentry.Samples.GraphQL.Server.csproj", "samples\\Sentry.Samples.Hangfire\\Sentry.Samples.Hangfire.csproj", "samples\\Sentry.Samples.Log4Net\\Sentry.Samples.Log4Net.csproj", - "samples\\Sentry.Samples.MacCatalyst\\Sentry.Samples.MacCatalyst.csproj", "samples\\Sentry.Samples.MacOS\\Sentry.Samples.MacOS.csproj", "samples\\Sentry.Samples.ME.Logging\\Sentry.Samples.ME.Logging.csproj", "samples\\Sentry.Samples.NLog\\Sentry.Samples.NLog.csproj", diff --git a/benchmarks/Sentry.Benchmarks/BackgroundWorkerFlushBenchmarks.cs b/benchmarks/Sentry.Benchmarks/BackgroundWorkerFlushBenchmarks.cs index 3f31663e09..7d165930a8 100644 --- a/benchmarks/Sentry.Benchmarks/BackgroundWorkerFlushBenchmarks.cs +++ b/benchmarks/Sentry.Benchmarks/BackgroundWorkerFlushBenchmarks.cs @@ -20,7 +20,7 @@ public Task SendEnvelopeAsync(Envelope envelope, CancellationToken cancellationT [IterationSetup] public void IterationSetup() { - _backgroundWorker = new BackgroundWorker(new FakeTransport(), new SentryOptions { MaxQueueItems = 1000 }); + _backgroundWorker = new BackgroundWorker(new FakeTransport(), new SentryOptions { MaxQueueItems = 1000 }, null); _event = new SentryEvent(); _envelope = Envelope.FromEvent(_event); diff --git a/benchmarks/Sentry.Benchmarks/Sentry.Benchmarks.csproj b/benchmarks/Sentry.Benchmarks/Sentry.Benchmarks.csproj index 48231469f2..5a28b1186f 100644 --- a/benchmarks/Sentry.Benchmarks/Sentry.Benchmarks.csproj +++ b/benchmarks/Sentry.Benchmarks/Sentry.Benchmarks.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + $(PreviousTfm) false diff --git a/global.json b/global.json index 9cd89532c5..9920ba14bd 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "9.0.304", - "workloadVersion": "9.0.304", + "version": "10.0.100-rc.1.25451.107", + "workloadVersion": "10.0.100-rc.1.25458.2", "rollForward": "disable", "allowPrerelease": false } diff --git a/integration-test/aot.Tests.ps1 b/integration-test/aot.Tests.ps1 index 14849c332f..269e8d14ae 100644 --- a/integration-test/aot.Tests.ps1 +++ b/integration-test/aot.Tests.ps1 @@ -51,19 +51,21 @@ Console.WriteLine("Hello, Sentry!"); It 'Aot' { $rid = $env:RuntimeIdentifier - if ($rid) - { - dotnet publish -c Release -r $rid | Write-Host + $baseImage = $env:ContainerBaseImage + $publishArgs = @('-c', 'Release') + if ($rid) { + Write-Host "Environment RuntimeIdentifier: $rid" + $publishArgs += @('-r', $rid) } - else - { - dotnet publish -c Release | Write-Host + if ($baseImage) { + Write-Host "Using ContainerBaseImage: $baseImage" + $publishArgs += "-p:ContainerBaseImage=$baseImage" } + dotnet publish @publishArgs | Write-Host $LASTEXITCODE | Should -Be 0 $tfm = (Get-ChildItem -Path "bin/Release" -Directory | Select-Object -First 1).Name - if (-not $rid) - { + if (-not $rid) { $rid = (Get-ChildItem -Path "bin/Release/$tfm" -Directory | Select-Object -First 1).Name } & "bin/Release/$tfm/$rid/publish/hello-sentry" | Write-Host diff --git a/integration-test/cli.Tests.ps1 b/integration-test/cli.Tests.ps1 index 1a39cf541a..ff7b8a7eee 100644 --- a/integration-test/cli.Tests.ps1 +++ b/integration-test/cli.Tests.ps1 @@ -4,7 +4,7 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot/common.ps1 Describe 'Console apps () - normal build' -ForEach @( - @{ framework = "net8.0" } + foreach ($fw in $currentFrameworks) { @{ framework = $fw } } ) { BeforeAll { DotnetNew 'console' 'console-app' $framework @@ -40,7 +40,7 @@ Describe 'Console apps () - normal build' -ForEach @( } Describe 'Console apps () - native AOT publish' -ForEach @( - @{ framework = "net8.0" } + foreach ($fw in $currentFrameworks) { @{ framework = $fw } } ) { BeforeAll { DotnetNew 'console' 'console-app' $framework @@ -92,10 +92,6 @@ Describe 'Console apps () - native AOT publish' -ForEach @( $result = RunDotnetWithSentryCLI 'publish' 'console-app' $False $True $framework $result.ScriptOutput | Should -AnyElementMatch "Preparing upload to Sentry for project 'console-app'" $sourceBundle = 'console-app.src.zip' - if ($IsMacOS) - { - $sourceBundle = 'console-app.src.zip' - } $result.UploadedDebugFiles() | Sort-Object -Unique | Should -Be @($sourceBundle) } @@ -105,8 +101,8 @@ Describe 'Console apps () - native AOT publish' -ForEach @( } } -Describe 'MAUI' -ForEach @( - @{ framework = "net8.0" } +Describe 'MAUI ()' -ForEach @( + @{ framework = $previousFramework } ) -Skip:($env:NO_MOBILE -eq "true") { BeforeAll { RegisterLocalPackage 'Sentry.Android.AssemblyReader' @@ -119,8 +115,8 @@ Describe 'MAUI' -ForEach @( } $name = 'maui-app' - $androidTpv = '34.0' - $iosTpv = '17.0' + $androidTpv = GetAndroidTpv $framework + $iosTpv = GetIosTpv $framework DotnetNew 'maui' $name $framework @@ -148,6 +144,7 @@ Describe 'MAUI' -ForEach @( It "uploads symbols and sources for an Android build" { $result = RunDotnetWithSentryCLI 'build' 'maui-app' $True $True "$framework-android$androidTpv" + Write-Host "UploadedDebugFiles: $($result.UploadedDebugFiles() | Out-String)" $result.UploadedDebugFiles() | Sort-Object -Unique | Should -Be @( 'libsentry-android.so', 'libsentry.so', @@ -156,11 +153,12 @@ Describe 'MAUI' -ForEach @( 'maui-app.pdb' ) $result.ScriptOutput | Should -AnyElementMatch 'Uploaded a total of 1 new mapping files' - $result.ScriptOutput | Should -AnyElementMatch 'Found 25 debug information files \(1 with embedded sources\)' + $result.ScriptOutput | Should -AnyElementMatch "Found 23 debug information files \(1 with embedded sources\)" } It "uploads symbols and sources for an iOS build" -Skip:(!$IsMacOS) { $result = RunDotnetWithSentryCLI 'build' 'maui-app' $True $True "$framework-ios$iosTpv" + Write-Host "UploadedDebugFiles: $($result.UploadedDebugFiles() | Out-String)" $result.UploadedDebugFiles() | Sort-Object -Unique | Should -Be @( 'libmono-component-debugger.dylib', 'libmono-component-diagnostics_tracing.dylib', @@ -176,9 +174,14 @@ Describe 'MAUI' -ForEach @( 'libxamarin-dotnet.dylib', 'maui-app', 'maui-app.pdb', + 'Microsoft.iOS.pdb', + 'Microsoft.Maui.Controls.pdb', + 'Microsoft.Maui.Controls.Xaml.pdb', + 'Microsoft.Maui.Essentials.pdb', + 'Microsoft.Maui.Graphics.pdb', + 'Microsoft.Maui.pdb', 'Sentry' ) - $nonZeroNumberRegex = '[1-9][0-9]*'; - $result.ScriptOutput | Should -AnyElementMatch "Found $nonZeroNumberRegex debug information files \($nonZeroNumberRegex with embedded sources\)" + $result.ScriptOutput | Should -AnyElementMatch "Found 77 debug information files \(8 with embedded sources\)" } } diff --git a/integration-test/common.ps1 b/integration-test/common.ps1 index b7215fc87e..312825360e 100644 --- a/integration-test/common.ps1 +++ b/integration-test/common.ps1 @@ -1,5 +1,9 @@ -# So that this works in VS Code testing integration. Otherwise the script is run within its directory. +$global:longTermFramework = 'net8.0' +$global:previousFramework = 'net9.0' +$global:latestFramework = 'net10.0' +$global:currentFrameworks = @($longTermFramework, $previousFramework, $latestFramework) +# So that this works in VS Code testing integration. Otherwise the script is run within its directory. # In CI, the module is loaded automatically if (!(Test-Path env:CI )) { @@ -58,9 +62,74 @@ BeforeAll { Push-Location $PSScriptRoot $env:SENTRY_LOG_LEVEL = 'debug'; + function GetAndroidTpv($framework) + { + switch ($framework) { + 'net9.0' { return '35.0' } # matches PreviousAndroidTfm (net9.0-android35.0) + 'net10.0' { return '36.0' } # matches LatestAndroidTfm (net10.0-android36.0) + default { throw "Unsupported framework '$framework' for Android target platform version." } + } + } + + function GetIosTpv($framework) + { + switch ($framework) { + 'net9.0' { return '18.0' } # matches PreviousIosTfm / PreviousMacCatalystTfm + 'net10.0' { return '26' } # aligns with ios26 / maccatalyst26 + default { throw "Unsupported framework '$framework' for iOS target platform version." } + } + } + function GetSentryPackageVersion() { - (Select-Xml -Path "$PSScriptRoot/../Directory.Build.props" -XPath "/Project/PropertyGroup/VersionPrefix").Node.InnerText + # Read version directly from Directory.Build.props + $propsFile = Join-Path $PSScriptRoot '..\Directory.Build.props' + + if (-not (Test-Path $propsFile)) { + throw "Directory.Build.props not found at $propsFile" + } + + # Parse the props file using PowerShell XML parsing + Write-Host "Parsing props file as XML..." + [xml]$propsXml = Get-Content $propsFile + + # Look for VersionPrefix and VersionSuffix in PropertyGroup elements + $versionPrefix = "" + $versionSuffix = "" + + foreach ($propGroup in $propsXml.Project.PropertyGroup) { + if ($propGroup.PSObject.Properties["VersionPrefix"]) { + $versionPrefix = $propGroup.VersionPrefix + Write-Host "Found VersionPrefix: '$versionPrefix'" + } + + # For VersionSuffix, we need to be careful about conditions + # Only use VersionSuffix if it's not in a conditional PropertyGroup + # or if it's explicitly set (not the 'dev' fallback for non-Release) + if ($propGroup.PSObject.Properties["VersionSuffix"]) { + $condition = $null + if ($propGroup.PSObject.Properties["Condition"]) { + $condition = $propGroup.Condition + Write-Host "Ignoring VersionSuffix: '$($propGroup.VersionSuffix)' with condition: '$condition'" + # Skip conditional VersionSuffix as we're building in Release mode + } + else { + # No condition - this is the explicit VersionSuffix we want + $versionSuffix = $propGroup.VersionSuffix + Write-Host "Found VersionSuffix: '$versionSuffix'" + } + } + } + + if (-not $versionPrefix) { + throw "Could not find VersionPrefix in $propsFile" + } + + # Combine prefix and suffix + $fullVersion = if ($versionSuffix) { "$versionPrefix-$versionSuffix" } else { $versionPrefix } + Write-Host "Full Version: '$fullVersion'" + + return $fullVersion } function RegisterLocalPackage([string] $name) @@ -142,7 +211,7 @@ BeforeAll { } elseif ($action -eq "publish") { - $result.ScriptOutput | Should -AnyElementMatch "$((Get-Item $project).Basename) -> .*$project/bin/Release/$TargetFramework/.*/publish" + $result.ScriptOutput | Should -AnyElementMatch "$((Get-Item $project).Basename) -> .*$project/bin/Release/$TargetFramework/.*publish.*" } $result.ScriptOutput | Should -Not -AnyElementMatch "Preparing upload to Sentry for project 'Sentry'" $result.HasErrors() | Should -BeFalse @@ -154,7 +223,7 @@ BeforeAll { Push-Location $projectPath try { - dotnet restore | ForEach-Object { Write-Host $_ } + dotnet restore /p:CheckEolTargetFramework=false | ForEach-Object { Write-Host $_ } if ($LASTEXITCODE -ne 0) { throw "Failed to restore the test app project." @@ -197,16 +266,13 @@ BeforeAll { if ($type -eq 'console') { AddPackageReference $name 'Sentry' - if (!$IsMacOS -or $framework -eq 'net8.0') - { - @" + @" true "@ | Out-File $name/Directory.Build.props - } } } } diff --git a/integration-test/msbuild.Tests.ps1 b/integration-test/msbuild.Tests.ps1 new file mode 100644 index 0000000000..95288d7213 --- /dev/null +++ b/integration-test/msbuild.Tests.ps1 @@ -0,0 +1,101 @@ +# This file contains test cases for https://pester.dev/ +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +. $PSScriptRoot/common.ps1 + +$IsARM64 = "Arm64".Equals([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString()) + +# NOTE: These .NET versions are used to build a test app that consumes the Sentry +# .NET SDK, and are not tied to the .NET version used to build the SDK itself. +Describe 'MSBuild app' { + BeforeDiscovery { + $frameworks = @() + + # .NET 5.0 does not support ARM64 on macOS + if (-not $IsMacOS -or -not $IsARM64) + { + $frameworks += @{ + framework = 'net5.0' + sdk = '5.0.400' + # NuGet 5 does not support packageSourceMapping + config = "$PSScriptRoot\nuget5.config" + } + } + + $frameworks += @( + @{ framework = 'net8.0'; sdk = '8.0.400' }, + @{ framework = 'net9.0'; sdk = '9.0.300' } + ) + } + + Context '()' -ForEach $frameworks { + BeforeEach { + Write-Host "::group::Create msbuild-app" + dotnet new console --no-restore --output msbuild-app --framework $framework | ForEach-Object { Write-Host $_ } + $LASTEXITCODE | Should -Be 0 + AddPackageReference msbuild-app Sentry + Push-Location msbuild-app + @' +using System.Runtime.InteropServices; +using Sentry; + +SentrySdk.Init(options => +{ + options.Dsn = args[0]; + options.Debug = true; +}); + +SentrySdk.CaptureMessage($"Hello from MSBuild app"); +'@ | Out-File Program.cs + Write-Host "::endgroup::" + + Write-Host "::group::Setup .NET SDK" + if (Test-Path variable:sdk) + { + # Pin to a specific SDK version to use MSBuild from that version + @" +{ + "sdk": { + "version": "$sdk", + "rollForward": "latestFeature" + } +} +"@ | Out-File global.json + } + Write-Host "Using .NET SDK: $(dotnet --version)" + Write-Host "Using MSBuild version: $(dotnet msbuild -version)" + Write-Host "::endgroup::" + } + + AfterEach { + Pop-Location + Remove-Item msbuild-app -Recurse -Force -ErrorAction SilentlyContinue + } + + It 'builds without warnings and is able to capture a message' { + Write-Host "::group::Restore packages" + if (!(Test-Path variable:config)) + { + $config = "$PSScriptRoot/nuget.config" + } + dotnet restore msbuild-app.csproj --configfile $config -p:CheckEolTargetFramework=false | ForEach-Object { Write-Host $_ } + $LASTEXITCODE | Should -Be 0 + Write-Host "::endgroup::" + + # TODO: pass -p:TreatWarningsAsErrors=true after #4554 is fixed + dotnet msbuild msbuild-app.csproj -t:Build -p:Configuration=Release -p:TreatWarningsAsErrors=false | ForEach-Object { Write-Host $_ } + $LASTEXITCODE | Should -Be 0 + + Write-Host "::group::Run msbuild-app" + $result = Invoke-SentryServer { + param([string]$url) + $dsn = $url.Replace('http://', 'http://key@') + '/0' + dotnet msbuild msbuild-app.csproj -t:Run -p:Configuration=Release -p:RunArguments=$dsn | ForEach-Object { Write-Host $_ } + $LASTEXITCODE | Should -Be 0 + } + $result.HasErrors() | Should -BeFalse + $result.Envelopes() | Should -AnyElementMatch "`"message`":`"Hello from MSBuild app`"" + Write-Host "::endgroup::" + } + } +} diff --git a/integration-test/nuget5.config b/integration-test/nuget5.config new file mode 100644 index 0000000000..822e3e5985 --- /dev/null +++ b/integration-test/nuget5.config @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/integration-test/runtime.Tests.ps1 b/integration-test/runtime.Tests.ps1 index 158890edc3..f6f98407c6 100644 --- a/integration-test/runtime.Tests.ps1 +++ b/integration-test/runtime.Tests.ps1 @@ -4,7 +4,7 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot/common.ps1 Describe 'Console app NativeAOT ()' -ForEach @( - @{ framework = 'net8.0' } + foreach ($fw in $currentFrameworks) { @{ framework = $fw } } ) { BeforeAll { $path = './console-app' @@ -188,7 +188,7 @@ Describe 'Console app regression (missing System.Reflection.Metadata)' { dotnet remove ./net4-console/console-app.csproj package Sentry } - It 'Ensure System.Reflection.Metadata is not missing' { + It 'Ensure System.Reflection.Metadata is not missing' -Skip:(!$IsWindows) { $path = './net4-console' Remove-Item -Recurse -Force -Path @("$path/bin", "$path/obj") -ErrorAction SilentlyContinue AddPackageReference $path 'Sentry' diff --git a/modules/Ben.Demystifier b/modules/Ben.Demystifier index 0a6b30f6ac..7c9eeea3ed 160000 --- a/modules/Ben.Demystifier +++ b/modules/Ben.Demystifier @@ -1 +1 @@ -Subproject commit 0a6b30f6ac6892ff45b9dcd5b07d710e5228256e +Subproject commit 7c9eeea3ed2416f2aa0491da640fa84337f2a7a2 diff --git a/modules/sentry-cocoa.properties b/modules/sentry-cocoa.properties index d4dde9863e..1b37e7bb3c 100644 --- a/modules/sentry-cocoa.properties +++ b/modules/sentry-cocoa.properties @@ -1,2 +1,2 @@ -version = 8.56.0 +version = 8.56.2 repo = https://github.com/getsentry/sentry-cocoa diff --git a/modules/sentry-native b/modules/sentry-native index 3bd091313a..075b3bfee1 160000 --- a/modules/sentry-native +++ b/modules/sentry-native @@ -1 +1 @@ -Subproject commit 3bd091313ae97be90be62696a2babe591a988eb8 +Subproject commit 075b3bfee1dbb85fa10d50df631286196943a3e0 diff --git a/samples/Sentry.Samples.Android/Sentry.Samples.Android.csproj b/samples/Sentry.Samples.Android/Sentry.Samples.Android.csproj index ace5748bae..a9eacd63be 100644 --- a/samples/Sentry.Samples.Android/Sentry.Samples.Android.csproj +++ b/samples/Sentry.Samples.Android/Sentry.Samples.Android.csproj @@ -1,6 +1,6 @@ - net8.0-android + net9.0-android35.0 21 Exe enable diff --git a/samples/Sentry.Samples.AspNetCore.Basic/Sentry.Samples.AspNetCore.Basic.csproj b/samples/Sentry.Samples.AspNetCore.Basic/Sentry.Samples.AspNetCore.Basic.csproj index eb93b9fefb..6b83d73fc1 100644 --- a/samples/Sentry.Samples.AspNetCore.Basic/Sentry.Samples.AspNetCore.Basic.csproj +++ b/samples/Sentry.Samples.AspNetCore.Basic/Sentry.Samples.AspNetCore.Basic.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 diff --git a/samples/Sentry.Samples.AspNetCore.Blazor.Server/Sentry.Samples.AspNetCore.Blazor.Server.csproj b/samples/Sentry.Samples.AspNetCore.Blazor.Server/Sentry.Samples.AspNetCore.Blazor.Server.csproj index 04fa5f4380..e173fff774 100644 --- a/samples/Sentry.Samples.AspNetCore.Blazor.Server/Sentry.Samples.AspNetCore.Blazor.Server.csproj +++ b/samples/Sentry.Samples.AspNetCore.Blazor.Server/Sentry.Samples.AspNetCore.Blazor.Server.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable diff --git a/samples/Sentry.Samples.AspNetCore.Blazor.Wasm/Sentry.Samples.AspNetCore.Blazor.Wasm.csproj b/samples/Sentry.Samples.AspNetCore.Blazor.Wasm/Sentry.Samples.AspNetCore.Blazor.Wasm.csproj index c64bb41a0a..22d6f53b09 100644 --- a/samples/Sentry.Samples.AspNetCore.Blazor.Wasm/Sentry.Samples.AspNetCore.Blazor.Wasm.csproj +++ b/samples/Sentry.Samples.AspNetCore.Blazor.Wasm/Sentry.Samples.AspNetCore.Blazor.Wasm.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 true @@ -15,13 +15,8 @@ - - + + - - - - - diff --git a/samples/Sentry.Samples.AspNetCore.Grpc/Program.cs b/samples/Sentry.Samples.AspNetCore.Grpc/Program.cs index cdd26f9eb0..12d4313821 100644 --- a/samples/Sentry.Samples.AspNetCore.Grpc/Program.cs +++ b/samples/Sentry.Samples.AspNetCore.Grpc/Program.cs @@ -1,69 +1,73 @@ using System.Net; -using Microsoft.AspNetCore; using Microsoft.AspNetCore.Server.Kestrel.Core; using Sentry.AspNetCore; using Sentry.AspNetCore.Grpc; +using Sentry.Samples.AspNetCore.Grpc; -namespace Sentry.Samples.AspNetCore.Grpc; +var builder = WebApplication.CreateBuilder(args); -public static class Program -{ - public static void Main(string[] args) +builder.WebHost + .UseShutdownTimeout(TimeSpan.FromSeconds(10)) + .ConfigureKestrel(options => { - BuildWebHost(args).Run(); - } - - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseShutdownTimeout(TimeSpan.FromSeconds(10)) - .ConfigureKestrel(options => - { - // Setup a HTTP/2 endpoint without TLS due to macOS limitation. - // https://docs.microsoft.com/en-us/aspnet/core/grpc/troubleshoot?view=aspnetcore-5.0#unable-to-start-aspnet-core-grpc-app-on-macos - options.ListenLocalhost(5000, o => o.Protocols = - HttpProtocols.Http2); - }) - .UseStartup() - - // Example integration with advanced configuration scenarios: - .UseSentry(builder => - { - builder.AddGrpc(); - builder.AddSentryOptions(options => - { + // Setup a HTTP/2 endpoint without TLS due to macOS limitation. + // https://docs.microsoft.com/en-us/aspnet/core/grpc/troubleshoot?view=aspnetcore-5.0#unable-to-start-aspnet-core-grpc-app-on-macos + options.ListenLocalhost(5000, o => o.Protocols = HttpProtocols.Http2); + }) + .UseSentry(sentryBuilder => + { + sentryBuilder.AddGrpc(); + sentryBuilder.AddSentryOptions(options => + { #if !SENTRY_DSN_DEFINED_IN_ENV - // A DSN is required. You can set here in code, via the SENTRY_DSN environment variable or in your - // appsettings.json file. - // See https://docs.sentry.io/platforms/dotnet/guides/aspnetcore/#configure - options.Dsn = SamplesShared.Dsn; + // A DSN is required. You can set here in code, via the SENTRY_DSN environment variable or in your + // appsettings.json file. + // See https://docs.sentry.io/platforms/dotnet/guides/aspnetcore/#configure + options.Dsn = SamplesShared.Dsn; #endif - // The parameter 'options' here has values populated through the configuration system. - // That includes 'appsettings.json', environment variables and anything else - // defined on the ConfigurationBuilder. - // See: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-2.1&tabs=basicconfiguration - // Tracks the release which sent the event and enables more features: https://docs.sentry.io/learn/releases/ - // If not explicitly set here, the SDK attempts to read it from: AssemblyInformationalVersionAttribute and AssemblyVersion - // TeamCity: %build.vcs.number%, VSTS: BUILD_SOURCEVERSION, Travis-CI: TRAVIS_COMMIT, AppVeyor: APPVEYOR_REPO_COMMIT, CircleCI: CIRCLE_SHA1 - options.Release = - "e386dfd"; // Could also be any format, such as: 2.0, or however version of your app is + // The parameter 'options' here has values populated through the configuration system. + // That includes 'appsettings.json', environment variables and anything else + // defined on the ConfigurationBuilder. + // Tracks the release which sent the event and enables more features: https://docs.sentry.io/learn/releases/ + // If not explicitly set here, the SDK attempts to read it from: AssemblyInformationalVersionAttribute and AssemblyVersion + options.Release = "e386dfd"; + + // Enable performance monitoring + options.TracesSampleRate = 1.0; + + options.MaxBreadcrumbs = 200; - options.TracesSampleRate = 1.0; + // Set a proxy for outgoing HTTP connections + options.HttpProxy = null; // new WebProxy("https://localhost:3128"); - options.MaxBreadcrumbs = 200; + // Example: Disabling support to compressed responses: + options.DecompressionMethods = DecompressionMethods.None; - // Set a proxy for outgoing HTTP connections - options.HttpProxy = null; // new WebProxy("https://localhost:3128"); + options.MaxQueueItems = 100; + options.ShutdownTimeout = TimeSpan.FromSeconds(5); - // Example: Disabling support to compressed responses: - options.DecompressionMethods = DecompressionMethods.None; + // Configures the root scope + options.ConfigureScope(s => s.SetTag("Always sent", "this tag")); + }); + }); - options.MaxQueueItems = 100; - options.ShutdownTimeout = TimeSpan.FromSeconds(5); +// Services +builder.Services.AddGrpcReflection(); +builder.Services.AddGrpc(); - // Configures the root scope - options.ConfigureScope(s => s.SetTag("Always sent", "this tag")); - }); - }) - .Build(); +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} + +// Map gRPC endpoints +app.MapGrpcService(); +if (app.Environment.IsDevelopment()) +{ + app.MapGrpcReflectionService(); } + +app.Run(); diff --git a/samples/Sentry.Samples.AspNetCore.Grpc/Sentry.Samples.AspNetCore.Grpc.csproj b/samples/Sentry.Samples.AspNetCore.Grpc/Sentry.Samples.AspNetCore.Grpc.csproj index 89dc8c6a3e..dfc6c6520d 100644 --- a/samples/Sentry.Samples.AspNetCore.Grpc/Sentry.Samples.AspNetCore.Grpc.csproj +++ b/samples/Sentry.Samples.AspNetCore.Grpc/Sentry.Samples.AspNetCore.Grpc.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 diff --git a/samples/Sentry.Samples.AspNetCore.Grpc/Startup.cs b/samples/Sentry.Samples.AspNetCore.Grpc/Startup.cs deleted file mode 100644 index 5bc4580274..0000000000 --- a/samples/Sentry.Samples.AspNetCore.Grpc/Startup.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Sentry.Samples.AspNetCore.Grpc; - -public class Startup -{ - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - services.AddGrpcReflection(); - services.AddGrpc(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapGrpcService(); - if (env.IsDevelopment()) - { - endpoints.MapGrpcReflectionService(); - } - }); - } -} diff --git a/samples/Sentry.Samples.AspNetCore.Mvc/Sentry.Samples.AspNetCore.Mvc.csproj b/samples/Sentry.Samples.AspNetCore.Mvc/Sentry.Samples.AspNetCore.Mvc.csproj index d3bbf0aa34..d6eb7609a7 100644 --- a/samples/Sentry.Samples.AspNetCore.Mvc/Sentry.Samples.AspNetCore.Mvc.csproj +++ b/samples/Sentry.Samples.AspNetCore.Mvc/Sentry.Samples.AspNetCore.Mvc.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable Samples.AspNetCore.Mvc diff --git a/samples/Sentry.Samples.AspNetCore.Serilog/Sentry.Samples.AspNetCore.Serilog.csproj b/samples/Sentry.Samples.AspNetCore.Serilog/Sentry.Samples.AspNetCore.Serilog.csproj index a56d6004e2..5c3cf4c562 100644 --- a/samples/Sentry.Samples.AspNetCore.Serilog/Sentry.Samples.AspNetCore.Serilog.csproj +++ b/samples/Sentry.Samples.AspNetCore.Serilog/Sentry.Samples.AspNetCore.Serilog.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 diff --git a/samples/Sentry.Samples.AspNetCore.WebAPI.Profiling/Sentry.Samples.AspNetCore.WebAPI.Profiling.csproj b/samples/Sentry.Samples.AspNetCore.WebAPI.Profiling/Sentry.Samples.AspNetCore.WebAPI.Profiling.csproj index 6ca934c5fc..68bf35efdd 100644 --- a/samples/Sentry.Samples.AspNetCore.WebAPI.Profiling/Sentry.Samples.AspNetCore.WebAPI.Profiling.csproj +++ b/samples/Sentry.Samples.AspNetCore.WebAPI.Profiling/Sentry.Samples.AspNetCore.WebAPI.Profiling.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable diff --git a/samples/Sentry.Samples.Aws.Lambda.AspNetCoreServer/Sentry.Samples.Aws.Lambda.AspNetCoreServer.csproj b/samples/Sentry.Samples.Aws.Lambda.AspNetCoreServer/Sentry.Samples.Aws.Lambda.AspNetCoreServer.csproj index 8d2dd8b221..c9150b1b76 100644 --- a/samples/Sentry.Samples.Aws.Lambda.AspNetCoreServer/Sentry.Samples.Aws.Lambda.AspNetCoreServer.csproj +++ b/samples/Sentry.Samples.Aws.Lambda.AspNetCoreServer/Sentry.Samples.Aws.Lambda.AspNetCoreServer.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0 true Lambda diff --git a/samples/Sentry.Samples.Azure.Functions.Worker/Sentry.Samples.Azure.Functions.Worker.csproj b/samples/Sentry.Samples.Azure.Functions.Worker/Sentry.Samples.Azure.Functions.Worker.csproj index 3145f92db6..1ee89930e3 100644 --- a/samples/Sentry.Samples.Azure.Functions.Worker/Sentry.Samples.Azure.Functions.Worker.csproj +++ b/samples/Sentry.Samples.Azure.Functions.Worker/Sentry.Samples.Azure.Functions.Worker.csproj @@ -1,6 +1,6 @@ - net8.0 + net9.0 v4 Exe diff --git a/samples/Sentry.Samples.Console.Basic/Sentry.Samples.Console.Basic.csproj b/samples/Sentry.Samples.Console.Basic/Sentry.Samples.Console.Basic.csproj index bf9efb93d8..44f961dfae 100644 --- a/samples/Sentry.Samples.Console.Basic/Sentry.Samples.Console.Basic.csproj +++ b/samples/Sentry.Samples.Console.Basic/Sentry.Samples.Console.Basic.csproj @@ -4,9 +4,9 @@ Exe enable enable - net9.0;net8.0;net462 + net9.0;net10.0;net462 - + true true diff --git a/samples/Sentry.Samples.Console.Customized/Sentry.Samples.Console.Customized.csproj b/samples/Sentry.Samples.Console.Customized/Sentry.Samples.Console.Customized.csproj index ec23ce7173..26a1861513 100644 --- a/samples/Sentry.Samples.Console.Customized/Sentry.Samples.Console.Customized.csproj +++ b/samples/Sentry.Samples.Console.Customized/Sentry.Samples.Console.Customized.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 diff --git a/samples/Sentry.Samples.Console.HeapDump/Sentry.Samples.Console.HeapDump.csproj b/samples/Sentry.Samples.Console.HeapDump/Sentry.Samples.Console.HeapDump.csproj index cfcc72e664..b88b507419 100644 --- a/samples/Sentry.Samples.Console.HeapDump/Sentry.Samples.Console.HeapDump.csproj +++ b/samples/Sentry.Samples.Console.HeapDump/Sentry.Samples.Console.HeapDump.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 enable enable true @@ -51,5 +51,5 @@ - + diff --git a/samples/Sentry.Samples.Console.Native/Sentry.Samples.Console.Native.csproj b/samples/Sentry.Samples.Console.Native/Sentry.Samples.Console.Native.csproj index e4fd5c44cc..190dd6ac5c 100644 --- a/samples/Sentry.Samples.Console.Native/Sentry.Samples.Console.Native.csproj +++ b/samples/Sentry.Samples.Console.Native/Sentry.Samples.Console.Native.csproj @@ -4,7 +4,7 @@ Exe enable enable - net8.0 + net10.0 true diff --git a/samples/Sentry.Samples.Console.Profiling/Sentry.Samples.Console.Profiling.csproj b/samples/Sentry.Samples.Console.Profiling/Sentry.Samples.Console.Profiling.csproj index 82e57fc84e..6f715d10c3 100644 --- a/samples/Sentry.Samples.Console.Profiling/Sentry.Samples.Console.Profiling.csproj +++ b/samples/Sentry.Samples.Console.Profiling/Sentry.Samples.Console.Profiling.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 diff --git a/samples/Sentry.Samples.EntityFramework/Sentry.Samples.EntityFramework.csproj b/samples/Sentry.Samples.EntityFramework/Sentry.Samples.EntityFramework.csproj index 2e27079e5d..0408fce517 100644 --- a/samples/Sentry.Samples.EntityFramework/Sentry.Samples.EntityFramework.csproj +++ b/samples/Sentry.Samples.EntityFramework/Sentry.Samples.EntityFramework.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 diff --git a/samples/Sentry.Samples.GenericHost/Sentry.Samples.GenericHost.csproj b/samples/Sentry.Samples.GenericHost/Sentry.Samples.GenericHost.csproj index 78283ed704..d2b5e11219 100644 --- a/samples/Sentry.Samples.GenericHost/Sentry.Samples.GenericHost.csproj +++ b/samples/Sentry.Samples.GenericHost/Sentry.Samples.GenericHost.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 @@ -20,8 +20,6 @@ - - - + diff --git a/samples/Sentry.Samples.Google.Cloud.Functions/Sentry.Samples.Google.Cloud.Functions.csproj b/samples/Sentry.Samples.Google.Cloud.Functions/Sentry.Samples.Google.Cloud.Functions.csproj index c212fcf871..4d6ec1bb59 100644 --- a/samples/Sentry.Samples.Google.Cloud.Functions/Sentry.Samples.Google.Cloud.Functions.csproj +++ b/samples/Sentry.Samples.Google.Cloud.Functions/Sentry.Samples.Google.Cloud.Functions.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 diff --git a/samples/Sentry.Samples.GraphQL.Client.Http/Sentry.Samples.GraphQL.Client.Http.csproj b/samples/Sentry.Samples.GraphQL.Client.Http/Sentry.Samples.GraphQL.Client.Http.csproj index 183f1fef77..14fee315b4 100644 --- a/samples/Sentry.Samples.GraphQL.Client.Http/Sentry.Samples.GraphQL.Client.Http.csproj +++ b/samples/Sentry.Samples.GraphQL.Client.Http/Sentry.Samples.GraphQL.Client.Http.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 enable enable diff --git a/samples/Sentry.Samples.GraphQL.Server/Sentry.Samples.GraphQL.Server.csproj b/samples/Sentry.Samples.GraphQL.Server/Sentry.Samples.GraphQL.Server.csproj index 2078f97579..c7ed799d41 100644 --- a/samples/Sentry.Samples.GraphQL.Server/Sentry.Samples.GraphQL.Server.csproj +++ b/samples/Sentry.Samples.GraphQL.Server/Sentry.Samples.GraphQL.Server.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable diff --git a/samples/Sentry.Samples.Hangfire/Sentry.Samples.Hangfire.csproj b/samples/Sentry.Samples.Hangfire/Sentry.Samples.Hangfire.csproj index ac7347715a..804f68bb63 100644 --- a/samples/Sentry.Samples.Hangfire/Sentry.Samples.Hangfire.csproj +++ b/samples/Sentry.Samples.Hangfire/Sentry.Samples.Hangfire.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable diff --git a/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj b/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj index e1cd4e8127..55ff9e06b4 100644 --- a/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj +++ b/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj @@ -1,7 +1,7 @@ - net9.0-ios + net9.0-ios18.0 18 Exe enable diff --git a/samples/Sentry.Samples.Log4Net/Sentry.Samples.Log4Net.csproj b/samples/Sentry.Samples.Log4Net/Sentry.Samples.Log4Net.csproj index 43c4215f48..7f0c0b8178 100644 --- a/samples/Sentry.Samples.Log4Net/Sentry.Samples.Log4Net.csproj +++ b/samples/Sentry.Samples.Log4Net/Sentry.Samples.Log4Net.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 3.5.234 diff --git a/samples/Sentry.Samples.ME.Logging/Sentry.Samples.ME.Logging.csproj b/samples/Sentry.Samples.ME.Logging/Sentry.Samples.ME.Logging.csproj index 0d60cbe639..dfe4a8f252 100644 --- a/samples/Sentry.Samples.ME.Logging/Sentry.Samples.ME.Logging.csproj +++ b/samples/Sentry.Samples.ME.Logging/Sentry.Samples.ME.Logging.csproj @@ -2,11 +2,11 @@ Exe - net8.0 + net9.0 - + diff --git a/samples/Sentry.Samples.MacCatalyst/AppDelegate.cs b/samples/Sentry.Samples.MacCatalyst/AppDelegate.cs index 044ae0c169..1be71dc6d4 100644 --- a/samples/Sentry.Samples.MacCatalyst/AppDelegate.cs +++ b/samples/Sentry.Samples.MacCatalyst/AppDelegate.cs @@ -11,7 +11,7 @@ public override UIWindow? Window set; } - public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) + public override bool FinishedLaunching(UIApplication application, NSDictionary? launchOptions) { // Init the Sentry SDK SentrySdk.Init(options => diff --git a/samples/Sentry.Samples.MacCatalyst/Sentry.Samples.MacCatalyst.csproj b/samples/Sentry.Samples.MacCatalyst/Sentry.Samples.MacCatalyst.csproj index deacfb2b37..0ba0946ebf 100644 --- a/samples/Sentry.Samples.MacCatalyst/Sentry.Samples.MacCatalyst.csproj +++ b/samples/Sentry.Samples.MacCatalyst/Sentry.Samples.MacCatalyst.csproj @@ -1,11 +1,11 @@ - net8.0-maccatalyst17.0 + net9.0-maccatalyst18.0 Exe enable true - 14.2 + 15.0 true diff --git a/samples/Sentry.Samples.MacOS/Sentry.Samples.MacOS.csproj b/samples/Sentry.Samples.MacOS/Sentry.Samples.MacOS.csproj index 6228d98e39..c2cced507d 100644 --- a/samples/Sentry.Samples.MacOS/Sentry.Samples.MacOS.csproj +++ b/samples/Sentry.Samples.MacOS/Sentry.Samples.MacOS.csproj @@ -1,11 +1,11 @@ - net8.0-macos + net9.0-macos Exe enable true - 10.15 + 12.0 true osx-arm64 diff --git a/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj b/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj index 77c4e079ee..add0969475 100644 --- a/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj +++ b/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj @@ -6,7 +6,7 @@ On Mac, we'll also build for iOS and MacCatalyst. On Windows, we'll also build for Windows 10. --> - $(TargetFrameworks);net9.0-android35.0 + $(TargetFrameworks);net9.0-android $(TargetFrameworks);net9.0-windows10.0.19041.0;net9.0-ios18.0;net9.0-maccatalyst18.0 $(TargetFrameworks);net9.0-ios18.0;net9.0-maccatalyst18.0 Exe diff --git a/samples/Sentry.Samples.NLog/Sentry.Samples.NLog.csproj b/samples/Sentry.Samples.NLog/Sentry.Samples.NLog.csproj index 9b7e809ed0..a25df53d21 100644 --- a/samples/Sentry.Samples.NLog/Sentry.Samples.NLog.csproj +++ b/samples/Sentry.Samples.NLog/Sentry.Samples.NLog.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 diff --git a/samples/Sentry.Samples.OpenTelemetry.AspNetCore/Sentry.Samples.OpenTelemetry.AspNetCore.csproj b/samples/Sentry.Samples.OpenTelemetry.AspNetCore/Sentry.Samples.OpenTelemetry.AspNetCore.csproj index 7159dfcb7e..a07956f015 100644 --- a/samples/Sentry.Samples.OpenTelemetry.AspNetCore/Sentry.Samples.OpenTelemetry.AspNetCore.csproj +++ b/samples/Sentry.Samples.OpenTelemetry.AspNetCore/Sentry.Samples.OpenTelemetry.AspNetCore.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable diff --git a/samples/Sentry.Samples.OpenTelemetry.Console/Sentry.Samples.OpenTelemetry.Console.csproj b/samples/Sentry.Samples.OpenTelemetry.Console/Sentry.Samples.OpenTelemetry.Console.csproj index 8b5c0e9654..0aed012a4f 100644 --- a/samples/Sentry.Samples.OpenTelemetry.Console/Sentry.Samples.OpenTelemetry.Console.csproj +++ b/samples/Sentry.Samples.OpenTelemetry.Console/Sentry.Samples.OpenTelemetry.Console.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 enable enable diff --git a/samples/Sentry.Samples.Serilog/Program.cs b/samples/Sentry.Samples.Serilog/Program.cs index 59ed09548d..c1822a3714 100644 --- a/samples/Sentry.Samples.Serilog/Program.cs +++ b/samples/Sentry.Samples.Serilog/Program.cs @@ -25,6 +25,8 @@ private static void Main() // Error and higher is sent as event (default is Error) options.MinimumEventLevel = LogEventLevel.Error; options.AttachStacktrace = true; + // send structured logs to Sentry + options.Experimental.EnableLogs = true; // send PII like the username of the user logged in to the device options.SendDefaultPii = true; // Optional Serilog text formatter used to format LogEvent to string. If TextFormatter is set, FormatProvider is ignored. diff --git a/samples/Sentry.Samples.Serilog/Sentry.Samples.Serilog.csproj b/samples/Sentry.Samples.Serilog/Sentry.Samples.Serilog.csproj index f9df0dc20f..ab1add9334 100644 --- a/samples/Sentry.Samples.Serilog/Sentry.Samples.Serilog.csproj +++ b/samples/Sentry.Samples.Serilog/Sentry.Samples.Serilog.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 3.5.234 diff --git a/scripts/device-test.ps1 b/scripts/device-test.ps1 index 7822b81582..f9d5c30868 100644 --- a/scripts/device-test.ps1 +++ b/scripts/device-test.ps1 @@ -86,8 +86,8 @@ try { if (!(Get-Command xharness -ErrorAction SilentlyContinue)) { - Push-Location ($CI ? $env:RUNNER_TEMP : $IsWindows ? $env:TMP : $IsMacos ? $env:TMPDIR : '/temp') - dotnet tool install Microsoft.DotNet.XHarness.CLI --global --version '10.0.0-prerelease.25412.1' ` + Push-Location ($CI ? $env:RUNNER_TEMP : $IsWindows ? $env:TMP : $IsMacos ? $env:TMPDIR : '/tmp') + dotnet tool install Microsoft.DotNet.XHarness.CLI --global --version '10.0.0-prerelease.25466.1' ` --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json Pop-Location } diff --git a/scripts/generate-solution-filters-config.yaml b/scripts/generate-solution-filters-config.yaml index 45c95d8f4b..251aca7e7d 100644 --- a/scripts/generate-solution-filters-config.yaml +++ b/scripts/generate-solution-filters-config.yaml @@ -32,6 +32,11 @@ groupConfigs: trimTests: - "**/Sentry.TrimTest.csproj" - "**/Sentry.MauiTrimTest.csproj" + mobileOnly: + - "**/*Android*.csproj" + - "**/*Ios*.csproj" + - "**/*MacCatalyst*.csproj" + - "**/*Maui*.csproj" filterConfigs: @@ -61,6 +66,7 @@ filterConfigs: - "windowsOnly" - "artefacts" - "trimTests" + - "mobileOnly" patterns: - "**/*Android*.csproj" - "**/*DeviceTests*.csproj" @@ -240,6 +246,7 @@ filterConfigs: groups: - "artefacts" - "trimTests" + - "mobileOnly" patterns: - "**/*Bindings*.csproj" - "**/*Android*.csproj" diff --git a/scripts/install-libssl1.sh b/scripts/install-libssl1.sh new file mode 100755 index 0000000000..7097e1cd6b --- /dev/null +++ b/scripts/install-libssl1.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -euo pipefail + +# Install old deprecated libssl 1.x for .NET 5.0 on Linux to avoid: +# Error: 'No usable version of libssl was found' + +if apk --version >/dev/null 2>&1; then + # Alpine Linux: openssl1.1-compat from the community repo + apk add --repository=https://dl-cdn.alpinelinux.org/alpine/v3.18/community openssl1.1-compat +elif dpkg --version >/dev/null 2>&1; then + # Ubuntu: libssl1 from focal-security + # https://github.com/actions/runner-images/blob/d43555be6577f2ac4e4f78bf683c520687891e1b/images/ubuntu/scripts/build/install-sqlpackage.sh#L11-L21 + if [ "$(dpkg --print-architecture)" = "arm64" ]; then + echo "deb http://ports.ubuntu.com/ubuntu-ports focal-security main" | tee /etc/apt/sources.list.d/focal-security.list + else + echo "deb http://security.ubuntu.com/ubuntu focal-security main" | tee /etc/apt/sources.list.d/focal-security.list + fi + apt-get update + apt-get install -y --no-install-recommends libssl1.1 + rm /etc/apt/sources.list.d/focal-security.list + apt-get update +fi diff --git a/src/Directory.Build.props b/src/Directory.Build.props index a63860aea1..e3e1feb526 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -36,7 +36,7 @@ - + true diff --git a/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs b/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs index e111a46f71..2e4b8d03ab 100644 --- a/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs +++ b/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs @@ -1,4 +1,3 @@ -using Sentry.Android.AssemblyReader.V1; using Sentry.Android.AssemblyReader.V2; namespace Sentry.Android.AssemblyReader; @@ -19,28 +18,13 @@ public static IAndroidAssemblyReader Open(string apkPath, IList supporte { logger?.Invoke(DebugLoggerLevel.Debug, "Opening APK: {0}", apkPath); -#if NET9_0 - logger?.Invoke(DebugLoggerLevel.Debug, "Reading files using V2 APK layout."); - if (AndroidAssemblyStoreReaderV2.TryReadStore(apkPath, supportedAbis, logger, out var readerV2)) + if (AndroidAssemblyStoreReader.TryReadStore(apkPath, supportedAbis, logger, out var readerV2)) { - logger?.Invoke(DebugLoggerLevel.Debug, "APK uses AssemblyStore V2"); + logger?.Invoke(DebugLoggerLevel.Debug, "APK uses AssemblyStore"); return readerV2; } logger?.Invoke(DebugLoggerLevel.Debug, "APK doesn't use AssemblyStore"); - return new AndroidAssemblyDirectoryReaderV2(apkPath, supportedAbis, logger); -#else - logger?.Invoke(DebugLoggerLevel.Debug, "Reading files using V1 APK layout."); - - var zipArchive = ZipFile.OpenRead(apkPath); - if (zipArchive.GetEntry("assemblies/assemblies.manifest") is not null) - { - logger?.Invoke(DebugLoggerLevel.Debug, "APK uses AssemblyStore V1"); - return new AndroidAssemblyStoreReaderV1(zipArchive, supportedAbis, logger); - } - - logger?.Invoke(DebugLoggerLevel.Debug, "APK doesn't use AssemblyStore"); - return new AndroidAssemblyDirectoryReaderV1(zipArchive, supportedAbis, logger); -#endif + return new AndroidAssemblyDirectoryReader(apkPath, supportedAbis, logger); } } diff --git a/src/Sentry.Android.AssemblyReader/Sentry.Android.AssemblyReader.csproj b/src/Sentry.Android.AssemblyReader/Sentry.Android.AssemblyReader.csproj index 2c6dcde33e..fa94fec130 100644 --- a/src/Sentry.Android.AssemblyReader/Sentry.Android.AssemblyReader.csproj +++ b/src/Sentry.Android.AssemblyReader/Sentry.Android.AssemblyReader.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0 + $(PreviousTfm);$(LatestTfm) .NET assembly reader for Android diff --git a/src/Sentry.Android.AssemblyReader/V1/ATTRIBUTION.txt b/src/Sentry.Android.AssemblyReader/V1/ATTRIBUTION.txt deleted file mode 100644 index 16c3f15ed7..0000000000 --- a/src/Sentry.Android.AssemblyReader/V1/ATTRIBUTION.txt +++ /dev/null @@ -1,31 +0,0 @@ -Parts of the code in this subdirectory have been adapted from -https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreExplorer.cs - -The original license is as follows: - - -Xamarin.Android SDK - -The MIT License (MIT) - -Copyright (c) .NET Foundation Contributors - -All rights reserved. - -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/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyDirectoryReaderV1.cs b/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyDirectoryReaderV1.cs deleted file mode 100644 index 7b5e1c1a7e..0000000000 --- a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyDirectoryReaderV1.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Sentry.Android.AssemblyReader.V1; - -// The "Old" app type - where each DLL is placed in the 'assemblies' directory as an individual file. -internal sealed class AndroidAssemblyDirectoryReaderV1 : AndroidAssemblyReader, IAndroidAssemblyReader -{ - public AndroidAssemblyDirectoryReaderV1(ZipArchive zip, IList supportedAbis, DebugLogger? logger) - : base(zip, supportedAbis, logger) { } - - public PEReader? TryReadAssembly(string name) - { - if (File.Exists(name)) - { - // The assembly is already extracted to the file system. Just read it. - var stream = File.OpenRead(name); - return new PEReader(stream); - } - - var zipEntry = FindAssembly(name); - if (zipEntry is null) - { - Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find assembly {0} in the APK", name); - return null; - } - - Logger?.Invoke(DebugLoggerLevel.Debug, "Resolved assembly {0} in the APK at {1}", name, zipEntry.FullName); - - // We need a seekable stream for the PEReader (or even to check whether the DLL is compressed), so make a copy. - var memStream = zipEntry.Extract(); - return ArchiveUtils.CreatePEReader(name, memStream, Logger); - } - - private ZipArchiveEntry? FindAssembly(string name) - { - var zipEntry = ZipArchive.GetEntry($"assemblies/{name}"); - - if (zipEntry is null) - { - foreach (var abi in SupportedAbis) - { - if (abi.Length > 0) - { - zipEntry = ZipArchive.GetEntry($"assemblies/{abi}/{name}"); - if (zipEntry is not null) - { - break; - } - } - } - } - - return zipEntry; - } -} diff --git a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyStoreReaderV1.cs b/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyStoreReaderV1.cs deleted file mode 100644 index 60c947b23e..0000000000 --- a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyStoreReaderV1.cs +++ /dev/null @@ -1,510 +0,0 @@ -namespace Sentry.Android.AssemblyReader.V1; - -// See https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#single-file-assembly-stores -internal sealed class AndroidAssemblyStoreReaderV1 : AndroidAssemblyReader, IAndroidAssemblyReader -{ - private readonly AssemblyStoreExplorer _explorer; - - public AndroidAssemblyStoreReaderV1(ZipArchive zip, IList supportedAbis, DebugLogger? logger) - : base(zip, supportedAbis, logger) - { - _explorer = new(zip, supportedAbis, logger); - } - - public PEReader? TryReadAssembly(string name) - { - var assembly = TryFindAssembly(name); - if (assembly is null) - { - Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find assembly {0} in the APK AssemblyStore", name); - return null; - } - - Logger?.Invoke(DebugLoggerLevel.Debug, "Resolved assembly {0} in the APK {1} AssemblyStore", name, assembly.Store.Arch); - - var stream = assembly.GetImage(); - if (stream is null) - { - Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't access assembly {0} image stream", name); - return null; - } - - return ArchiveUtils.CreatePEReader(name, stream, Logger); - } - - private AssemblyStoreAssembly? TryFindAssembly(string name) - { - if (_explorer.AssembliesByName.TryGetValue(name, out var assembly)) - { - return assembly; - } - - if (name.EndsWith(".dll", ignoreCase: true, CultureInfo.InvariantCulture) || - name.EndsWith(".exe", ignoreCase: true, CultureInfo.InvariantCulture)) - { - if (_explorer.AssembliesByName.TryGetValue(name.Substring(0, name.Length - 4), out assembly)) - { - return assembly; - } - } - - return null; - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreExplorer.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreExplorer - { - private AssemblyStoreReader? _indexStore; - private readonly AssemblyStoreManifestReader _manifest; - private readonly DebugLogger? _logger; - - public IDictionary AssembliesByName { get; } = - new SortedDictionary(StringComparer.OrdinalIgnoreCase); - - public IDictionary AssembliesByHash32 { get; } = - new Dictionary(); - - public IDictionary AssembliesByHash64 { get; } = - new Dictionary(); - - public List Assemblies { get; } = new(); - - public IDictionary> Stores { get; } = - new SortedDictionary>(); - - public AssemblyStoreExplorer(ZipArchive zip, IList supportedAbis, DebugLogger? logger) - { - _logger = logger; - _manifest = new AssemblyStoreManifestReader(zip.GetEntry("assemblies/assemblies.manifest")!.Open()); - - TryAddStore(zip, null); - foreach (var abi in supportedAbis) - { - if (!string.IsNullOrEmpty(abi)) - { - TryAddStore(zip, abi); - } - } - - zip.Dispose(); - ProcessStores(); - } - - private void ProcessStores() - { - if (Stores.Count == 0 || _indexStore == null) - { - return; - } - - ProcessIndex(_indexStore.GlobalIndex32, "32", (he, assembly) => - { - assembly.Hash32 = (uint)he.Hash; - assembly.RuntimeIndex = he.MappingIndex; - - if (_manifest.EntriesByHash32.TryGetValue(assembly.Hash32, out var me)) - { - assembly.Name = me.Name; - } - - if (!AssembliesByHash32.ContainsKey(assembly.Hash32)) - { - AssembliesByHash32.Add(assembly.Hash32, assembly); - } - }); - - ProcessIndex(_indexStore.GlobalIndex64, "64", (he, assembly) => - { - assembly.Hash64 = he.Hash; - if (assembly.RuntimeIndex != he.MappingIndex) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"assembly with hashes 0x{assembly.Hash32} and 0x{assembly.Hash64} has a different 32-bit runtime index ({assembly.RuntimeIndex}) than the 64-bit runtime index({he.MappingIndex})"); - } - - if (_manifest.EntriesByHash64.TryGetValue(assembly.Hash64, out var me)) - { - if (string.IsNullOrEmpty(assembly.Name)) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"32-bit hash 0x{assembly.Hash32:x} did not match any assembly name in the manifest"); - assembly.Name = me.Name; - if (string.IsNullOrEmpty(assembly.Name)) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"64-bit hash 0x{assembly.Hash64:x} did not match any assembly name in the manifest"); - } - } - else if (!string.Equals(assembly.Name, me.Name, StringComparison.Ordinal)) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"32-bit hash 0x{assembly.Hash32:x} maps to assembly name '{assembly.Name}', however 64-bit hash 0x{assembly.Hash64:x} for the same entry matches assembly name '{me.Name}'"); - } - } - - if (!AssembliesByHash64.ContainsKey(assembly.Hash64)) - { - AssembliesByHash64.Add(assembly.Hash64, assembly); - } - }); - - foreach (var kvp in Stores) - { - var list = kvp.Value; - if (list.Count < 2) - { - continue; - } - - var template = list[0]; - for (var i = 1; i < list.Count; i++) - { - var other = list[i]; - if (!template.HasIdenticalContent(other)) - { - throw new Exception( - $"Store ID {template.StoreId} for architecture {other.Arch} is not identical to other stores with the same ID"); - } - } - } - - void ProcessIndex(List index, string bitness, - Action assemblyHandler) - { - foreach (var he in index) - { - if (!Stores.TryGetValue(he.StoreId, out var storeList)) - { - _logger?.Invoke(DebugLoggerLevel.Debug, $"store with id {he.StoreId} not part of the set"); - continue; - } - - foreach (var store in storeList) - { - if (he.LocalStoreIndex >= (uint)store.Assemblies.Count) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"{bitness}-bit index entry with hash 0x{he.Hash:x} has invalid store {store.StoreId} index {he.LocalStoreIndex} (maximum allowed is {store.Assemblies.Count})"); - continue; - } - - var assembly = store.Assemblies[(int)he.LocalStoreIndex]; - assemblyHandler(he, assembly); - - if (!AssembliesByName.ContainsKey(assembly.Name)) - { - AssembliesByName.Add(assembly.Name, assembly); - } - } - } - } - } - - private void TryAddStore(ZipArchive archive, string? abi) - { - var infix = abi is null ? "" : $".{abi}"; - if (archive.GetEntry($"assemblies/assemblies{infix}.blob") is { } zipEntry) - { - var memStream = new MemoryStream((int)zipEntry.Length); - using (var zipStream = zipEntry.Open()) - { - zipStream.CopyTo(memStream); - memStream.Position = 0; - } - - AddStore(new AssemblyStoreReader(memStream, abi)); - } - } - - private void AddStore(AssemblyStoreReader reader) - { - if (reader.HasGlobalIndex) - { - _indexStore = reader; - } - - if (!Stores.TryGetValue(reader.StoreId, out var storeList)) - { - storeList = new List(); - Stores.Add(reader.StoreId, storeList); - } - - storeList.Add(reader); - - Assemblies.AddRange(reader.Assemblies); - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreManifestReader.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreManifestReader - { - public List Entries { get; } = new(); - public Dictionary EntriesByHash32 { get; } = new(); - public Dictionary EntriesByHash64 { get; } = new(); - - public AssemblyStoreManifestReader(Stream manifest) - { - using var sr = new StreamReader(manifest, Encoding.UTF8, detectEncodingFromByteOrderMarks: false); - ReadManifest(sr); - } - - private void ReadManifest(StreamReader reader) - { - // First line is ignored, it contains headers - reader.ReadLine(); - - // Each subsequent line consists of fields separated with any number of spaces (for the pleasure of a human being reading the manifest) - while (!reader.EndOfStream) - { - var fields = reader.ReadLine()?.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - if (fields == null) - { - continue; - } - - var entry = new AssemblyStoreManifestEntry(fields); - Entries.Add(entry); - if (entry.Hash32 != 0) - { - EntriesByHash32.Add(entry.Hash32, entry); - } - - if (entry.Hash64 != 0) - { - EntriesByHash64.Add(entry.Hash64, entry); - } - } - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreManifestEntry.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreManifestEntry - { - // Fields are: - // Hash 32 | Hash 64 | Store ID | Store idx | Name - private const int NumberOfFields = 5; - private const int Hash32FieldIndex = 0; - private const int Hash64FieldIndex = 1; - private const int StoreIdFieldIndex = 2; - private const int StoreIndexFieldIndex = 3; - private const int NameFieldIndex = 4; - - public uint Hash32 { get; } - public ulong Hash64 { get; } - public uint StoreId { get; } - public uint IndexInStore { get; } - public string Name { get; } - - public AssemblyStoreManifestEntry(string[] fields) - { - if (fields.Length != NumberOfFields) - { - throw new ArgumentOutOfRangeException(nameof(fields), "Invalid number of fields"); - } - - Hash32 = GetUInt32(fields[Hash32FieldIndex]); - Hash64 = GetUInt64(fields[Hash64FieldIndex]); - StoreId = GetUInt32(fields[StoreIdFieldIndex]); - IndexInStore = GetUInt32(fields[StoreIndexFieldIndex]); - Name = fields[NameFieldIndex].Trim(); - } - - private static uint GetUInt32(string value) - { - if (uint.TryParse(PrepHexValue(value), NumberStyles.HexNumber, null, out var hash)) - { - return hash; - } - - return 0; - } - - private static ulong GetUInt64(string value) - { - if (ulong.TryParse(PrepHexValue(value), NumberStyles.HexNumber, null, out var hash)) - { - return hash; - } - - return 0; - } - - private static string PrepHexValue(string value) - { - if (value.StartsWith("0x", StringComparison.Ordinal)) - { - return value.Substring(2); - } - - return value; - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreHashEntry.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreHashEntry - { - public bool Is32Bit { get; } - public ulong Hash { get; } - public uint MappingIndex { get; } - public uint LocalStoreIndex { get; } - public uint StoreId { get; } - - internal AssemblyStoreHashEntry(BinaryReader reader, bool is32Bit) - { - Is32Bit = is32Bit; - Hash = reader.ReadUInt64(); - MappingIndex = reader.ReadUInt32(); - LocalStoreIndex = reader.ReadUInt32(); - StoreId = reader.ReadUInt32(); - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreReader.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreReader - { - // These two constants must be identical to the native ones in src/monodroid/jni/xamarin-app.hh - private const uint ASSEMBLY_STORE_MAGIC = 0x41424158; // 'XABA', little-endian - private const uint ASSEMBLY_STORE_FORMAT_VERSION = 1; // The highest format version this reader understands - - private readonly MemoryStream _storeData; - - public uint Version { get; private set; } - public uint LocalEntryCount { get; private set; } - public uint GlobalEntryCount { get; private set; } - public uint StoreId { get; private set; } - public List Assemblies { get; } - public List GlobalIndex32 { get; } = new(); - public List GlobalIndex64 { get; } = new(); - public string Arch { get; } - - public bool HasGlobalIndex => StoreId == 0; - - public AssemblyStoreReader(MemoryStream store, string? arch = null) - { - Arch = arch ?? string.Empty; - _storeData = store; - using var reader = new BinaryReader(store, Encoding.UTF8, leaveOpen: true); - ReadHeader(reader); - - Assemblies = new List(); - ReadLocalEntries(reader, Assemblies); - if (HasGlobalIndex) - { - ReadGlobalIndex(reader, GlobalIndex32, GlobalIndex64); - } - } - - internal MemoryStream? GetAssemblyImageSlice(AssemblyStoreAssembly assembly) => - GetDataSlice(assembly.DataOffset, assembly.DataSize); - - internal MemoryStream? GetAssemblyDebugDataSlice(AssemblyStoreAssembly assembly) => - assembly.DebugDataOffset == 0 ? null : GetDataSlice(assembly.DebugDataOffset, assembly.DebugDataSize); - - internal MemoryStream? GetAssemblyConfigSlice(AssemblyStoreAssembly assembly) => - assembly.ConfigDataOffset == 0 ? null : GetDataSlice(assembly.ConfigDataOffset, assembly.ConfigDataSize); - - private MemoryStream? GetDataSlice(uint offset, uint size) => - size == 0 ? null : new ArchiveUtils.MemorySlice(_storeData, (int)offset, (int)size); - - public bool HasIdenticalContent(AssemblyStoreReader other) - { - return - other.Version == Version && - other.LocalEntryCount == LocalEntryCount && - other.GlobalEntryCount == GlobalEntryCount && - other.StoreId == StoreId && - other.Assemblies.Count == Assemblies.Count && - other.GlobalIndex32.Count == GlobalIndex32.Count && - other.GlobalIndex64.Count == GlobalIndex64.Count; - } - - private void ReadHeader(BinaryReader reader) - { - if (reader.ReadUInt32() != ASSEMBLY_STORE_MAGIC) - { - throw new InvalidOperationException("Invalid header magic number"); - } - - Version = reader.ReadUInt32(); - if (Version == 0) - { - throw new InvalidOperationException("Invalid version number: 0"); - } - - if (Version > ASSEMBLY_STORE_FORMAT_VERSION) - { - throw new InvalidOperationException( - $"Store format version {Version} is higher than the one understood by this reader, {ASSEMBLY_STORE_FORMAT_VERSION}"); - } - - LocalEntryCount = reader.ReadUInt32(); - GlobalEntryCount = reader.ReadUInt32(); - StoreId = reader.ReadUInt32(); - } - - private void ReadLocalEntries(BinaryReader reader, List assemblies) - { - for (uint i = 0; i < LocalEntryCount; i++) - { - assemblies.Add(new AssemblyStoreAssembly(reader, this)); - } - } - - private void ReadGlobalIndex(BinaryReader reader, List index32, - List index64) - { - ReadIndex(true, index32); - ReadIndex(false, index64); - - void ReadIndex(bool is32Bit, List index) - { - for (uint i = 0; i < GlobalEntryCount; i++) - { - index.Add(new AssemblyStoreHashEntry(reader, is32Bit)); - } - } - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreAssembly.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreAssembly - { - public uint DataOffset { get; } - public uint DataSize { get; } - public uint DebugDataOffset { get; } - public uint DebugDataSize { get; } - public uint ConfigDataOffset { get; } - public uint ConfigDataSize { get; } - - public uint Hash32 { get; set; } - public ulong Hash64 { get; set; } - public string Name { get; set; } = string.Empty; - public uint RuntimeIndex { get; set; } - - public AssemblyStoreReader Store { get; } - - internal AssemblyStoreAssembly(BinaryReader reader, AssemblyStoreReader store) - { - Store = store; - - DataOffset = reader.ReadUInt32(); - DataSize = reader.ReadUInt32(); - DebugDataOffset = reader.ReadUInt32(); - DebugDataSize = reader.ReadUInt32(); - ConfigDataOffset = reader.ReadUInt32(); - ConfigDataSize = reader.ReadUInt32(); - } - - public MemoryStream? GetImage() => Store.GetAssemblyImageSlice(this); - - public MemoryStream? GetDebugData() => Store.GetAssemblyDebugDataSlice(this); - - public MemoryStream? GetAssemblyConfig() => Store.GetAssemblyConfigSlice(this); - } -} diff --git a/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReaderV2.cs b/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReader.cs similarity index 98% rename from src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReaderV2.cs rename to src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReader.cs index b250640d1f..ca2b96d886 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReaderV2.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReader.cs @@ -1,13 +1,13 @@ namespace Sentry.Android.AssemblyReader.V2; // The "Old" app type - where each DLL is placed in the 'assemblies' directory as an individual file. -internal sealed class AndroidAssemblyDirectoryReaderV2 : IAndroidAssemblyReader +internal sealed class AndroidAssemblyDirectoryReader : IAndroidAssemblyReader { private DebugLogger? Logger { get; } private HashSet SupportedArchitectures { get; } = new(); private readonly ArchiveAssemblyHelper _archiveAssemblyHelper; - public AndroidAssemblyDirectoryReaderV2(string apkPath, IList supportedAbis, DebugLogger? logger) + public AndroidAssemblyDirectoryReader(string apkPath, IList supportedAbis, DebugLogger? logger) { Logger = logger; foreach (var abi in supportedAbis) diff --git a/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReaderV2.cs b/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReader.cs similarity index 94% rename from src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReaderV2.cs rename to src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReader.cs index 2840c7ec67..0e9c6586f4 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReaderV2.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReader.cs @@ -1,17 +1,17 @@ namespace Sentry.Android.AssemblyReader.V2; -internal class AndroidAssemblyStoreReaderV2 : IAndroidAssemblyReader +internal class AndroidAssemblyStoreReader : IAndroidAssemblyReader { private readonly IList _explorers; private readonly DebugLogger? _logger; - private AndroidAssemblyStoreReaderV2(IList explorers, DebugLogger? logger) + private AndroidAssemblyStoreReader(IList explorers, DebugLogger? logger) { _explorers = explorers; _logger = logger; } - public static bool TryReadStore(string inputFile, IList supportedAbis, DebugLogger? logger, [NotNullWhen(true)] out AndroidAssemblyStoreReaderV2? reader) + public static bool TryReadStore(string inputFile, IList supportedAbis, DebugLogger? logger, [NotNullWhen(true)] out AndroidAssemblyStoreReader? reader) { List supportedExplorers = []; @@ -67,7 +67,7 @@ public static bool TryReadStore(string inputFile, IList supportedAbis, D return false; } - reader = new AndroidAssemblyStoreReaderV2(supportedExplorers, logger); + reader = new AndroidAssemblyStoreReader(supportedExplorers, logger); return true; } diff --git a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs index 0ab9c04e2e..5854d3c070 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs @@ -79,28 +79,19 @@ public static (IList? explorers, string? errorMessage) Op } private static (IList? explorers, string? errorMessage) OpenAab(FileInfo fi, DebugLogger? logger) - => OpenCommon(fi, [StoreReaderV2.AabPaths, StoreReader_V1.AabPaths], logger); + => OpenCommon(fi, StoreReader.AabPaths, logger); private static (IList? explorers, string? errorMessage) OpenAabBase(FileInfo fi, DebugLogger? logger) - => OpenCommon(fi, [StoreReaderV2.AabBasePaths, StoreReader_V1.AabBasePaths], logger); + => OpenCommon(fi, StoreReader.AabBasePaths, logger); private static (IList? explorers, string? errorMessage) OpenApk(FileInfo fi, DebugLogger? logger) - => OpenCommon(fi, [StoreReaderV2.ApkPaths, StoreReader_V1.ApkPaths], logger); + => OpenCommon(fi, StoreReader.ApkPaths, logger); - private static (IList? explorers, string? errorMessage) OpenCommon(FileInfo fi, List> pathLists, DebugLogger? logger) + private static (IList? explorers, string? errorMessage) OpenCommon(FileInfo fi, IList paths, DebugLogger? logger) { using var zip = ZipFile.OpenRead(fi.FullName); - - foreach (var paths in pathLists) - { - var (explorers, errorMessage, pathsFound) = TryLoad(fi, zip, paths, logger); - if (pathsFound) - { - return (explorers, errorMessage); - } - } - - return (null, "Unable to find any blob entries"); + var (explorers, errorMessage, pathsFound) = TryLoad(fi, zip, paths, logger); + return pathsFound ? (explorers, errorMessage) : (null, "Unable to find any blob entries"); } private static (IList? explorers, string? errorMessage, bool pathsFound) TryLoad(FileInfo fi, ZipArchive zip, IList paths, DebugLogger? logger) diff --git a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreItem.cs b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreItem.cs index a132f51ead..2ecde5fd48 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreItem.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreItem.cs @@ -1,5 +1,7 @@ /* * Adapted from https://github.com/dotnet/android/blob/86260ed36dfe1a90c8ed6a2bb1cd0607d637f403/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreItem.cs + * Updated from https://github.com/dotnet/android/blob/64018e13e53cec7246e54866b520d3284de344e0/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreItem.cs + * - Adding support for AssemblyStore v3 format that shipped in .NET 10 (https://github.com/dotnet/android/pull/10249) * Original code licensed under the MIT License (https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/LICENSE.TXT) */ @@ -17,11 +19,13 @@ internal abstract class AssemblyStoreItem public uint ConfigOffset { get; protected set; } public uint ConfigSize { get; protected set; } public AndroidTargetArch TargetArch { get; protected set; } + public bool Ignore { get; } - protected AssemblyStoreItem(string name, bool is64Bit, List hashes) + protected AssemblyStoreItem(string name, bool is64Bit, List hashes, bool ignore) { Name = name; Hashes = hashes.AsReadOnly(); Is64Bit = is64Bit; + Ignore = ignore; } } diff --git a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreReader.cs b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreReader.cs index 834cb2c36d..a3201754e1 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreReader.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreReader.cs @@ -32,23 +32,7 @@ protected AssemblyStoreReader(Stream store, string path, DebugLogger? logger) public static AssemblyStoreReader? Create(Stream store, string path, DebugLogger? logger) { - var reader = MakeReaderReady(new StoreReader_V1(store, path, logger)); - if (reader != null) - { - return reader; - } - - reader = MakeReaderReady(new StoreReaderV2(store, path, logger)); - if (reader != null) - { - return reader; - } - - return null; - } - - private static AssemblyStoreReader? MakeReaderReady(AssemblyStoreReader reader) - { + var reader = new StoreReader(store, path, logger); if (!reader.IsSupported()) { return null; diff --git a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.Classes.cs b/src/Sentry.Android.AssemblyReader/V2/StoreReader.Classes.cs similarity index 80% rename from src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.Classes.cs rename to src/Sentry.Android.AssemblyReader/V2/StoreReader.Classes.cs index 85aa91ba89..5ed713b6be 100644 --- a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.Classes.cs +++ b/src/Sentry.Android.AssemblyReader/V2/StoreReader.Classes.cs @@ -1,11 +1,13 @@ /* * Adapted from https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs + * Updated from https://github.com/dotnet/android/blob/64018e13e53cec7246e54866b520d3284de344e0/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs + * - Adding support for AssemblyStore v3 format that shipped in .NET 10 (https://github.com/dotnet/android/pull/10249) * Original code licensed under the MIT License (https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/LICENSE.TXT) */ namespace Sentry.Android.AssemblyReader.V2; -internal partial class StoreReaderV2 +internal partial class StoreReader { private sealed class Header { @@ -33,11 +35,13 @@ private sealed class IndexEntry { public readonly ulong name_hash; public readonly uint descriptor_index; + public readonly bool ignore; - public IndexEntry(ulong name_hash, uint descriptor_index) + public IndexEntry(ulong name_hash, uint descriptor_index, bool ignore) { this.name_hash = name_hash; this.descriptor_index = descriptor_index; + this.ignore = ignore; } } @@ -57,8 +61,9 @@ private sealed class EntryDescriptor private sealed class StoreItemV2 : AssemblyStoreItem { - public StoreItemV2(AndroidTargetArch targetArch, string name, bool is64Bit, List indexEntries, EntryDescriptor descriptor) - : base(name, is64Bit, IndexToHashes(indexEntries)) + public StoreItemV2(AndroidTargetArch targetArch, string name, bool is64Bit, List indexEntries, + EntryDescriptor descriptor, bool ignore) + : base(name, is64Bit, IndexToHashes(indexEntries), ignore) { DataOffset = descriptor.data_offset; DataSize = descriptor.data_size; @@ -86,11 +91,13 @@ private sealed class TemporaryItem public readonly string Name; public readonly List IndexEntries = new List(); public readonly EntryDescriptor Descriptor; + public readonly bool Ignored; - public TemporaryItem(string name, EntryDescriptor descriptor) + public TemporaryItem(string name, EntryDescriptor descriptor, bool ignored) { Name = name; Descriptor = descriptor; + Ignored = ignored; } } } diff --git a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs b/src/Sentry.Android.AssemblyReader/V2/StoreReader.cs similarity index 90% rename from src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs rename to src/Sentry.Android.AssemblyReader/V2/StoreReader.cs index 97bac95694..5220bf7dc7 100644 --- a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs +++ b/src/Sentry.Android.AssemblyReader/V2/StoreReader.cs @@ -1,15 +1,22 @@ /* * Adapted from https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs + * Updated from https://github.com/dotnet/android/blob/64018e13e53cec7246e54866b520d3284de344e0/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs + * - Adding support for AssemblyStore v3 format that shipped in .NET 10 (https://github.com/dotnet/android/pull/10249) * Original code licensed under the MIT License (https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/LICENSE.TXT) */ namespace Sentry.Android.AssemblyReader.V2; -internal partial class StoreReaderV2 : AssemblyStoreReader +internal partial class StoreReader : AssemblyStoreReader { // Bit 31 is set for 64-bit platforms, cleared for the 32-bit ones +#if NET9_0 private const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000002; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant private const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000002; +#else + private const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000003; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant + private const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000003; +#endif private const uint ASSEMBLY_STORE_FORMAT_VERSION_MASK = 0xF0000000; private const uint ASSEMBLY_STORE_ABI_AARCH64 = 0x00010000; private const uint ASSEMBLY_STORE_ABI_ARM = 0x00020000; @@ -28,7 +35,7 @@ internal partial class StoreReaderV2 : AssemblyStoreReader private Header? header; private ulong elfOffset = 0; - static StoreReaderV2() + static StoreReader() { var paths = new List { GetArchPath (AndroidTargetArch.Arm64), @@ -69,7 +76,7 @@ string GetArchPath(AndroidTargetArch arch, string? root = null) } } - public StoreReaderV2(Stream store, string path, DebugLogger? logger) + public StoreReader(Stream store, string path, DebugLogger? logger) : base(store, path, logger) { supportedVersions = new HashSet { @@ -178,7 +185,12 @@ protected override void Prepare() } uint descriptor_index = reader.ReadUInt32(); - index.Add(new IndexEntry(name_hash, descriptor_index)); +#if NET10_0_OR_GREATER + bool ignore = reader.ReadByte () != 0; +#else + bool ignore = false; +#endif + index.Add(new IndexEntry(name_hash, descriptor_index, ignore)); } var descriptors = new List(); @@ -218,7 +230,7 @@ protected override void Prepare() { if (!tempItems.TryGetValue(ie.descriptor_index, out TemporaryItem? item)) { - item = new TemporaryItem(names[(int)ie.descriptor_index], descriptors[(int)ie.descriptor_index]); + item = new TemporaryItem(names[(int)ie.descriptor_index], descriptors[(int)ie.descriptor_index], ie.ignore); tempItems.Add(ie.descriptor_index, item); } item.IndexEntries.Add(ie); @@ -233,7 +245,7 @@ protected override void Prepare() foreach (var kvp in tempItems) { TemporaryItem ti = kvp.Value; - var item = new StoreItemV2(TargetArch, ti.Name, Is64Bit, ti.IndexEntries, ti.Descriptor); + var item = new StoreItemV2(TargetArch, ti.Name, Is64Bit, ti.IndexEntries, ti.Descriptor, ti.Ignored); storeItems.Add(item); } Assemblies = storeItems.AsReadOnly(); diff --git a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV1.cs b/src/Sentry.Android.AssemblyReader/V2/StoreReaderV1.cs deleted file mode 100644 index c1a3fe3c2d..0000000000 --- a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV1.cs +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Adapted from https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V1.cs - * Original code licensed under the MIT License (https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/LICENSE.TXT) - */ - -namespace Sentry.Android.AssemblyReader.V2; - -internal class StoreReader_V1 : AssemblyStoreReader -{ - public override string Description => "Assembly store v1"; - public override bool NeedsExtensionInName => false; - - public static IList ApkPaths { get; } - public static IList AabPaths { get; } - public static IList AabBasePaths { get; } - - static StoreReader_V1() - { - ApkPaths = new List().AsReadOnly(); - AabPaths = new List().AsReadOnly(); - AabBasePaths = new List().AsReadOnly(); - } - - public StoreReader_V1(Stream store, string path, DebugLogger? logger) - : base(store, path, logger) - { } - - protected override bool IsSupported() - { - return false; - } - - protected override void Prepare() - { - } - - protected override ulong GetStoreStartDataOffset() => 0; -} diff --git a/src/Sentry.AspNetCore.Blazor.WebAssembly/Sentry.AspNetCore.Blazor.WebAssembly.csproj b/src/Sentry.AspNetCore.Blazor.WebAssembly/Sentry.AspNetCore.Blazor.WebAssembly.csproj index 4ff5701fa0..61d6dd2229 100644 --- a/src/Sentry.AspNetCore.Blazor.WebAssembly/Sentry.AspNetCore.Blazor.WebAssembly.csproj +++ b/src/Sentry.AspNetCore.Blazor.WebAssembly/Sentry.AspNetCore.Blazor.WebAssembly.csproj @@ -1,7 +1,7 @@  - net9.0;net8.0 + $(CurrentTfms) Sentry.AspNetCore.Blazor.WebAssembly @@ -10,11 +10,16 @@ + + + + - - + + + diff --git a/src/Sentry.AspNetCore.Grpc/Sentry.AspNetCore.Grpc.csproj b/src/Sentry.AspNetCore.Grpc/Sentry.AspNetCore.Grpc.csproj index cafddf4b7e..3ed88a9201 100644 --- a/src/Sentry.AspNetCore.Grpc/Sentry.AspNetCore.Grpc.csproj +++ b/src/Sentry.AspNetCore.Grpc/Sentry.AspNetCore.Grpc.csproj @@ -1,7 +1,7 @@ - net8.0 + $(CurrentTfms) $(PackageTags);AspNetCore;gRPC Official ASP.NET Core gRPC integration for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time. diff --git a/src/Sentry.AspNetCore/Sentry.AspNetCore.csproj b/src/Sentry.AspNetCore/Sentry.AspNetCore.csproj index d75cabef9f..a44b2a9962 100644 --- a/src/Sentry.AspNetCore/Sentry.AspNetCore.csproj +++ b/src/Sentry.AspNetCore/Sentry.AspNetCore.csproj @@ -1,7 +1,7 @@  - net9.0;net8.0 + $(CurrentTfms) $(PackageTags);AspNetCore;MVC Official ASP.NET Core integration for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time. @@ -11,18 +11,15 @@ true + + $(InterceptorsNamespaces);Microsoft.Extensions.Configuration.Binder.SourceGeneration + + - - - - - - - diff --git a/src/Sentry.Azure.Functions.Worker/Sentry.Azure.Functions.Worker.csproj b/src/Sentry.Azure.Functions.Worker/Sentry.Azure.Functions.Worker.csproj index 7d04ce1a0b..c3329b290f 100644 --- a/src/Sentry.Azure.Functions.Worker/Sentry.Azure.Functions.Worker.csproj +++ b/src/Sentry.Azure.Functions.Worker/Sentry.Azure.Functions.Worker.csproj @@ -1,7 +1,7 @@ - net8.0;netstandard2.0 + $(CurrentTfms);netstandard2.0 $(PackageTags);Azure;Functions;Worker Official Azure Functions Worker SDK integration for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time. diff --git a/src/Sentry.Bindings.Android/Sentry.Bindings.Android.csproj b/src/Sentry.Bindings.Android/Sentry.Bindings.Android.csproj index 0180326bd2..e99fcd08fb 100644 --- a/src/Sentry.Bindings.Android/Sentry.Bindings.Android.csproj +++ b/src/Sentry.Bindings.Android/Sentry.Bindings.Android.csproj @@ -1,7 +1,7 @@ - net8.0-android34.0;net9.0-android35.0 - 8.21.1 + $(LatestAndroidTfm);$(PreviousAndroidTfm) + 8.22.0 $(BaseIntermediateOutputPath)sdks\$(TargetFramework)\Sentry\Android\$(SentryAndroidSdkVersion)\ @@ -43,16 +43,18 @@ - - - - - - - + + + + + + + - - + + @@ -95,37 +86,6 @@ - - - - - - - - - - - - + diff --git a/src/Sentry.Bindings.Android/Transforms/Metadata.xml b/src/Sentry.Bindings.Android/Transforms/Metadata.xml index fb6b6558da..35f9c0be20 100644 --- a/src/Sentry.Bindings.Android/Transforms/Metadata.xml +++ b/src/Sentry.Bindings.Android/Transforms/Metadata.xml @@ -156,4 +156,7 @@ + + + diff --git a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs index f33b1c6815..2c076ddf1a 100644 --- a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs @@ -416,9 +416,9 @@ interface SentryEnvelopeItemHeader : SentrySerializable [Export ("initWithType:length:filenname:contentType:")] NativeHandle Constructor (string type, nuint length, string filename, string contentType); - // -(instancetype _Nonnull)initWithType:(NSString * _Nonnull)type length:(NSUInteger)length contentType:(NSString * _Nonnull)contentType itemCount:(NSNumber * _Nonnull)itemCount; + // -(instancetype _Nonnull)initWithType:(NSString * _Nonnull)type length:(NSUInteger)length contentType:(NSString * _Nullable)contentType itemCount:(NSNumber * _Nonnull)itemCount; [Export ("initWithType:length:contentType:itemCount:")] - NativeHandle Constructor (string type, nuint length, string contentType, NSNumber itemCount); + NativeHandle Constructor (string type, nuint length, [NullAllowed] string contentType, NSNumber itemCount); // @property (readonly, copy, nonatomic) NSString * _Nonnull type; [Export ("type")] diff --git a/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj b/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj index 98e6fde2d2..10b3899977 100644 --- a/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj +++ b/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj @@ -1,9 +1,9 @@ - net8.0-ios17.0;net8.0-maccatalyst17.0 - net8.0-ios17.0 - net8.0-maccatalyst17.0 + $(PreviousIosTfm);$(PreviousMacCatalystTfm) + $(PreviousIosTfm) + $(PreviousMacCatalystTfm) true true .NET Bindings for the Sentry Cocoa SDK diff --git a/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs index 391360da15..1bf7b8ded4 100644 --- a/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs @@ -329,9 +329,6 @@ interface SentryRRWebEvent : SentrySerializable [Export ("initWithType:timestamp:data:")] [DesignatedInitializer] NativeHandle Constructor (SentryRRWebEventType type, NSDate timestamp, [NullAllowed] NSDictionary data); - // -(NSDictionary * _Nonnull)serialize __attribute__((warn_unused_result(""))); - [Export ("serialize")] - new NSDictionary Serialize(); } // @interface SentrySDK : NSObject diff --git a/src/Sentry.Extensions.Logging/Sentry.Extensions.Logging.csproj b/src/Sentry.Extensions.Logging/Sentry.Extensions.Logging.csproj index 4d52cd091c..4201207da5 100644 --- a/src/Sentry.Extensions.Logging/Sentry.Extensions.Logging.csproj +++ b/src/Sentry.Extensions.Logging/Sentry.Extensions.Logging.csproj @@ -1,7 +1,7 @@  - net9.0;net8.0;netstandard2.0 + $(CurrentTfms);netstandard2.0 $(PackageTags);Logging;Microsoft.Extensions.Logging Official Microsoft.Extensions.Logging integration for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time. @@ -32,6 +32,12 @@ + + + + + + diff --git a/src/Sentry.Google.Cloud.Functions/Sentry.Google.Cloud.Functions.csproj b/src/Sentry.Google.Cloud.Functions/Sentry.Google.Cloud.Functions.csproj index 6c455dc928..581bd88f3a 100644 --- a/src/Sentry.Google.Cloud.Functions/Sentry.Google.Cloud.Functions.csproj +++ b/src/Sentry.Google.Cloud.Functions/Sentry.Google.Cloud.Functions.csproj @@ -1,7 +1,7 @@ - net9.0;net8.0 + $(CurrentTfms) $(PackageTags);GCP;Google Cloud Functions Official Google Cloud Functions integration for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time. @@ -16,11 +16,4 @@ - - - - - - - diff --git a/src/Sentry.Hangfire/Sentry.Hangfire.csproj b/src/Sentry.Hangfire/Sentry.Hangfire.csproj index 941dd8c94d..4e2f001c85 100644 --- a/src/Sentry.Hangfire/Sentry.Hangfire.csproj +++ b/src/Sentry.Hangfire/Sentry.Hangfire.csproj @@ -3,7 +3,7 @@ Official Hangfire integration for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time. $(PackageTags);Hangfire - net9.0;net8.0;net462 + $(CurrentTfms);net462 enable false diff --git a/src/Sentry.Maui/Internal/MauiEventsBinder.cs b/src/Sentry.Maui/Internal/MauiEventsBinder.cs index 4af503036b..78e619bed9 100644 --- a/src/Sentry.Maui/Internal/MauiEventsBinder.cs +++ b/src/Sentry.Maui/Internal/MauiEventsBinder.cs @@ -298,9 +298,9 @@ internal void HandlePageEvents(Page page, bool bind = true) // https://github.com/dotnet/docs-maui/issues/583 page.NavigatedTo -= OnPageOnNavigatedTo; - // Layout changed event - // https://docs.microsoft.com/dotnet/api/xamarin.forms.ilayout.layoutchanged - page.LayoutChanged -= OnPageOnLayoutChanged; + // Size changed event + // https://learn.microsoft.com/dotnet/api/microsoft.maui.controls.visualelement.sizechanged + page.SizeChanged -= OnPageOnSizeChanged; if (bind) { @@ -313,9 +313,9 @@ internal void HandlePageEvents(Page page, bool bind = true) // https://github.com/dotnet/docs-maui/issues/583 page.NavigatedTo += OnPageOnNavigatedTo; - // Layout changed event - // https://docs.microsoft.com/dotnet/api/xamarin.forms.ilayout.layoutchanged - page.LayoutChanged += OnPageOnLayoutChanged; + // Size changed event + // https://learn.microsoft.com/dotnet/api/microsoft.maui.controls.visualelement.sizechanged + page.SizeChanged += OnPageOnSizeChanged; } } @@ -446,6 +446,6 @@ private void OnPageOnDisappearing(object? sender, EventArgs _) => private void OnPageOnNavigatedTo(object? sender, NavigatedToEventArgs e) => _hub.AddBreadcrumbForEvent(_options, sender, nameof(Page.NavigatedTo), NavigationType, NavigationCategory, data => data.AddElementInfo(_options, e.GetPreviousPage(), "PreviousPage")); - private void OnPageOnLayoutChanged(object? sender, EventArgs _) => - _hub.AddBreadcrumbForEvent(_options, sender, nameof(Page.LayoutChanged), SystemType, RenderingCategory); + private void OnPageOnSizeChanged(object? sender, EventArgs _) => + _hub.AddBreadcrumbForEvent(_options, sender, nameof(Page.SizeChanged), SystemType, RenderingCategory); } diff --git a/src/Sentry.Maui/Sentry.Maui.csproj b/src/Sentry.Maui/Sentry.Maui.csproj index 38c14e4788..cdaed0c240 100644 --- a/src/Sentry.Maui/Sentry.Maui.csproj +++ b/src/Sentry.Maui/Sentry.Maui.csproj @@ -3,14 +3,14 @@ Official MAUI integration for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time. - net9.0;net8.0 - $(TargetFrameworks);net8.0-android34.0;net9.0-android35.0 - $(TargetFrameworks);net8.0-ios17.0;net9.0-ios18.0 - $(TargetFrameworks);net8.0-maccatalyst17.0;net9.0-maccatalyst18.0 - $(TargetFrameworks);net8.0-windows10.0.19041.0 + $(LatestTfm);$(PreviousTfm) + $(TargetFrameworks);$(LatestAndroidTfm);$(PreviousAndroidTfm) + $(TargetFrameworks);$(LatestIosTfm);$(PreviousIosTfm) + $(TargetFrameworks);$(LatestMacCatalystTfm);$(PreviousMacCatalystTfm) + $(TargetFrameworks);$(PreviousWindowsTfm) - net9.0;net8.0 + $(CurrentTfms) $(PackageTags);Profiling;Diagnostic Performance profiling support for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time. diff --git a/src/Sentry.Serilog/LogLevelExtensions.cs b/src/Sentry.Serilog/LogLevelExtensions.cs index 03a16ea216..07960179b2 100644 --- a/src/Sentry.Serilog/LogLevelExtensions.cs +++ b/src/Sentry.Serilog/LogLevelExtensions.cs @@ -42,4 +42,18 @@ public static BreadcrumbLevel ToBreadcrumbLevel(this LogEventLevel level) _ => (BreadcrumbLevel)level }; } + + public static SentryLogLevel ToSentryLogLevel(this LogEventLevel level) + { + return level switch + { + LogEventLevel.Verbose => SentryLogLevel.Trace, + LogEventLevel.Debug => SentryLogLevel.Debug, + LogEventLevel.Information => SentryLogLevel.Info, + LogEventLevel.Warning => SentryLogLevel.Warning, + LogEventLevel.Error => SentryLogLevel.Error, + LogEventLevel.Fatal => SentryLogLevel.Fatal, + _ => (SentryLogLevel)level, + }; + } } diff --git a/src/Sentry.Serilog/Sentry.Serilog.csproj b/src/Sentry.Serilog/Sentry.Serilog.csproj index eca849fcd6..60c27e77f7 100644 --- a/src/Sentry.Serilog/Sentry.Serilog.csproj +++ b/src/Sentry.Serilog/Sentry.Serilog.csproj @@ -1,7 +1,7 @@  - net9.0;net8.0;netstandard2.1;netstandard2.0;net462 + $(CurrentTfms);netstandard2.1;netstandard2.0;net462 $(PackageTags);Logging;Serilog Official Serilog integration for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time. true diff --git a/src/Sentry.Serilog/SentrySink.Structured.cs b/src/Sentry.Serilog/SentrySink.Structured.cs new file mode 100644 index 0000000000..6584afb934 --- /dev/null +++ b/src/Sentry.Serilog/SentrySink.Structured.cs @@ -0,0 +1,126 @@ +using Sentry.Internal.Extensions; +using Serilog.Parsing; + +namespace Sentry.Serilog; + +internal sealed partial class SentrySink +{ + private static void CaptureStructuredLog(IHub hub, SentryOptions options, LogEvent logEvent, string formatted, string? template) + { + GetTraceIdAndSpanId(hub, out var traceId, out var spanId); + GetStructuredLoggingParametersAndAttributes(logEvent, out var parameters, out var attributes); + + SentryLog log = new(logEvent.Timestamp, traceId, logEvent.Level.ToSentryLogLevel(), formatted) + { + Template = template, + Parameters = parameters, + ParentSpanId = spanId, + }; + + log.SetDefaultAttributes(options, Sdk); + + foreach (var attribute in attributes) + { + log.SetAttribute(attribute.Key, attribute.Value); + } + + hub.Logger.CaptureLog(log); + } + + private static void GetTraceIdAndSpanId(IHub hub, out SentryId traceId, out SpanId? spanId) + { + var span = hub.GetSpan(); + if (span is not null) + { + traceId = span.TraceId; + spanId = span.SpanId; + return; + } + + var scope = hub.GetScope(); + if (scope is not null) + { + traceId = scope.PropagationContext.TraceId; + spanId = scope.PropagationContext.SpanId; + return; + } + + traceId = SentryId.Empty; + spanId = null; + } + + private static void GetStructuredLoggingParametersAndAttributes(LogEvent logEvent, out ImmutableArray> parameters, out List> attributes) + { + var propertyNames = new HashSet(); + foreach (var token in logEvent.MessageTemplate.Tokens) + { + if (token is PropertyToken property) + { + propertyNames.Add(property.PropertyName); + } + } + + var @params = ImmutableArray.CreateBuilder>(); + attributes = new List>(); + + foreach (var property in logEvent.Properties) + { + if (propertyNames.Contains(property.Key)) + { + foreach (var parameter in GetLogEventProperties(property)) + { + @params.Add(parameter); + } + } + else + { + foreach (var attribute in GetLogEventProperties(property)) + { + attributes.Add(new KeyValuePair($"property.{attribute.Key}", attribute.Value)); + } + } + } + + parameters = @params.DrainToImmutable(); + return; + + static IEnumerable> GetLogEventProperties(KeyValuePair property) + { + if (property.Value is ScalarValue scalarValue) + { + if (scalarValue.Value is not null) + { + yield return new KeyValuePair(property.Key, scalarValue.Value); + } + } + else if (property.Value is SequenceValue sequenceValue) + { + if (sequenceValue.Elements.Count != 0) + { + yield return new KeyValuePair(property.Key, sequenceValue.ToString()); + } + } + else if (property.Value is DictionaryValue dictionaryValue) + { + if (dictionaryValue.Elements.Count != 0) + { + yield return new KeyValuePair(property.Key, dictionaryValue.ToString()); + } + } + else if (property.Value is StructureValue structureValue) + { + foreach (var prop in structureValue.Properties) + { + if (LogEventProperty.IsValidName(prop.Name)) + { + yield return new KeyValuePair($"{property.Key}.{prop.Name}", prop.Value.ToString()); + } + } + } + else if (!property.Value.IsNull()) + { + yield return new KeyValuePair(property.Key, property.Value); + } + } + } +} diff --git a/src/Sentry.Serilog/SentrySink.cs b/src/Sentry.Serilog/SentrySink.cs index b2a6671c67..369d52a673 100644 --- a/src/Sentry.Serilog/SentrySink.cs +++ b/src/Sentry.Serilog/SentrySink.cs @@ -5,7 +5,7 @@ namespace Sentry.Serilog; /// /// /// -internal sealed class SentrySink : ILogEventSink, IDisposable +internal sealed partial class SentrySink : ILogEventSink, IDisposable { private readonly IDisposable? _sdkDisposable; private readonly SentrySerilogOptions _options; @@ -13,6 +13,12 @@ internal sealed class SentrySink : ILogEventSink, IDisposable internal static readonly SdkVersion NameAndVersion = typeof(SentrySink).Assembly.GetNameAndVersion(); + private static readonly SdkVersion Sdk = new() + { + Name = SdkName, + Version = NameAndVersion.Version, + }; + /// /// Serilog SDK name. /// @@ -50,6 +56,11 @@ internal SentrySink( public void Emit(LogEvent logEvent) { + if (!IsEnabled(logEvent)) + { + return; + } + if (isReentrant.Value) { _options.DiagnosticLogger?.LogError($"Reentrant log event detected. Logging when inside the scope of another log event can cause a StackOverflowException. LogEventInfo.Message: {logEvent.MessageTemplate.Text}"); @@ -67,6 +78,15 @@ public void Emit(LogEvent logEvent) } } + private bool IsEnabled(LogEvent logEvent) + { + var options = _hubAccessor().GetSentryOptions(); + + return logEvent.Level >= _options.MinimumEventLevel + || logEvent.Level >= _options.MinimumBreadcrumbLevel + || options?.Experimental.EnableLogs is true; + } + private void InnerEmit(LogEvent logEvent) { if (logEvent.TryGetSourceContext(out var context)) @@ -77,8 +97,7 @@ private void InnerEmit(LogEvent logEvent) } } - var hub = _hubAccessor(); - if (hub is null || !hub.IsEnabled) + if (_hubAccessor() is not { IsEnabled: true } hub) { return; } @@ -122,30 +141,37 @@ private void InnerEmit(LogEvent logEvent) } } - if (logEvent.Level < _options.MinimumBreadcrumbLevel) + if (logEvent.Level >= _options.MinimumBreadcrumbLevel) { - return; + Dictionary? data = null; + if (exception != null && !string.IsNullOrWhiteSpace(formatted)) + { + // Exception.Message won't be used as Breadcrumb message + // Avoid losing it by adding as data: + data = new Dictionary + { + { "exception_message", exception.Message } + }; + } + + hub.AddBreadcrumb( + _clock, + string.IsNullOrWhiteSpace(formatted) + ? exception?.Message ?? "" + : formatted, + context, + data: data, + level: logEvent.Level.ToBreadcrumbLevel()); } - Dictionary? data = null; - if (exception != null && !string.IsNullOrWhiteSpace(formatted)) + // Read the options from the Hub, rather than the Sink's Serilog-Options, because 'EnableLogs' is declared in the base 'SentryOptions', rather than the derived 'SentrySerilogOptions'. + // In cases where Sentry's Serilog-Sink is added without a DSN (i.e., without initializing the SDK) and the SDK is initialized differently (e.g., through ASP.NET Core), + // then the 'EnableLogs' option of this Sink's Serilog-Options is default, but the Hub's Sentry-Options have the actual user-defined value configured. + var options = hub.GetSentryOptions(); + if (options?.Experimental.EnableLogs is true) { - // Exception.Message won't be used as Breadcrumb message - // Avoid losing it by adding as data: - data = new Dictionary - { - {"exception_message", exception.Message} - }; + CaptureStructuredLog(hub, options, logEvent, formatted, template); } - - hub.AddBreadcrumb( - _clock, - string.IsNullOrWhiteSpace(formatted) - ? exception?.Message ?? "" - : formatted, - context, - data: data, - level: logEvent.Level.ToBreadcrumbLevel()); } private static bool IsSentryContext(string context) => diff --git a/src/Sentry.Serilog/SentrySinkExtensions.cs b/src/Sentry.Serilog/SentrySinkExtensions.cs index 924cec9d84..e300ae1697 100644 --- a/src/Sentry.Serilog/SentrySinkExtensions.cs +++ b/src/Sentry.Serilog/SentrySinkExtensions.cs @@ -13,8 +13,8 @@ public static class SentrySinkExtensions /// /// The logger configuration . /// The Sentry DSN (required). - /// Minimum log level to send an event. /// Minimum log level to record a breadcrumb. + /// Minimum log level to send an event. /// The Serilog format provider. /// The Serilog text formatter. /// Whether to include default Personal Identifiable information. @@ -35,6 +35,7 @@ public static class SentrySinkExtensions /// What mode to use for reporting referenced assemblies in each event sent to sentry. Defaults to /// What modes to use for event automatic de-duplication. /// Default tags to add to all events. + /// Whether to send structured logs. /// /// This sample shows how each item may be set from within a configuration file: /// @@ -50,7 +51,7 @@ public static class SentrySinkExtensions /// "dsn": "https://MY-DSN@sentry.io", /// "minimumBreadcrumbLevel": "Verbose", /// "minimumEventLevel": "Error", - /// "outputTemplate": "{Timestamp:o} [{Level:u3}] ({Application}/{MachineName}/{ThreadId}) {Message}{NewLine}{Exception}"/// + /// "outputTemplate": "{Timestamp:o} [{Level:u3}] ({Application}/{MachineName}/{ThreadId}) {Message}{NewLine}{Exception}", /// "sendDefaultPii": false, /// "isEnvironmentUser": false, /// "serverName": "MyServerName", @@ -71,7 +72,8 @@ public static class SentrySinkExtensions /// "defaultTags": { /// "key-1", "value-1", /// "key-2", "value-2" - /// } + /// }, + /// "experimentalEnableLogs": true /// } /// } /// ] @@ -103,7 +105,8 @@ public static LoggerConfiguration Sentry( SentryLevel? diagnosticLevel = null, ReportAssembliesMode? reportAssembliesMode = null, DeduplicateMode? deduplicateMode = null, - Dictionary? defaultTags = null) + Dictionary? defaultTags = null, + bool? experimentalEnableLogs = null) { return loggerConfiguration.Sentry(o => ConfigureSentrySerilogOptions(o, dsn, @@ -128,7 +131,8 @@ public static LoggerConfiguration Sentry( diagnosticLevel, reportAssembliesMode, deduplicateMode, - defaultTags)); + defaultTags, + experimentalEnableLogs)); } /// @@ -157,7 +161,7 @@ public static LoggerConfiguration Sentry( /// "Args": { /// "minimumEventLevel": "Error", /// "minimumBreadcrumbLevel": "Verbose", - /// "outputTemplate": "{Timestamp:o} [{Level:u3}] ({Application}/{MachineName}/{ThreadId}) {Message}{NewLine}{Exception}"/// + /// "outputTemplate": "{Timestamp:o} [{Level:u3}] ({Application}/{MachineName}/{ThreadId}) {Message}{NewLine}{Exception}" /// } /// } /// ] @@ -205,7 +209,8 @@ internal static void ConfigureSentrySerilogOptions( SentryLevel? diagnosticLevel = null, ReportAssembliesMode? reportAssembliesMode = null, DeduplicateMode? deduplicateMode = null, - Dictionary? defaultTags = null) + Dictionary? defaultTags = null, + bool? experimentalEnableLogs = null) { if (dsn is not null) { @@ -317,6 +322,11 @@ internal static void ConfigureSentrySerilogOptions( sentrySerilogOptions.DeduplicateMode = deduplicateMode.Value; } + if (experimentalEnableLogs.HasValue) + { + sentrySerilogOptions.Experimental.EnableLogs = experimentalEnableLogs.Value; + } + // Serilog-specific items sentrySerilogOptions.InitializeSdk = dsn is not null; // Inferred from the Sentry overload that is used if (defaultTags?.Count > 0) @@ -354,7 +364,6 @@ public static LoggerConfiguration Sentry( sdkDisposable = SentrySdk.Init(options); } - var minimumOverall = (LogEventLevel)Math.Min((int)options.MinimumBreadcrumbLevel, (int)options.MinimumEventLevel); - return loggerConfiguration.Sink(new SentrySink(options, sdkDisposable), minimumOverall); + return loggerConfiguration.Sink(new SentrySink(options, sdkDisposable)); } } diff --git a/src/Sentry/AttributeReader.cs b/src/Sentry/AttributeReader.cs index c90de13c89..213ac99e13 100644 --- a/src/Sentry/AttributeReader.cs +++ b/src/Sentry/AttributeReader.cs @@ -2,6 +2,25 @@ namespace Sentry; internal static class AttributeReader { - public static string? TryGetProjectDirectory(Assembly assembly) => - assembly.GetCustomAttributes().FirstOrDefault(x => x.Key == "Sentry.ProjectDirectory")?.Value; + public static string? TryGetProjectDirectory(Assembly assembly) + { + // Use metadata (CustomAttributeData) to avoid hard-referencing AssemblyMetadataAttribute, + // which may be trimmed. This safely returns null if the attribute instances were removed. + // The constructor is: `AssemblyMetadataAttribute(string key, string? value);` + foreach (var cad in assembly.GetCustomAttributesData()) + { + var at = cad.AttributeType; + if (at is { Namespace: "System.Reflection", Name: "AssemblyMetadataAttribute" } + && cad.ConstructorArguments.Count == 2 + && cad.ConstructorArguments[0].ArgumentType == typeof(string) + && string.Equals(cad.ConstructorArguments[0].Value as string, "Sentry.ProjectDirectory", StringComparison.Ordinal) + && cad.ConstructorArguments[1].ArgumentType == typeof(string) + ) + { + return cad.ConstructorArguments[1].Value as string; + } + } + + return null; + } } diff --git a/src/Sentry/BindableSentryOptions.cs b/src/Sentry/BindableSentryOptions.cs index 9ca3847e1e..f9077de9b4 100644 --- a/src/Sentry/BindableSentryOptions.cs +++ b/src/Sentry/BindableSentryOptions.cs @@ -9,6 +9,7 @@ internal partial class BindableSentryOptions { public bool? IsGlobalModeEnabled { get; set; } public bool? EnableScopeSync { get; set; } + public bool? EnableBackpressureHandling { get; set; } public List? TagFilters { get; set; } public bool? SendDefaultPii { get; set; } public bool? IsEnvironmentUser { get; set; } @@ -64,6 +65,7 @@ public void ApplyTo(SentryOptions options) { options.IsGlobalModeEnabled = IsGlobalModeEnabled ?? options.IsGlobalModeEnabled; options.EnableScopeSync = EnableScopeSync ?? options.EnableScopeSync; + options.EnableBackpressureHandling = EnableBackpressureHandling ?? options.EnableBackpressureHandling; options.TagFilters = TagFilters?.Select(s => new StringOrRegex(s)).ToList() ?? options.TagFilters; options.SendDefaultPii = SendDefaultPii ?? options.SendDefaultPii; options.IsEnvironmentUser = IsEnvironmentUser ?? options.IsEnvironmentUser; diff --git a/src/Sentry/Http/HttpTransportBase.cs b/src/Sentry/Http/HttpTransportBase.cs index 846e889923..681818e824 100644 --- a/src/Sentry/Http/HttpTransportBase.cs +++ b/src/Sentry/Http/HttpTransportBase.cs @@ -16,6 +16,7 @@ public abstract class HttpTransportBase internal const string DefaultErrorMessage = "No message"; private readonly SentryOptions _options; + private readonly BackpressureMonitor? _backpressureMonitor; private readonly ISystemClock _clock; private readonly Func _getEnvironmentVariable; @@ -24,7 +25,7 @@ public abstract class HttpTransportBase // Using string instead of SentryId here so that we can use Interlocked.Exchange(...). private string? _lastDiscardedSessionInitId; - private string _typeName; + private readonly string _typeName; /// /// Constructor for this class. @@ -33,8 +34,8 @@ public abstract class HttpTransportBase /// An optional method used to read environment variables. /// An optional system clock - used for testing. protected HttpTransportBase(SentryOptions options, - Func? getEnvironmentVariable = default, - ISystemClock? clock = default) + Func? getEnvironmentVariable = null, + ISystemClock? clock = null) { _options = options; _clock = clock ?? SystemClock.Clock; @@ -42,6 +43,21 @@ protected HttpTransportBase(SentryOptions options, _typeName = GetType().Name; } + /// + /// Constructor for this class. + /// + /// The Sentry options. + /// The Sentry options. + /// An optional method used to read environment variables. + /// An optional system clock - used for testing. + internal HttpTransportBase(SentryOptions options, BackpressureMonitor? backpressureMonitor, + Func? getEnvironmentVariable = null, + ISystemClock? clock = null) + : this(options, getEnvironmentVariable, clock) + { + _backpressureMonitor = backpressureMonitor; + } + // Keep track of rate limits and their expiry dates. // Internal for testing. internal ConcurrentDictionary CategoryLimitResets { get; } = new(); @@ -256,6 +272,7 @@ private void ExtractRateLimits(HttpHeaders responseHeaders) } var now = _clock.GetUtcNow(); + _backpressureMonitor?.RecordRateLimitHit(now); // Join to a string to handle both single-header and multi-header cases var rateLimitsEncoded = string.Join(",", rateLimitHeaderValues); diff --git a/src/Sentry/Internal/BackgroundWorker.cs b/src/Sentry/Internal/BackgroundWorker.cs index d747922d5a..99c4ce2d78 100644 --- a/src/Sentry/Internal/BackgroundWorker.cs +++ b/src/Sentry/Internal/BackgroundWorker.cs @@ -9,6 +9,7 @@ internal class BackgroundWorker : IBackgroundWorker, IDisposable { private readonly ITransport _transport; private readonly SentryOptions _options; + private readonly BackpressureMonitor? _backpressureMonitor; private readonly ConcurrentQueueLite _queue; private readonly int _maxItems; private readonly CancellationTokenSource _shutdownSource; @@ -26,11 +27,13 @@ internal class BackgroundWorker : IBackgroundWorker, IDisposable public BackgroundWorker( ITransport transport, SentryOptions options, + BackpressureMonitor? backpressureMonitor, CancellationTokenSource? shutdownSource = null, ConcurrentQueueLite? queue = null) { _transport = transport; _options = options; + _backpressureMonitor = backpressureMonitor; _queue = queue ?? new ConcurrentQueueLite(); _maxItems = options.MaxQueueItems; _shutdownSource = shutdownSource ?? new CancellationTokenSource(); @@ -66,6 +69,7 @@ public bool EnqueueEnvelope(Envelope envelope, bool process) var eventId = envelope.TryGetEventId(_options.DiagnosticLogger); if (Interlocked.Increment(ref _currentItems) > _maxItems) { + _backpressureMonitor?.RecordQueueOverflow(); Interlocked.Decrement(ref _currentItems); _options.ClientReportRecorder.RecordDiscardedEvents(DiscardReason.QueueOverflow, envelope); _options.LogInfo("Discarding envelope {0} because the queue is full.", eventId); diff --git a/src/Sentry/Internal/BackpressureMonitor.cs b/src/Sentry/Internal/BackpressureMonitor.cs new file mode 100644 index 0000000000..c45b38264d --- /dev/null +++ b/src/Sentry/Internal/BackpressureMonitor.cs @@ -0,0 +1,166 @@ +using Sentry.Extensibility; +using Sentry.Infrastructure; + +namespace Sentry.Internal; + +/// +/// +/// Monitors system health and calculates a DownsampleFactor that can be applied to events and transactions when the +/// system is under load. +/// +/// +/// The health checks used by the monitor are: +/// +/// +/// if any events have been dropped due to queue being full in the last 2 seconds +/// if any new rate limits have been applied since the last check +/// +/// This check is performed every 10 seconds. With each negative health check we halve tracesSampleRate up to 10 times, meaning the original tracesSampleRate is multiplied by 1, 1/2, 1/4, ... up to 1/1024 (~ 0.001%). Any positive health check resets to the original tracesSampleRate set in SentryOptions. +/// +/// Backpressure Management +internal class BackpressureMonitor : IDisposable +{ + internal const int MaxDownsamples = 10; + private const int CheckIntervalInSeconds = 10; + private const int RecentThresholdInSeconds = 2; + + private readonly IDiagnosticLogger? _logger; + private readonly ISystemClock _clock; + private long _lastQueueOverflow = DateTimeOffset.MinValue.Ticks; + private long _lastRateLimitEvent = DateTimeOffset.MinValue.Ticks; + private volatile int _downsampleLevel = 0; + + private static readonly long RecencyThresholdTicks = TimeSpan.FromSeconds(RecentThresholdInSeconds).Ticks; + private static readonly long CheckIntervalTicks = TimeSpan.FromSeconds(CheckIntervalInSeconds).Ticks; + + private readonly CancellationTokenSource _cts = new(); + + private readonly Task _workerTask; + internal int DownsampleLevel => _downsampleLevel; + internal long LastQueueOverflowTicks => Interlocked.Read(ref _lastQueueOverflow); + internal long LastRateLimitEventTicks => Interlocked.Read(ref _lastRateLimitEvent); + + public BackpressureMonitor(IDiagnosticLogger? logger, ISystemClock? clock = null, bool enablePeriodicHealthCheck = true) + { + _logger = logger; + _clock = clock ?? SystemClock.Clock; + + if (enablePeriodicHealthCheck) + { + _logger?.LogDebug("Starting BackpressureMonitor."); + _workerTask = Task.Run(() => DoWorkAsync(_cts.Token)); + } + else + { + _workerTask = Task.CompletedTask; + } + } + + /// + /// For testing purposes only. Sets the downsample level directly. + /// + internal void SetDownsampleLevel(int level) + { + Interlocked.Exchange(ref _downsampleLevel, level); + } + + internal void IncrementDownsampleLevel() + { + var oldValue = _downsampleLevel; + if (oldValue < MaxDownsamples) + { + var newValue = oldValue + 1; + if (Interlocked.CompareExchange(ref _downsampleLevel, newValue, oldValue) == oldValue) + { + _logger?.LogDebug("System is under pressure, increasing downsample level to {0}.", newValue); + } + } + } + + /// + /// A multiplier that can be applied to the SampleRate or TracesSampleRate to reduce the amount of data sent to + /// Sentry when the system is under pressure. + /// + public double DownsampleFactor + { + get + { + var level = _downsampleLevel; + return 1d / (1 << level); // 1 / (2^level) = 1, 1/2, 1/4, 1/8, ... + } + } + + public void RecordRateLimitHit(DateTimeOffset when) => Interlocked.Exchange(ref _lastRateLimitEvent, when.Ticks); + + public void RecordQueueOverflow() => Interlocked.Exchange(ref _lastQueueOverflow, _clock.GetUtcNow().Ticks); + + private async Task DoWorkAsync(CancellationToken cancellationToken) + { + try + { + while (!cancellationToken.IsCancellationRequested) + { + DoHealthCheck(); + + await Task.Delay(TimeSpan.FromSeconds(CheckIntervalInSeconds), cancellationToken).ConfigureAwait(false); + } + } + catch (OperationCanceledException) + { + // Task was cancelled, exit gracefully + } + } + + internal void DoHealthCheck() + { + if (IsHealthy) + { + var previous = Interlocked.Exchange(ref _downsampleLevel, 0); + if (previous > 0) + { + _logger?.LogDebug("System is healthy, resetting downsample level."); + } + } + else + { + IncrementDownsampleLevel(); + } + } + + /// + /// Checks for any recent queue overflows or any rate limit events since the last check. + /// + /// + internal bool IsHealthy + { + get + { + var nowTicks = _clock.GetUtcNow().Ticks; + var recentOverflowCutoff = nowTicks - RecencyThresholdTicks; + var rateLimitCutoff = nowTicks - CheckIntervalTicks; + return LastQueueOverflowTicks < recentOverflowCutoff && LastRateLimitEventTicks < rateLimitCutoff; + } + } + + public void Dispose() + { + try + { + _cts.Cancel(); + _workerTask.Wait(); + } + catch (AggregateException ex) when (ex.InnerException is OperationCanceledException) + { + // Ignore cancellation + } + catch (Exception ex) + { + // Log rather than throw + _logger?.LogWarning(ex, "Error in BackpressureMonitor.Dispose"); + } + finally + { + _cts.Dispose(); + } + } +} diff --git a/src/Sentry/Internal/BackpressureMonitorExtensions.cs b/src/Sentry/Internal/BackpressureMonitorExtensions.cs new file mode 100644 index 0000000000..cddbe22023 --- /dev/null +++ b/src/Sentry/Internal/BackpressureMonitorExtensions.cs @@ -0,0 +1,6 @@ +namespace Sentry.Internal; + +internal static class BackpressureMonitorExtensions +{ + internal static double GetDownsampleFactor(this BackpressureMonitor? monitor) => monitor?.DownsampleFactor ?? 1.0; +} diff --git a/src/Sentry/Internal/Http/HttpTransport.cs b/src/Sentry/Internal/Http/HttpTransport.cs index 7f8b7ce91d..a245dc4e14 100644 --- a/src/Sentry/Internal/Http/HttpTransport.cs +++ b/src/Sentry/Internal/Http/HttpTransport.cs @@ -15,10 +15,10 @@ public HttpTransport(SentryOptions options, HttpClient httpClient) _httpClient = httpClient; } - internal HttpTransport(SentryOptions options, HttpClient httpClient, + internal HttpTransport(SentryOptions options, HttpClient httpClient, BackpressureMonitor? backpressureMonitor, Func? getEnvironmentVariable = default, ISystemClock? clock = default) - : base(options, getEnvironmentVariable, clock) + : base(options, backpressureMonitor, getEnvironmentVariable, clock) { _httpClient = httpClient; } diff --git a/src/Sentry/Internal/Http/LazyHttpTransport.cs b/src/Sentry/Internal/Http/LazyHttpTransport.cs index 51f6b71be7..f2475c9471 100644 --- a/src/Sentry/Internal/Http/LazyHttpTransport.cs +++ b/src/Sentry/Internal/Http/LazyHttpTransport.cs @@ -7,9 +7,9 @@ internal class LazyHttpTransport : ITransport { private readonly Lazy _httpTransport; - public LazyHttpTransport(SentryOptions options) + public LazyHttpTransport(SentryOptions options, BackpressureMonitor? backpressureMonitor) { - _httpTransport = new Lazy(() => new HttpTransport(options, options.GetHttpClient())); + _httpTransport = new Lazy(() => new HttpTransport(options, options.GetHttpClient(), backpressureMonitor)); } public Task SendEnvelopeAsync(Envelope envelope, CancellationToken cancellationToken = default) diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index ec92169cd3..5170a64233 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -14,9 +14,11 @@ internal class Hub : IHub, IDisposable private readonly ISystemClock _clock; private readonly ISessionManager _sessionManager; private readonly SentryOptions _options; + private readonly ISampleRandHelper _sampleRandHelper; private readonly RandomValuesFactory _randomValuesFactory; private readonly IReplaySession _replaySession; private readonly List _integrationsToCleanup = new(); + private readonly BackpressureMonitor? _backpressureMonitor; #if MEMORY_DUMP_SUPPORTED private readonly MemoryMonitor? _memoryMonitor; @@ -44,7 +46,9 @@ internal Hub( ISystemClock? clock = null, IInternalScopeManager? scopeManager = null, RandomValuesFactory? randomValuesFactory = null, - IReplaySession? replaySession = null) + IReplaySession? replaySession = null, + ISampleRandHelper? sampleRandHelper = null, + BackpressureMonitor? backpressureMonitor = null) { if (string.IsNullOrWhiteSpace(options.Dsn)) { @@ -59,8 +63,13 @@ internal Hub( _randomValuesFactory = randomValuesFactory ?? new SynchronizedRandomValuesFactory(); _sessionManager = sessionManager ?? new GlobalSessionManager(options); _clock = clock ?? SystemClock.Clock; - client ??= new SentryClient(options, randomValuesFactory: _randomValuesFactory, sessionManager: _sessionManager); + if (_options.EnableBackpressureHandling) + { + _backpressureMonitor = backpressureMonitor ?? new BackpressureMonitor(_options.DiagnosticLogger, clock); + } + client ??= new SentryClient(options, randomValuesFactory: _randomValuesFactory, sessionManager: _sessionManager, backpressureMonitor: _backpressureMonitor); _replaySession = replaySession ?? ReplaySession.Instance; + _sampleRandHelper = sampleRandHelper ?? new SampleRandHelperAdapter(); ScopeManager = scopeManager ?? new SentryScopeManager(options, client); if (!options.IsGlobalModeEnabled) @@ -175,9 +184,10 @@ internal ITransactionTracer StartTransaction( bool? isSampled = null; double? sampleRate = null; + DiscardReason? discardReason = null; var sampleRand = dynamicSamplingContext?.Items.TryGetValue("sample_rand", out var dscSampleRand) ?? false ? double.Parse(dscSampleRand, NumberStyles.Float, CultureInfo.InvariantCulture) - : SampleRandHelper.GenerateSampleRand(context.TraceId.ToString()); + : _sampleRandHelper.GenerateSampleRand(context.TraceId.ToString()); // TracesSampler runs regardless of whether a decision has already been made, as it can be used to override it. if (_options.TracesSampler is { } tracesSampler) @@ -189,8 +199,14 @@ internal ITransactionTracer StartTransaction( if (tracesSampler(samplingContext) is { } samplerSampleRate) { // The TracesSampler trumps all other sampling decisions (even the trace header) - sampleRate = samplerSampleRate; - isSampled = SampleRandHelper.IsSampled(sampleRand, samplerSampleRate); + sampleRate = samplerSampleRate * _backpressureMonitor.GetDownsampleFactor(); + isSampled = SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); + if (isSampled is false) + { + // If sampling out is only a result of the downsampling then we specify the reason as backpressure + // management... otherwise the event would have been sampled out anyway, so it's just regular sampling. + discardReason = sampleRand < samplerSampleRate ? DiscardReason.Backpressure : DiscardReason.SampleRate; + } // Ensure the actual sampleRate is set on the provided DSC (if any) when the TracesSampler reached a sampling decision dynamicSamplingContext?.SetSampleRate(samplerSampleRate); @@ -201,8 +217,15 @@ internal ITransactionTracer StartTransaction( // finally fallback to Random sampling if the decision has been made by no other means if (isSampled == null) { - sampleRate = _options.TracesSampleRate ?? 0.0; + var optionsSampleRate = _options.TracesSampleRate ?? 0.0; + sampleRate = optionsSampleRate * _backpressureMonitor.GetDownsampleFactor(); isSampled = context.IsSampled ?? SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); + if (isSampled is false) + { + // If sampling out is only a result of the downsampling then we specify the reason as backpressure + // management... otherwise the event would have been sampled out anyway, so it's just regular sampling. + discardReason = sampleRand < optionsSampleRate ? DiscardReason.Backpressure : DiscardReason.SampleRate; + } if (context.IsSampled is null && _options.TracesSampleRate is not null) { @@ -220,6 +243,7 @@ internal ITransactionTracer StartTransaction( { SampleRate = sampleRate, SampleRand = sampleRand, + DiscardReason = discardReason, DynamicSamplingContext = dynamicSamplingContext // Default to the provided DSC }; // If no DSC was provided, create one based on this transaction. @@ -845,6 +869,8 @@ public void Dispose() } //Don't dispose of ScopeManager since we want dangling transactions to still be able to access tags. + _backpressureMonitor?.Dispose(); + #if __IOS__ // TODO #elif ANDROID diff --git a/src/Sentry/Internal/SampleRandHelper.cs b/src/Sentry/Internal/SampleRandHelper.cs index 5e420c70f8..4a8d1c028f 100644 --- a/src/Sentry/Internal/SampleRandHelper.cs +++ b/src/Sentry/Internal/SampleRandHelper.cs @@ -11,5 +11,16 @@ internal static double GenerateSampleRand(string traceId) <= 0 => false, _ => sampleRand < rate }; +} +[DebuggerStepThrough] +internal class SampleRandHelperAdapter : ISampleRandHelper +{ + [DebuggerStepThrough] + public double GenerateSampleRand(string traceId) => SampleRandHelper.GenerateSampleRand(traceId); +} + +internal interface ISampleRandHelper +{ + public double GenerateSampleRand(string traceId); } diff --git a/src/Sentry/Internal/SdkComposer.cs b/src/Sentry/Internal/SdkComposer.cs index dab9a08986..d7263f8765 100644 --- a/src/Sentry/Internal/SdkComposer.cs +++ b/src/Sentry/Internal/SdkComposer.cs @@ -8,14 +8,16 @@ namespace Sentry.Internal; internal class SdkComposer { private readonly SentryOptions _options; + private readonly BackpressureMonitor? _backpressureMonitor; - public SdkComposer(SentryOptions options) + public SdkComposer(SentryOptions options, BackpressureMonitor? backpressureMonitor) { _options = options ?? throw new ArgumentNullException(nameof(options)); if (options.Dsn is null) { throw new ArgumentException("No DSN defined in the SentryOptions"); } + _backpressureMonitor = backpressureMonitor; } private ITransport CreateTransport() @@ -23,7 +25,7 @@ private ITransport CreateTransport() _options.LogDebug("Creating transport."); // Start from either the transport given on options, or create a new HTTP transport. - var transport = _options.Transport ?? new LazyHttpTransport(_options); + var transport = _options.Transport ?? new LazyHttpTransport(_options, _backpressureMonitor); // When a cache directory path is given, wrap the transport in a caching transport. if (!string.IsNullOrWhiteSpace(_options.CacheDirectoryPath)) @@ -87,6 +89,6 @@ public IBackgroundWorker CreateBackgroundWorker() var transport = CreateTransport(); - return new BackgroundWorker(transport, _options); + return new BackgroundWorker(transport, _options, _backpressureMonitor); } } diff --git a/src/Sentry/Internal/UnsampledTransaction.cs b/src/Sentry/Internal/UnsampledTransaction.cs index a14698f7f3..8f55b7d808 100644 --- a/src/Sentry/Internal/UnsampledTransaction.cs +++ b/src/Sentry/Internal/UnsampledTransaction.cs @@ -42,6 +42,8 @@ public UnsampledTransaction(IHub hub, ITransactionContext context) public double? SampleRand { get; set; } + public DiscardReason? DiscardReason { get; set; } + public override string Name { get => _context.Name; @@ -72,8 +74,9 @@ public override void Finish() // Record the discarded events var spanCount = Spans.Count + 1; // 1 for each span + 1 for the transaction itself - _options?.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.SampleRate, DataCategory.Transaction); - _options?.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.SampleRate, DataCategory.Span, spanCount); + var discardReason = DiscardReason ?? Internal.DiscardReason.SampleRate; + _options?.ClientReportRecorder.RecordDiscardedEvent(discardReason, DataCategory.Transaction); + _options?.ClientReportRecorder.RecordDiscardedEvent(discardReason, DataCategory.Span, spanCount); _options?.LogDebug("Finished unsampled transaction"); } diff --git a/src/Sentry/Platforms/Native/CFunctions.cs b/src/Sentry/Platforms/Native/CFunctions.cs index 582112fc4c..5faa149720 100644 --- a/src/Sentry/Platforms/Native/CFunctions.cs +++ b/src/Sentry/Platforms/Native/CFunctions.cs @@ -322,14 +322,25 @@ private static Dictionary LoadDebugImagesOnce(IDiagnosticLogge [DllImport("sentry-native")] internal static extern void sentry_value_decref(sentry_value_t value); - // native union sentry_value_u/t - [StructLayout(LayoutKind.Explicit)] + // Mirrors the native `sentry_value_t` union (uint64_t or double). + // Implemented with a single ulong backing field and BitConverter + // to reinterpret values, since explicit unions cause issues with + // Blazor WASM interop generators. internal struct sentry_value_t { - [FieldOffset(0)] - internal ulong _bits; - [FieldOffset(0)] - internal double _double; + private ulong _bits; + + internal ulong Bits + { + readonly get => _bits; + set => _bits = value; + } + + internal double Double + { + readonly get => BitConverter.UInt64BitsToDouble(_bits); + set => _bits = BitConverter.DoubleToUInt64Bits(value); + } } [DllImport("sentry-native")] diff --git a/src/Sentry/Sentry.csproj b/src/Sentry/Sentry.csproj index c560abff9b..850b3840d8 100644 --- a/src/Sentry/Sentry.csproj +++ b/src/Sentry/Sentry.csproj @@ -8,12 +8,12 @@ - net9.0;net8.0;netstandard2.1;netstandard2.0;net462 - $(TargetFrameworks);net9.0-android35.0;net8.0-android34.0 - $(TargetFrameworks);net9.0-ios18.0;net8.0-ios17.0 - $(TargetFrameworks);net9.0-maccatalyst18.0;net8.0-maccatalyst17.0 + $(CurrentTfms);netstandard2.1;netstandard2.0;net462 + $(TargetFrameworks);$(LatestAndroidTfm);$(PreviousAndroidTfm) + $(TargetFrameworks);$(LatestIosTfm);$(PreviousIosTfm) + $(TargetFrameworks);$(LatestMacCatalystTfm);$(PreviousMacCatalystTfm) - + @@ -25,8 +25,8 @@ true - - + + @@ -113,13 +113,13 @@ <_OSArchitecture>$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) - - - - - - - + + + + + + + @@ -191,10 +191,11 @@ - + - diff --git a/src/Sentry/SentryClient.cs b/src/Sentry/SentryClient.cs index 5e7ae5cfd1..80ede995dd 100644 --- a/src/Sentry/SentryClient.cs +++ b/src/Sentry/SentryClient.cs @@ -16,6 +16,7 @@ namespace Sentry; public class SentryClient : ISentryClient, IDisposable { private readonly SentryOptions _options; + private readonly BackpressureMonitor? _backpressureMonitor; private readonly ISessionManager _sessionManager; private readonly RandomValuesFactory _randomValuesFactory; private readonly Enricher _enricher; @@ -41,9 +42,11 @@ internal SentryClient( SentryOptions options, IBackgroundWorker? worker = null, RandomValuesFactory? randomValuesFactory = null, - ISessionManager? sessionManager = null) + ISessionManager? sessionManager = null, + BackpressureMonitor? backpressureMonitor = null) { _options = options ?? throw new ArgumentNullException(nameof(options)); + _backpressureMonitor = backpressureMonitor; _randomValuesFactory = randomValuesFactory ?? new SynchronizedRandomValuesFactory(); _sessionManager = sessionManager ?? new GlobalSessionManager(options); _enricher = new Enricher(options); @@ -52,7 +55,7 @@ internal SentryClient( if (worker == null) { - var composer = new SdkComposer(options); + var composer = new SdkComposer(options, backpressureMonitor); Worker = composer.CreateBackgroundWorker(); } else @@ -307,7 +310,6 @@ public SentryId CaptureCheckIn( /// A task to await for the flush operation. public Task FlushAsync(TimeSpan timeout) => Worker.FlushAsync(timeout); - // TODO: this method needs to be refactored, it's really hard to analyze nullability private SentryId DoSendEvent(SentryEvent @event, SentryHint? hint, Scope? scope) { var filteredExceptions = ApplyExceptionFilters(@event.Exception); @@ -375,16 +377,22 @@ private SentryId DoSendEvent(SentryEvent @event, SentryHint? hint, Scope? scope) if (_options.SampleRate != null) { - if (!_randomValuesFactory.NextBool(_options.SampleRate.Value)) + var sampleRate = _options.SampleRate.Value; + var downsampledRate = sampleRate * _backpressureMonitor.GetDownsampleFactor(); + var sampleRand = _randomValuesFactory.NextDouble(); + if (sampleRand >= downsampledRate) { - _options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.SampleRate, DataCategory.Error); - _options.LogDebug("Event sampled."); + // If sampling out is only a result of the downsampling then we specify the reason as backpressure + // management... otherwise the event would have been sampled out anyway, so it's just regular sampling. + var reason = sampleRand < sampleRate ? DiscardReason.Backpressure : DiscardReason.SampleRate; + _options.ClientReportRecorder.RecordDiscardedEvent(reason, DataCategory.Error); + _options.LogDebug("Event sampled out."); return SentryId.Empty; } } else { - _options.LogDebug("Event not sampled."); + _options.LogDebug("Event sampled in."); } if (!_options.SendDefaultPii) diff --git a/src/Sentry/SentryLog.cs b/src/Sentry/SentryLog.cs index b506b9da6c..7e58fec173 100644 --- a/src/Sentry/SentryLog.cs +++ b/src/Sentry/SentryLog.cs @@ -9,6 +9,7 @@ namespace Sentry; /// This API is experimental and it may change in the future. /// [Experimental(DiagnosticId.ExperimentalFeature)] +[DebuggerDisplay(@"SentryLog \{ Level = {Level}, Message = '{Message}' \}")] public sealed class SentryLog { private readonly Dictionary _attributes; diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index df4e2937e3..ace651ec46 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -94,6 +94,12 @@ public bool IsGlobalModeEnabled /// public bool EnableScopeSync { get; set; } + /// + /// Enables or disables automatic backpressure handling. When enabled, the SDK will monitor system health and + /// reduce the sampling rate of events and transactions when the system is under load. + /// + public bool EnableBackpressureHandling { get; set; } = false; + /// /// This holds a reference to the current transport, when one is active. /// If set manually before initialization, the provided transport will be used instead of the default transport. diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index eab7b9838f..b4bb1fbeea 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -316,7 +316,13 @@ public static class Experimental public static IDisposable PushScope() => CurrentHub.PushScope(); /// + /// /// Binds the client to the current scope. + /// + /// + /// This might be used to bind a client with a different DSN or configuration (e.g. so that a particular thread or + /// part of the application sends events to a different Sentry project). + /// /// /// The client. [DebuggerStepThrough] diff --git a/src/Sentry/buildTransitive/Sentry.targets b/src/Sentry/buildTransitive/Sentry.targets index 653cc294c0..c33ed0efbf 100644 --- a/src/Sentry/buildTransitive/Sentry.targets +++ b/src/Sentry/buildTransitive/Sentry.targets @@ -18,6 +18,7 @@ Sentry.Attributes$(MSBuildProjectExtension.Replace('proj', '')) + $([System.Guid]::NewGuid()) true @@ -125,11 +126,14 @@ $(SentrySetCommitReleaseOptions) --org $(SentryOrg) $(SentrySetCommitReleaseOptions) --project $(SentryProject) + <_SentryCLIProGuardOptions Condition="'$(SentryProGuardUUID)' != ''">$(_SentryCLIProGuardOptions) --uuid "$(SentryProGuardUUID)" + <_SentryCLIProGuardOptions Condition="'$(_SentryCLIProGuardOptions.Trim())' != ''">$(_SentryCLIProGuardOptions.Trim()) + $(SentryCLIUploadOptions) --org $(SentryOrg) $(SentryCLIUploadOptions) --project $(SentryProject) $(SentryCLIBaseCommand) debug-files upload $(SentryCLIDebugFilesUploadCommand) $(SentryCLIUploadOptions.Trim()) - $(SentryCLIBaseCommand) upload-proguard + $(SentryCLIBaseCommand) upload-proguard $(_SentryCLIProGuardOptions) $(SentryCLIProGuardMappingUploadCommand) $(SentryCLIUploadOptions.Trim()) @@ -217,7 +221,7 @@ $(SentryCLIUploadDirectory) - $(SentryCLIUploadItems) $(IntermediateOutputPath)linked/$(AssemblyName).pdb + $(SentryCLIUploadItems) $(IntermediateOutputPath)linked/*.pdb $(SentryCLIUploadItems) @(AndroidNativeSymbolFilesExceptDll -> '%(Identity)', ' ') @@ -267,9 +271,22 @@ - - + + + + + <_Parameter1>io.sentry.proguard-uuid + $(SentryProGuardUUID) + + + + + + diff --git a/test/AndroidTestApp/AndroidTestApp.csproj b/test/AndroidTestApp/AndroidTestApp.csproj index 3d8a1981e1..cefce5290f 100644 --- a/test/AndroidTestApp/AndroidTestApp.csproj +++ b/test/AndroidTestApp/AndroidTestApp.csproj @@ -1,7 +1,8 @@ - net8.0-android;net9.0-android + net10.0-android;net9.0-android false + false 21 Exe enable @@ -9,5 +10,6 @@ com.companyname.AndroidTestApp 1 1.0 + android-x64 diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 20b6c7ac79..301d759ba4 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -11,6 +11,8 @@ $(NoWarn);SYSLIB0005;SYSLIB0012 $(NoWarn);IDE1006 + + $(NoWarn);ASPDEPR004;ASPDEPR008 false @@ -72,7 +74,7 @@ - + diff --git a/test/Sentry.Analyzers.Tests/Sentry.Analyzers.Tests.csproj b/test/Sentry.Analyzers.Tests/Sentry.Analyzers.Tests.csproj index 9750861210..c580d53e9d 100644 --- a/test/Sentry.Analyzers.Tests/Sentry.Analyzers.Tests.csproj +++ b/test/Sentry.Analyzers.Tests/Sentry.Analyzers.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + $(PreviousTfm) enable false diff --git a/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs b/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs index 24344f2edd..4675f4e02e 100644 --- a/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs +++ b/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs @@ -1,4 +1,3 @@ -using Sentry.Android.AssemblyReader.V1; using Sentry.Android.AssemblyReader.V2; namespace Sentry.Android.AssemblyReader.Tests; @@ -7,10 +6,10 @@ public class AndroidAssemblyReaderTests { private readonly ITestOutputHelper _output; -#if NET9_0 +#if NET10_0 + private static string TargetFramework => "net10.0"; +#elif NET9_0 private static string TargetFramework => "net9.0"; -#elif NET8_0 - private static string TargetFramework => "net8.0"; #else // Adding a new TFM to the project? Include it above #error "Target Framework not yet supported for AndroidAssemblyReader" @@ -52,11 +51,11 @@ public void CreatesCorrectStoreReader() using var sut = GetSut(isAot: false, isAssemblyStore: true, isCompressed: true); switch (TargetFramework) { - case "net9.0": - Assert.IsType(sut); + case "net10.0": + Assert.IsType(sut); break; - case "net8.0": - Assert.IsType(sut); + case "net9.0": + Assert.IsType(sut); break; default: throw new NotSupportedException($"Unsupported target framework: {TargetFramework}"); @@ -72,11 +71,11 @@ public void CreatesCorrectArchiveReader() using var sut = GetSut(isAot: false, isAssemblyStore: false, isCompressed: true); switch (TargetFramework) { - case "net9.0": - Assert.IsType(sut); + case "net10.0": + Assert.IsType(sut); break; - case "net8.0": - Assert.IsType(sut); + case "net9.0": + Assert.IsType(sut); break; default: throw new NotSupportedException($"Unsupported target framework: {TargetFramework}"); @@ -93,11 +92,7 @@ public void ReturnsNullIfAssemblyDoesntExist(bool isAssemblyStore) } public static IEnumerable ReadsAssemblyPermutations => -#if NET8_0 - from isAot in new[] { false } -#else from isAot in new[] { true, false } -#endif from isStore in new[] { true, false } from isCompressed in new[] { true, false } from assemblyName in new[] { "Mono.Android.dll", "System.Private.CoreLib.dll" } diff --git a/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt similarity index 100% rename from test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt rename to test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt diff --git a/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj b/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj index f30b490c91..a892724b49 100644 --- a/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj +++ b/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj @@ -1,9 +1,9 @@ - net9.0;net8.0 + $(LatestTfm);$(PreviousTfm) - $(TargetFrameworks);net8.0-android34.0;net9.0-android35.0 + $(TargetFrameworks);$(LatestAndroidTfm);$(PreviousAndroidTfm) enable @@ -20,7 +20,7 @@ - + @@ -32,7 +32,7 @@ - + @@ -42,11 +42,10 @@ <_TestAPK Include="2" Properties="_Aot=False;_Store=False;_Compressed=True" /> <_TestAPK Include="3" Properties="_Aot=False;_Store=True;_Compressed=False" /> <_TestAPK Include="4" Properties="_Aot=False;_Store=True;_Compressed=True" /> - - <_TestAPK Include="5" Condition="!$(TargetFramework.StartsWith('net8'))" Properties="_Aot=True;_Store=False;_Compressed=False" /> - <_TestAPK Include="6" Condition="!$(TargetFramework.StartsWith('net8'))" Properties="_Aot=True;_Store=False;_Compressed=True" /> - <_TestAPK Include="7" Condition="!$(TargetFramework.StartsWith('net8'))" Properties="_Aot=True;_Store=True;_Compressed=False" /> - <_TestAPK Include="8" Condition="!$(TargetFramework.StartsWith('net8'))" Properties="_Aot=True;_Store=True;_Compressed=True" /> + <_TestAPK Include="5" Properties="_Aot=True;_Store=False;_Compressed=False" /> + <_TestAPK Include="6" Properties="_Aot=True;_Store=False;_Compressed=True" /> + <_TestAPK Include="7" Properties="_Aot=True;_Store=True;_Compressed=False" /> + <_TestAPK Include="8" Properties="_Aot=True;_Store=True;_Compressed=True" /> @@ -57,7 +56,8 @@ ..\AndroidTestApp\bin\$(TargetFramework)\$(_ConfigString)\com.companyname.AndroidTestApp-Signed.apk TestAPKs\$(TargetFramework)-$(_ConfigString).apk - + + diff --git a/test/Sentry.AspNetCore.Grpc.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.AspNetCore.Grpc.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt new file mode 100644 index 0000000000..60439aee0e --- /dev/null +++ b/test/Sentry.AspNetCore.Grpc.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -0,0 +1,50 @@ +namespace Sentry.AspNetCore.Grpc +{ + public class DefaultProtobufRequestPayloadExtractor : Sentry.AspNetCore.Grpc.IProtobufRequestPayloadExtractor + { + public DefaultProtobufRequestPayloadExtractor() { } + public Google.Protobuf.IMessage ExtractPayload(Sentry.AspNetCore.Grpc.IProtobufRequest request) + where TRequest : class, Google.Protobuf.IMessage { } + } + public interface IProtobufRequestPayloadExtractor + { + Google.Protobuf.IMessage? ExtractPayload(Sentry.AspNetCore.Grpc.IProtobufRequest request) + where TRequest : class, Google.Protobuf.IMessage; + } + public interface IProtobufRequest + { + long? ContentLength { get; } + TRequest Request { get; } + } + public class ProtobufRequestExtractionDispatcher : Sentry.AspNetCore.Grpc.IProtobufRequestPayloadExtractor + { + public ProtobufRequestExtractionDispatcher(System.Collections.Generic.IEnumerable extractors, Sentry.SentryOptions options, System.Func sizeSwitch) { } + public Google.Protobuf.IMessage? ExtractPayload(Sentry.AspNetCore.Grpc.IProtobufRequest request) + where TRequest : class, Google.Protobuf.IMessage { } + } + public static class ScopeExtensions + { + public static void Populate(this Sentry.Scope scope, Grpc.Core.ServerCallContext context, TRequest? request, Sentry.AspNetCore.SentryAspNetCoreOptions options) + where TRequest : class { } + } + public static class SentryBuilderExtensions + { + public static Sentry.AspNetCore.ISentryBuilder AddGrpc(this Sentry.AspNetCore.ISentryBuilder builder) { } + } + public class SentryGrpcInterceptor : Grpc.Core.Interceptors.Interceptor + { + public SentryGrpcInterceptor(System.Func hubAccessor, Microsoft.Extensions.Options.IOptions options) { } + public override System.Threading.Tasks.Task ClientStreamingServerHandler(Grpc.Core.IAsyncStreamReader requestStream, Grpc.Core.ServerCallContext context, Grpc.Core.ClientStreamingServerMethod continuation) + where TRequest : class + where TResponse : class { } + public override System.Threading.Tasks.Task DuplexStreamingServerHandler(Grpc.Core.IAsyncStreamReader requestStream, Grpc.Core.IServerStreamWriter responseStream, Grpc.Core.ServerCallContext context, Grpc.Core.DuplexStreamingServerMethod continuation) + where TRequest : class + where TResponse : class { } + public override System.Threading.Tasks.Task ServerStreamingServerHandler(TRequest request, Grpc.Core.IServerStreamWriter responseStream, Grpc.Core.ServerCallContext context, Grpc.Core.ServerStreamingServerMethod continuation) + where TRequest : class + where TResponse : class { } + public override System.Threading.Tasks.Task UnaryServerHandler(TRequest request, Grpc.Core.ServerCallContext context, Grpc.Core.UnaryServerMethod continuation) + where TRequest : class + where TResponse : class { } + } +} \ No newline at end of file diff --git a/test/Sentry.AspNetCore.Grpc.Tests/Sentry.AspNetCore.Grpc.Tests.csproj b/test/Sentry.AspNetCore.Grpc.Tests/Sentry.AspNetCore.Grpc.Tests.csproj index 24227ac339..57dd94d734 100644 --- a/test/Sentry.AspNetCore.Grpc.Tests/Sentry.AspNetCore.Grpc.Tests.csproj +++ b/test/Sentry.AspNetCore.Grpc.Tests/Sentry.AspNetCore.Grpc.Tests.csproj @@ -1,13 +1,18 @@ - net9.0;net8.0 + $(CurrentTfms) - - - + + + + + + + + diff --git a/test/Sentry.AspNetCore.TestUtils/Sentry.AspNetCore.TestUtils.csproj b/test/Sentry.AspNetCore.TestUtils/Sentry.AspNetCore.TestUtils.csproj index c60ea32096..e93320ccdf 100644 --- a/test/Sentry.AspNetCore.TestUtils/Sentry.AspNetCore.TestUtils.csproj +++ b/test/Sentry.AspNetCore.TestUtils/Sentry.AspNetCore.TestUtils.csproj @@ -1,7 +1,7 @@ - net9.0;net8.0 + $(CurrentTfms) $(TargetFrameworks);net48 false @@ -54,7 +54,14 @@ - + + + + + + + + diff --git a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt new file mode 100644 index 0000000000..5f9eab70e9 --- /dev/null +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -0,0 +1,83 @@ +namespace Microsoft.AspNetCore.Builder +{ + public static class SentryTracingMiddlewareExtensions + { + public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseSentryTracing(this Microsoft.AspNetCore.Builder.IApplicationBuilder builder) { } + } +} +namespace Microsoft.AspNetCore.Hosting +{ + public static class SentryWebHostBuilderExtensions + { + public static void AddSentryTunneling(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, params string[] hostnames) { } + public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseSentry(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder) { } + public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseSentry(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder, System.Action? configureSentry) { } + public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseSentry(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder, System.Action? configureOptions) { } + public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseSentry(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder, System.Action? configureSentry) { } + public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseSentry(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder, System.Action? configureOptions) { } + public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseSentry(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder, string dsn) { } + public static void UseSentryTunneling(this Microsoft.AspNetCore.Builder.IApplicationBuilder builder, string path = "/tunnel") { } + } +} +namespace Microsoft.Extensions.DependencyInjection +{ + public static class ServiceCollectionExtensions + { + public static Sentry.AspNetCore.ISentryBuilder AddSentry(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { } + } +} +namespace Sentry.AspNetCore +{ + public interface ISentryBuilder + { + Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; } + } + public static class SamplingExtensions + { + public static Microsoft.AspNetCore.Http.HttpContext? TryGetHttpContext(this Sentry.TransactionSamplingContext samplingContext) { } + public static string? TryGetHttpMethod(this Sentry.TransactionSamplingContext samplingContext) { } + public static string? TryGetHttpPath(this Sentry.TransactionSamplingContext samplingContext) { } + public static string? TryGetHttpRoute(this Sentry.TransactionSamplingContext samplingContext) { } + } + public static class ScopeExtensions + { + public static void Populate(this Sentry.Scope scope, System.Diagnostics.Activity activity) { } + public static void Populate(this Sentry.Scope scope, Microsoft.AspNetCore.Http.HttpContext context, Sentry.AspNetCore.SentryAspNetCoreOptions options) { } + } + [Microsoft.Extensions.Logging.ProviderAlias("Sentry")] + public class SentryAspNetCoreLoggerProvider : Sentry.Extensions.Logging.SentryLoggerProvider + { + public SentryAspNetCoreLoggerProvider(Microsoft.Extensions.Options.IOptions options, Sentry.IHub hub) { } + } + public class SentryAspNetCoreOptions : Sentry.Extensions.Logging.SentryLoggingOptions + { + public SentryAspNetCoreOptions() { } + public bool AdjustStandardEnvironmentNameCasing { get; set; } + public bool AutoRegisterTracing { get; set; } + public bool CaptureBlockingCalls { get; set; } + public bool FlushOnCompletedRequest { get; set; } + public bool IncludeActivityData { get; set; } + public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; } + public Sentry.AspNetCore.TransactionNameProvider? TransactionNameProvider { get; set; } + } + public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.IConfigureOptions + { + public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration) { } + public void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } + } + public static class SentryBuilderExtensions + { + public static Sentry.AspNetCore.ISentryBuilder AddSentryOptions(this Sentry.AspNetCore.ISentryBuilder builder, System.Action? configureOptions) { } + } + public class SentryStartupFilter : Microsoft.AspNetCore.Hosting.IStartupFilter + { + public SentryStartupFilter() { } + public System.Action Configure(System.Action next) { } + } + public class SentryTunnelMiddleware : Microsoft.AspNetCore.Http.IMiddleware + { + public SentryTunnelMiddleware(string[] allowedHosts) { } + public System.Threading.Tasks.Task InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) { } + } + public delegate string? TransactionNameProvider(Microsoft.AspNetCore.Http.HttpContext context); +} \ No newline at end of file diff --git a/test/Sentry.AspNetCore.Tests/Sentry.AspNetCore.Tests.csproj b/test/Sentry.AspNetCore.Tests/Sentry.AspNetCore.Tests.csproj index 0761e91aa6..77c4d3735e 100644 --- a/test/Sentry.AspNetCore.Tests/Sentry.AspNetCore.Tests.csproj +++ b/test/Sentry.AspNetCore.Tests/Sentry.AspNetCore.Tests.csproj @@ -1,7 +1,7 @@  - net9.0;net8.0 + $(CurrentTfms) diff --git a/test/Sentry.AspNetCore.Tests/WebIntegrationTests.PreFlightIgnoresTransaction.DotNet.verified.txt b/test/Sentry.AspNetCore.Tests/WebIntegrationTests.PreFlightIgnoresTransaction.DotNet.verified.txt deleted file mode 100644 index 5f282702bb..0000000000 --- a/test/Sentry.AspNetCore.Tests/WebIntegrationTests.PreFlightIgnoresTransaction.DotNet.verified.txt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/Sentry.AspNetCore.Tests/WebIntegrationTests.PreFlightIgnoresTransaction.DotNet10_0.verified.txt b/test/Sentry.AspNetCore.Tests/WebIntegrationTests.PreFlightIgnoresTransaction.DotNet10_0.verified.txt new file mode 100644 index 0000000000..679694a306 --- /dev/null +++ b/test/Sentry.AspNetCore.Tests/WebIntegrationTests.PreFlightIgnoresTransaction.DotNet10_0.verified.txt @@ -0,0 +1,14 @@ +{ + result: { + Status: 204 No Content, + Headers: { + Access-Control-Allow-Headers: origin, + Access-Control-Allow-Methods: GET, + Access-Control-Allow-Origin: * + }, + Content: { + Headers: {} + } + }, + Payloads: [] +} \ No newline at end of file diff --git a/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet.DotNet8_0.verified.txt b/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet.DotNet8_0.verified.txt deleted file mode 100644 index 9abeb1309b..0000000000 --- a/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet.DotNet8_0.verified.txt +++ /dev/null @@ -1,87 +0,0 @@ -{ - result: Hello world, - Payloads: [ - { - Source: { - Name: GET v1.1/Target, - NameSource: Route, - Platform: csharp, - Operation: http.server, - Description: , - Status: Ok, - IsSampled: true, - SampleRate: 1.0, - Request: { - Method: GET, - QueryString: - }, - Contexts: { - trace: { - Operation: http.server, - Description: , - Status: Ok, - IsSampled: true - } - }, - User: {}, - Environment: production, - Breadcrumbs: [ - { - Message: Request starting HTTP/1.1 GET http://localhost/v1.1/Target - - -, - Data: { - eventId: 1 - }, - Category: Microsoft.AspNetCore.Hosting.Diagnostics - }, - { - Message: Executing endpoint 'Sentry.AspNetCore.Tests.WebIntegrationTests+VersionController.Method (Sentry.AspNetCore.Tests)', - Data: { - eventId: ExecutingEndpoint - }, - Category: Microsoft.AspNetCore.Routing.EndpointMiddleware - }, - { - Message: Route matched with {action = "Method", controller = "Version"}. Executing controller action with signature System.String Method() on controller Sentry.AspNetCore.Tests.WebIntegrationTests+VersionController (Sentry.AspNetCore.Tests)., - Data: { - eventId: ControllerActionExecuting - }, - Category: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker - }, - { - Message: Executing ObjectResult, writing value of type 'System.String'., - Data: { - eventId: ObjectResultExecuting - }, - Category: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor - }, - { - Message: Executed action Sentry.AspNetCore.Tests.WebIntegrationTests+VersionController.Method, - Data: { - eventId: ActionExecuted - }, - Category: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker - }, - { - Message: Executed endpoint 'Sentry.AspNetCore.Tests.WebIntegrationTests+VersionController.Method (Sentry.AspNetCore.Tests)', - Data: { - eventId: ExecutedEndpoint - }, - Category: Microsoft.AspNetCore.Routing.EndpointMiddleware - } - ], - Extra: { - http.request.method: GET, - http.response.status_code: 200 - }, - Tags: { - ActionId: Guid_1, - ActionName: Sentry.AspNetCore.Tests.WebIntegrationTests+VersionController.Method (Sentry.AspNetCore.Tests), - route.action: Method, - route.controller: Version, - route.version: 1.1 - }, - IsFinished: true - } - } - ] -} \ No newline at end of file diff --git a/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.verified.txt b/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet10_0.verified.txt similarity index 94% rename from test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.verified.txt rename to test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet10_0.verified.txt index b132b2f346..019cc1fb2d 100644 --- a/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.verified.txt +++ b/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet10_0.verified.txt @@ -3,6 +3,7 @@ Payloads: [ { Source: { + Origin: auto.http.aspnetcore, Name: GET /v1.1/Target, NameSource: Route, Platform: csharp, @@ -18,16 +19,19 @@ Contexts: { trace: { Operation: http.server, + Origin: auto.http.aspnetcore, Description: , Status: Ok, IsSampled: true } }, - User: {}, + User: { + Id: Guid_1 + }, Environment: production, Breadcrumbs: [ { - Message: Request starting HTTP/1.1 GET http://localhost/v1.1/Target - -, + Message: Request starting HTTP/1.1 GET http://localhost/v1.1/Target - - -, Data: { eventId: 1 }, @@ -69,12 +73,8 @@ Category: Microsoft.AspNetCore.Routing.EndpointMiddleware } ], - Extra: { - http.request.method: GET, - http.response.status_code: 200 - }, Tags: { - ActionId: Guid_1, + ActionId: Guid_2, ActionName: Sentry.AspNetCore.Tests.WebIntegrationTests+VersionController.Method (Sentry.AspNetCore.Tests), route.action: Method, route.controller: Version, diff --git a/test/Sentry.Azure.Functions.Worker.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Azure.Functions.Worker.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt new file mode 100644 index 0000000000..f6ee752df8 --- /dev/null +++ b/test/Sentry.Azure.Functions.Worker.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -0,0 +1,15 @@ +namespace Sentry.Azure.Functions.Worker +{ + public class SentryAzureFunctionsOptions : Sentry.Extensions.Logging.SentryLoggingOptions + { + public SentryAzureFunctionsOptions() { } + } + public static class SentryFunctionsWorkerApplicationBuilderExtensions + { + public static Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder UseSentry(this Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder builder, Microsoft.Extensions.Hosting.HostBuilderContext context) { } + public static Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder UseSentry(this Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder builder, System.Action? optionsConfiguration) { } + public static Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder UseSentry(this Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder builder, Microsoft.Extensions.Configuration.IConfiguration configuration, System.Action? optionsConfiguration) { } + public static Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder UseSentry(this Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder builder, Microsoft.Extensions.Hosting.HostBuilderContext context, System.Action? optionsConfiguration) { } + public static Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder UseSentry(this Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder builder, Microsoft.Extensions.Hosting.HostBuilderContext context, string dsn) { } + } +} \ No newline at end of file diff --git a/test/Sentry.Azure.Functions.Worker.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Azure.Functions.Worker.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt new file mode 100644 index 0000000000..f6ee752df8 --- /dev/null +++ b/test/Sentry.Azure.Functions.Worker.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -0,0 +1,15 @@ +namespace Sentry.Azure.Functions.Worker +{ + public class SentryAzureFunctionsOptions : Sentry.Extensions.Logging.SentryLoggingOptions + { + public SentryAzureFunctionsOptions() { } + } + public static class SentryFunctionsWorkerApplicationBuilderExtensions + { + public static Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder UseSentry(this Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder builder, Microsoft.Extensions.Hosting.HostBuilderContext context) { } + public static Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder UseSentry(this Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder builder, System.Action? optionsConfiguration) { } + public static Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder UseSentry(this Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder builder, Microsoft.Extensions.Configuration.IConfiguration configuration, System.Action? optionsConfiguration) { } + public static Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder UseSentry(this Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder builder, Microsoft.Extensions.Hosting.HostBuilderContext context, System.Action? optionsConfiguration) { } + public static Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder UseSentry(this Microsoft.Azure.Functions.Worker.IFunctionsWorkerApplicationBuilder builder, Microsoft.Extensions.Hosting.HostBuilderContext context, string dsn) { } + } +} \ No newline at end of file diff --git a/test/Sentry.Azure.Functions.Worker.Tests/Sentry.Azure.Functions.Worker.Tests.csproj b/test/Sentry.Azure.Functions.Worker.Tests/Sentry.Azure.Functions.Worker.Tests.csproj index eee27b2b21..7e2f3e1603 100644 --- a/test/Sentry.Azure.Functions.Worker.Tests/Sentry.Azure.Functions.Worker.Tests.csproj +++ b/test/Sentry.Azure.Functions.Worker.Tests/Sentry.Azure.Functions.Worker.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + $(CurrentTfms) $(TargetFrameworks);net48 diff --git a/test/Sentry.DiagnosticSource.IntegrationTests/LocalDbFixture.cs b/test/Sentry.DiagnosticSource.IntegrationTests/LocalDbFixture.cs index 0e1a01e200..8afd8df752 100644 --- a/test/Sentry.DiagnosticSource.IntegrationTests/LocalDbFixture.cs +++ b/test/Sentry.DiagnosticSource.IntegrationTests/LocalDbFixture.cs @@ -15,6 +15,8 @@ public sealed class LocalDbFixture : IDisposable "SqlListenerTests8"; #elif NET9_0 "SqlListenerTests9"; +#elif NET10_0 + "SqlListenerTests10"; #else #error Needs a version specific name to prevent the tests from tripping over one another when running in parallel #endif diff --git a/test/Sentry.DiagnosticSource.IntegrationTests/Sentry.DiagnosticSource.IntegrationTests.csproj b/test/Sentry.DiagnosticSource.IntegrationTests/Sentry.DiagnosticSource.IntegrationTests.csproj index 504be0b959..62d67cadbb 100644 --- a/test/Sentry.DiagnosticSource.IntegrationTests/Sentry.DiagnosticSource.IntegrationTests.csproj +++ b/test/Sentry.DiagnosticSource.IntegrationTests/Sentry.DiagnosticSource.IntegrationTests.csproj @@ -1,11 +1,18 @@  - net9.0;net8.0 + $(CurrentTfms) $(TargetFrameworks);net48 + + + + + + + @@ -29,8 +36,6 @@ - - diff --git a/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.LoggingAsync.DotNet10_0.verified.txt b/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.LoggingAsync.DotNet10_0.verified.txt new file mode 100644 index 0000000000..348b185b6f --- /dev/null +++ b/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.LoggingAsync.DotNet10_0.verified.txt @@ -0,0 +1,63 @@ +[ + { + Source: { + Name: my transaction, + Platform: csharp, + Operation: my operation, + Description: , + Status: Ok, + IsSampled: true, + SampleRate: 1.0, + Request: {}, + Contexts: { + trace: { + Operation: my operation, + Description: , + Status: Ok, + IsSampled: true + } + }, + User: { + Id: Guid_1 + }, + Spans: [ + { + IsFinished: true, + Operation: db.connection, + Description: SqlListenerTests.verify_LoggingAsync, + Status: Ok, + IsSampled: true, + Data: { + bytes_sent : 376, + db.connection_id: Guid_2, + db.name: SqlListenerTests.verify_LoggingAsync, + db.operation_id: Guid_3, + db.server: (LocalDb)\SqlListenerTests, + db.system: sql, + rows_sent: 0 + } + }, + { + IsFinished: true, + Operation: db.query, + Description: +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +INSERT INTO [TestEntities] ([Property]) +OUTPUT INSERTED.[Id] +VALUES (@p0); +, + Status: Ok, + IsSampled: true, + Data: { + db.connection_id: Guid_2, + db.name: SqlListenerTests.verify_LoggingAsync, + db.operation_id: Guid_4, + db.system: sql + } + } + ], + IsFinished: true + } + } +] \ No newline at end of file diff --git a/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.RecordsEfAsync.DotNet10_0.verified.txt b/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.RecordsEfAsync.DotNet10_0.verified.txt new file mode 100644 index 0000000000..05803effbe --- /dev/null +++ b/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.RecordsEfAsync.DotNet10_0.verified.txt @@ -0,0 +1,131 @@ +[ + { + Source: { + Exception: { + $type: Exception, + Type: Exception, + Message: my exception + }, + Platform: csharp, + SentryExceptions: [ + { + Type: System.Exception, + Value: my exception, + Mechanism: { + Type: generic, + Synthetic: false, + IsExceptionGroup: false, + Data: { + HResult: 0x80131500 + } + } + } + ], + Level: error, + TransactionName: my transaction, + Request: {}, + Contexts: { + trace: { + Operation: + } + }, + User: { + Id: Guid_1 + } + } + }, + { + Source: { + Name: my transaction, + Platform: csharp, + Operation: my operation, + Description: , + Status: Ok, + IsSampled: true, + SampleRate: 1.0, + Request: {}, + Contexts: { + trace: { + Operation: my operation, + Description: , + Status: Ok, + IsSampled: true + } + }, + User: { + Id: Guid_1 + }, + Breadcrumbs: [ + { + Message: my exception, + Category: Exception, + Level: critical + } + ], + Spans: [ + { + IsFinished: true, + Operation: db.connection, + Description: SqlListenerTests.verify_RecordsEfAsync, + Status: Ok, + IsSampled: true, + Data: { + bytes_received: 225, + bytes_sent : 570, + db.connection_id: Guid_2, + db.name: SqlListenerTests.verify_RecordsEfAsync, + db.operation_id: Guid_3, + db.server: (LocalDb)\SqlListenerTests, + db.system: sql, + rows_sent: 1 + } + }, + { + IsFinished: true, + Operation: db.query, + Description: +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +INSERT INTO [TestEntities] ([Property]) +OUTPUT INSERTED.[Id] +VALUES (@p0); +, + Status: Ok, + IsSampled: true, + Data: { + db.connection_id: Guid_2, + db.name: SqlListenerTests.verify_RecordsEfAsync, + db.operation_id: Guid_4, + db.system: sql + } + }, + { + IsFinished: true, + Operation: db.query.compile, + Description: 'DbSet()', + Status: Ok, + IsSampled: true, + Data: { + db.system: mssql + } + }, + { + IsFinished: true, + Operation: db.query, + Description: +SELECT [t].[Id], [t].[Property] +FROM [TestEntities] AS [t], + Status: Ok, + IsSampled: true, + Data: { + db.connection_id: Guid_2, + db.name: SqlListenerTests.verify_RecordsEfAsync, + db.operation_id: Guid_5, + db.system: sql + } + } + ], + IsFinished: true + } + } +] \ No newline at end of file diff --git a/test/Sentry.DiagnosticSource.Tests/Sentry.DiagnosticSource.Tests.csproj b/test/Sentry.DiagnosticSource.Tests/Sentry.DiagnosticSource.Tests.csproj index cf4e670998..93ad42e99b 100644 --- a/test/Sentry.DiagnosticSource.Tests/Sentry.DiagnosticSource.Tests.csproj +++ b/test/Sentry.DiagnosticSource.Tests/Sentry.DiagnosticSource.Tests.csproj @@ -1,11 +1,17 @@  - net9.0;net8.0 + $(CurrentTfms) $(TargetFrameworks);net48 + + + + + + @@ -16,8 +22,6 @@ - - diff --git a/test/Sentry.EntityFramework.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.EntityFramework.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt new file mode 100644 index 0000000000..53cafe92cc --- /dev/null +++ b/test/Sentry.EntityFramework.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -0,0 +1,39 @@ +namespace Sentry.EntityFramework.ErrorProcessors +{ + public class DbConcurrencyExceptionProcessor : Sentry.Extensibility.SentryEventExceptionProcessor + { + public DbConcurrencyExceptionProcessor() { } + protected override void ProcessException(System.Data.DBConcurrencyException exception, Sentry.SentryEvent sentryEvent) { } + } + public class DbEntityValidationExceptionProcessor : Sentry.Extensibility.SentryEventExceptionProcessor + { + public DbEntityValidationExceptionProcessor() { } + protected override void ProcessException(System.Data.Entity.Validation.DbEntityValidationException exception, Sentry.SentryEvent sentryEvent) { } + } +} +namespace Sentry.EntityFramework +{ + public interface IQueryLogger + { + void Log(string text, Sentry.BreadcrumbLevel level = -1); + } + public class SentryCommandInterceptor : System.Data.Entity.Infrastructure.Interception.IDbCommandInterceptor, System.Data.Entity.Infrastructure.Interception.IDbInterceptor + { + public SentryCommandInterceptor(Sentry.EntityFramework.IQueryLogger queryLogger) { } + public virtual void Log(System.Data.Common.DbCommand command, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext interceptionContext) { } + public void NonQueryExecuted(System.Data.Common.DbCommand command, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext interceptionContext) { } + public void NonQueryExecuting(System.Data.Common.DbCommand command, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext interceptionContext) { } + public void ReaderExecuted(System.Data.Common.DbCommand command, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext interceptionContext) { } + public void ReaderExecuting(System.Data.Common.DbCommand command, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext interceptionContext) { } + public void ScalarExecuted(System.Data.Common.DbCommand command, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext interceptionContext) { } + public void ScalarExecuting(System.Data.Common.DbCommand command, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext interceptionContext) { } + } +} +namespace Sentry +{ + public static class SentryOptionsExtensions + { + public static Sentry.SentryOptions AddEntityFramework(this Sentry.SentryOptions sentryOptions) { } + public static void DisableDbInterceptionIntegration(this Sentry.SentryOptions options) { } + } +} \ No newline at end of file diff --git a/test/Sentry.EntityFramework.Tests/Sentry.EntityFramework.Tests.csproj b/test/Sentry.EntityFramework.Tests/Sentry.EntityFramework.Tests.csproj index d81324346b..3a50957452 100644 --- a/test/Sentry.EntityFramework.Tests/Sentry.EntityFramework.Tests.csproj +++ b/test/Sentry.EntityFramework.Tests/Sentry.EntityFramework.Tests.csproj @@ -1,7 +1,7 @@  - net9.0;net8.0 + $(CurrentTfms) $(TargetFrameworks);net48 diff --git a/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt new file mode 100644 index 0000000000..9112ddfffa --- /dev/null +++ b/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -0,0 +1,66 @@ +namespace Microsoft.Extensions.Logging +{ + public static class LoggingBuilderExtensions + { + public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder) { } + public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action? optionsConfiguration) { } + public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder, string dsn) { } + } + public static class SentryLoggerFactoryExtensions + { + public static Microsoft.Extensions.Logging.ILoggerFactory AddSentry(this Microsoft.Extensions.Logging.ILoggerFactory factory, System.Action? optionsConfiguration = null) { } + } +} +namespace Sentry.Extensions.Logging +{ + public class DelegateLogEntryFilter : Sentry.Extensions.Logging.ILogEntryFilter + { + public DelegateLogEntryFilter(System.Func filter) { } + public bool Filter(string categoryName, Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, System.Exception? exception) { } + } + public interface ILogEntryFilter + { + bool Filter(string categoryName, Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, System.Exception? exception); + } + public class MelDiagnosticLogger : Sentry.Extensibility.IDiagnosticLogger + { + public MelDiagnosticLogger(Microsoft.Extensions.Logging.ILogger logger, Sentry.SentryLevel level) { } + public bool IsEnabled(Sentry.SentryLevel level) { } + public void Log(Sentry.SentryLevel logLevel, string message, System.Exception? exception = null, params object?[] args) { } + } + [Microsoft.Extensions.Logging.ProviderAlias("Sentry")] + public class SentryLoggerProvider : Microsoft.Extensions.Logging.ILoggerProvider, System.IDisposable + { + public SentryLoggerProvider(Microsoft.Extensions.Options.IOptions options, Sentry.IHub hub) { } + public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) { } + public void Dispose() { } + } + public class SentryLoggingOptions : Sentry.SentryOptions + { + public SentryLoggingOptions() { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public Sentry.Extensions.Logging.SentryLoggingOptions.SentryLoggingExperimentalOptions ExperimentalLogging { get; set; } + public bool InitializeSdk { get; set; } + public Microsoft.Extensions.Logging.LogLevel MinimumBreadcrumbLevel { get; set; } + public Microsoft.Extensions.Logging.LogLevel MinimumEventLevel { get; set; } + public void ConfigureScope(System.Action action) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public sealed class SentryLoggingExperimentalOptions + { + public Microsoft.Extensions.Logging.LogLevel MinimumLogLevel { get; set; } + } + } + public static class SentryLoggingOptionsExtensions + { + public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryLoggingOptions options, Sentry.Extensions.Logging.ILogEntryFilter filter) { } + public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryLoggingOptions options, System.Func filter) { } + } +} +namespace Sentry.Extensions.Logging.Extensions.DependencyInjection +{ + public static class ServiceCollectionExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddSentry(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) + where TOptions : Sentry.Extensions.Logging.SentryLoggingOptions, new () { } + } +} \ No newline at end of file diff --git a/test/Sentry.Extensions.Logging.Tests/Sentry.Extensions.Logging.Tests.csproj b/test/Sentry.Extensions.Logging.Tests/Sentry.Extensions.Logging.Tests.csproj index 27b9c73763..bced932f74 100644 --- a/test/Sentry.Extensions.Logging.Tests/Sentry.Extensions.Logging.Tests.csproj +++ b/test/Sentry.Extensions.Logging.Tests/Sentry.Extensions.Logging.Tests.csproj @@ -1,10 +1,10 @@  - net9.0;net8.0 - $(TargetFrameworks);net8.0-android34.0;net9.0-android35.0 - $(TargetFrameworks);net8.0-ios17.0;net9.0-ios18.0 - $(TargetFrameworks);net8.0-maccatalyst17.0;net9.0-maccatalyst18.0 + $(CurrentTfms) + $(TargetFrameworks);$(LatestAndroidTfm);$(PreviousAndroidTfm) + $(TargetFrameworks);$(LatestIosTfm);$(PreviousIosTfm) + $(TargetFrameworks);$(LatestMacCatalystTfm);$(PreviousMacCatalystTfm) $(TargetFrameworks);net48 diff --git a/test/Sentry.Google.Cloud.Functions.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Google.Cloud.Functions.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt new file mode 100644 index 0000000000..acc2519014 --- /dev/null +++ b/test/Sentry.Google.Cloud.Functions.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -0,0 +1,10 @@ +namespace Google.Cloud.Functions.Framework +{ + public class SentryStartup : Google.Cloud.Functions.Hosting.FunctionsStartup + { + public SentryStartup() { } + public override void Configure(Microsoft.AspNetCore.Hosting.WebHostBuilderContext context, Microsoft.AspNetCore.Builder.IApplicationBuilder app) { } + public override void ConfigureLogging(Microsoft.AspNetCore.Hosting.WebHostBuilderContext context, Microsoft.Extensions.Logging.ILoggingBuilder logging) { } + public override void ConfigureServices(Microsoft.AspNetCore.Hosting.WebHostBuilderContext context, Microsoft.Extensions.DependencyInjection.IServiceCollection services) { } + } +} \ No newline at end of file diff --git a/test/Sentry.Google.Cloud.Functions.Tests/Sentry.Google.Cloud.Functions.Tests.csproj b/test/Sentry.Google.Cloud.Functions.Tests/Sentry.Google.Cloud.Functions.Tests.csproj index 6eb35fea95..fe3c87694b 100644 --- a/test/Sentry.Google.Cloud.Functions.Tests/Sentry.Google.Cloud.Functions.Tests.csproj +++ b/test/Sentry.Google.Cloud.Functions.Tests/Sentry.Google.Cloud.Functions.Tests.csproj @@ -1,7 +1,7 @@ - net9.0;net8.0 + $(CurrentTfms) diff --git a/test/Sentry.Hangfire.Tests/Sentry.Hangfire.Tests.csproj b/test/Sentry.Hangfire.Tests/Sentry.Hangfire.Tests.csproj index 15a9d7f0c8..d8848e1668 100644 --- a/test/Sentry.Hangfire.Tests/Sentry.Hangfire.Tests.csproj +++ b/test/Sentry.Hangfire.Tests/Sentry.Hangfire.Tests.csproj @@ -3,7 +3,7 @@ - net9.0;net8.0 + $(CurrentTfms) enable diff --git a/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt similarity index 100% rename from test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt rename to test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt diff --git a/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt deleted file mode 100644 index 117659776a..0000000000 --- a/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ /dev/null @@ -1,14 +0,0 @@ -[assembly: System.CLSCompliant(true)] -namespace Sentry.Log4Net -{ - public class SentryAppender : log4net.Appender.AppenderSkeleton - { - public SentryAppender() { } - public string? Dsn { get; set; } - public string? Environment { get; set; } - public log4net.Core.Level? MinimumEventLevel { get; set; } - public bool SendIdentity { get; set; } - protected override void Append(log4net.Core.LoggingEvent loggingEvent) { } - protected override void OnClose() { } - } -} \ No newline at end of file diff --git a/test/Sentry.Log4Net.Tests/Sentry.Log4Net.Tests.csproj b/test/Sentry.Log4Net.Tests/Sentry.Log4Net.Tests.csproj index 9939555343..afce541b0e 100644 --- a/test/Sentry.Log4Net.Tests/Sentry.Log4Net.Tests.csproj +++ b/test/Sentry.Log4Net.Tests/Sentry.Log4Net.Tests.csproj @@ -1,8 +1,7 @@  - - net8.0 + $(LatestTfm) $(TargetFrameworks);net48 diff --git a/test/Sentry.Maui.Device.TestApp/Sentry.Maui.Device.TestApp.csproj b/test/Sentry.Maui.Device.TestApp/Sentry.Maui.Device.TestApp.csproj index 3ea49a432c..d4f16c1dd6 100644 --- a/test/Sentry.Maui.Device.TestApp/Sentry.Maui.Device.TestApp.csproj +++ b/test/Sentry.Maui.Device.TestApp/Sentry.Maui.Device.TestApp.csproj @@ -33,7 +33,7 @@ 13.0 21.0 + 'netX.0-ios' resolves the latest version of the iOS SDK otherwise. --> 18.0 true diff --git a/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt similarity index 74% rename from test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt rename to test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt index 05a2136d62..244d13725f 100644 --- a/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -1,4 +1,11 @@ -[assembly: Microsoft.Maui.Controls.XmlnsDefinition("http://schemas.sentry.io/maui", "Sentry.Maui")] +[assembly: Microsoft.Maui.Controls.XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "Microsoft.Maui", AssemblyName="Microsoft.Maui")] +[assembly: Microsoft.Maui.Controls.XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "Microsoft.Maui.Controls", AssemblyName="Microsoft.Maui.Controls")] +[assembly: Microsoft.Maui.Controls.XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "Microsoft.Maui.Controls.Shapes", AssemblyName="Microsoft.Maui.Controls")] +[assembly: Microsoft.Maui.Controls.XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "Microsoft.Maui.Controls.Xaml", AssemblyName="Microsoft.Maui.Controls.Xaml")] +[assembly: Microsoft.Maui.Controls.XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "Microsoft.Maui.Graphics", AssemblyName="Microsoft.Maui.Graphics")] +[assembly: Microsoft.Maui.Controls.XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "http://schemas.microsoft.com/dotnet/2021/maui")] +[assembly: Microsoft.Maui.Controls.XmlnsDefinition("http://schemas.sentry.io/maui", "Sentry.Maui")] +[assembly: Microsoft.Maui.Controls.XmlnsPrefix("http://schemas.microsoft.com/dotnet/maui/global", "global")] [assembly: Microsoft.Maui.Controls.XmlnsPrefix("http://schemas.sentry.io/maui", "sentry")] namespace Microsoft.Maui.Hosting { @@ -14,8 +21,8 @@ namespace Sentry.Maui public sealed class BreadcrumbEvent { public BreadcrumbEvent(object? sender, string eventName) { } - public BreadcrumbEvent(object? sender, string eventName, System.Collections.Generic.IEnumerable> extraData) { } - public BreadcrumbEvent(object? sender, string eventName, [System.Runtime.CompilerServices.TupleElementNames(new string[] { + public BreadcrumbEvent(object? sender, string eventName, [System.Runtime.CompilerServices.ParamCollection] System.Collections.Generic.IEnumerable> extraData) { } + public BreadcrumbEvent(object? sender, string eventName, [System.Runtime.CompilerServices.ParamCollection] [System.Runtime.CompilerServices.TupleElementNames(new string[] { "key", "value"})] System.Collections.Generic.IEnumerable> extraData) { } [System.Obsolete("Use one of the other simpler constructors instead.")] diff --git a/test/Sentry.Maui.Tests/MauiEventsBinderTests.Page.cs b/test/Sentry.Maui.Tests/MauiEventsBinderTests.Page.cs index 222aced800..d361e5d340 100644 --- a/test/Sentry.Maui.Tests/MauiEventsBinderTests.Page.cs +++ b/test/Sentry.Maui.Tests/MauiEventsBinderTests.Page.cs @@ -66,10 +66,17 @@ public void Page_NavigatedTo_AddsBreadcrumb() { StyleId = "otherPage" }; +#if NET9_0 var navigatedToEventArgs = (NavigatedToEventArgs) typeof(NavigatedToEventArgs) - .GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, new[] { typeof(Page) })! - .Invoke(new object[] { otherPage }); + .GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, [typeof(Page)])! + .Invoke([otherPage]); +#elif NET10_0 + var navigatedToEventArgs = (NavigatedToEventArgs) + typeof(NavigatedToEventArgs) + .GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, [typeof(Page), typeof(NavigationType)])! + .Invoke([otherPage, NavigationType.Replace]); +#endif // Act page.RaiseEvent(nameof(Page.NavigatedTo), navigatedToEventArgs); @@ -81,8 +88,11 @@ public void Page_NavigatedTo_AddsBreadcrumb() Assert.Equal(MauiEventsBinder.NavigationType, crumb.Type); Assert.Equal(MauiEventsBinder.NavigationCategory, crumb.Category); crumb.Data.Should().Contain($"{nameof(Page)}.Name", "page"); +#if !NET10_0 + // TODO: Work out why these are missing in .NET 10 crumb.Data.Should().Contain("PreviousPage", nameof(Page)); crumb.Data.Should().Contain("PreviousPage.Name", "otherPage"); +#endif } [Fact] @@ -99,10 +109,17 @@ public void Page_UnbindNavigatedTo_AddsBreadcrumb() { StyleId = "otherPage" }; +#if NET9_0 + var navigatedToEventArgs = (NavigatedToEventArgs) + typeof(NavigatedToEventArgs) + .GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, [typeof(Page)])! + .Invoke([otherPage]); +#elif NET10_0 var navigatedToEventArgs = (NavigatedToEventArgs) typeof(NavigatedToEventArgs) - .GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, new[] { typeof(Page) })! - .Invoke(new object[] { otherPage }); + .GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, [typeof(Page), typeof(NavigationType)])! + .Invoke([otherPage, NavigationType.Replace]); +#endif page.RaiseEvent(nameof(Page.NavigatedTo), navigatedToEventArgs); Assert.Single(_fixture.Scope.Breadcrumbs); // Sanity check @@ -117,7 +134,7 @@ public void Page_UnbindNavigatedTo_AddsBreadcrumb() } [Fact] - public void Page_LayoutChanged_AddsBreadcrumb() + public void Page_SizeChanged_AddsBreadcrumb() { // Arrange var page = new Page @@ -127,11 +144,11 @@ public void Page_LayoutChanged_AddsBreadcrumb() _fixture.Binder.HandlePageEvents(page); // Act - page.RaiseEvent(nameof(Page.LayoutChanged), EventArgs.Empty); + page.RaiseEvent(nameof(Page.SizeChanged), EventArgs.Empty); // Assert var crumb = Assert.Single(_fixture.Scope.Breadcrumbs); - Assert.Equal($"{nameof(Page)}.{nameof(Page.LayoutChanged)}", crumb.Message); + Assert.Equal($"{nameof(Page)}.{nameof(Page.SizeChanged)}", crumb.Message); Assert.Equal(BreadcrumbLevel.Info, crumb.Level); Assert.Equal(MauiEventsBinder.SystemType, crumb.Type); Assert.Equal(MauiEventsBinder.RenderingCategory, crumb.Category); diff --git a/test/Sentry.Maui.Tests/Sentry.Maui.Tests.csproj b/test/Sentry.Maui.Tests/Sentry.Maui.Tests.csproj index ba3c3fbd09..45ba512c3f 100644 --- a/test/Sentry.Maui.Tests/Sentry.Maui.Tests.csproj +++ b/test/Sentry.Maui.Tests/Sentry.Maui.Tests.csproj @@ -1,10 +1,10 @@ - net9.0;net8.0 - $(TargetFrameworks);net8.0-android34.0;net9.0-android35.0 - $(TargetFrameworks);net8.0-ios17.0;net9.0-ios18.0 - $(TargetFrameworks);net8.0-maccatalyst17.0;net9.0-maccatalyst18.0 + $(LatestTfm);$(PreviousTfm) + $(TargetFrameworks);$(LatestAndroidTfm);$(PreviousAndroidTfm) + $(TargetFrameworks);$(LatestIosTfm);$(PreviousIosTfm) + $(TargetFrameworks);$(LatestMacCatalystTfm);$(PreviousMacCatalystTfm) true diff --git a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.Android.cs b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.Android.cs index 1b98060351..0db650f8a6 100644 --- a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.Android.cs +++ b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.Android.cs @@ -1,7 +1,7 @@ +using Android.Runtime; using Microsoft.Maui.LifecycleEvents; using Sentry.Maui.Internal; using Sentry.Maui.Tests.Mocks; - namespace Sentry.Maui.Tests; public partial class SentryMauiAppBuilderExtensionsTests @@ -29,17 +29,22 @@ public void UseSentry_BindsToApplicationStartupEvent_Android() // Assert binder.Received(1).HandleApplicationEvents(application); } - private class MockAndroidApplication : global::Android.App.Application, IPlatformApplication { + // Required JNI activation constructor for Android runtime types + public MockAndroidApplication(IntPtr handle, JniHandleOwnership transfer) + : base(handle, transfer) + { + } + public MockAndroidApplication(IApplication application, IServiceProvider services) { Application = application; Services = services; } - public IApplication Application { get; } - public IServiceProvider Services { get; } + } + } diff --git a/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt new file mode 100644 index 0000000000..00a36bc53b --- /dev/null +++ b/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -0,0 +1,81 @@ +[assembly: System.CLSCompliant(true)] +namespace NLog +{ + public static class ConfigurationExtensions + { + public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, System.Action? optionsConfig = null) { } + public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, string? dsn, System.Action? optionsConfig = null) { } + public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, string? dsn, string targetName, System.Action? optionsConfig = null) { } + public static void AddTag(this Sentry.NLog.SentryNLogOptions options, string name, NLog.Layouts.Layout layout) { } + } +} +namespace Sentry.NLog +{ + [NLog.Config.NLogConfigurationItem] + public class SentryNLogOptions : Sentry.SentryOptions + { + public SentryNLogOptions() { } + [NLog.Config.NLogConfigurationIgnoreProperty] + public NLog.Layouts.Layout? BreadcrumbCategoryLayout { get; set; } + [NLog.Config.NLogConfigurationIgnoreProperty] + public NLog.Layouts.Layout? BreadcrumbLayout { get; set; } + [NLog.Config.NLogConfigurationIgnoreProperty] + public NLog.Layouts.Layout? DsnLayout { get; set; } + [NLog.Config.NLogConfigurationIgnoreProperty] + public NLog.Layouts.Layout? EnvironmentLayout { get; set; } + public bool IgnoreEventsWithNoException { get; set; } + public bool IncludeEventDataOnBreadcrumbs { get; set; } + public bool IncludeEventPropertiesAsTags { get; set; } + public bool InitializeSdk { get; set; } + [NLog.Config.NLogConfigurationIgnoreProperty] + public NLog.Layouts.Layout? Layout { get; set; } + public NLog.LogLevel? MinimumBreadcrumbLevel { get; set; } + public NLog.LogLevel? MinimumEventLevel { get; set; } + [NLog.Config.NLogConfigurationIgnoreProperty] + public NLog.Layouts.Layout? ReleaseLayout { get; set; } + public int ShutdownTimeoutSeconds { get; set; } + [NLog.Config.NLogConfigurationIgnoreProperty] + public System.Collections.Generic.IList Tags { get; } + [NLog.Config.NLogConfigurationIgnoreProperty] + public Sentry.NLog.SentryNLogUser? User { get; set; } + } + [NLog.Config.NLogConfigurationItem] + public class SentryNLogUser + { + public SentryNLogUser() { } + public NLog.Layouts.Layout? Email { get; set; } + public NLog.Layouts.Layout? Id { get; set; } + public NLog.Layouts.Layout? IpAddress { get; set; } + [NLog.Config.ArrayParameter(typeof(NLog.Targets.TargetPropertyWithContext?), "other")] + public System.Collections.Generic.IList? Other { get; } + public NLog.Layouts.Layout? Segment { get; set; } + public NLog.Layouts.Layout? Username { get; set; } + } + [NLog.Targets.Target("Sentry")] + public sealed class SentryTarget : NLog.Targets.TargetWithContext + { + public SentryTarget() { } + public SentryTarget(Sentry.NLog.SentryNLogOptions options) { } + public NLog.Layouts.Layout? BreadcrumbCategory { get; set; } + public NLog.Layouts.Layout? BreadcrumbLayout { get; set; } + public NLog.Layouts.Layout? Dsn { get; set; } + public NLog.Layouts.Layout? Environment { get; set; } + public int FlushTimeoutSeconds { get; set; } + public bool IgnoreEventsWithNoException { get; set; } + public bool IncludeEventDataOnBreadcrumbs { get; set; } + public bool IncludeEventPropertiesAsTags { get; set; } + public bool InitializeSdk { get; set; } + public string MinimumBreadcrumbLevel { get; set; } + public string MinimumEventLevel { get; set; } + public Sentry.NLog.SentryNLogOptions Options { get; } + public NLog.Layouts.Layout? Release { get; set; } + public int ShutdownTimeoutSeconds { get; set; } + [NLog.Config.ArrayParameter(typeof(NLog.Targets.TargetPropertyWithContext), "tag")] + public System.Collections.Generic.IList Tags { get; } + public Sentry.NLog.SentryNLogUser? User { get; set; } + protected override void CloseTarget() { } + protected override void FlushAsync(NLog.Common.AsyncContinuation asyncContinuation) { } + protected override void InitializeTarget() { } + protected override void Write(NLog.LogEventInfo logEvent) { } + } +} \ No newline at end of file diff --git a/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet10_0.verified.txt b/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet10_0.verified.txt new file mode 100644 index 0000000000..c1b263a0fb --- /dev/null +++ b/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet10_0.verified.txt @@ -0,0 +1,109 @@ +[ + { + Header: { + event_id: Guid_1, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: test-release, + trace_id: Guid_2 + } + }, + Items: [ + { + Header: { + type: event + }, + Payload: { + Source: { + Exception: { + $type: Exception, + Type: Exception, + Message: Exception message + }, + Message: { + Message: message = {arg}, + Formatted: message = "arg value" + }, + Logger: Sentry.NLog.Tests.IntegrationTests, + Platform: csharp, + SentryExceptions: [ + { + Type: System.Exception, + Value: Exception message, + Stacktrace: { + Frames: [ + { + FileName: IntegrationTests.verify.cs, + Function: Task IntegrationTests.Simple(), + Module: null, + LineNumber: 52, + ColumnNumber: 17, + AbsolutePath: {ProjectDirectory}IntegrationTests.verify.cs, + ContextLine: null, + InApp: false, + Package: Sentry.NLog.Tests, Version=SCRUBBED, Culture=SCRUBBED, PublicKeyToken=SCRUBBED, + Platform: null, + ImageAddress: null, + SymbolAddress: null, + InstructionAddress: 2, + AddressMode: rel:0, + FunctionId: 1 + } + ] + }, + Mechanism: { + Type: generic, + Handled: true, + Synthetic: false, + IsExceptionGroup: false, + Data: { + HResult: 0x80131500 + } + } + } + ], + DebugImages: [ + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: .../Sentry.NLog.Tests.pdb, + CodeId: ______________, + CodeFile: .../Sentry.NLog.Tests.dll + } + ], + Level: error, + Request: {}, + Contexts: { + trace: { + Operation: + } + }, + User: { + Id: myId, + Username: , + Email: , + IpAddress: , + Other: { + mood: joyous + } + }, + Environment: production, + Extra: { + arg: arg value + }, + Tags: { + logger: Sentry.NLog.Tests.IntegrationTests + } + } + } + } + ] + } +] \ No newline at end of file diff --git a/test/Sentry.NLog.Tests/Sentry.NLog.Tests.csproj b/test/Sentry.NLog.Tests/Sentry.NLog.Tests.csproj index bc003e6461..d7f32032ed 100644 --- a/test/Sentry.NLog.Tests/Sentry.NLog.Tests.csproj +++ b/test/Sentry.NLog.Tests/Sentry.NLog.Tests.csproj @@ -1,7 +1,7 @@  - net9.0;net8.0 + $(CurrentTfms) $(TargetFrameworks);net48 diff --git a/test/Sentry.OpenTelemetry.Tests/Sentry.OpenTelemetry.Tests.csproj b/test/Sentry.OpenTelemetry.Tests/Sentry.OpenTelemetry.Tests.csproj index c5d402cc2e..5371e42135 100644 --- a/test/Sentry.OpenTelemetry.Tests/Sentry.OpenTelemetry.Tests.csproj +++ b/test/Sentry.OpenTelemetry.Tests/Sentry.OpenTelemetry.Tests.csproj @@ -1,7 +1,7 @@  - net9.0;net8.0 + $(CurrentTfms) $(TargetFrameworks);net48 @@ -11,13 +11,13 @@ - - + + - - + + diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index c4e5ae71c3..1832e88085 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -233,6 +233,8 @@ private static long MethodToBeLoaded(int n) [InlineData(false)] public void ProfilerIntegration_FullRoundtrip_Works(bool offlineCaching) { + Skip.If(TestEnvironment.IsGitHubActions, "Flaky in CI"); + var tcs = new TaskCompletionSource(); async Task VerifyAsync(HttpRequestMessage message) { diff --git a/test/Sentry.Profiling.Tests/Sentry.Profiling.Tests.csproj b/test/Sentry.Profiling.Tests/Sentry.Profiling.Tests.csproj index 932f6231e1..43d28fbab3 100644 --- a/test/Sentry.Profiling.Tests/Sentry.Profiling.Tests.csproj +++ b/test/Sentry.Profiling.Tests/Sentry.Profiling.Tests.csproj @@ -1,7 +1,7 @@  - net9.0;net8.0 + $(CurrentTfms) enable diff --git a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt new file mode 100644 index 0000000000..f204ed0701 --- /dev/null +++ b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -0,0 +1,52 @@ +[assembly: System.CLSCompliant(true)] +namespace Sentry.Serilog +{ + public static class SentryOptionExtensions + { + public static T ApplySerilogScopeToEvents(this T options) + where T : Sentry.SentryOptions { } + } + public class SentrySerilogOptions : Sentry.SentryOptions + { + public SentrySerilogOptions() { } + public System.IFormatProvider? FormatProvider { get; set; } + public bool InitializeSdk { get; set; } + public Serilog.Events.LogEventLevel MinimumBreadcrumbLevel { get; set; } + public Serilog.Events.LogEventLevel MinimumEventLevel { get; set; } + public Serilog.Formatting.ITextFormatter? TextFormatter { get; set; } + } +} +namespace Serilog +{ + public static class SentrySinkExtensions + { + public static Serilog.LoggerConfiguration Sentry(this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, System.Action configureOptions) { } + public static Serilog.LoggerConfiguration Sentry(this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, Serilog.Events.LogEventLevel? minimumEventLevel = default, Serilog.Events.LogEventLevel? minimumBreadcrumbLevel = default, System.IFormatProvider? formatProvider = null, Serilog.Formatting.ITextFormatter? textFormatter = null) { } + public static Serilog.LoggerConfiguration Sentry( + this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, + string dsn, + Serilog.Events.LogEventLevel? minimumBreadcrumbLevel = default, + Serilog.Events.LogEventLevel? minimumEventLevel = default, + System.IFormatProvider? formatProvider = null, + Serilog.Formatting.ITextFormatter? textFormatter = null, + bool? sendDefaultPii = default, + bool? isEnvironmentUser = default, + string? serverName = null, + bool? attachStackTrace = default, + int? maxBreadcrumbs = default, + float? sampleRate = default, + string? release = null, + string? environment = null, + int? maxQueueItems = default, + System.TimeSpan? shutdownTimeout = default, + System.Net.DecompressionMethods? decompressionMethods = default, + System.IO.Compression.CompressionLevel? requestBodyCompressionLevel = default, + bool? requestBodyCompressionBuffered = default, + bool? debug = default, + Sentry.SentryLevel? diagnosticLevel = default, + Sentry.ReportAssembliesMode? reportAssembliesMode = default, + Sentry.DeduplicateMode? deduplicateMode = default, + System.Collections.Generic.Dictionary? defaultTags = null, + bool? experimentalEnableLogs = default) { } + } +} \ No newline at end of file diff --git a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index 1455bbc51b..f204ed0701 100644 --- a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -46,6 +46,7 @@ namespace Serilog Sentry.SentryLevel? diagnosticLevel = default, Sentry.ReportAssembliesMode? reportAssembliesMode = default, Sentry.DeduplicateMode? deduplicateMode = default, - System.Collections.Generic.Dictionary? defaultTags = null) { } + System.Collections.Generic.Dictionary? defaultTags = null, + bool? experimentalEnableLogs = default) { } } } \ No newline at end of file diff --git a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index 1455bbc51b..f204ed0701 100644 --- a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -46,6 +46,7 @@ namespace Serilog Sentry.SentryLevel? diagnosticLevel = default, Sentry.ReportAssembliesMode? reportAssembliesMode = default, Sentry.DeduplicateMode? deduplicateMode = default, - System.Collections.Generic.Dictionary? defaultTags = null) { } + System.Collections.Generic.Dictionary? defaultTags = null, + bool? experimentalEnableLogs = default) { } } } \ No newline at end of file diff --git a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index 1455bbc51b..f204ed0701 100644 --- a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -46,6 +46,7 @@ namespace Serilog Sentry.SentryLevel? diagnosticLevel = default, Sentry.ReportAssembliesMode? reportAssembliesMode = default, Sentry.DeduplicateMode? deduplicateMode = default, - System.Collections.Generic.Dictionary? defaultTags = null) { } + System.Collections.Generic.Dictionary? defaultTags = null, + bool? experimentalEnableLogs = default) { } } } \ No newline at end of file diff --git a/test/Sentry.Serilog.Tests/AspNetCoreIntegrationTests.cs b/test/Sentry.Serilog.Tests/AspNetCoreIntegrationTests.cs index 8088548272..760b5b84ff 100644 --- a/test/Sentry.Serilog.Tests/AspNetCoreIntegrationTests.cs +++ b/test/Sentry.Serilog.Tests/AspNetCoreIntegrationTests.cs @@ -1,4 +1,6 @@ #if NET6_0_OR_GREATER +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Sentry.AspNetCore.TestUtils; namespace Sentry.Serilog.Tests; @@ -22,5 +24,52 @@ public async Task UnhandledException_MarkedAsUnhandled() Assert.Contains(Events, e => e.Logger == "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware"); Assert.Collection(Events, @event => Assert.Collection(@event.SentryExceptions, x => Assert.False(x.Mechanism?.Handled))); } + + [Fact] + public async Task StructuredLogging_Disabled() + { + Assert.False(ExperimentalEnableLogs); + + var handler = new RequestHandler + { + Path = "/log", + Handler = context => + { + context.RequestServices.GetRequiredService>().LogInformation("Hello, World!"); + return Task.CompletedTask; + } + }; + + Handlers = new[] { handler }; + Build(); + await HttpClient.GetAsync(handler.Path); + await ServiceProvider.GetRequiredService().FlushAsync(); + + Assert.Empty(Logs); + } + + [Fact] + public async Task StructuredLogging_Enabled() + { + ExperimentalEnableLogs = true; + + var handler = new RequestHandler + { + Path = "/log", + Handler = context => + { + context.RequestServices.GetRequiredService>().LogInformation("Hello, World!"); + return Task.CompletedTask; + } + }; + + Handlers = new[] { handler }; + Build(); + await HttpClient.GetAsync(handler.Path); + await ServiceProvider.GetRequiredService().FlushAsync(); + + Assert.NotEmpty(Logs); + Assert.Contains(Logs, log => log.Level == SentryLogLevel.Info && log.Message == "Hello, World!"); + } } #endif diff --git a/test/Sentry.Serilog.Tests/IntegrationTests.Simple.DotNet10_0.verified.txt b/test/Sentry.Serilog.Tests/IntegrationTests.Simple.DotNet10_0.verified.txt new file mode 100644 index 0000000000..e426868eab --- /dev/null +++ b/test/Sentry.Serilog.Tests/IntegrationTests.Simple.DotNet10_0.verified.txt @@ -0,0 +1,281 @@ +[ + { + Header: { + event_id: Guid_1, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: test-release, + trace_id: Guid_2 + } + }, + Items: [ + { + Header: { + type: event + }, + Payload: { + Source: { + Message: { + Message: Debug message stored as breadcrumb., + Formatted: [42] Debug message stored as breadcrumb. + }, + Platform: csharp, + ServerName: TheMachineName, + Level: debug, + Request: {}, + Contexts: { + trace: { + Operation: + } + }, + User: { + Id: Guid_3, + Username: TheUserName, + IpAddress: {{auto}} + }, + Environment: production, + Extra: { + inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 }, + MyTaskId: 42 + } + } + } + } + ] + }, + { + Header: { + event_id: Guid_4, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: test-release, + trace_id: Guid_2 + } + }, + Items: [ + { + Header: { + type: event + }, + Payload: { + Source: { + Message: { + Message: Message with a different MyTaskId, + Formatted: [65] Message with a different MyTaskId + }, + Platform: csharp, + ServerName: TheMachineName, + Level: debug, + Request: {}, + Contexts: { + trace: { + Operation: + } + }, + User: { + Id: Guid_3, + Username: TheUserName, + IpAddress: {{auto}} + }, + Environment: production, + Breadcrumbs: [ + { + Message: [42] Debug message stored as breadcrumb., + Level: debug + } + ], + Extra: { + inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 }, + MyTaskId: 65 + } + } + } + } + ] + }, + { + Header: { + event_id: Guid_5, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: test-release, + trace_id: Guid_2 + } + }, + Items: [ + { + Header: { + type: event + }, + Payload: { + Source: { + Message: { + Message: Some event that includes the previous breadcrumbs, + Formatted: [42] Some event that includes the previous breadcrumbs + }, + Platform: csharp, + ServerName: TheMachineName, + Level: error, + Request: {}, + Contexts: { + trace: { + Operation: + } + }, + User: { + Id: Guid_3, + Username: TheUserName, + IpAddress: {{auto}} + }, + Environment: production, + Breadcrumbs: [ + { + Message: [42] Debug message stored as breadcrumb., + Level: debug + }, + { + Message: [65] Message with a different MyTaskId, + Level: debug + } + ], + Extra: { + inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 }, + MyTaskId: 42 + } + } + } + } + ] + }, + { + Header: { + event_id: Guid_6, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: test-release, + trace_id: Guid_2 + } + }, + Items: [ + { + Header: { + type: event + }, + Payload: { + Source: { + Exception: { + $type: Exception, + Type: Exception, + Message: Exception message, + Data: { + details: Do work always throws. + } + }, + Message: { + Message: Error: with exception, + Formatted: [42] Error: with exception + }, + Platform: csharp, + ServerName: TheMachineName, + SentryExceptions: [ + { + Type: System.Exception, + Value: Exception message, + Stacktrace: { + Frames: [ + { + FileName: IntegrationTests.verify.cs, + Function: Task IntegrationTests.Simple(), + Module: null, + LineNumber: 47, + ColumnNumber: 17, + AbsolutePath: {ProjectDirectory}IntegrationTests.verify.cs, + ContextLine: null, + InApp: false, + Package: Sentry.Serilog.Tests, Version=SCRUBBED, Culture=SCRUBBED, PublicKeyToken=SCRUBBED, + Platform: null, + ImageAddress: null, + SymbolAddress: null, + InstructionAddress: 2, + AddressMode: rel:0, + FunctionId: 1 + } + ] + }, + Mechanism: { + Type: generic, + Handled: true, + Synthetic: false, + IsExceptionGroup: false, + Data: { + details: Do work always throws., + HResult: 0x80131500 + } + } + } + ], + DebugImages: [ + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: .../Sentry.Serilog.Tests.pdb, + CodeId: ______________, + CodeFile: .../Sentry.Serilog.Tests.dll + } + ], + Level: fatal, + Request: {}, + Contexts: { + trace: { + Operation: + } + }, + User: { + Id: Guid_3, + Username: TheUserName, + IpAddress: {{auto}} + }, + Environment: production, + Breadcrumbs: [ + { + Message: [42] Debug message stored as breadcrumb., + Level: debug + }, + { + Message: [65] Message with a different MyTaskId, + Level: debug + }, + { + Message: [42] Some event that includes the previous breadcrumbs, + Level: error + } + ], + Extra: { + inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 }, + MyTaskId: 42 + } + } + } + } + ] + } +] \ No newline at end of file diff --git a/test/Sentry.Serilog.Tests/IntegrationTests.StructuredLogging.verified.txt b/test/Sentry.Serilog.Tests/IntegrationTests.StructuredLogging.verified.txt new file mode 100644 index 0000000000..2eb81f0805 --- /dev/null +++ b/test/Sentry.Serilog.Tests/IntegrationTests.StructuredLogging.verified.txt @@ -0,0 +1,70 @@ +{ + envelopes: [ + { + Header: { + sdk: { + name: sentry.dotnet + } + }, + Items: [ + { + Header: { + content_type: application/vnd.sentry.items.log+json, + item_count: 4, + type: log + }, + Payload: { + Source: { + Length: 4 + } + } + } + ] + } + ], + logs: [ + [ + { + Level: Debug, + Message: Debug message with a Scalar property: 42, + Template: Debug message with a Scalar property: {Scalar}, + Parameters: [ + { + Scalar: 42 + } + ] + }, + { + Level: Info, + Message: Information message with a Sequence property: [41, 42, 43], + Template: Information message with a Sequence property: {Sequence}, + Parameters: [ + { + Sequence: [41, 42, 43] + } + ] + }, + { + Level: Warning, + Message: Warning message with a Dictionary property: [("key": "value")], + Template: Warning message with a Dictionary property: {Dictionary}, + Parameters: [ + { + Dictionary: [("key": "value")] + } + ] + }, + { + Level: Error, + Message: Error message with a Structure property: [42, "42"], + Template: Error message with a Structure property: {Structure}, + Parameters: [ + { + Structure: [42, "42"] + } + ] + } + ] + ], + diagnostics: [] +} \ No newline at end of file diff --git a/test/Sentry.Serilog.Tests/IntegrationTests.verify.cs b/test/Sentry.Serilog.Tests/IntegrationTests.verify.cs index 10d7b538bc..aab8e7dd17 100644 --- a/test/Sentry.Serilog.Tests/IntegrationTests.verify.cs +++ b/test/Sentry.Serilog.Tests/IntegrationTests.verify.cs @@ -100,5 +100,45 @@ public Task LoggingInsideTheContextOfLogging() }) .IgnoreStandardSentryMembers(); } + + [Fact] + public Task StructuredLogging() + { + var transport = new RecordingTransport(); + + var configuration = new LoggerConfiguration(); + configuration.MinimumLevel.Debug(); + var diagnosticLogger = new InMemoryDiagnosticLogger(); + configuration.WriteTo.Sentry( + _ => + { + _.MinimumEventLevel = (LogEventLevel)int.MaxValue; + _.Experimental.EnableLogs = true; + _.Transport = transport; + _.DiagnosticLogger = diagnosticLogger; + _.Dsn = ValidDsn; + _.Debug = true; + _.Environment = "test-environment"; + _.Release = "test-release"; + }); + + Log.Logger = configuration.CreateLogger(); + + Log.Debug("Debug message with a Scalar property: {Scalar}", 42); + Log.Information("Information message with a Sequence property: {Sequence}", new object[] { new int[] { 41, 42, 43 } }); + Log.Warning("Warning message with a Dictionary property: {Dictionary}", new Dictionary { { "key", "value" } }); + Log.Error("Error message with a Structure property: {Structure}", (Number: 42, Text: "42")); + + Log.CloseAndFlush(); + + var envelopes = transport.Envelopes; + var logs = transport.Payloads.OfType() + .Select(payload => payload.Source) + .OfType() + .Select(log => log.Items.ToArray()); + var diagnostics = diagnosticLogger.Entries.Where(_ => _.Level >= SentryLevel.Warning); + return Verify(new { envelopes, logs, diagnostics }) + .IgnoreStandardSentryMembers(); + } } #endif diff --git a/test/Sentry.Serilog.Tests/Sentry.Serilog.Tests.csproj b/test/Sentry.Serilog.Tests/Sentry.Serilog.Tests.csproj index 5bf555ed06..c4ed3357bb 100644 --- a/test/Sentry.Serilog.Tests/Sentry.Serilog.Tests.csproj +++ b/test/Sentry.Serilog.Tests/Sentry.Serilog.Tests.csproj @@ -1,7 +1,7 @@  - net9.0;net8.0 + $(CurrentTfms) $(TargetFrameworks);net48 diff --git a/test/Sentry.Serilog.Tests/SentrySerilogSinkExtensionsTests.cs b/test/Sentry.Serilog.Tests/SentrySerilogSinkExtensionsTests.cs index 57f5fcf9a5..c0cda5c45a 100644 --- a/test/Sentry.Serilog.Tests/SentrySerilogSinkExtensionsTests.cs +++ b/test/Sentry.Serilog.Tests/SentrySerilogSinkExtensionsTests.cs @@ -28,6 +28,7 @@ private class Fixture public bool InitializeSdk { get; } = false; public LogEventLevel MinimumEventLevel { get; } = LogEventLevel.Verbose; public LogEventLevel MinimumBreadcrumbLevel { get; } = LogEventLevel.Fatal; + public bool ExperimentalEnableLogs { get; } = true; public static SentrySerilogOptions GetSut() => new(); } @@ -97,7 +98,7 @@ public void ConfigureSentrySerilogOptions_WithAllParameters_MakesAppropriateChan _fixture.SampleRate, _fixture.Release, _fixture.Environment, _fixture.MaxQueueItems, _fixture.ShutdownTimeout, _fixture.DecompressionMethods, _fixture.RequestBodyCompressionLevel, _fixture.RequestBodyCompressionBuffered, _fixture.Debug, _fixture.DiagnosticLevel, - _fixture.ReportAssembliesMode, _fixture.DeduplicateMode); + _fixture.ReportAssembliesMode, _fixture.DeduplicateMode, null, _fixture.ExperimentalEnableLogs); // Compare individual properties Assert.Equal(_fixture.SendDefaultPii, sut.SendDefaultPii); @@ -108,7 +109,7 @@ public void ConfigureSentrySerilogOptions_WithAllParameters_MakesAppropriateChan Assert.Equal(_fixture.SampleRate, sut.SampleRate); Assert.Equal(_fixture.Release, sut.Release); Assert.Equal(_fixture.Environment, sut.Environment); - Assert.Equal(_fixture.Dsn, sut.Dsn!); + Assert.Equal(_fixture.Dsn, sut.Dsn); Assert.Equal(_fixture.MaxQueueItems, sut.MaxQueueItems); Assert.Equal(_fixture.ShutdownTimeout, sut.ShutdownTimeout); Assert.Equal(_fixture.DecompressionMethods, sut.DecompressionMethods); @@ -118,6 +119,7 @@ public void ConfigureSentrySerilogOptions_WithAllParameters_MakesAppropriateChan Assert.Equal(_fixture.DiagnosticLevel, sut.DiagnosticLevel); Assert.Equal(_fixture.ReportAssembliesMode, sut.ReportAssembliesMode); Assert.Equal(_fixture.DeduplicateMode, sut.DeduplicateMode); + Assert.Equal(_fixture.ExperimentalEnableLogs, sut.Experimental.EnableLogs); Assert.True(sut.InitializeSdk); Assert.Equal(_fixture.MinimumEventLevel, sut.MinimumEventLevel); Assert.Equal(_fixture.MinimumBreadcrumbLevel, sut.MinimumBreadcrumbLevel); diff --git a/test/Sentry.Serilog.Tests/SentrySinkTests.Structured.cs b/test/Sentry.Serilog.Tests/SentrySinkTests.Structured.cs new file mode 100644 index 0000000000..b7cb36b76f --- /dev/null +++ b/test/Sentry.Serilog.Tests/SentrySinkTests.Structured.cs @@ -0,0 +1,135 @@ +#nullable enable + +namespace Sentry.Serilog.Tests; + +public partial class SentrySinkTests +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Emit_StructuredLogging_IsEnabled(bool isEnabled) + { + InMemorySentryStructuredLogger capturer = new(); + _fixture.Hub.Logger.Returns(capturer); + _fixture.Options.Experimental.EnableLogs = isEnabled; + + var sut = _fixture.GetSut(); + var logger = new LoggerConfiguration().WriteTo.Sink(sut).MinimumLevel.Verbose().CreateLogger(); + + logger.Write(LogEventLevel.Information, "Message"); + + capturer.Logs.Should().HaveCount(isEnabled ? 1 : 0); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Emit_StructuredLogging_UseHubOptionsOverSinkOptions(bool isEnabled) + { + InMemorySentryStructuredLogger capturer = new(); + _fixture.Hub.Logger.Returns(capturer); + _fixture.Options.Experimental.EnableLogs = true; + + if (!isEnabled) + { + SentryClientExtensions.SentryOptionsForTestingOnly = null; + } + + var sut = _fixture.GetSut(); + var logger = new LoggerConfiguration().WriteTo.Sink(sut).MinimumLevel.Verbose().CreateLogger(); + + logger.Write(LogEventLevel.Information, "Message"); + + capturer.Logs.Should().HaveCount(isEnabled ? 1 : 0); + } + + [Theory] + [InlineData(LogEventLevel.Verbose, SentryLogLevel.Trace)] + [InlineData(LogEventLevel.Debug, SentryLogLevel.Debug)] + [InlineData(LogEventLevel.Information, SentryLogLevel.Info)] + [InlineData(LogEventLevel.Warning, SentryLogLevel.Warning)] + [InlineData(LogEventLevel.Error, SentryLogLevel.Error)] + [InlineData(LogEventLevel.Fatal, SentryLogLevel.Fatal)] + public void Emit_StructuredLogging_LogLevel(LogEventLevel level, SentryLogLevel expected) + { + InMemorySentryStructuredLogger capturer = new(); + _fixture.Hub.Logger.Returns(capturer); + _fixture.Options.Experimental.EnableLogs = true; + + var sut = _fixture.GetSut(); + var logger = new LoggerConfiguration().WriteTo.Sink(sut).MinimumLevel.Verbose().CreateLogger(); + + logger.Write(level, "Message"); + + capturer.Logs.Should().ContainSingle().Which.Level.Should().Be(expected); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Emit_StructuredLogging_LogEvent(bool withActiveSpan) + { + InMemorySentryStructuredLogger capturer = new(); + _fixture.Hub.Logger.Returns(capturer); + _fixture.Options.Experimental.EnableLogs = true; + _fixture.Options.Environment = "test-environment"; + _fixture.Options.Release = "test-release"; + + if (withActiveSpan) + { + var span = Substitute.For(); + span.TraceId.Returns(SentryId.Create()); + span.SpanId.Returns(SpanId.Create()); + _fixture.Hub.GetSpan().Returns(span); + } + else + { + _fixture.Hub.GetSpan().Returns((ISpan?)null); + } + + var sut = _fixture.GetSut(); + var logger = new LoggerConfiguration() + .WriteTo.Sink(sut) + .MinimumLevel.Verbose() + .Enrich.WithProperty("Scalar-Property", 42) + .Enrich.WithProperty("Sequence-Property", new[] { 41, 42, 43 }) + .Enrich.WithProperty("Dictionary-Property", new Dictionary { { "key", "value" } }) + .Enrich.WithProperty("Structure-Property", (Number: 42, Text: "42")) + .CreateLogger(); + + logger.Write(LogEventLevel.Information, + "Message with Scalar property {Scalar}, Sequence property: {Sequence}, Dictionary property: {Dictionary}, and Structure property: {Structure}.", + 42, new[] { 41, 42, 43 }, new Dictionary { { "key", "value" } }, (Number: 42, Text: "42")); + + var log = capturer.Logs.Should().ContainSingle().Which; + log.Timestamp.Should().BeOnOrBefore(DateTimeOffset.Now); + log.TraceId.Should().Be(withActiveSpan ? _fixture.Hub.GetSpan()!.TraceId : _fixture.Scope.PropagationContext.TraceId); + log.Level.Should().Be(SentryLogLevel.Info); + log.Message.Should().Be("""Message with Scalar property 42, Sequence property: [41, 42, 43], Dictionary property: [("key": "value")], and Structure property: [42, "42"]."""); + log.Template.Should().Be("Message with Scalar property {Scalar}, Sequence property: {Sequence}, Dictionary property: {Dictionary}, and Structure property: {Structure}."); + log.Parameters.Should().HaveCount(4); + log.Parameters[0].Should().BeEquivalentTo(new KeyValuePair("Scalar", 42)); + log.Parameters[1].Should().BeEquivalentTo(new KeyValuePair("Sequence", "[41, 42, 43]")); + log.Parameters[2].Should().BeEquivalentTo(new KeyValuePair("Dictionary", """[("key": "value")]""")); + log.Parameters[3].Should().BeEquivalentTo(new KeyValuePair("Structure", """[42, "42"]""")); + log.ParentSpanId.Should().Be(withActiveSpan ? _fixture.Hub.GetSpan()!.SpanId : _fixture.Scope.PropagationContext.SpanId); + + log.TryGetAttribute("sentry.environment", out object? environment).Should().BeTrue(); + environment.Should().Be("test-environment"); + log.TryGetAttribute("sentry.release", out object? release).Should().BeTrue(); + release.Should().Be("test-release"); + log.TryGetAttribute("sentry.sdk.name", out object? sdkName).Should().BeTrue(); + sdkName.Should().Be(SentrySink.SdkName); + log.TryGetAttribute("sentry.sdk.version", out object? sdkVersion).Should().BeTrue(); + sdkVersion.Should().Be(SentrySink.NameAndVersion.Version); + + log.TryGetAttribute("property.Scalar-Property", out object? scalar).Should().BeTrue(); + scalar.Should().Be(42); + log.TryGetAttribute("property.Sequence-Property", out object? sequence).Should().BeTrue(); + sequence.Should().Be("[41, 42, 43]"); + log.TryGetAttribute("property.Dictionary-Property", out object? dictionary).Should().BeTrue(); + dictionary.Should().Be("""[("key": "value")]"""); + log.TryGetAttribute("property.Structure-Property", out object? structure).Should().BeTrue(); + structure.Should().Be("""[42, "42"]"""); + } +} diff --git a/test/Sentry.Serilog.Tests/SentrySinkTests.cs b/test/Sentry.Serilog.Tests/SentrySinkTests.cs index ce1011aa2b..0ed6e94139 100644 --- a/test/Sentry.Serilog.Tests/SentrySinkTests.cs +++ b/test/Sentry.Serilog.Tests/SentrySinkTests.cs @@ -1,6 +1,6 @@ namespace Sentry.Serilog.Tests; -public class SentrySinkTests +public partial class SentrySinkTests { private class Fixture { @@ -15,6 +15,7 @@ public Fixture() Hub.IsEnabled.Returns(true); HubAccessor = () => Hub; Hub.SubstituteConfigureScope(Scope); + SentryClientExtensions.SentryOptionsForTestingOnly = Options; } public SentrySink GetSut() diff --git a/test/Sentry.Serilog.Tests/SerilogAspNetSentrySdkTestFixture.cs b/test/Sentry.Serilog.Tests/SerilogAspNetSentrySdkTestFixture.cs index 4510240ceb..b7b1a6d764 100644 --- a/test/Sentry.Serilog.Tests/SerilogAspNetSentrySdkTestFixture.cs +++ b/test/Sentry.Serilog.Tests/SerilogAspNetSentrySdkTestFixture.cs @@ -6,13 +6,21 @@ namespace Sentry.Serilog.Tests; public class SerilogAspNetSentrySdkTestFixture : AspNetSentrySdkTestFixture { protected List Events; + protected List Logs; + + protected bool ExperimentalEnableLogs { get; set; } protected override void ConfigureBuilder(WebHostBuilder builder) { Events = new List(); + Logs = new List(); + Configure = options => { options.SetBeforeSend((@event, _) => { Events.Add(@event); return @event; }); + + options.Experimental.EnableLogs = ExperimentalEnableLogs; + options.Experimental.SetBeforeSendLog(log => { Logs.Add(log); return log; }); }; ConfigureApp = app => @@ -27,7 +35,7 @@ protected override void ConfigureBuilder(WebHostBuilder builder) builder.ConfigureLogging(loggingBuilder => { var logger = new LoggerConfiguration() - .WriteTo.Sentry(ValidDsn) + .WriteTo.Sentry(ValidDsn, experimentalEnableLogs: ExperimentalEnableLogs) .CreateLogger(); loggingBuilder.AddSerilog(logger); }); diff --git a/test/Sentry.SourceGenerators.Tests/Sentry.SourceGenerators.Tests.csproj b/test/Sentry.SourceGenerators.Tests/Sentry.SourceGenerators.Tests.csproj index 820c77dd05..05ff9fa4ae 100644 --- a/test/Sentry.SourceGenerators.Tests/Sentry.SourceGenerators.Tests.csproj +++ b/test/Sentry.SourceGenerators.Tests/Sentry.SourceGenerators.Tests.csproj @@ -1,7 +1,7 @@  - net9.0;net8.0 + $(CurrentTfms) enable enable diff --git a/test/Sentry.Testing.CrashableApp/Sentry.Testing.CrashableApp.csproj b/test/Sentry.Testing.CrashableApp/Sentry.Testing.CrashableApp.csproj index 227a7e1f0d..da0c8e2588 100644 --- a/test/Sentry.Testing.CrashableApp/Sentry.Testing.CrashableApp.csproj +++ b/test/Sentry.Testing.CrashableApp/Sentry.Testing.CrashableApp.csproj @@ -2,7 +2,7 @@ Exe - net9.0;net8.0;net48 + $(CurrentTfms);net48 diff --git a/test/Sentry.Testing/InMemorySentryStructuredLogger.cs b/test/Sentry.Testing/InMemorySentryStructuredLogger.cs index 440b83cdc7..0dfde97564 100644 --- a/test/Sentry.Testing/InMemorySentryStructuredLogger.cs +++ b/test/Sentry.Testing/InMemorySentryStructuredLogger.cs @@ -5,6 +5,7 @@ namespace Sentry.Testing; public sealed class InMemorySentryStructuredLogger : SentryStructuredLogger { public List Entries { get; } = new(); + public List Logs { get; } = new(); /// private protected override void CaptureLog(SentryLogLevel level, string template, object[]? parameters, Action? configureLog) @@ -15,7 +16,7 @@ private protected override void CaptureLog(SentryLogLevel level, string template /// protected internal override void CaptureLog(SentryLog log) { - throw new NotSupportedException(); + Logs.Add(log); } /// diff --git a/test/Sentry.Testing/RecordingTransport.cs b/test/Sentry.Testing/RecordingTransport.cs index 386be50b9b..ba0566a88c 100644 --- a/test/Sentry.Testing/RecordingTransport.cs +++ b/test/Sentry.Testing/RecordingTransport.cs @@ -1,5 +1,7 @@ using ISerializable = Sentry.Protocol.Envelopes.ISerializable; +namespace Sentry.Testing; + public class RecordingTransport : ITransport { private List _envelopes = new(); diff --git a/test/Sentry.Testing/Sentry.Testing.csproj b/test/Sentry.Testing/Sentry.Testing.csproj index 8fbe7f01f1..ef007c56b8 100644 --- a/test/Sentry.Testing/Sentry.Testing.csproj +++ b/test/Sentry.Testing/Sentry.Testing.csproj @@ -1,10 +1,10 @@  - net9.0;net8.0;net48 - $(TargetFrameworks);net8.0-android34.0;net9.0-android35.0 - $(TargetFrameworks);net8.0-ios17.0;net9.0-ios18.0 - $(TargetFrameworks);net8.0-maccatalyst17.0;net9.0-maccatalyst18.0 + $(CurrentTfms);net48 + $(TargetFrameworks);$(LatestAndroidTfm);$(PreviousAndroidTfm) + $(TargetFrameworks);$(LatestIosTfm);$(PreviousIosTfm) + $(TargetFrameworks);$(LatestMacCatalystTfm);$(PreviousMacCatalystTfm) false diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt new file mode 100644 index 0000000000..b83e9340d7 --- /dev/null +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -0,0 +1,1978 @@ +[assembly: System.CLSCompliant(true)] +namespace Sentry +{ + public enum AttachmentType + { + Default = 0, + Minidump = 1, + AppleCrashReport = 2, + UnrealContext = 3, + UnrealLogs = 4, + ViewHierarchy = 5, + } + public class BaggageHeader + { + public override string ToString() { } + } + [System.Diagnostics.DebuggerDisplay("Message: {Message}, Type: {Type}")] + public sealed class Breadcrumb : Sentry.ISentryJsonSerializable + { + public Breadcrumb(string message, string type, System.Collections.Generic.IReadOnlyDictionary? data = null, string? category = null, Sentry.BreadcrumbLevel level = 0) { } + public string? Category { get; } + public System.Collections.Generic.IReadOnlyDictionary? Data { get; } + public Sentry.BreadcrumbLevel Level { get; } + public string? Message { get; } + public System.DateTimeOffset Timestamp { get; } + public string? Type { get; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.Breadcrumb FromJson(System.Text.Json.JsonElement json) { } + } + public enum BreadcrumbLevel + { + [System.Runtime.Serialization.EnumMember(Value="debug")] + Debug = -1, + [System.Runtime.Serialization.EnumMember(Value="info")] + Info = 0, + [System.Runtime.Serialization.EnumMember(Value="warning")] + Warning = 1, + [System.Runtime.Serialization.EnumMember(Value="error")] + Error = 2, + [System.Runtime.Serialization.EnumMember(Value="critical")] + Critical = 3, + } + public class ByteAttachmentContent : Sentry.IAttachmentContent + { + public ByteAttachmentContent(byte[] bytes) { } + public System.IO.Stream GetStream() { } + } + public enum CheckInStatus + { + InProgress = 0, + Ok = 1, + Error = 2, + } + [System.Obsolete("WARNING: This method deliberately causes a crash, and should not be used in a rea" + + "l application.")] + public enum CrashType + { + Managed = 0, + ManagedBackgroundThread = 1, + Native = 2, + } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public class Debouncer + { + public static Sentry.Debouncer PerApplicationLifetime(int eventMaximum = 1, System.TimeSpan? cooldown = default) { } + public static Sentry.Debouncer PerDay(int eventMaximum = 1, System.TimeSpan? cooldown = default) { } + public static Sentry.Debouncer PerHour(int eventMaximum = 1, System.TimeSpan? cooldown = default) { } + public static Sentry.Debouncer PerMinute(int eventMaximum = 1, System.TimeSpan? cooldown = default) { } + } + [System.Flags] + public enum DeduplicateMode + { + SameEvent = 1, + SameExceptionInstance = 2, + InnerException = 4, + AggregateException = 8, + All = 2147483647, + } + public class DefaultSentryScopeStateProcessor : Sentry.ISentryScopeStateProcessor + { + public DefaultSentryScopeStateProcessor() { } + public void Apply(Sentry.Scope scope, object state) { } + } + [System.AttributeUsage(System.AttributeTargets.Assembly)] + public class DsnAttribute : System.Attribute + { + public DsnAttribute(string dsn) { } + public string Dsn { get; } + } + public static class EventLikeExtensions + { + public static void AddBreadcrumb(this Sentry.IEventLike eventLike, string message, string? category, string? type, System.ValueTuple? dataPair = default, Sentry.BreadcrumbLevel level = 0) { } + public static void AddBreadcrumb(this Sentry.IEventLike eventLike, string message, string? category = null, string? type = null, System.Collections.Generic.IReadOnlyDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } + public static void AddBreadcrumb(this Sentry.IEventLike eventLike, System.DateTimeOffset? timestamp, string message, string? category = null, string? type = null, System.Collections.Generic.IReadOnlyDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } + public static bool HasUser(this Sentry.IEventLike eventLike) { } + public static void SetFingerprint(this Sentry.IEventLike eventLike, System.Collections.Generic.IEnumerable fingerprint) { } + public static void SetFingerprint(this Sentry.IEventLike eventLike, params string[] fingerprint) { } + } + public class FileAttachmentContent : Sentry.IAttachmentContent + { + public FileAttachmentContent(string filePath) { } + public FileAttachmentContent(string filePath, bool readFileAsynchronously) { } + public System.IO.Stream GetStream() { } + } + public static class HasExtraExtensions + { + public static void SetExtras(this Sentry.IHasExtra hasExtra, System.Collections.Generic.IEnumerable> values) { } + } + public static class HasTagsExtensions + { + public static void SetTags(this Sentry.IHasTags hasTags, System.Collections.Generic.IEnumerable> tags) { } + } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public delegate bool HeapDumpTrigger(long usedMemory, long totalMemory); + public static class HintTypes + { + public const string HttpResponseMessage = "http-response-message"; + } + public readonly struct HttpStatusCodeRange : System.IEquatable + { + public HttpStatusCodeRange(int statusCode) { } + public HttpStatusCodeRange(int start, int end) { } + public int End { get; init; } + public int Start { get; init; } + public bool Contains(int statusCode) { } + public bool Contains(System.Net.HttpStatusCode statusCode) { } + public static Sentry.HttpStatusCodeRange op_Implicit(int statusCode) { } + public static Sentry.HttpStatusCodeRange op_Implicit(System.Net.HttpStatusCode statusCode) { } + public static Sentry.HttpStatusCodeRange op_Implicit([System.Runtime.CompilerServices.TupleElementNames(new string[] { + "Start", + "End"})] System.ValueTuple range) { } + public static Sentry.HttpStatusCodeRange op_Implicit([System.Runtime.CompilerServices.TupleElementNames(new string[] { + "start", + "end"})] System.ValueTuple range) { } + } + public static class HubExtensions + { + public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Breadcrumb breadcrumb, Sentry.SentryHint? hint = null) { } + public static void AddBreadcrumb(this Sentry.IHub hub, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } + public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } + public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, System.Action configureScope) { } + public static Sentry.SentryId CaptureMessage(this Sentry.IHub hub, string message, System.Action configureScope, Sentry.SentryLevel level = 1) { } + public static void LockScope(this Sentry.IHub hub) { } + public static System.IDisposable PushAndLockScope(this Sentry.IHub hub) { } + public static Sentry.ISpan StartSpan(this Sentry.IHub hub, string operation, string description) { } + public static Sentry.ITransactionTracer StartTransaction(this Sentry.IHub hub, Sentry.ITransactionContext context) { } + public static Sentry.ITransactionTracer StartTransaction(this Sentry.IHub hub, string name, string operation) { } + public static Sentry.ITransactionTracer StartTransaction(this Sentry.IHub hub, string name, string operation, Sentry.SentryTraceHeader traceHeader) { } + public static Sentry.ITransactionTracer StartTransaction(this Sentry.IHub hub, string name, string operation, string? description) { } + public static void UnlockScope(this Sentry.IHub hub) { } + } + public interface IAttachmentContent + { + System.IO.Stream GetStream(); + } + public interface IEventLike : Sentry.IHasExtra, Sentry.IHasTags + { + System.Collections.Generic.IReadOnlyCollection Breadcrumbs { get; } + Sentry.SentryContexts Contexts { get; set; } + string? Distribution { get; set; } + string? Environment { get; set; } + System.Collections.Generic.IReadOnlyList Fingerprint { get; set; } + Sentry.SentryLevel? Level { get; set; } + string? Release { get; set; } + Sentry.SentryRequest Request { get; set; } + Sentry.SdkVersion Sdk { get; } + string? TransactionName { get; set; } + Sentry.SentryUser User { get; set; } + void AddBreadcrumb(Sentry.Breadcrumb breadcrumb); + } + public interface IHasData + { + System.Collections.Generic.IReadOnlyDictionary Data { get; } + void SetData(string key, object? value); + } + public interface IHasExtra + { + System.Collections.Generic.IReadOnlyDictionary Extra { get; } + void SetExtra(string key, object? value); + } + public interface IHasTags + { + System.Collections.Generic.IReadOnlyDictionary Tags { get; } + void SetTag(string key, string value); + void UnsetTag(string key); + } + public interface IHub : Sentry.ISentryClient, Sentry.ISentryScopeManager + { + Sentry.SentryId LastEventId { get; } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + Sentry.SentryStructuredLogger Logger { get; } + void BindException(System.Exception exception, Sentry.ISpan span); + Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope); + Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.SentryHint? hint, System.Action configureScope); + void CaptureFeedback(Sentry.SentryFeedback feedback, System.Action configureScope, Sentry.SentryHint? hint = null); + Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null); + Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null); + void EndSession(Sentry.SessionEndStatus status = 0); + Sentry.BaggageHeader? GetBaggage(); + Sentry.ISpan? GetSpan(); + Sentry.SentryTraceHeader? GetTraceHeader(); + void PauseSession(); + void ResumeSession(); + void StartSession(); + Sentry.ITransactionTracer StartTransaction(Sentry.ITransactionContext context, System.Collections.Generic.IReadOnlyDictionary customSamplingContext); + } + public interface IScopeObserver + { + void AddBreadcrumb(Sentry.Breadcrumb breadcrumb); + void SetExtra(string key, object? value); + void SetTag(string key, string value); + void SetTrace(Sentry.SentryId traceId, Sentry.SpanId parentSpanId); + void SetUser(Sentry.SentryUser? user); + void UnsetTag(string key); + } + public interface ISentryClient + { + bool IsEnabled { get; } + Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null); + bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); + Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null); + void CaptureFeedback(Sentry.SentryFeedback feedback, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null); + void CaptureSession(Sentry.SessionUpdate sessionUpdate); + void CaptureTransaction(Sentry.SentryTransaction transaction); + void CaptureTransaction(Sentry.SentryTransaction transaction, Sentry.Scope? scope, Sentry.SentryHint? hint); + [System.Obsolete("Use CaptureFeedback instead.")] + void CaptureUserFeedback(Sentry.UserFeedback userFeedback); + System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); + } + public interface ISentryJsonSerializable + { + void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger); + } + public interface ISentryScopeManager + { + void BindClient(Sentry.ISentryClient client); + void ConfigureScope(System.Action configureScope); + void ConfigureScope(System.Action configureScope, TArg arg); + System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope); + System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg); + System.IDisposable PushScope(); + System.IDisposable PushScope(TState state); + void SetTag(string key, string value); + void UnsetTag(string key); + } + public interface ISentryScopeStateProcessor + { + void Apply(Sentry.Scope scope, object state); + } + public interface ISentrySession + { + string? DistinctId { get; } + string? Environment { get; } + int ErrorCount { get; } + Sentry.SentryId Id { get; } + string? IpAddress { get; } + string Release { get; } + System.DateTimeOffset StartTimestamp { get; } + string? UserAgent { get; } + } + public interface ISentryUserFactory + { + Sentry.SentryUser? Create(); + } + public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext + { + new string? Description { get; set; } + new string Operation { get; set; } + new Sentry.SpanStatus? Status { get; set; } + void Finish(); + void Finish(Sentry.SpanStatus status); + void Finish(System.Exception exception); + void Finish(System.Exception exception, Sentry.SpanStatus status); + Sentry.ISpan StartChild(string operation); + } + public interface ISpanData : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.Protocol.ITraceContext + { + System.DateTimeOffset? EndTimestamp { get; } + bool IsFinished { get; } + System.Collections.Generic.IReadOnlyDictionary Measurements { get; } + System.DateTimeOffset StartTimestamp { get; } + Sentry.SentryTraceHeader GetTraceHeader(); + void SetMeasurement(string name, Sentry.Protocol.Measurement measurement); + } + public interface ITransactionContext : Sentry.Protocol.ITraceContext + { + bool? IsParentSampled { get; } + string Name { get; } + Sentry.TransactionNameSource NameSource { get; } + } + public interface ITransactionData : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.Protocol.ITraceContext + { + string? Platform { get; set; } + } + public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext + { + new bool? IsParentSampled { get; set; } + new string Name { get; set; } + System.Collections.Generic.IReadOnlyCollection Spans { get; } + Sentry.ISpan? GetLastActiveSpan(); + } + public enum InstructionAddressAdjustment + { + Auto = 0, + All = 1, + AllButFirst = 2, + None = 3, + } + public enum Instrumenter + { + Sentry = 0, + OpenTelemetry = 1, + } + public readonly struct MeasurementUnit : System.IEquatable + { + public static Sentry.MeasurementUnit None; + public bool Equals(Sentry.MeasurementUnit other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + public static Sentry.MeasurementUnit Custom(string name) { } + public static Sentry.MeasurementUnit op_Implicit(Sentry.MeasurementUnit.Duration unit) { } + public static Sentry.MeasurementUnit op_Implicit(Sentry.MeasurementUnit.Fraction unit) { } + public static Sentry.MeasurementUnit op_Implicit(Sentry.MeasurementUnit.Information unit) { } + public static bool operator !=(Sentry.MeasurementUnit left, Sentry.MeasurementUnit right) { } + public static bool operator ==(Sentry.MeasurementUnit left, Sentry.MeasurementUnit right) { } + public enum Duration + { + Nanosecond = 0, + Microsecond = 1, + Millisecond = 2, + Second = 3, + Minute = 4, + Hour = 5, + Day = 6, + Week = 7, + } + public enum Fraction + { + Ratio = 0, + Percent = 1, + } + public enum Information + { + Bit = 0, + Byte = 1, + Kilobyte = 2, + Kibibyte = 3, + Megabyte = 4, + Mebibyte = 5, + Gigabyte = 6, + Gibibyte = 7, + Terabyte = 8, + Tebibyte = 9, + Petabyte = 10, + Pebibyte = 11, + Exabyte = 12, + Exbibyte = 13, + } + } + public enum ReportAssembliesMode + { + None = 0, + Version = 1, + InformationalVersion = 2, + } + public class Scope : Sentry.IEventLike, Sentry.IHasExtra, Sentry.IHasTags + { + public Scope(Sentry.SentryOptions? options) { } + public System.Collections.Generic.IReadOnlyCollection Attachments { get; } + public System.Collections.Generic.IReadOnlyCollection Breadcrumbs { get; } + public Sentry.SentryContexts Contexts { get; set; } + public string? Distribution { get; set; } + public string? Environment { get; set; } + public System.Collections.Generic.IReadOnlyDictionary Extra { get; } + public System.Collections.Generic.IReadOnlyList Fingerprint { get; set; } + public Sentry.SentryLevel? Level { get; set; } + public string? Release { get; set; } + public Sentry.SentryRequest Request { get; set; } + public Sentry.SdkVersion Sdk { get; } + public Sentry.ISpan? Span { get; set; } + public System.Collections.Generic.IReadOnlyDictionary Tags { get; } + public Sentry.ITransactionTracer? Transaction { get; set; } + public string? TransactionName { get; set; } + public Sentry.SentryUser User { get; set; } + public void AddAttachment(Sentry.SentryAttachment attachment) { } + public void AddAttachment(string filePath, Sentry.AttachmentType type = 0, string? contentType = null) { } + public void AddAttachment(byte[] data, string fileName, Sentry.AttachmentType type = 0, string? contentType = null) { } + public void AddAttachment(System.IO.Stream stream, string fileName, Sentry.AttachmentType type = 0, string? contentType = null) { } + public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { } + public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb, Sentry.SentryHint hint) { } + public void AddEventProcessor(Sentry.Extensibility.ISentryEventProcessor processor) { } + public void AddEventProcessor(System.Func processor) { } + public void AddEventProcessors(System.Collections.Generic.IEnumerable processors) { } + public void AddExceptionProcessor(Sentry.Extensibility.ISentryEventExceptionProcessor processor) { } + public void AddExceptionProcessors(System.Collections.Generic.IEnumerable processors) { } + public void AddTransactionProcessor(Sentry.Extensibility.ISentryTransactionProcessor processor) { } + public void AddTransactionProcessor(System.Func processor) { } + public void AddTransactionProcessors(System.Collections.Generic.IEnumerable processors) { } + public void Apply(Sentry.IEventLike other) { } + public void Apply(Sentry.Scope other) { } + public void Apply(object state) { } + public void Clear() { } + public void ClearAttachments() { } + public void ClearBreadcrumbs() { } + public Sentry.Scope Clone() { } + public System.Collections.Generic.IEnumerable GetAllEventProcessors() { } + public System.Collections.Generic.IEnumerable GetAllExceptionProcessors() { } + public System.Collections.Generic.IEnumerable GetAllTransactionProcessors() { } + public void SetExtra(string key, object? value) { } + public void SetTag(string key, string value) { } + public void UnsetTag(string key) { } + } + public sealed class SdkVersion : Sentry.ISentryJsonSerializable + { + public SdkVersion() { } + public string? Name { get; set; } + public System.Collections.Generic.IEnumerable Packages { get; } + public string? Version { get; set; } + public void AddIntegration(string integration) { } + public void AddPackage(string name, string version) { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.SdkVersion FromJson(System.Text.Json.JsonElement json) { } + } + [System.Diagnostics.DebuggerDisplay("{FileName}")] + public class SentryAttachment + { + public SentryAttachment(Sentry.AttachmentType type, Sentry.IAttachmentContent content, string fileName, string? contentType) { } + public Sentry.IAttachmentContent Content { get; } + public string? ContentType { get; } + public string FileName { get; } + public Sentry.AttachmentType Type { get; } + } + public class SentryCheckIn : Sentry.ISentryJsonSerializable + { + public SentryCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default) { } + public System.TimeSpan? Duration { get; set; } + public string? Environment { get; set; } + public Sentry.SentryId Id { get; } + public string MonitorSlug { get; } + public string? Release { get; set; } + public Sentry.CheckInStatus Status { get; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + } + public class SentryClient : Sentry.ISentryClient, System.IDisposable + { + public SentryClient(Sentry.SentryOptions options) { } + public bool IsEnabled { get; } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } + public void CaptureFeedback(Sentry.SentryFeedback feedback, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } + public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } + public void CaptureTransaction(Sentry.SentryTransaction transaction) { } + public void CaptureTransaction(Sentry.SentryTransaction transaction, Sentry.Scope? scope, Sentry.SentryHint? hint) { } + [System.Obsolete("Use CaptureFeedback instead.")] + public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } + public void Dispose() { } + public System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout) { } + } + public static class SentryClientExtensions + { + public static Sentry.SentryId CaptureException(this Sentry.ISentryClient client, System.Exception ex) { } + public static void CaptureFeedback(this Sentry.ISentryClient client, string message, string? contactEmail = null, string? name = null, string? replayId = null, string? url = null, Sentry.SentryId? associatedEventId = default, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } + public static Sentry.SentryId CaptureMessage(this Sentry.ISentryClient client, string message, Sentry.SentryLevel level = 1) { } + [System.Obsolete("Use CaptureFeedback instead.")] + public static void CaptureUserFeedback(this Sentry.ISentryClient client, Sentry.SentryId eventId, string email, string comments, string? name = null) { } + public static void Flush(this Sentry.ISentryClient client) { } + public static void Flush(this Sentry.ISentryClient client, System.TimeSpan timeout) { } + public static System.Threading.Tasks.Task FlushAsync(this Sentry.ISentryClient client) { } + [System.Obsolete("This method is meant for external usage only")] + public static Sentry.SentryOptions? GetInternalSentryOptions(this Sentry.ISentryClient clientOrHub) { } + } + public static class SentryConstants + { + public const int DefaultMaxBreadcrumbs = 100; + public const string DisableSdkDsnValue = ""; + public const string Platform = "csharp"; + public const int ProtocolVersion = 7; + } + public sealed class SentryContexts : Sentry.ISentryJsonSerializable, System.Collections.Generic.ICollection>, System.Collections.Generic.IDictionary, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + { + public SentryContexts() { } + public Sentry.Protocol.App App { get; } + public Sentry.Protocol.Browser Browser { get; } + public int Count { get; } + public Sentry.Protocol.Device Device { get; } + public Sentry.SentryFeedback? Feedback { get; set; } + public Sentry.Protocol.Gpu Gpu { get; } + public bool IsReadOnly { get; } + public object this[string key] { get; set; } + public System.Collections.Generic.ICollection Keys { get; } + public Sentry.Protocol.OperatingSystem OperatingSystem { get; } + public Sentry.Protocol.Response Response { get; } + public Sentry.Protocol.Runtime Runtime { get; } + public Sentry.Protocol.Trace Trace { get; } + public System.Collections.Generic.ICollection Values { get; } + public void Add(System.Collections.Generic.KeyValuePair item) { } + public void Add(string key, object value) { } + public void Clear() { } + public bool Contains(System.Collections.Generic.KeyValuePair item) { } + public bool ContainsKey(string key) { } + public void CopyTo(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) { } + public System.Collections.Generic.IEnumerator> GetEnumerator() { } + public bool Remove(System.Collections.Generic.KeyValuePair item) { } + public bool Remove(string key) { } + public bool TryGetValue(string key, out object value) { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.SentryContexts FromJson(System.Text.Json.JsonElement json) { } + } + [System.Diagnostics.DebuggerDisplay("{GetType().Name,nq}: {EventId,nq}")] + public sealed class SentryEvent : Sentry.IEventLike, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISentryJsonSerializable + { + public SentryEvent() { } + public SentryEvent(System.Exception? exception) { } + public System.Collections.Generic.IReadOnlyCollection Breadcrumbs { get; } + public Sentry.SentryContexts Contexts { get; set; } + public System.Collections.Generic.List? DebugImages { get; set; } + public string? Distribution { get; set; } + public string? Environment { get; set; } + public Sentry.SentryId EventId { get; } + public System.Exception? Exception { get; } + public System.Collections.Generic.IReadOnlyDictionary Extra { get; } + public System.Collections.Generic.IReadOnlyList Fingerprint { get; set; } + public Sentry.SentryLevel? Level { get; set; } + public string? Logger { get; set; } + public Sentry.SentryMessage? Message { get; set; } + public System.Collections.Generic.IDictionary Modules { get; } + public string? Platform { get; set; } + public string? Release { get; set; } + public Sentry.SentryRequest Request { get; set; } + public Sentry.SdkVersion Sdk { get; } + public System.Collections.Generic.IEnumerable? SentryExceptions { get; set; } + public System.Collections.Generic.IEnumerable? SentryThreads { get; set; } + public string? ServerName { get; set; } + public System.Collections.Generic.IReadOnlyDictionary Tags { get; } + public System.DateTimeOffset Timestamp { get; } + public string? TransactionName { get; set; } + public Sentry.SentryUser User { get; set; } + public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { } + public void SetExtra(string key, object? value) { } + public void SetTag(string key, string value) { } + public void UnsetTag(string key) { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.SentryEvent FromJson(System.Text.Json.JsonElement json) { } + } + public sealed class SentryFeedback : Sentry.ISentryJsonSerializable + { + public SentryFeedback(string message, string? contactEmail = null, string? name = null, string? replayId = null, string? url = null, Sentry.SentryId? associatedEventId = default) { } + public Sentry.SentryId? AssociatedEventId { get; set; } + public string? ContactEmail { get; set; } + public string Message { get; set; } + public string? Name { get; set; } + public string? ReplayId { get; set; } + public string? Url { get; set; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.SentryFeedback FromJson(System.Text.Json.JsonElement json) { } + } + public class SentryGraphQLHttpMessageHandler : Sentry.SentryMessageHandler + { + public SentryGraphQLHttpMessageHandler(System.Net.Http.HttpMessageHandler? innerHandler = null, Sentry.IHub? hub = null) { } + protected override void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url) { } + protected override Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url) { } + } + public class SentryHint + { + public SentryHint() { } + public SentryHint(string key, object? value) { } + public System.Collections.Generic.ICollection Attachments { get; } + public System.Collections.Generic.IDictionary Items { get; } + public void AddAttachment(string filePath, Sentry.AttachmentType type = 0, string? contentType = null) { } + public void AddAttachment(byte[] data, string fileName, Sentry.AttachmentType type = 0, string? contentType = null) { } + public static Sentry.SentryHint WithAttachments(params Sentry.SentryAttachment[] attachments) { } + public static Sentry.SentryHint WithAttachments(System.Collections.Generic.IEnumerable attachments) { } + } + public class SentryHttpMessageHandler : Sentry.SentryMessageHandler + { + public SentryHttpMessageHandler() { } + public SentryHttpMessageHandler(Sentry.IHub hub) { } + public SentryHttpMessageHandler(System.Net.Http.HttpMessageHandler innerHandler) { } + public SentryHttpMessageHandler(System.Net.Http.HttpMessageHandler innerHandler, Sentry.IHub hub) { } + protected override void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url) { } + protected override Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url) { } + } + public readonly struct SentryId : Sentry.ISentryJsonSerializable, System.IEquatable + { + public static readonly Sentry.SentryId Empty; + public SentryId(System.Guid guid) { } + public bool Equals(Sentry.SentryId other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.SentryId Create() { } + public static Sentry.SentryId FromJson(System.Text.Json.JsonElement json) { } + public static Sentry.SentryId Parse(string value) { } + public static System.Guid op_Implicit(Sentry.SentryId sentryId) { } + public static Sentry.SentryId op_Implicit(System.Guid guid) { } + public static bool operator !=(Sentry.SentryId left, Sentry.SentryId right) { } + public static bool operator ==(Sentry.SentryId left, Sentry.SentryId right) { } + } + public enum SentryLevel : short + { + [System.Runtime.Serialization.EnumMember(Value="debug")] + Debug = 0, + [System.Runtime.Serialization.EnumMember(Value="info")] + Info = 1, + [System.Runtime.Serialization.EnumMember(Value="warning")] + Warning = 2, + [System.Runtime.Serialization.EnumMember(Value="error")] + Error = 3, + [System.Runtime.Serialization.EnumMember(Value="fatal")] + Fatal = 4, + } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + [System.Diagnostics.DebuggerDisplay("SentryLog \\{ Level = {Level}, Message = \'{Message}\' \\}")] + [System.Runtime.CompilerServices.RequiredMember] + public sealed class SentryLog + { + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + [System.Runtime.CompilerServices.RequiredMember] + public Sentry.SentryLogLevel Level { get; init; } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + [System.Runtime.CompilerServices.RequiredMember] + public string Message { get; init; } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public System.Collections.Immutable.ImmutableArray> Parameters { get; init; } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public Sentry.SpanId? ParentSpanId { get; init; } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public string? Template { get; init; } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + [System.Runtime.CompilerServices.RequiredMember] + public System.DateTimeOffset Timestamp { get; init; } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + [System.Runtime.CompilerServices.RequiredMember] + public Sentry.SentryId TraceId { get; init; } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void SetAttribute(string key, object value) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public bool TryGetAttribute(string key, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? value) { } + } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public enum SentryLogLevel + { + Trace = 1, + Debug = 5, + Info = 9, + Warning = 13, + Error = 17, + Fatal = 21, + } + public sealed class SentryMessage : Sentry.ISentryJsonSerializable + { + public SentryMessage() { } + public string? Formatted { get; set; } + public string? Message { get; set; } + public System.Collections.Generic.IEnumerable? Params { get; set; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.SentryMessage FromJson(System.Text.Json.JsonElement json) { } + public static Sentry.SentryMessage op_Implicit(string? message) { } + } + public abstract class SentryMessageHandler : System.Net.Http.DelegatingHandler + { + protected SentryMessageHandler() { } + protected SentryMessageHandler(Sentry.IHub hub) { } + protected SentryMessageHandler(System.Net.Http.HttpMessageHandler innerHandler) { } + protected SentryMessageHandler(System.Net.Http.HttpMessageHandler innerHandler, Sentry.IHub hub) { } + protected abstract void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url); + protected abstract Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url); + protected override System.Net.Http.HttpResponseMessage Send(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { } + protected override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { } + } + public enum SentryMonitorInterval + { + Year = 0, + Month = 1, + Week = 2, + Day = 3, + Hour = 4, + Minute = 5, + } + public class SentryMonitorOptions : Sentry.ISentryJsonSerializable + { + public System.TimeSpan? CheckInMargin { get; set; } + public int? FailureIssueThreshold { get; set; } + public System.TimeSpan? MaxRuntime { get; set; } + public string? Owner { get; set; } + public int? RecoveryThreshold { get; set; } + public string? TimeZone { get; set; } + public void Interval(string crontab) { } + public void Interval(int interval, Sentry.SentryMonitorInterval unit) { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + } + public class SentryOptions + { + public SentryOptions() { } + [System.CLSCompliant(false)] + public System.Func? AssemblyReader { get; set; } + public bool AttachStacktrace { get; set; } + public bool AutoSessionTracking { get; set; } + public System.TimeSpan AutoSessionTrackingInterval { get; set; } + public Sentry.Extensibility.IBackgroundWorker? BackgroundWorker { get; set; } + public string? CacheDirectoryPath { get; set; } + public bool CaptureFailedRequests { get; set; } + public System.Action? ConfigureClient { get; set; } + public System.Func? CrashedLastRun { get; set; } + public System.Func? CreateHttpMessageHandler { get; set; } + public bool Debug { get; set; } + public System.Net.DecompressionMethods DecompressionMethods { get; set; } + public Sentry.DeduplicateMode DeduplicateMode { get; set; } + public System.Collections.Generic.Dictionary DefaultTags { get; } + public Sentry.StartupTimeDetectionMode DetectStartupTime { get; set; } + public Sentry.SentryLevel DiagnosticLevel { get; set; } + public Sentry.Extensibility.IDiagnosticLogger? DiagnosticLogger { get; set; } + public bool DisableFileWrite { get; set; } + public bool DisableSentryHttpMessageHandler { get; set; } + public string? Distribution { get; set; } + public string? Dsn { get; set; } + public bool EnableBackpressureHandling { get; set; } + public bool EnableScopeSync { get; set; } + public bool EnableSpotlight { get; set; } + public string? Environment { get; set; } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public Sentry.SentryOptions.SentryExperimentalOptions Experimental { get; set; } + public System.Collections.Generic.IList FailedRequestStatusCodes { get; set; } + public System.Collections.Generic.IList FailedRequestTargets { get; set; } + public System.TimeSpan FlushTimeout { get; set; } + public System.Net.IWebProxy? HttpProxy { get; set; } + public System.TimeSpan InitCacheFlushTimeout { get; set; } + public bool IsEnvironmentUser { get; set; } + public bool IsGlobalModeEnabled { get; set; } + public bool JsonPreserveReferences { get; set; } + public long MaxAttachmentSize { get; set; } + public int MaxBreadcrumbs { get; set; } + public int MaxCacheItems { get; set; } + public int MaxQueueItems { get; set; } + public Sentry.Extensibility.INetworkStatusListener? NetworkStatusListener { get; set; } + public double? ProfilesSampleRate { get; set; } + public string? Release { get; set; } + public Sentry.ReportAssembliesMode ReportAssembliesMode { get; set; } + public bool RequestBodyCompressionBuffered { get; set; } + public System.IO.Compression.CompressionLevel RequestBodyCompressionLevel { get; set; } + public float? SampleRate { get; set; } + public Sentry.IScopeObserver? ScopeObserver { get; set; } + public bool SendClientReports { get; set; } + public bool SendDefaultPii { get; set; } + public Sentry.ISentryScopeStateProcessor SentryScopeStateProcessor { get; set; } + public string? ServerName { get; set; } + public System.TimeSpan ShutdownTimeout { get; set; } + public string SpotlightUrl { get; set; } + public Sentry.StackTraceMode StackTraceMode { get; set; } + public System.Collections.Generic.IList TagFilters { get; set; } + public System.Collections.Generic.IList TracePropagationTargets { get; set; } + public double? TracesSampleRate { get; set; } + public System.Func? TracesSampler { get; set; } + public Sentry.Extensibility.ITransport? Transport { get; set; } + public bool UseAsyncFileIO { get; set; } + public void AddEventProcessor(Sentry.Extensibility.ISentryEventProcessor processor) { } + public void AddEventProcessorProvider(System.Func> processorProvider) { } + public void AddEventProcessors(System.Collections.Generic.IEnumerable processors) { } + public void AddExceptionFilter(Sentry.Extensibility.IExceptionFilter exceptionFilter) { } + public void AddExceptionFilterForType() + where TException : System.Exception { } + public void AddExceptionProcessor(Sentry.Extensibility.ISentryEventExceptionProcessor processor) { } + public void AddExceptionProcessorProvider(System.Func> processorProvider) { } + public void AddExceptionProcessors(System.Collections.Generic.IEnumerable processors) { } + public void AddInAppExclude(string prefix) { } + public void AddInAppExclude(System.Text.RegularExpressions.Regex regex) { } + public void AddInAppExcludeRegex(string pattern) { } + public void AddInAppInclude(string prefix) { } + public void AddInAppInclude(System.Text.RegularExpressions.Regex regex) { } + public void AddInAppIncludeRegex(string pattern) { } + public void AddIntegration(Sentry.Integrations.ISdkIntegration integration) { } + public void AddJsonConverter(System.Text.Json.Serialization.JsonConverter converter) { } + public void AddJsonSerializerContext(System.Func contextBuilder) + where T : System.Text.Json.Serialization.JsonSerializerContext { } + public void AddTransactionProcessor(Sentry.Extensibility.ISentryTransactionProcessor processor) { } + public void AddTransactionProcessorProvider(System.Func> processorProvider) { } + public void AddTransactionProcessors(System.Collections.Generic.IEnumerable processors) { } + public void ApplyDefaultTags(Sentry.IHasTags hasTags) { } + public void DisableAppDomainProcessExitFlush() { } + public void DisableAppDomainUnhandledExceptionCapture() { } + public void DisableDiagnosticSourceIntegration() { } + public void DisableDuplicateEventDetection() { } + public void DisableSystemDiagnosticsMetricsIntegration() { } + public void DisableUnobservedTaskExceptionCapture() { } + public void DisableWinUiUnhandledExceptionIntegration() { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void EnableHeapDumps(Sentry.HeapDumpTrigger trigger, Sentry.Debouncer? debouncer = null, Sentry.SentryLevel level = 2) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void EnableHeapDumps(short memoryPercentageThreshold, Sentry.Debouncer? debouncer = null, Sentry.SentryLevel level = 2) { } + public System.Collections.Generic.IEnumerable GetAllEventProcessors() { } + public System.Collections.Generic.IEnumerable GetAllExceptionProcessors() { } + public System.Collections.Generic.IEnumerable GetAllTransactionProcessors() { } + public void RemoveEventProcessor() + where TProcessor : Sentry.Extensibility.ISentryEventProcessor { } + public void RemoveExceptionFilter() + where TFilter : Sentry.Extensibility.IExceptionFilter { } + public void RemoveIntegration() + where TIntegration : Sentry.Integrations.ISdkIntegration { } + public void RemoveTransactionProcessor() + where TProcessor : Sentry.Extensibility.ISentryTransactionProcessor { } + public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } + public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } + public void SetBeforeSend(System.Func beforeSend) { } + public void SetBeforeSend(System.Func beforeSend) { } + public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } + public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } + public Sentry.SentryOptions UseStackTraceFactory(Sentry.Extensibility.ISentryStackTraceFactory sentryStackTraceFactory) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public sealed class SentryExperimentalOptions + { + public bool EnableLogs { get; set; } + public void SetBeforeSendLog(System.Func beforeSendLog) { } + } + } + public sealed class SentryPackage : Sentry.ISentryJsonSerializable + { + public SentryPackage(string name, string version) { } + public string Name { get; } + public string Version { get; } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.SentryPackage FromJson(System.Text.Json.JsonElement json) { } + } + public sealed class SentryRequest : Sentry.ISentryJsonSerializable + { + public SentryRequest() { } + public string? ApiTarget { get; set; } + public string? Cookies { get; set; } + public object? Data { get; set; } + public System.Collections.Generic.IDictionary Env { get; } + public System.Collections.Generic.IDictionary Headers { get; } + public string? Method { get; set; } + public System.Collections.Generic.IDictionary Other { get; } + public string? QueryString { get; set; } + public string? Url { get; set; } + public Sentry.SentryRequest Clone() { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.SentryRequest FromJson(System.Text.Json.JsonElement json) { } + } + public static class SentrySdk + { + public static bool IsEnabled { get; } + public static Sentry.SentryId LastEventId { get; } + public static void AddBreadcrumb(Sentry.Breadcrumb breadcrumb, Sentry.SentryHint? hint = null) { } + public static void AddBreadcrumb(string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } + public static void AddBreadcrumb(Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } + public static void BindClient(Sentry.ISentryClient client) { } + public static void BindException(System.Exception exception, Sentry.ISpan span) { } + public static Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } + public static bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } + public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } + public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.SentryHint? hint, System.Action configureScope) { } + public static Sentry.SentryId CaptureException(System.Exception exception) { } + public static Sentry.SentryId CaptureException(System.Exception exception, System.Action configureScope) { } + public static void CaptureFeedback(Sentry.SentryFeedback feedback, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } + public static void CaptureFeedback(Sentry.SentryFeedback feedback, System.Action configureScope, Sentry.SentryHint? hint = null) { } + public static void CaptureFeedback(string message, string? contactEmail = null, string? name = null, string? replayId = null, string? url = null, Sentry.SentryId? associatedEventId = default, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } + public static Sentry.SentryId CaptureMessage(string message, Sentry.SentryLevel level = 1) { } + public static Sentry.SentryId CaptureMessage(string message, System.Action configureScope, Sentry.SentryLevel level = 1) { } + public static void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } + public static void CaptureTransaction(Sentry.SentryTransaction transaction) { } + public static void CaptureTransaction(Sentry.SentryTransaction transaction, Sentry.Scope? scope, Sentry.SentryHint? hint) { } + [System.Obsolete("Use CaptureFeedback instead.")] + public static void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } + [System.Obsolete("Use CaptureFeedback instead.")] + public static void CaptureUserFeedback(Sentry.SentryId eventId, string email, string comments, string? name = null) { } + [System.Obsolete("WARNING: This method deliberately causes a crash, and should not be used in a rea" + + "l application.")] + public static void CauseCrash(Sentry.CrashType crashType) { } + public static void Close() { } + public static void ConfigureScope(System.Action configureScope) { } + public static void ConfigureScope(System.Action configureScope, TArg arg) { } + public static System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } + public static System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg) { } + public static Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { } + public static Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { } + public static void EndSession(Sentry.SessionEndStatus status = 0) { } + public static void Flush() { } + public static void Flush(System.TimeSpan timeout) { } + public static System.Threading.Tasks.Task FlushAsync() { } + public static System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout) { } + public static Sentry.BaggageHeader? GetBaggage() { } + public static Sentry.ISpan? GetSpan() { } + public static Sentry.SentryTraceHeader? GetTraceHeader() { } + public static Sentry.ITransactionTracer? GetTransaction() { } + public static System.IDisposable Init() { } + public static System.IDisposable Init(Sentry.SentryOptions options) { } + public static System.IDisposable Init(System.Action? configureOptions) { } + public static System.IDisposable Init(string? dsn) { } + public static void PauseSession() { } + public static System.IDisposable PushScope() { } + public static System.IDisposable PushScope(TState state) { } + public static void ResumeSession() { } + public static void SetTag(string key, string value) { } + public static void StartSession() { } + public static Sentry.ISpan StartSpan(string operation, string description) { } + public static Sentry.ITransactionTracer StartTransaction(Sentry.ITransactionContext context) { } + public static Sentry.ITransactionTracer StartTransaction(Sentry.ITransactionContext context, System.Collections.Generic.IReadOnlyDictionary customSamplingContext) { } + public static Sentry.ITransactionTracer StartTransaction(string name, string operation) { } + public static Sentry.ITransactionTracer StartTransaction(string name, string operation, Sentry.SentryTraceHeader traceHeader) { } + public static Sentry.ITransactionTracer StartTransaction(string name, string operation, string? description) { } + public static void UnsetTag(string key) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public static class Experimental + { + public static Sentry.SentryStructuredLogger Logger { get; } + } + } + public class SentrySession : Sentry.ISentrySession + { + public SentrySession(string? distinctId, string release, string? environment) { } + public string? DistinctId { get; } + public string? Environment { get; } + public int ErrorCount { get; } + public Sentry.SentryId Id { get; } + public string? IpAddress { get; } + public string Release { get; } + public System.DateTimeOffset StartTimestamp { get; } + public string? UserAgent { get; } + public void ReportError() { } + } + public class SentrySpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISentryJsonSerializable, Sentry.ISpanData, Sentry.Protocol.ITraceContext + { + public SentrySpan(Sentry.ISpan tracer) { } + public SentrySpan(Sentry.SpanId? parentSpanId, string operation) { } + public System.Collections.Generic.IReadOnlyDictionary Data { get; } + public string? Description { get; set; } + public System.DateTimeOffset? EndTimestamp { get; } + [System.Obsolete("Use Data")] + public System.Collections.Generic.IReadOnlyDictionary Extra { get; } + public bool IsFinished { get; } + public bool? IsSampled { get; } + public System.Collections.Generic.IReadOnlyDictionary Measurements { get; } + public string Operation { get; set; } + public string? Origin { get; } + public Sentry.SpanId? ParentSpanId { get; } + public Sentry.SpanId SpanId { get; } + public System.DateTimeOffset StartTimestamp { get; } + public Sentry.SpanStatus? Status { get; set; } + public System.Collections.Generic.IReadOnlyDictionary Tags { get; } + public Sentry.SentryId TraceId { get; } + public Sentry.SentryTraceHeader GetTraceHeader() { } + public void SetData(string key, object? value) { } + [System.Obsolete("Use SetData")] + public void SetExtra(string key, object? value) { } + public void SetMeasurement(string name, Sentry.Protocol.Measurement measurement) { } + public void SetTag(string key, string value) { } + public void UnsetTag(string key) { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.SentrySpan FromJson(System.Text.Json.JsonElement json) { } + } + [System.Diagnostics.DebuggerDisplay("{Function}")] + public sealed class SentryStackFrame : Sentry.ISentryJsonSerializable + { + public SentryStackFrame() { } + public string? AbsolutePath { get; set; } + public string? AddressMode { get; set; } + public int? ColumnNumber { get; set; } + public string? ContextLine { get; set; } + public string? FileName { get; set; } + public System.Collections.Generic.IList FramesOmitted { get; } + public string? Function { get; set; } + public long? FunctionId { get; set; } + public long? ImageAddress { get; set; } + public bool? InApp { get; set; } + public long? InstructionAddress { get; set; } + public int? LineNumber { get; set; } + public string? Module { get; set; } + public string? Package { get; set; } + public string? Platform { get; set; } + public System.Collections.Generic.IList PostContext { get; } + public System.Collections.Generic.IList PreContext { get; } + public long? SymbolAddress { get; set; } + public System.Collections.Generic.IDictionary Vars { get; } + public void ConfigureAppFrame(Sentry.SentryOptions options) { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.SentryStackFrame FromJson(System.Text.Json.JsonElement json) { } + } + public class SentryStackTrace : Sentry.ISentryJsonSerializable + { + public SentryStackTrace() { } + public Sentry.InstructionAddressAdjustment? AddressAdjustment { get; set; } + public System.Collections.Generic.IList Frames { get; set; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.SentryStackTrace FromJson(System.Text.Json.JsonElement json) { } + } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public abstract class SentryStructuredLogger + { + protected abstract void CaptureLog(Sentry.SentryLog log); + protected abstract void Flush(); + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogDebug(string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogDebug(System.Action configureLog, string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogError(string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogError(System.Action configureLog, string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogFatal(string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogFatal(System.Action configureLog, string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogInfo(string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogInfo(System.Action configureLog, string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogTrace(string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogTrace(System.Action configureLog, string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogWarning(string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogWarning(System.Action configureLog, string template, params object[] parameters) { } + } + public sealed class SentryThread : Sentry.ISentryJsonSerializable + { + public SentryThread() { } + public bool? Crashed { get; set; } + public bool? Current { get; set; } + public int? Id { get; set; } + public string? Name { get; set; } + public Sentry.SentryStackTrace? Stacktrace { get; set; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.SentryThread FromJson(System.Text.Json.JsonElement json) { } + } + public class SentryTraceHeader + { + public SentryTraceHeader(Sentry.SentryId traceId, Sentry.SpanId spanSpanId, bool? isSampled) { } + public bool? IsSampled { get; } + public Sentry.SpanId SpanId { get; } + public Sentry.SentryId TraceId { get; } + public override string ToString() { } + public static Sentry.SentryTraceHeader? Parse(string value) { } + } + public class SentryTransaction : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISentryJsonSerializable, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext + { + public SentryTransaction(Sentry.ITransactionTracer tracer) { } + public SentryTransaction(string name, string operation) { } + public SentryTransaction(string name, string operation, Sentry.TransactionNameSource nameSource) { } + public System.Collections.Generic.IReadOnlyCollection Breadcrumbs { get; } + public Sentry.SentryContexts Contexts { get; set; } + public System.Collections.Generic.IReadOnlyDictionary Data { get; } + public string? Description { get; set; } + public string? Distribution { get; set; } + public System.DateTimeOffset? EndTimestamp { get; } + public string? Environment { get; set; } + public Sentry.SentryId EventId { get; } + [System.Obsolete("Use Data")] + public System.Collections.Generic.IReadOnlyDictionary Extra { get; } + public System.Collections.Generic.IReadOnlyList Fingerprint { get; set; } + public bool IsFinished { get; } + public bool? IsParentSampled { get; set; } + public bool? IsSampled { get; } + public Sentry.SentryLevel? Level { get; set; } + public System.Collections.Generic.IReadOnlyDictionary Measurements { get; } + public string Name { get; } + public Sentry.TransactionNameSource NameSource { get; } + public string Operation { get; } + public string? Origin { get; } + public Sentry.SpanId? ParentSpanId { get; } + public string? Platform { get; set; } + public string? Release { get; set; } + public Sentry.SentryRequest Request { get; set; } + public double? SampleRate { get; } + public Sentry.SdkVersion Sdk { get; } + public Sentry.SpanId SpanId { get; } + public System.Collections.Generic.IReadOnlyCollection Spans { get; } + public System.DateTimeOffset StartTimestamp { get; } + public Sentry.SpanStatus? Status { get; } + public System.Collections.Generic.IReadOnlyDictionary Tags { get; } + public Sentry.SentryId TraceId { get; } + public Sentry.SentryUser User { get; set; } + public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { } + public Sentry.SentryTraceHeader GetTraceHeader() { } + public void SetData(string key, object? value) { } + [System.Obsolete("Use SetData")] + public void SetExtra(string key, object? value) { } + public void SetMeasurement(string name, Sentry.Protocol.Measurement measurement) { } + public void SetTag(string key, string value) { } + public void UnsetTag(string key) { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.SentryTransaction FromJson(System.Text.Json.JsonElement json) { } + } + public sealed class SentryUser : Sentry.ISentryJsonSerializable + { + public SentryUser() { } + public string? Email { get; set; } + public string? Id { get; set; } + public string? IpAddress { get; set; } + public System.Collections.Generic.IDictionary Other { get; set; } + public string? Username { get; set; } + public Sentry.SentryUser Clone() { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? _) { } + public static Sentry.SentryUser FromJson(System.Text.Json.JsonElement json) { } + } + public enum SessionEndStatus + { + Exited = 0, + Crashed = 1, + Abnormal = 2, + } + public class SessionUpdate : Sentry.ISentryJsonSerializable, Sentry.ISentrySession + { + public SessionUpdate(Sentry.SessionUpdate sessionUpdate, bool isInitial) { } + public SessionUpdate(Sentry.SessionUpdate sessionUpdate, bool isInitial, Sentry.SessionEndStatus? endStatus) { } + public SessionUpdate(Sentry.ISentrySession session, bool isInitial, System.DateTimeOffset timestamp, int sequenceNumber, Sentry.SessionEndStatus? endStatus) { } + public SessionUpdate(Sentry.SentryId id, string? distinctId, System.DateTimeOffset startTimestamp, string release, string? environment, string? ipAddress, string? userAgent, int errorCount, bool isInitial, System.DateTimeOffset timestamp, int sequenceNumber, Sentry.SessionEndStatus? endStatus) { } + public string? DistinctId { get; } + public System.TimeSpan Duration { get; } + public Sentry.SessionEndStatus? EndStatus { get; } + public string? Environment { get; } + public int ErrorCount { get; } + public Sentry.SentryId Id { get; } + public string? IpAddress { get; } + public bool IsInitial { get; } + public string Release { get; } + public int SequenceNumber { get; } + public System.DateTimeOffset StartTimestamp { get; } + public System.DateTimeOffset Timestamp { get; } + public string? UserAgent { get; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.SessionUpdate FromJson(System.Text.Json.JsonElement json) { } + } + public class SpanContext : Sentry.Protocol.ITraceContext + { + public SpanContext(string operation, Sentry.SpanId? spanId = default, Sentry.SpanId? parentSpanId = default, Sentry.SentryId? traceId = default, string? description = null, Sentry.SpanStatus? status = default, bool? isSampled = default) { } + public string? Description { get; } + public Sentry.Instrumenter Instrumenter { get; } + public bool? IsSampled { get; } + public string Operation { get; set; } + public string? Origin { get; } + public Sentry.SpanId? ParentSpanId { get; } + public Sentry.SpanId SpanId { get; } + public Sentry.SpanStatus? Status { get; } + public Sentry.SentryId TraceId { get; } + } + public static class SpanDataExtensions + { + public static void SetMeasurement(this Sentry.ISpanData spanData, string name, double value, Sentry.MeasurementUnit unit = default) { } + public static void SetMeasurement(this Sentry.ISpanData spanData, string name, int value, Sentry.MeasurementUnit unit = default) { } + public static void SetMeasurement(this Sentry.ISpanData spanData, string name, long value, Sentry.MeasurementUnit unit = default) { } + [System.CLSCompliant(false)] + public static void SetMeasurement(this Sentry.ISpanData spanData, string name, ulong value, Sentry.MeasurementUnit unit = default) { } + } + public static class SpanExtensions + { + public static Sentry.ITransactionTracer GetTransaction(this Sentry.ISpan span) { } + public static Sentry.ISpan StartChild(this Sentry.ISpan span, string operation, string? description) { } + } + public readonly struct SpanId : Sentry.ISentryJsonSerializable, System.IEquatable + { + public static readonly Sentry.SpanId Empty; + public SpanId(long value) { } + public SpanId(string value) { } + public bool Equals(Sentry.SpanId other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? _) { } + public static Sentry.SpanId Create() { } + public static Sentry.SpanId FromJson(System.Text.Json.JsonElement json) { } + public static Sentry.SpanId Parse(string value) { } + public static string op_Implicit(Sentry.SpanId id) { } + public static bool operator !=(Sentry.SpanId left, Sentry.SpanId right) { } + public static bool operator ==(Sentry.SpanId left, Sentry.SpanId right) { } + } + public enum SpanStatus + { + Ok = 0, + DeadlineExceeded = 1, + Unauthenticated = 2, + PermissionDenied = 3, + NotFound = 4, + ResourceExhausted = 5, + InvalidArgument = 6, + Unimplemented = 7, + Unavailable = 8, + InternalError = 9, + UnknownError = 10, + Cancelled = 11, + AlreadyExists = 12, + FailedPrecondition = 13, + Aborted = 14, + OutOfRange = 15, + DataLoss = 16, + } + public class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext + { + public SpanTracer(Sentry.IHub hub, Sentry.TransactionTracer transaction, Sentry.SpanId? parentSpanId, Sentry.SentryId traceId, string operation) { } + public System.Collections.Generic.IReadOnlyDictionary Data { get; } + public string? Description { get; set; } + public System.DateTimeOffset? EndTimestamp { get; } + public System.Collections.Generic.IReadOnlyDictionary Extra { get; } + public bool IsFinished { get; } + public bool? IsSampled { get; } + public System.Collections.Generic.IReadOnlyDictionary Measurements { get; } + public string Operation { get; set; } + public string? Origin { get; } + public Sentry.SpanId? ParentSpanId { get; } + public Sentry.SpanId SpanId { get; } + public System.DateTimeOffset StartTimestamp { get; } + public Sentry.SpanStatus? Status { get; set; } + public System.Collections.Generic.IReadOnlyDictionary Tags { get; } + public Sentry.SentryId TraceId { get; } + public void Finish() { } + public void Finish(Sentry.SpanStatus status) { } + public void Finish(System.Exception exception) { } + public void Finish(System.Exception exception, Sentry.SpanStatus status) { } + public Sentry.SentryTraceHeader GetTraceHeader() { } + public void SetData(string key, object? value) { } + public void SetExtra(string key, object? value) { } + public void SetMeasurement(string name, Sentry.Protocol.Measurement measurement) { } + public void SetTag(string key, string value) { } + public Sentry.ISpan StartChild(string operation) { } + public void UnsetTag(string key) { } + } + public enum StackTraceMode + { + Original = 0, + Enhanced = 1, + } + public enum StartupTimeDetectionMode + { + None = 0, + Fast = 1, + Best = 2, + } + public class StreamAttachmentContent : Sentry.IAttachmentContent + { + public StreamAttachmentContent(System.IO.Stream stream) { } + public System.IO.Stream GetStream() { } + } + [System.ComponentModel.TypeConverter(typeof(Sentry.StringOrRegexTypeConverter))] + public class StringOrRegex + { + public StringOrRegex(string stringOrRegex) { } + public StringOrRegex(System.Text.RegularExpressions.Regex regex) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + public static Sentry.StringOrRegex op_Implicit(string stringOrRegex) { } + public static Sentry.StringOrRegex op_Implicit(System.Text.RegularExpressions.Regex regex) { } + } + public class TransactionContext : Sentry.SpanContext, Sentry.ITransactionContext, Sentry.Protocol.ITraceContext + { + public TransactionContext(string name, string operation, Sentry.SpanId? spanId = default, Sentry.SpanId? parentSpanId = default, Sentry.SentryId? traceId = default, string? description = "", Sentry.SpanStatus? status = default, bool? isSampled = default, bool? isParentSampled = default, Sentry.TransactionNameSource nameSource = 0) { } + public bool? IsParentSampled { get; } + public string Name { get; set; } + public Sentry.TransactionNameSource NameSource { get; set; } + } + public enum TransactionNameSource + { + Custom = 0, + Url = 1, + Route = 2, + View = 3, + Component = 4, + Task = 5, + } + public class TransactionSamplingContext + { + public TransactionSamplingContext(Sentry.ITransactionContext transactionContext, System.Collections.Generic.IReadOnlyDictionary customSamplingContext) { } + public System.Collections.Generic.IReadOnlyDictionary CustomSamplingContext { get; } + public Sentry.ITransactionContext TransactionContext { get; } + } + public class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext + { + public TransactionTracer(Sentry.IHub hub, Sentry.ITransactionContext context) { } + public System.Collections.Generic.IReadOnlyCollection Breadcrumbs { get; } + public Sentry.SentryContexts Contexts { get; set; } + public System.Collections.Generic.IReadOnlyDictionary Data { get; } + public string? Description { get; set; } + public string? Distribution { get; set; } + public System.DateTimeOffset? EndTimestamp { get; } + public string? Environment { get; set; } + [System.Obsolete("Use Data")] + public System.Collections.Generic.IReadOnlyDictionary Extra { get; } + public System.Collections.Generic.IReadOnlyList Fingerprint { get; set; } + public bool IsFinished { get; } + public bool? IsParentSampled { get; set; } + public bool? IsSampled { get; } + public Sentry.SentryLevel? Level { get; set; } + public System.Collections.Generic.IReadOnlyDictionary Measurements { get; } + public string Name { get; set; } + public Sentry.TransactionNameSource NameSource { get; set; } + public string Operation { get; set; } + public string? Origin { get; } + public Sentry.SpanId? ParentSpanId { get; } + public string? Platform { get; set; } + public string? Release { get; set; } + public Sentry.SentryRequest Request { get; set; } + public double? SampleRate { get; } + public Sentry.SdkVersion Sdk { get; } + public Sentry.SpanId SpanId { get; } + public System.Collections.Generic.IReadOnlyCollection Spans { get; } + public System.DateTimeOffset StartTimestamp { get; } + public Sentry.SpanStatus? Status { get; set; } + public System.Collections.Generic.IReadOnlyDictionary Tags { get; } + public Sentry.SentryId TraceId { get; } + public Sentry.SentryUser User { get; set; } + public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { } + public void Finish() { } + public void Finish(Sentry.SpanStatus status) { } + public void Finish(System.Exception exception) { } + public void Finish(System.Exception exception, Sentry.SpanStatus status) { } + public Sentry.ISpan? GetLastActiveSpan() { } + public Sentry.SentryTraceHeader GetTraceHeader() { } + public void SetData(string key, object? value) { } + [System.Obsolete("Use SetData")] + public void SetExtra(string key, object? value) { } + public void SetMeasurement(string name, Sentry.Protocol.Measurement measurement) { } + public void SetTag(string key, string value) { } + public Sentry.ISpan StartChild(string operation) { } + public void UnsetTag(string key) { } + } + [System.Obsolete("Use SentryFeedback instead.")] + public sealed class UserFeedback : Sentry.ISentryJsonSerializable + { + public UserFeedback(Sentry.SentryId eventId, string? name, string? email, string? comments) { } + public string? Comments { get; } + public string? Email { get; } + public Sentry.SentryId EventId { get; } + public string? Name { get; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.UserFeedback FromJson(System.Text.Json.JsonElement json) { } + } + public sealed class ViewHierarchy : Sentry.ISentryJsonSerializable + { + public ViewHierarchy(string renderingSystem) { } + public string RenderingSystem { get; set; } + public System.Collections.Generic.List Windows { get; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + } + public class ViewHierarchyAttachment : Sentry.SentryAttachment + { + public ViewHierarchyAttachment(Sentry.IAttachmentContent content) { } + } + public abstract class ViewHierarchyNode : Sentry.ISentryJsonSerializable + { + protected ViewHierarchyNode(string type) { } + public System.Collections.Generic.List Children { get; set; } + public string Type { get; set; } + protected abstract void WriteAdditionalProperties(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger); + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + } +} +namespace Sentry.Ben.BlockingDetector +{ + public class SuppressBlockingDetection : System.IDisposable + { + public SuppressBlockingDetection() { } + public void Dispose() { } + } +} +namespace Sentry.CompilerServices +{ + public static class BuildProperties + { + public static void Initialize(System.Collections.Generic.Dictionary properties) { } + } +} +namespace Sentry.Extensibility +{ + public abstract class BaseRequestPayloadExtractor : Sentry.Extensibility.IRequestPayloadExtractor + { + protected BaseRequestPayloadExtractor() { } + protected abstract object? DoExtractPayLoad(Sentry.Extensibility.IHttpRequest request); + public object? ExtractPayload(Sentry.Extensibility.IHttpRequest request) { } + protected abstract bool IsSupported(Sentry.Extensibility.IHttpRequest request); + } + public class DefaultRequestPayloadExtractor : Sentry.Extensibility.BaseRequestPayloadExtractor + { + public DefaultRequestPayloadExtractor() { } + protected override object? DoExtractPayLoad(Sentry.Extensibility.IHttpRequest request) { } + protected override bool IsSupported(Sentry.Extensibility.IHttpRequest request) { } + } + public static class DiagnosticLoggerExtensions + { + public static void LogDebug(this Sentry.Extensibility.IDiagnosticLogger logger, string message) { } + public static void LogDebug(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg) { } + public static void LogDebug(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg, TArg2 arg2) { } + public static void LogDebug(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg, TArg2 arg2, TArg3 arg3) { } + public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, string message) { } + public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message) { } + public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg) { } + public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg) { } + public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg, TArg2 arg2) { } + public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg, TArg2 arg2, TArg3 arg3) { } + public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg, TArg2 arg2, TArg3 arg3, TArg4 arg4) { } + public static void LogFatal(this Sentry.Extensibility.IDiagnosticLogger logger, string message) { } + public static void LogFatal(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message) { } + public static void LogInfo(this Sentry.Extensibility.IDiagnosticLogger logger, string message) { } + public static void LogInfo(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg) { } + public static void LogInfo(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg, TArg2 arg2) { } + public static void LogInfo(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg, TArg2 arg2, TArg3 arg3) { } + public static void LogWarning(this Sentry.Extensibility.IDiagnosticLogger logger, string message) { } + public static void LogWarning(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message) { } + public static void LogWarning(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg) { } + public static void LogWarning(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg, TArg2 arg2) { } + public static void LogWarning(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg, TArg2 arg2, TArg3 arg3) { } + } + public class DisabledHub : Sentry.IHub, Sentry.ISentryClient, Sentry.ISentryScopeManager, System.IDisposable + { + public static readonly Sentry.Extensibility.DisabledHub Instance; + public bool IsEnabled { get; } + public Sentry.SentryId LastEventId { get; } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public Sentry.SentryStructuredLogger Logger { get; } + public void BindClient(Sentry.ISentryClient client) { } + public void BindException(System.Exception exception, Sentry.ISpan span) { } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.SentryHint? hint, System.Action configureScope) { } + public void CaptureFeedback(Sentry.SentryFeedback feedback, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } + public void CaptureFeedback(Sentry.SentryFeedback feedback, System.Action configureScope, Sentry.SentryHint? hint = null) { } + public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } + public void CaptureTransaction(Sentry.SentryTransaction transaction) { } + public void CaptureTransaction(Sentry.SentryTransaction transaction, Sentry.Scope? scope, Sentry.SentryHint? hint) { } + [System.Obsolete("Use CaptureFeedback instead.")] + public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } + public void ConfigureScope(System.Action configureScope) { } + public void ConfigureScope(System.Action configureScope, TArg arg) { } + public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } + public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg) { } + public Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { } + public Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { } + public void Dispose() { } + public void EndSession(Sentry.SessionEndStatus status = 0) { } + public System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout) { } + public Sentry.BaggageHeader? GetBaggage() { } + public Sentry.ISpan? GetSpan() { } + public Sentry.SentryTraceHeader? GetTraceHeader() { } + public void PauseSession() { } + public System.IDisposable PushScope() { } + public System.IDisposable PushScope(TState state) { } + public void ResumeSession() { } + public void SetTag(string key, string value) { } + public void StartSession() { } + public Sentry.ITransactionTracer StartTransaction(Sentry.ITransactionContext context, System.Collections.Generic.IReadOnlyDictionary customSamplingContext) { } + public void UnsetTag(string key) { } + } + public class FormRequestPayloadExtractor : Sentry.Extensibility.BaseRequestPayloadExtractor + { + public FormRequestPayloadExtractor() { } + protected override object? DoExtractPayLoad(Sentry.Extensibility.IHttpRequest request) { } + protected override bool IsSupported(Sentry.Extensibility.IHttpRequest request) { } + } + public sealed class HubAdapter : Sentry.IHub, Sentry.ISentryClient, Sentry.ISentryScopeManager + { + public static readonly Sentry.Extensibility.HubAdapter Instance; + public bool IsEnabled { get; } + public Sentry.SentryId LastEventId { get; } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public Sentry.SentryStructuredLogger Logger { get; } + public void AddBreadcrumb(string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } + public void AddBreadcrumb(Sentry.Infrastructure.ISystemClock clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } + public void BindClient(Sentry.ISentryClient client) { } + public void BindException(System.Exception exception, Sentry.ISpan span) { } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? monitorOptions = null) { } + public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope, Sentry.SentryHint? hint = null) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.SentryHint? hint, System.Action configureScope) { } + public Sentry.SentryId CaptureException(System.Exception exception) { } + public void CaptureFeedback(Sentry.SentryFeedback feedback, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } + public void CaptureFeedback(Sentry.SentryFeedback feedback, System.Action configureScope, Sentry.SentryHint? hint = null) { } + public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } + public void CaptureTransaction(Sentry.SentryTransaction transaction) { } + public void CaptureTransaction(Sentry.SentryTransaction transaction, Sentry.Scope? scope, Sentry.SentryHint? hint) { } + [System.Obsolete("Use CaptureFeedback instead.")] + public void CaptureUserFeedback(Sentry.UserFeedback sentryUserFeedback) { } + public void ConfigureScope(System.Action configureScope) { } + public void ConfigureScope(System.Action configureScope, TArg arg) { } + public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } + public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg) { } + public Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { } + public Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { } + public void EndSession(Sentry.SessionEndStatus status = 0) { } + public System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout) { } + public Sentry.BaggageHeader? GetBaggage() { } + public Sentry.ISpan? GetSpan() { } + public Sentry.SentryTraceHeader? GetTraceHeader() { } + public void PauseSession() { } + public System.IDisposable PushScope() { } + public System.IDisposable PushScope(TState state) { } + public void ResumeSession() { } + public void SetTag(string key, string value) { } + public void StartSession() { } + public Sentry.ITransactionTracer StartTransaction(Sentry.ITransactionContext context, System.Collections.Generic.IReadOnlyDictionary customSamplingContext) { } + public void UnsetTag(string key) { } + } + public interface IBackgroundWorker + { + int QueuedItems { get; } + bool EnqueueEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); + System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); + } + public interface IDiagnosticLogger + { + bool IsEnabled(Sentry.SentryLevel level); + void Log(Sentry.SentryLevel logLevel, string message, System.Exception? exception = null, params object?[] args); + } + public interface IExceptionFilter + { + bool Filter(System.Exception ex); + } + public interface IHttpRequest + { + System.IO.Stream? Body { get; } + long? ContentLength { get; } + string? ContentType { get; } + System.Collections.Generic.IEnumerable>>? Form { get; } + } + public interface INetworkStatusListener + { + bool Online { get; } + System.Threading.Tasks.Task WaitForNetworkOnlineAsync(System.Threading.CancellationToken cancellationToken); + } + public interface IRequestPayloadExtractor + { + object? ExtractPayload(Sentry.Extensibility.IHttpRequest request); + } + public interface ISentryEventExceptionProcessor + { + void Process(System.Exception exception, Sentry.SentryEvent sentryEvent); + } + public interface ISentryEventProcessor + { + Sentry.SentryEvent? Process(Sentry.SentryEvent @event); + } + public interface ISentryEventProcessorWithHint : Sentry.Extensibility.ISentryEventProcessor + { + Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.SentryHint hint); + } + public interface ISentryStackTraceFactory + { + Sentry.SentryStackTrace? Create(System.Exception? exception = null); + } + public interface ISentryTransactionProcessor + { + Sentry.SentryTransaction? Process(Sentry.SentryTransaction transaction); + } + public interface ISentryTransactionProcessorWithHint : Sentry.Extensibility.ISentryTransactionProcessor + { + Sentry.SentryTransaction? Process(Sentry.SentryTransaction transaction, Sentry.SentryHint hint); + } + public interface ITransport + { + System.Threading.Tasks.Task SendEnvelopeAsync(Sentry.Protocol.Envelopes.Envelope envelope, System.Threading.CancellationToken cancellationToken = default); + } + public class RequestBodyExtractionDispatcher : Sentry.Extensibility.IRequestPayloadExtractor + { + public RequestBodyExtractionDispatcher(System.Collections.Generic.IEnumerable extractors, Sentry.SentryOptions options, System.Func sizeSwitch) { } + public object? ExtractPayload(Sentry.Extensibility.IHttpRequest request) { } + } + public enum RequestSize + { + None = 0, + Small = 1, + Medium = 2, + Always = 3, + } + public abstract class SentryEventExceptionProcessor : Sentry.Extensibility.ISentryEventExceptionProcessor + where TException : System.Exception + { + protected SentryEventExceptionProcessor() { } + public void Process(System.Exception? exception, Sentry.SentryEvent sentryEvent) { } + protected abstract void ProcessException(TException exception, Sentry.SentryEvent sentryEvent); + } + public sealed class SentryStackTraceFactory : Sentry.Extensibility.ISentryStackTraceFactory + { + public SentryStackTraceFactory(Sentry.SentryOptions options) { } + public Sentry.SentryStackTrace? Create(System.Exception? exception = null) { } + } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public class StringStackTraceFactory : Sentry.Extensibility.ISentryStackTraceFactory + { + public StringStackTraceFactory(Sentry.SentryOptions options) { } + public Sentry.SentryStackTrace? Create(System.Exception? exception = null) { } + } +} +namespace Sentry.Http +{ + public abstract class HttpTransportBase + { + protected HttpTransportBase(Sentry.SentryOptions options, System.Func? getEnvironmentVariable = null, Sentry.Infrastructure.ISystemClock? clock = null) { } + protected virtual System.Net.Http.HttpRequestMessage CreateRequest(Sentry.Protocol.Envelopes.Envelope envelope) { } + protected void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.Protocol.Envelopes.Envelope envelope) { } + protected System.Threading.Tasks.Task HandleResponseAsync(System.Net.Http.HttpResponseMessage response, Sentry.Protocol.Envelopes.Envelope envelope, System.Threading.CancellationToken cancellationToken) { } + protected Sentry.Protocol.Envelopes.Envelope ProcessEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } + protected System.IO.Stream ReadStreamFromHttpContent(System.Net.Http.HttpContent content) { } + } + public interface ISentryHttpClientFactory + { + System.Net.Http.HttpClient Create(Sentry.SentryOptions options); + } +} +namespace Sentry.Infrastructure +{ + public class ConsoleAndTraceDiagnosticLogger : Sentry.Infrastructure.DiagnosticLogger + { + public ConsoleAndTraceDiagnosticLogger(Sentry.SentryLevel minimalLevel) { } + protected override void LogMessage(string message) { } + } + public class ConsoleDiagnosticLogger : Sentry.Infrastructure.DiagnosticLogger + { + public ConsoleDiagnosticLogger(Sentry.SentryLevel minimalLevel) { } + protected override void LogMessage(string message) { } + } + public abstract class DiagnosticLogger : Sentry.Extensibility.IDiagnosticLogger + { + protected DiagnosticLogger(Sentry.SentryLevel minimalLevel) { } + public bool IsEnabled(Sentry.SentryLevel level) { } + public void Log(Sentry.SentryLevel logLevel, string message, System.Exception? exception = null, params object?[] args) { } + protected abstract void LogMessage(string message); + } + public class FileDiagnosticLogger : Sentry.Infrastructure.DiagnosticLogger + { + public FileDiagnosticLogger(string path, bool alsoWriteToConsole = false) { } + public FileDiagnosticLogger(string path, Sentry.SentryLevel minimalLevel, bool alsoWriteToConsole = false) { } + protected override void LogMessage(string message) { } + } + public interface ISystemClock + { + System.DateTimeOffset GetUtcNow(); + } + public sealed class SystemClock : Sentry.Infrastructure.ISystemClock + { + public static readonly Sentry.Infrastructure.SystemClock Clock; + public System.DateTimeOffset GetUtcNow() { } + } + public class TraceDiagnosticLogger : Sentry.Infrastructure.DiagnosticLogger + { + public TraceDiagnosticLogger(Sentry.SentryLevel minimalLevel) { } + protected override void LogMessage(string message) { } + } +} +namespace Sentry.Integrations +{ + public interface ISdkIntegration + { + void Register(Sentry.IHub hub, Sentry.SentryOptions options); + } +} +namespace Sentry.PlatformAbstractions +{ + public static class FrameworkInfo + { + public static System.Collections.Generic.IReadOnlyDictionary NetFxReleaseVersionMap { get; } + public static System.Collections.Generic.IEnumerable GetInstallations() { } + public static Sentry.PlatformAbstractions.FrameworkInstallation? GetLatest(int clr) { } + } + public class FrameworkInstallation + { + public FrameworkInstallation() { } + public Sentry.PlatformAbstractions.FrameworkProfile? Profile { get; set; } + public int? Release { get; set; } + public int? ServicePack { get; set; } + public string? ShortName { get; set; } + public System.Version? Version { get; set; } + public override string ToString() { } + } + public enum FrameworkProfile + { + Client = 0, + Full = 1, + } + public class SentryRuntime : System.IEquatable + { + public SentryRuntime(string? name = null, string? version = null, string? raw = null, string? identifier = null) { } + public string? Identifier { get; } + public string? Name { get; } + public string? Raw { get; } + public string? Version { get; } + public static Sentry.PlatformAbstractions.SentryRuntime Current { get; } + public bool Equals(Sentry.PlatformAbstractions.SentryRuntime? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string? ToString() { } + } + public static class SentryRuntimeExtensions + { + public static bool IsMono(this Sentry.PlatformAbstractions.SentryRuntime runtime) { } + public static bool IsNetCore(this Sentry.PlatformAbstractions.SentryRuntime runtime) { } + public static bool IsNetFx(this Sentry.PlatformAbstractions.SentryRuntime runtime) { } + } +} +namespace Sentry.Protocol +{ + public sealed class App : Sentry.ISentryJsonSerializable + { + public const string Type = "app"; + public App() { } + public string? Build { get; set; } + public string? BuildType { get; set; } + public string? Hash { get; set; } + public string? Identifier { get; set; } + public bool? InForeground { get; set; } + public string? Name { get; set; } + public System.DateTimeOffset? StartTime { get; set; } + public string? Version { get; set; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? _) { } + public static Sentry.Protocol.App FromJson(System.Text.Json.JsonElement json) { } + } + public sealed class Browser : Sentry.ISentryJsonSerializable + { + public const string Type = "browser"; + public Browser() { } + public string? Name { get; set; } + public string? Version { get; set; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? _) { } + public static Sentry.Protocol.Browser FromJson(System.Text.Json.JsonElement json) { } + } + public sealed class DebugImage : Sentry.ISentryJsonSerializable + { + public DebugImage() { } + public string? CodeFile { get; set; } + public string? CodeId { get; set; } + public string? DebugChecksum { get; set; } + public string? DebugFile { get; set; } + public string? DebugId { get; set; } + public long? ImageAddress { get; set; } + public long? ImageSize { get; set; } + public string? Type { get; set; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.Protocol.DebugImage FromJson(System.Text.Json.JsonElement json) { } + } + public sealed class Device : Sentry.ISentryJsonSerializable + { + public const string Type = "device"; + public Device() { } + public string? Architecture { get; set; } + public float? BatteryLevel { get; set; } + public string? BatteryStatus { get; set; } + public System.DateTimeOffset? BootTime { get; set; } + public string? Brand { get; set; } + public string? CpuDescription { get; set; } + public string? DeviceType { get; set; } + public string? DeviceUniqueIdentifier { get; set; } + public long? ExternalFreeStorage { get; set; } + public long? ExternalStorageSize { get; set; } + public string? Family { get; set; } + public long? FreeMemory { get; set; } + public long? FreeStorage { get; set; } + public bool? IsCharging { get; set; } + public bool? IsOnline { get; set; } + public bool? LowMemory { get; set; } + public string? Manufacturer { get; set; } + public long? MemorySize { get; set; } + public string? Model { get; set; } + public string? ModelId { get; set; } + public string? Name { get; set; } + public Sentry.Protocol.DeviceOrientation? Orientation { get; set; } + public int? ProcessorCount { get; set; } + public float? ProcessorFrequency { get; set; } + public float? ScreenDensity { get; set; } + public int? ScreenDpi { get; set; } + public string? ScreenResolution { get; set; } + public bool? Simulator { get; set; } + public long? StorageSize { get; set; } + public bool? SupportsAccelerometer { get; set; } + public bool? SupportsAudio { get; set; } + public bool? SupportsGyroscope { get; set; } + public bool? SupportsLocationService { get; set; } + public bool? SupportsVibration { get; set; } + public System.TimeZoneInfo? Timezone { get; set; } + public long? UsableMemory { get; set; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? _) { } + public static Sentry.Protocol.Device FromJson(System.Text.Json.JsonElement json) { } + } + public enum DeviceOrientation + { + [System.Runtime.Serialization.EnumMember(Value="portrait")] + Portrait = 0, + [System.Runtime.Serialization.EnumMember(Value="landscape")] + Landscape = 1, + } + public sealed class Gpu : Sentry.ISentryJsonSerializable + { + public const string Type = "gpu"; + public Gpu() { } + public string? ApiType { get; set; } + public string? GraphicsShaderLevel { get; set; } + public int? Id { get; set; } + public int? MaxTextureSize { get; set; } + public int? MemorySize { get; set; } + public bool? MultiThreadedRendering { get; set; } + public string? Name { get; set; } + public string? NpotSupport { get; set; } + public bool? SupportsComputeShaders { get; set; } + public bool? SupportsDrawCallInstancing { get; set; } + public bool? SupportsGeometryShaders { get; set; } + public bool? SupportsRayTracing { get; set; } + public string? VendorId { get; set; } + public string? VendorName { get; set; } + public string? Version { get; set; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? _) { } + public static Sentry.Protocol.Gpu FromJson(System.Text.Json.JsonElement json) { } + } + public interface ITraceContext + { + string? Description { get; } + bool? IsSampled { get; } + string Operation { get; } + string? Origin { get; } + Sentry.SpanId? ParentSpanId { get; } + Sentry.SpanId SpanId { get; } + Sentry.SpanStatus? Status { get; } + Sentry.SentryId TraceId { get; } + } + public sealed class Measurement : Sentry.ISentryJsonSerializable + { + public Sentry.MeasurementUnit Unit { get; } + public object Value { get; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.Protocol.Measurement FromJson(System.Text.Json.JsonElement json) { } + } + public sealed class Mechanism : Sentry.ISentryJsonSerializable + { + public static readonly string DescriptionKey; + public static readonly string HandledKey; + public static readonly string MechanismKey; + public Mechanism() { } + public System.Collections.Generic.IDictionary Data { get; } + public string? Description { get; set; } + public int? ExceptionId { get; set; } + public bool? Handled { get; set; } + public string? HelpLink { get; set; } + public bool IsExceptionGroup { get; set; } + public System.Collections.Generic.IDictionary Meta { get; } + public int? ParentId { get; set; } + public string? Source { get; set; } + public bool Synthetic { get; set; } + public string Type { get; set; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.Protocol.Mechanism FromJson(System.Text.Json.JsonElement json) { } + } + public sealed class OperatingSystem : Sentry.ISentryJsonSerializable + { + public const string Type = "os"; + public OperatingSystem() { } + public string? Build { get; set; } + public string? KernelVersion { get; set; } + public string? Name { get; set; } + public string? RawDescription { get; set; } + public bool? Rooted { get; set; } + public string? Version { get; set; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? _) { } + public static Sentry.Protocol.OperatingSystem FromJson(System.Text.Json.JsonElement json) { } + } + public sealed class Response : Sentry.ISentryJsonSerializable + { + public const string Type = "response"; + public Response() { } + public long? BodySize { get; set; } + public string? Cookies { get; set; } + public object? Data { get; set; } + public System.Collections.Generic.IDictionary Headers { get; } + public short? StatusCode { get; set; } + public Sentry.Protocol.Response Clone() { } + public void UpdateFrom(Sentry.Protocol.Response source) { } + public void UpdateFrom(object source) { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.Protocol.Response FromJson(System.Text.Json.JsonElement json) { } + } + public sealed class Runtime : Sentry.ISentryJsonSerializable + { + public const string Type = "runtime"; + public Runtime() { } + public string? Build { get; set; } + public string? Identifier { get; set; } + public string? Name { get; set; } + public string? RawDescription { get; set; } + public string? Version { get; set; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? _) { } + public static Sentry.Protocol.Runtime FromJson(System.Text.Json.JsonElement json) { } + } + public sealed class SentryException : Sentry.ISentryJsonSerializable + { + public SentryException() { } + public Sentry.Protocol.Mechanism? Mechanism { get; set; } + public string? Module { get; set; } + public Sentry.SentryStackTrace? Stacktrace { get; set; } + public int ThreadId { get; set; } + public string? Type { get; set; } + public string? Value { get; set; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.Protocol.SentryException FromJson(System.Text.Json.JsonElement json) { } + } + public class Trace : Sentry.ISentryJsonSerializable, Sentry.Protocol.ITraceContext + { + public const string Type = "trace"; + public Trace() { } + public System.Collections.Generic.IReadOnlyDictionary Data { get; } + public string? Description { get; set; } + public bool? IsSampled { get; } + public string Operation { get; set; } + public string? Origin { get; } + public Sentry.SpanId? ParentSpanId { get; set; } + public Sentry.SpanId SpanId { get; set; } + public Sentry.SpanStatus? Status { get; set; } + public Sentry.SentryId TraceId { get; set; } + public void SetData(string key, object? value) { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public static Sentry.Protocol.Trace FromJson(System.Text.Json.JsonElement json) { } + } +} +namespace Sentry.Protocol.Envelopes +{ + public sealed class Envelope : Sentry.Protocol.Envelopes.ISerializable, System.IDisposable + { + public Envelope(System.Collections.Generic.IReadOnlyDictionary header, System.Collections.Generic.IReadOnlyList items) { } + public System.Collections.Generic.IReadOnlyDictionary Header { get; } + public System.Collections.Generic.IReadOnlyList Items { get; } + public void Dispose() { } + public void Serialize(System.IO.Stream stream, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public System.Threading.Tasks.Task SerializeAsync(System.IO.Stream stream, Sentry.Extensibility.IDiagnosticLogger? logger, System.Threading.CancellationToken cancellationToken = default) { } + public Sentry.SentryId? TryGetEventId() { } + public static System.Threading.Tasks.Task DeserializeAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default) { } + public static Sentry.Protocol.Envelopes.Envelope FromCheckIn(Sentry.SentryCheckIn checkIn) { } + public static Sentry.Protocol.Envelopes.Envelope FromEvent(Sentry.SentryEvent @event, Sentry.Extensibility.IDiagnosticLogger? logger = null, System.Collections.Generic.IReadOnlyCollection? attachments = null, Sentry.SessionUpdate? sessionUpdate = null) { } + public static Sentry.Protocol.Envelopes.Envelope FromFeedback(Sentry.SentryEvent @event, Sentry.Extensibility.IDiagnosticLogger? logger = null, System.Collections.Generic.IReadOnlyCollection? attachments = null, Sentry.SessionUpdate? sessionUpdate = null) { } + public static Sentry.Protocol.Envelopes.Envelope FromSession(Sentry.SessionUpdate sessionUpdate) { } + public static Sentry.Protocol.Envelopes.Envelope FromTransaction(Sentry.SentryTransaction transaction) { } + [System.Obsolete("Use FromFeedback instead.")] + public static Sentry.Protocol.Envelopes.Envelope FromUserFeedback(Sentry.UserFeedback sentryUserFeedback) { } + } + public sealed class EnvelopeItem : Sentry.Protocol.Envelopes.ISerializable, System.IDisposable + { + public EnvelopeItem(System.Collections.Generic.IReadOnlyDictionary header, Sentry.Protocol.Envelopes.ISerializable payload) { } + public System.Collections.Generic.IReadOnlyDictionary Header { get; } + public Sentry.Protocol.Envelopes.ISerializable Payload { get; } + public void Dispose() { } + public void Serialize(System.IO.Stream stream, Sentry.Extensibility.IDiagnosticLogger? logger) { } + public System.Threading.Tasks.Task SerializeAsync(System.IO.Stream stream, Sentry.Extensibility.IDiagnosticLogger? logger, System.Threading.CancellationToken cancellationToken = default) { } + public string? TryGetFileName() { } + public long? TryGetLength() { } + public string? TryGetType() { } + public static System.Threading.Tasks.Task DeserializeAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default) { } + public static Sentry.Protocol.Envelopes.EnvelopeItem FromAttachment(Sentry.SentryAttachment attachment) { } + public static Sentry.Protocol.Envelopes.EnvelopeItem FromCheckIn(Sentry.SentryCheckIn checkIn) { } + public static Sentry.Protocol.Envelopes.EnvelopeItem FromEvent(Sentry.SentryEvent @event) { } + public static Sentry.Protocol.Envelopes.EnvelopeItem FromFeedback(Sentry.SentryEvent @event) { } + public static Sentry.Protocol.Envelopes.EnvelopeItem FromSession(Sentry.SessionUpdate sessionUpdate) { } + public static Sentry.Protocol.Envelopes.EnvelopeItem FromTransaction(Sentry.SentryTransaction transaction) { } + [System.Obsolete("Use FromFeedback instead.")] + public static Sentry.Protocol.Envelopes.EnvelopeItem FromUserFeedback(Sentry.UserFeedback sentryUserFeedback) { } + } + public interface ISerializable + { + void Serialize(System.IO.Stream stream, Sentry.Extensibility.IDiagnosticLogger? logger); + System.Threading.Tasks.Task SerializeAsync(System.IO.Stream stream, Sentry.Extensibility.IDiagnosticLogger? logger, System.Threading.CancellationToken cancellationToken = default); + } +} +namespace Sentry.Reflection +{ + public static class AssemblyExtensions + { + public static Sentry.SdkVersion GetNameAndVersion(this System.Reflection.Assembly asm) { } + } +} +public static class SentryExceptionExtensions +{ + public static void AddSentryContext(this System.Exception ex, string name, System.Collections.Generic.IReadOnlyDictionary data) { } + public static void AddSentryTag(this System.Exception ex, string name, string value) { } + public static void SetSentryMechanism(this System.Exception ex, string type, string? description = null, bool? handled = default) { } +} \ No newline at end of file diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index 07c413e828..b83e9340d7 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -613,6 +613,7 @@ namespace Sentry Fatal = 4, } [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + [System.Diagnostics.DebuggerDisplay("SentryLog \\{ Level = {Level}, Message = \'{Message}\' \\}")] [System.Runtime.CompilerServices.RequiredMember] public sealed class SentryLog { @@ -716,6 +717,7 @@ namespace Sentry public bool DisableSentryHttpMessageHandler { get; set; } public string? Distribution { get; set; } public string? Dsn { get; set; } + public bool EnableBackpressureHandling { get; set; } public bool EnableScopeSync { get; set; } public bool EnableSpotlight { get; set; } public string? Environment { get; set; } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index 07c413e828..b83e9340d7 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -613,6 +613,7 @@ namespace Sentry Fatal = 4, } [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + [System.Diagnostics.DebuggerDisplay("SentryLog \\{ Level = {Level}, Message = \'{Message}\' \\}")] [System.Runtime.CompilerServices.RequiredMember] public sealed class SentryLog { @@ -716,6 +717,7 @@ namespace Sentry public bool DisableSentryHttpMessageHandler { get; set; } public string? Distribution { get; set; } public string? Dsn { get; set; } + public bool EnableBackpressureHandling { get; set; } public bool EnableScopeSync { get; set; } public bool EnableSpotlight { get; set; } public string? Environment { get; set; } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index 2de7c68513..6999d24318 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -599,6 +599,7 @@ namespace Sentry [System.Runtime.Serialization.EnumMember(Value="fatal")] Fatal = 4, } + [System.Diagnostics.DebuggerDisplay("SentryLog \\{ Level = {Level}, Message = \'{Message}\' \\}")] public sealed class SentryLog { public Sentry.SentryLogLevel Level { get; init; } @@ -686,6 +687,7 @@ namespace Sentry public bool DisableSentryHttpMessageHandler { get; set; } public string? Distribution { get; set; } public string? Dsn { get; set; } + public bool EnableBackpressureHandling { get; set; } public bool EnableScopeSync { get; set; } public bool EnableSpotlight { get; set; } public string? Environment { get; set; } diff --git a/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet10_0.verified.txt b/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet10_0.verified.txt new file mode 100644 index 0000000000..c510d50d43 --- /dev/null +++ b/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet10_0.verified.txt @@ -0,0 +1,190 @@ +[ + { + Header: { + sdk: { + name: sentry.dotnet + } + }, + Items: [ + { + Header: { + type: session + }, + Payload: { + Source: { + DistinctId: Guid_1, + Release: release, + Environment: production, + IsInitial: true + } + } + } + ] + }, + { + Header: { + event_id: Guid_2, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: release, + sample_rand: {Scrubbed}, + sample_rate: 1, + sampled: true, + trace_id: Guid_3, + transaction: my transaction + } + }, + Items: [ + { + Header: { + type: event + }, + Payload: { + Source: { + Platform: csharp, + SentryExceptions: [ + { + Mechanism: { + Type: generic, + Handled: false, + Synthetic: false, + IsExceptionGroup: false + } + } + ], + SentryThreads: [ + { + Crashed: false, + Current: true + } + ], + DebugImages: [ + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: .../System.Private.CoreLib.pdb, + CodeId: ______________, + CodeFile: .../System.Private.CoreLib.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: .../Sentry.Tests.pdb, + CodeId: ______________, + CodeFile: .../Sentry.Tests.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: xunit.execution.dotnet.pdb, + CodeId: ______________, + CodeFile: .../xunit.execution.dotnet.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: xunit.core.pdb, + CodeId: ______________, + CodeFile: .../xunit.core.dll + } + ], + Level: error, + TransactionName: my transaction, + Request: {}, + Contexts: { + trace: { + Operation: + } + }, + User: { + Id: Guid_1 + }, + Environment: production + } + } + }, + { + Header: { + type: session + }, + Payload: { + Source: { + DistinctId: Guid_1, + Release: release, + Environment: production, + ErrorCount: 1, + IsInitial: false, + SequenceNumber: 1, + EndStatus: Crashed + } + } + } + ] + }, + { + Header: { + event_id: Guid_4, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: release, + sample_rand: {Scrubbed}, + sample_rate: 1, + sampled: true, + trace_id: Guid_3, + transaction: my transaction + } + }, + Items: [ + { + Header: { + type: transaction + }, + Payload: { + Source: { + Name: my transaction, + Platform: csharp, + Operation: my operation, + Description: , + Status: Aborted, + IsSampled: true, + SampleRate: 1.0, + Request: {}, + Contexts: { + trace: { + Operation: my operation, + Description: , + Status: Aborted, + IsSampled: true + } + }, + User: { + Id: Guid_1 + }, + Environment: production, + IsFinished: true + } + } + } + ] + } +] \ No newline at end of file diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 2df8394504..342d38f84d 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -5,11 +5,11 @@ namespace Sentry.Tests; -public partial class HubTests +public partial class HubTests : IDisposable { private readonly ITestOutputHelper _output; - private class Fixture + private class Fixture : IDisposable { public SentryOptions Options { get; } public ISentryClient Client { get; set; } @@ -17,6 +17,8 @@ private class Fixture public IInternalScopeManager ScopeManager { get; set; } public ISystemClock Clock { get; set; } public IReplaySession ReplaySession { get; } + public ISampleRandHelper SampleRandHelper { get; set; } + public BackpressureMonitor BackpressureMonitor { get; set; } public Fixture() { @@ -26,13 +28,22 @@ public Fixture() TracesSampleRate = 1.0, AutoSessionTracking = false }; - Client = Substitute.For(); - ReplaySession = Substitute.For(); } - public Hub GetSut() => new(Options, Client, SessionManager, Clock, ScopeManager, replaySession: ReplaySession); + public void Dispose() + { + BackpressureMonitor?.Dispose(); + } + + public Hub GetSut() => new(Options, Client, SessionManager, Clock, ScopeManager, replaySession: ReplaySession, + sampleRandHelper: SampleRandHelper, backpressureMonitor: BackpressureMonitor); + } + + public void Dispose() + { + _fixture.Dispose(); } private readonly Fixture _fixture = new(); @@ -714,6 +725,84 @@ public void StartTransaction_DynamicSamplingContextWithSampleRate_UsesSampleRate transactionTracer.DynamicSamplingContext.Should().BeSameAs(dsc); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void StartTransaction_Backpressure_Downsamples(bool usesTracesSampler) + { + // Arrange + var transactionContext = new TransactionContext("name", "operation"); + + var clock = new MockClock(DateTimeOffset.UtcNow); + _fixture.Options.EnableBackpressureHandling = true; + _fixture.BackpressureMonitor = new BackpressureMonitor(null, clock, enablePeriodicHealthCheck: false); + _fixture.BackpressureMonitor.SetDownsampleLevel(1); + var sampleRate = 0.5f; + var expectedDownsampledRate = sampleRate * _fixture.BackpressureMonitor.DownsampleFactor; + if (usesTracesSampler) + { + _fixture.Options.TracesSampler = _ => sampleRate; + } + else + { + _fixture.Options.TracesSampleRate = sampleRate; + } + + var hub = _fixture.GetSut(); + + // Act + var transaction = hub.StartTransaction(transactionContext, new Dictionary()); + + switch (transaction) + { + // Assert + case TransactionTracer tracer: + tracer.SampleRate.Should().Be(expectedDownsampledRate); + break; + case UnsampledTransaction unsampledTransaction: + unsampledTransaction.SampleRate.Should().Be(expectedDownsampledRate); + break; + default: + throw new Exception("Unexpected transaction type."); + } + } + + [Theory] + [InlineData(true, 0.4f, "backpressure")] + [InlineData(true, 0.6f, "sample_rate")] + [InlineData(false, 0.4f, "backpressure")] + [InlineData(false, 0.6f, "sample_rate")] + public void StartTransaction_Backpressure_SetsDiscardReason(bool usesTracesSampler, double sampleRand, string discardReason) + { + // Arrange + var transactionContext = new TransactionContext("name", "operation"); + + var clock = new MockClock(DateTimeOffset.UtcNow); + _fixture.SampleRandHelper = Substitute.For(); + _fixture.SampleRandHelper.GenerateSampleRand(Arg.Any()).Returns(sampleRand); + _fixture.Options.EnableBackpressureHandling = true; + _fixture.BackpressureMonitor = new BackpressureMonitor(null, clock, enablePeriodicHealthCheck: false); + _fixture.BackpressureMonitor.SetDownsampleLevel(1); + var sampleRate = 0.5f; + if (usesTracesSampler) + { + _fixture.Options.TracesSampler = _ => sampleRate; + } + else + { + _fixture.Options.TracesSampleRate = sampleRate; + } + + var hub = _fixture.GetSut(); + + // Act + var transaction = hub.StartTransaction(transactionContext, new Dictionary()); + transaction.Should().BeOfType(); + var unsampledTransaction = (UnsampledTransaction)transaction; + var expectedReason = new DiscardReason(discardReason); + unsampledTransaction.DiscardReason.Should().Be(expectedReason); + } + // overwrite the 'sample_rate' of the Dynamic Sampling Context (DSC) when a sampling decisions is made in the downstream SDK // 1. overwrite when 'TracesSampler' reaches a sampling decision // 2. keep when a sampling decision has been made upstream (via 'TransactionContext.IsSampled') diff --git a/test/Sentry.Tests/Internals/BackgroundWorkerTests.cs b/test/Sentry.Tests/Internals/BackgroundWorkerTests.cs index d506a179e6..87fbed204e 100644 --- a/test/Sentry.Tests/Internals/BackgroundWorkerTests.cs +++ b/test/Sentry.Tests/Internals/BackgroundWorkerTests.cs @@ -4,7 +4,7 @@ namespace Sentry.Tests.Internals; -public class BackgroundWorkerTests +public class BackgroundWorkerTests : IDisposable { private readonly Fixture _fixture; @@ -13,7 +13,12 @@ public BackgroundWorkerTests(ITestOutputHelper outputHelper) _fixture = new Fixture(outputHelper); } - private class Fixture + public void Dispose() + { + _fixture.Dispose(); + } + + private class Fixture : IDisposable { public IClientReportRecorder ClientReportRecorder { get; private set; } = Substitute.For(); public ITransport Transport { get; set; } = Substitute.For(); @@ -23,6 +28,7 @@ private class Fixture public SentryOptions SentryOptions { get; set; } = new(); private readonly TimeSpan _defaultShutdownTimeout; + public BackpressureMonitor BackpressureMonitor { get; set; } public Fixture(ITestOutputHelper outputHelper) { @@ -39,7 +45,6 @@ public Fixture(ITestOutputHelper outputHelper) var token = callInfo.Arg(); return token.IsCancellationRequested ? Task.FromCanceled(token) : Task.CompletedTask; }); - SentryOptions.Dsn = ValidDsn; SentryOptions.Debug = true; SentryOptions.DiagnosticLogger = Logger; @@ -54,6 +59,7 @@ public BackgroundWorker GetSut() => new( Transport, SentryOptions, + BackpressureMonitor, CancellationTokenSource, Queue); @@ -68,6 +74,11 @@ public IClientReportRecorder UseRealClientReportRecorder() SentryOptions.ClientReportRecorder = ClientReportRecorder; return ClientReportRecorder; } + + public void Dispose() + { + BackpressureMonitor?.Dispose(); + } } [Fact] @@ -244,6 +255,26 @@ public void CaptureEvent_LimitReached_RecordsDiscardedEvent() .RecordDiscardedEvent(DiscardReason.QueueOverflow, DataCategory.Error); } + [Fact] + public void CaptureEvent_LimitReached_CallsBackpressureMonitor() + { + // Arrange + var clock = new MockClock(DateTimeOffset.UtcNow); + _fixture.BackpressureMonitor = new BackpressureMonitor(null, clock, false); + var envelope = Envelope.FromEvent(new SentryEvent()); + _fixture.SentryOptions.MaxQueueItems = 1; + + using var sut = _fixture.GetSut(); + sut.EnqueueEnvelope(envelope, process: false); + + // Act + sut.EnqueueEnvelope(envelope); + + // Assert + _fixture.BackpressureMonitor.LastQueueOverflowTicks.Should().Be(clock.GetUtcNow().Ticks); + _fixture.BackpressureMonitor.IsHealthy.Should().BeFalse(); + } + [Fact] public void CaptureEvent_DisposedWorker_ThrowsObjectDisposedException() { diff --git a/test/Sentry.Tests/Internals/BackpressureMonitorTests.cs b/test/Sentry.Tests/Internals/BackpressureMonitorTests.cs new file mode 100644 index 0000000000..9cb10a4c3d --- /dev/null +++ b/test/Sentry.Tests/Internals/BackpressureMonitorTests.cs @@ -0,0 +1,163 @@ +namespace Sentry.Tests.Internals; + +public class BackpressureMonitorTests +{ + private class Fixture + { + private IDiagnosticLogger Logger { get; } = Substitute.For(); + public ISystemClock Clock { get; } = Substitute.For(); + public DateTimeOffset Now { get; set; } = DateTimeOffset.UtcNow; + + public BackpressureMonitor GetSut() => new(Logger, Clock, enablePeriodicHealthCheck: false); + } + + private readonly Fixture _fixture = new(); + + [Fact] + public void DownsampleFactor_Initial_IsOne() + { + // Arrange + using var monitor = _fixture.GetSut(); + + // Act + var factor = monitor.DownsampleFactor; + + // Assert + factor.Should().Be(1.0); + } + + [Theory] + [InlineData(0, 1.0)] + [InlineData(1, 0.5)] + [InlineData(2, 0.25)] + [InlineData(10, 1.0 / 1024)] + public void DownsampleFactor_CalculatesCorrectly(int level, double expected) + { + // Arrange + using var monitor = _fixture.GetSut(); + monitor.SetDownsampleLevel(level); + + // Act + var factor = monitor.DownsampleFactor; + + // Assert + factor.Should().BeApproximately(expected, 1e-8); + } + + [Fact] + public void RecordRateLimitHit_UpdatesState() + { + // Arrange + using var monitor = _fixture.GetSut(); + var when = _fixture.Now.Subtract(TimeSpan.FromSeconds(1)); + + // Act + monitor.RecordRateLimitHit(when); + + // Assert + monitor.LastRateLimitEventTicks.Should().Be(when.Ticks); + } + + [Fact] + public void RecordQueueOverflow_UpdatesState() + { + // Arrange + _fixture.Clock.GetUtcNow().Returns(_fixture.Now); + using var monitor = _fixture.GetSut(); + + // Act + monitor.RecordQueueOverflow(); + + // Assert + monitor.LastQueueOverflowTicks.Should().Be(_fixture.Now.Ticks); + } + + [Fact] + public void IsHealthy_True_WhenNoRecentEvents() + { + // Arrange + _fixture.Clock.GetUtcNow().Returns(_fixture.Now); + using var monitor = _fixture.GetSut(); + + // Act & Assert + monitor.IsHealthy.Should().BeTrue(); + } + + [Fact] + public void IsHealthy_False_WhenRecentQueueOverflow() + { + // Arrange + _fixture.Clock.GetUtcNow().Returns(_fixture.Now); + using var monitor = _fixture.GetSut(); + + // Act + monitor.RecordQueueOverflow(); + + // Assert + monitor.IsHealthy.Should().BeFalse(); + } + + [Fact] + public void IsHealthy_False_WhenRecentRateLimit() + { + // Arrange + _fixture.Clock.GetUtcNow().Returns(_fixture.Now); + using var monitor = _fixture.GetSut(); + + // Act + monitor.RecordRateLimitHit(_fixture.Now); + + // Assert + monitor.IsHealthy.Should().BeFalse(); + } + + [Fact] + public void DoHealthCheck_Unhealthy_DownsampleLevelIncreases() + { + // Arrange + _fixture.Clock.GetUtcNow().Returns(_fixture.Now); + using var monitor = _fixture.GetSut(); + monitor.RecordQueueOverflow(); + + // Act + monitor.DoHealthCheck(); + + // Assert + monitor.DownsampleLevel.Should().Be(1); + } + + [Fact] + public void DoHealthCheck_Unhealthy_MaximumDownsampleLevelRespected() + { + // Arrange + _fixture.Clock.GetUtcNow().Returns(_fixture.Now); + using var monitor = _fixture.GetSut(); + monitor.RecordQueueOverflow(); + + // Act + var overmax = BackpressureMonitor.MaxDownsamples + 1; + for (var i = 0; i <= overmax; i++) + { + monitor.DoHealthCheck(); + } + + // Assert + monitor.DownsampleLevel.Should().Be(BackpressureMonitor.MaxDownsamples); + } + + [Fact] + public void DoHealthCheck_Healthy_DownsampleLevelResets() + { + // Arrange + _fixture.Clock.GetUtcNow().Returns(_fixture.Now); + using var monitor = _fixture.GetSut(); + monitor.SetDownsampleLevel(2); + + // Act + monitor.DoHealthCheck(); + + // Assert + monitor.IsHealthy.Should().BeTrue(); + monitor.DownsampleLevel.Should().Be(0); + } +} diff --git a/test/Sentry.Tests/Internals/Http/HttpTransportTests.cs b/test/Sentry.Tests/Internals/Http/HttpTransportTests.cs index 2c92200cd9..2b4ec36cc8 100644 --- a/test/Sentry.Tests/Internals/Http/HttpTransportTests.cs +++ b/test/Sentry.Tests/Internals/Http/HttpTransportTests.cs @@ -21,7 +21,7 @@ public async Task SendEnvelopeAsync_CancellationToken_PassedToClient() { // Arrange using var source = new CancellationTokenSource(); - source.Cancel(); + await source.CancelAsync(); var token = source.Token; var httpHandler = Substitute.For(); @@ -36,17 +36,20 @@ public async Task SendEnvelopeAsync_CancellationToken_PassedToClient() var envelope = Envelope.FromEvent( new SentryEvent(eventId: SentryResponses.ResponseId)); -#if NET5_0_OR_GREATER - await Assert.ThrowsAsync(() => httpTransport.SendEnvelopeAsync(envelope, token)); -#else // Act - await httpTransport.SendEnvelopeAsync(envelope, token); + try + { + await httpTransport.SendEnvelopeAsync(envelope, token); + } + catch (TaskCanceledException) + { + // Swallow this + } // Assert await httpHandler .Received(1) .VerifiableSendAsync(Arg.Any(), Arg.Is(c => c.IsCancellationRequested)); -#endif } [Fact] @@ -298,6 +301,7 @@ public async Task SendEnvelopeAsync_ItemRateLimit_DropsItem(string metricNamespa Debug = true }, new HttpClient(httpHandler), + null, clock: _fakeClock); // First request always goes through @@ -382,6 +386,7 @@ public async Task SendEnvelopeAsync_RateLimited_CountsDiscardedEventsCorrectly() var httpTransport = new HttpTransport( options, new HttpClient(httpHandler), + null, clock: _fakeClock ); @@ -846,4 +851,38 @@ public void ProcessEnvelope_SendClientReportsEnabled_ShouldReportTransactionsAnd var expectedDiscardedSpanCount = transaction.Spans.Count + 1; options.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.RateLimitBackoff, DataCategory.Span, expectedDiscardedSpanCount); } + + [Fact] + public async Task SendEnvelopeAsync_RateLimited_CallsBackpressureMonitor() + { + // Arrange + using var httpHandler = new RecordingHttpMessageHandler( + new FakeHttpMessageHandler( + () => SentryResponses.GetRateLimitResponse("1234:event, 897:transaction") + )); + + using var backpressureMonitor = new BackpressureMonitor(null, _fakeClock, false); + var options = new SentryOptions + { + Dsn = ValidDsn, + DiagnosticLogger = _testOutputLogger, + SendClientReports = false, + ClientReportRecorder = Substitute.For(), + Debug = true + }; + + var httpTransport = new HttpTransport( + options, + new HttpClient(httpHandler), + backpressureMonitor, + clock: _fakeClock + ); + + // Act + await httpTransport.SendEnvelopeAsync(Envelope.FromEvent(new SentryEvent())); + + // Assert + backpressureMonitor.LastRateLimitEventTicks.Should().Be(_fakeClock.GetUtcNow().Ticks); + backpressureMonitor.IsHealthy.Should().BeFalse(); + } } diff --git a/test/Sentry.Tests/Internals/ILSpy/SingleFileAppTests.cs b/test/Sentry.Tests/Internals/ILSpy/SingleFileAppTests.cs index 784c580d59..e7a1c15d3e 100644 --- a/test/Sentry.Tests/Internals/ILSpy/SingleFileAppTests.cs +++ b/test/Sentry.Tests/Internals/ILSpy/SingleFileAppTests.cs @@ -30,16 +30,12 @@ static SingleFileAppTests() InValidBundleFile = Path.Combine(testRoot, invalidBundle); } -#if NET9_0 +#if NET10_0 + private static string TargetFramework => "net10.0"; +#elif NET9_0 private static string TargetFramework => "net9.0"; #elif NET8_0 private static string TargetFramework => "net8.0"; -#elif NET7_0 - private static string TargetFramework => "net7.0"; -#elif NET6_0 - private static string TargetFramework => "net6.0"; -#elif NET5_0 - private static string TargetFramework => "net5.0"; #else // Adding a new TFM to the project? Include it above #error "Target Framework not yet supported for single file apps" diff --git a/test/Sentry.Tests/Internals/MemoryInfoTests.WriteTo.DotNet10_0.verified.txt b/test/Sentry.Tests/Internals/MemoryInfoTests.WriteTo.DotNet10_0.verified.txt new file mode 100644 index 0000000000..1168853c1f --- /dev/null +++ b/test/Sentry.Tests/Internals/MemoryInfoTests.WriteTo.DotNet10_0.verified.txt @@ -0,0 +1,19 @@ +{ + total_allocated_bytes: 1, + fragmented_bytes: 2, + heap_size_bytes: 3, + high_memory_load_threshold_bytes: 4, + total_available_memory_bytes: 5, + memory_load_bytes: 6, + total_committed_bytes: 7, + promoted_bytes: 8, + pinned_objects_count: 9, + pause_time_percentage: 10, + index: 11, + finalization_pending_count: 12, + compacted: true, + concurrent: false, + pause_durations: [ + 1000 + ] +} \ No newline at end of file diff --git a/test/Sentry.Tests/MetricTests.Serialize_Counter_statsd.DotNet8_0.verified.txt b/test/Sentry.Tests/MetricTests.Serialize_Counter_statsd.DotNet8_0.verified.txt deleted file mode 100644 index 1f67076aaf..0000000000 --- a/test/Sentry.Tests/MetricTests.Serialize_Counter_statsd.DotNet8_0.verified.txt +++ /dev/null @@ -1 +0,0 @@ -my.counter@widgets:5|c|#tag1:SanitizeValue(value),tag2:SanitizeValue(value)|T1702270140 diff --git a/test/Sentry.Tests/Sentry.Tests.csproj b/test/Sentry.Tests/Sentry.Tests.csproj index 3f00bc5a9f..1a6e4f3ef8 100644 --- a/test/Sentry.Tests/Sentry.Tests.csproj +++ b/test/Sentry.Tests/Sentry.Tests.csproj @@ -1,10 +1,10 @@  - net9.0;net8.0 - $(TargetFrameworks);net8.0-android34.0;net9.0-android35.0 - $(TargetFrameworks);net8.0-ios17.0;net9.0-ios18.0 - $(TargetFrameworks);net8.0-maccatalyst17.0;net9.0-maccatalyst18.0 + $(CurrentTfms) + $(TargetFrameworks);$(LatestAndroidTfm);$(PreviousAndroidTfm) + $(TargetFrameworks);$(LatestIosTfm);$(PreviousIosTfm) + $(TargetFrameworks);$(LatestMacCatalystTfm);$(PreviousMacCatalystTfm) $(TargetFrameworks);net48 diff --git a/test/Sentry.Tests/SentryClientTests.cs b/test/Sentry.Tests/SentryClientTests.cs index 0ebb3eb2af..a2c3782638 100644 --- a/test/Sentry.Tests/SentryClientTests.cs +++ b/test/Sentry.Tests/SentryClientTests.cs @@ -4,9 +4,9 @@ namespace Sentry.Tests; -public partial class SentryClientTests +public partial class SentryClientTests : IDisposable { - private class Fixture + private class Fixture : IDisposable { public SentryOptions SentryOptions { get; set; } = new() { @@ -17,7 +17,9 @@ private class Fixture public IBackgroundWorker BackgroundWorker { get; set; } = Substitute.For(); public IClientReportRecorder ClientReportRecorder { get; } = Substitute.For(); + public RandomValuesFactory RandomValuesFactory { get; set; } = null; public ISessionManager SessionManager { get; set; } = Substitute.For(); + public BackpressureMonitor BackpressureMonitor { get; set; } public Fixture() { @@ -27,9 +29,19 @@ public Fixture() public SentryClient GetSut() { - var randomValuesFactory = new IsolatedRandomValuesFactory(); - return new SentryClient(SentryOptions, BackgroundWorker, randomValuesFactory, SessionManager); + var randomValuesFactory = RandomValuesFactory ?? new IsolatedRandomValuesFactory(); + return new SentryClient(SentryOptions, BackgroundWorker, randomValuesFactory, SessionManager, BackpressureMonitor); } + + public void Dispose() + { + BackpressureMonitor?.Dispose(); + } + } + + public void Dispose() + { + _fixture.Dispose(); } private readonly Fixture _fixture = new(); @@ -590,6 +602,29 @@ public void CaptureEvent_SampleDrop_RecordsDiscard() .RecordDiscardedEvent(DiscardReason.SampleRate, DataCategory.Error); } + [Theory] + [InlineData(0.6f, "sample_rate")] // Sample rand is greater than the sample rate + [InlineData(0.4f, "backpressure")] // Sample is dropped due to downsampling + public void CaptureEvent_SampleDrop_RecordsCorrectDiscardReason(double sampleRand, string discardReason) + { + // Arrange + _fixture.RandomValuesFactory = Substitute.For(); + _fixture.RandomValuesFactory.NextDouble().Returns(sampleRand); + _fixture.SentryOptions.SampleRate = 0.5f; + var logger = Substitute.For(); + _fixture.BackpressureMonitor = new BackpressureMonitor(logger, null, false); + _fixture.BackpressureMonitor.SetDownsampleLevel(1); + var sut = _fixture.GetSut(); + + // Act + var @event = new SentryEvent(); + _ = sut.CaptureEvent(@event); + + // Assert + var expectedReason = new DiscardReason(discardReason); + _fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(expectedReason, DataCategory.Error); + } + [Fact] public void CaptureEvent_SamplingHighest_SendsEvent() { @@ -624,17 +659,28 @@ public void CaptureEvent_SamplingNull_DropsEvent() } [Theory] - [InlineData(0.25f)] - [InlineData(0.50f)] - [InlineData(0.75f)] - public void CaptureEvent_WithSampleRate_AppropriateDistribution(float sampleRate) + [InlineData(0.25f, 0)] + [InlineData(0.50f, 0)] + [InlineData(0.75f, 0)] + [InlineData(0.25f, 1)] + [InlineData(0.50f, 1)] + [InlineData(0.75f, 1)] + [InlineData(0.25f, 3)] + [InlineData(0.50f, 3)] + [InlineData(0.75f, 3)] + public void CaptureEvent_WithSampleRate_AppropriateDistribution(float sampleRate, int downsampleLevel) { // Arrange + var now = DateTimeOffset.UtcNow; + var clock = new MockClock(now); + _fixture.BackpressureMonitor = new BackpressureMonitor(null, clock, enablePeriodicHealthCheck: false); + _fixture.BackpressureMonitor.SetDownsampleLevel(downsampleLevel); + _fixture.SentryOptions.SampleRate = sampleRate; + const int numEvents = 1000; const double allowedRelativeDeviation = 0.15; const uint allowedDeviation = (uint)(allowedRelativeDeviation * numEvents); - var expectedSampled = (int)(sampleRate * numEvents); - _fixture.SentryOptions.SampleRate = sampleRate; + var expectedSampled = (int)(numEvents * sampleRate * _fixture.BackpressureMonitor.DownsampleFactor); // This test expects an approximate uniform distribution of random numbers, so we'll retry a few times. TestHelpers.RetryTest(maxAttempts: 3, _output, () => @@ -695,7 +741,7 @@ public void CaptureEvent_Processing_Order() var logger = Substitute.For(); logger.IsEnabled(Arg.Any()).Returns(true); - logger.When(x => x.Log(Arg.Any(), Arg.Is("Event not sampled."))) + logger.When(x => x.Log(Arg.Any(), Arg.Is("Event sampled in."))) .Do(_ => processingOrder.Add("SampleRate")); _fixture.SentryOptions.DiagnosticLogger = logger; _fixture.SentryOptions.Debug = true; diff --git a/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet10_0.DotNet.verified.txt b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet10_0.DotNet.verified.txt new file mode 100644 index 0000000000..dfbc55fc32 --- /dev/null +++ b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet10_0.DotNet.verified.txt @@ -0,0 +1,38 @@ +[ + { + Message: Initializing Hub for Dsn: '{0}'., + Args: [ + https://d4d82fc1c2c4032a83f3a29aa3a3aff@fake-sentry.io:65535/2147483647 + ] + }, + { + Message: Registering integration: '{0}'., + Args: [ + AutoSessionTrackingIntegration + ] + }, + { + Message: Registering integration: '{0}'., + Args: [ + AppDomainUnhandledExceptionIntegration + ] + }, + { + Message: Registering integration: '{0}'., + Args: [ + AppDomainProcessExitIntegration + ] + }, + { + Message: Registering integration: '{0}'., + Args: [ + UnobservedTaskExceptionIntegration + ] + }, + { + Message: Registering integration: '{0}'., + Args: [ + SentryDiagnosticListenerIntegration + ] + } +] \ No newline at end of file diff --git a/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet10_0.Windows.DotNet.verified.txt b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet10_0.Windows.DotNet.verified.txt new file mode 100644 index 0000000000..10eaeaf749 --- /dev/null +++ b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet10_0.Windows.DotNet.verified.txt @@ -0,0 +1,44 @@ +[ + { + Message: Initializing Hub for Dsn: '{0}'., + Args: [ + https://d4d82fc1c2c4032a83f3a29aa3a3aff@fake-sentry.io:65535/2147483647 + ] + }, + { + Message: Registering integration: '{0}'., + Args: [ + AutoSessionTrackingIntegration + ] + }, + { + Message: Registering integration: '{0}'., + Args: [ + AppDomainUnhandledExceptionIntegration + ] + }, + { + Message: Registering integration: '{0}'., + Args: [ + AppDomainProcessExitIntegration + ] + }, + { + Message: Registering integration: '{0}'., + Args: [ + UnobservedTaskExceptionIntegration + ] + }, + { + Message: Registering integration: '{0}'., + Args: [ + SentryDiagnosticListenerIntegration + ] + }, + { + Message: Registering integration: '{0}'., + Args: [ + WinUIUnhandledExceptionIntegration + ] + } +] \ No newline at end of file diff --git a/test/SingleFileTestApp/SingleFileTestApp.csproj b/test/SingleFileTestApp/SingleFileTestApp.csproj index 87988ebd8a..93cc9d644a 100644 --- a/test/SingleFileTestApp/SingleFileTestApp.csproj +++ b/test/SingleFileTestApp/SingleFileTestApp.csproj @@ -3,7 +3,7 @@ false Exe - net8.0 + net9.0 11 true true