Skip to content

Commit

Permalink
Merge branch 'main' into feat/enable-windows-native
Browse files Browse the repository at this point in the history
  • Loading branch information
vaind committed Nov 21, 2024
2 parents 729bd1a + 07cd9e8 commit c135c54
Show file tree
Hide file tree
Showing 23 changed files with 252 additions and 154 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/drift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
cd drift
flutter test --coverage --test-randomize-ordering-seed=random
- uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # pin@v3
- uses: codecov/codecov-action@5c47607acb93fed5485fdbf7232e8a31425f672a # pin@v3
if: runner.os == 'Linux' && matrix.sdk == 'stable' && matrix.target == 'linux'
with:
name: sentry_drift
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/flutter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ jobs:
flutter test --coverage --test-randomize-ordering-seed=random
dart run remove_from_coverage -f coverage/lcov.info -r 'binding.dart'
- uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # pin@v3
- uses: codecov/codecov-action@5c47607acb93fed5485fdbf7232e8a31425f672a # pin@v3
if: matrix.sdk == 'stable' && matrix.target == 'linux'
with:
name: sentry_flutter
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/isar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
cd isar
flutter test -j 1 --coverage --test-randomize-ordering-seed=random
- uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # pin@v3
- uses: codecov/codecov-action@5c47607acb93fed5485fdbf7232e8a31425f672a # pin@v3
if: runner.os == 'Linux' && matrix.sdk == 'stable' && matrix.target == 'linux'
with:
name: sentry_isar
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sqflite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
cd sqflite
flutter test --coverage --test-randomize-ordering-seed=random
- uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # pin@v3
- uses: codecov/codecov-action@5c47607acb93fed5485fdbf7232e8a31425f672a # pin@v3
if: runner.os == 'Linux' && matrix.sdk == 'stable' && matrix.target == 'linux'
with:
name: sentry_sqflite
Expand Down
30 changes: 18 additions & 12 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,11 @@

## Unreleased

### Enhancements

- Remove `sentry` frames if SDK falls back to current stack trace ([#2351](https://github.com/getsentry/sentry-dart/pull/2351))
- Flutter doesn't always provide stack traces for unhandled errors - this is normal Flutter behavior
- When no stack trace is provided (in Flutter errors, `captureException`, or `captureMessage`):
- SDK creates a synthetic trace using `StackTrace.current`
- Internal SDK frames are removed to reduce noise
- Original stack traces (when provided) are left unchanged

### Features

- Windows native error & obfuscation support ([#2286](https://github.com/getsentry/sentry-dart/pull/2286), [#2426](https://github.com/getsentry/sentry-dart/pull/2426))
- Improve app start measurements by using `addTimingsCallback` instead of `addPostFrameCallback` to determine app start end ([#2405](https://github.com/getsentry/sentry-dart/pull/2405))
- ⚠️ This change may result in reporting of shorter app start durations
- Improve frame tracking accuracy ([#2372](https://github.com/getsentry/sentry-dart/pull/2372))
- Introduces `SentryWidgetsFlutterBinding` that tracks a frame starting from `handleBeginFrame` and ending in `handleDrawFrame`, this is approximately the [buildDuration](https://api.flutter.dev/flutter/dart-ui/FrameTiming/buildDuration.html) time
- By default, `SentryFlutter.init()` automatically initializes `SentryWidgetsFlutterBinding` through the `WidgetsFlutterBindingIntegration`
Expand All @@ -29,6 +22,16 @@
```
- ⚠️ Frame tracking will be disabled if a different binding is used

### Enhancements

- Only send debug images referenced in the stacktrace for events ([#2329](https://github.com/getsentry/sentry-dart/pull/2329))
- Remove `sentry` frames if SDK falls back to current stack trace ([#2351](https://github.com/getsentry/sentry-dart/pull/2351))
- Flutter doesn't always provide stack traces for unhandled errors - this is normal Flutter behavior
- When no stack trace is provided (in Flutter errors, `captureException`, or `captureMessage`):
- SDK creates a synthetic trace using `StackTrace.current`
- Internal SDK frames are removed to reduce noise
- Original stack traces (when provided) are left unchanged

### Fixes

- Apply default IP address (`{{auto}}`) to transactions ([#2395](https://github.com/getsentry/sentry-dart/pull/2395))
Expand All @@ -39,9 +42,12 @@

### Dependencies

- Bump Android SDK from v7.16.0 to v7.17.0 ([#2408](https://github.com/getsentry/sentry-dart/pull/2408))
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#7170)
- [diff](https://github.com/getsentry/sentry-java/compare/7.16.0...7.17.0)
- Bump Android SDK from v7.16.0 to v7.18.0 ([#2408](https://github.com/getsentry/sentry-dart/pull/2408), [#2419](https://github.com/getsentry/sentry-dart/pull/2419))
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#7180)
- [diff](https://github.com/getsentry/sentry-java/compare/7.16.0...7.18.0)
- Bump Native SDK from v0.7.12 to v0.7.13 ([#2420](https://github.com/getsentry/sentry-dart/pull/2420))
- [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0713)
- [diff](https://github.com/getsentry/sentry-native/compare/0.7.12...0.7.13)

## 8.10.1

Expand Down
2 changes: 1 addition & 1 deletion flutter/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ android {
}

dependencies {
api 'io.sentry:sentry-android:7.17.0'
api 'io.sentry:sentry-android:7.18.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"

// Required -- JUnit 4 framework
Expand Down
2 changes: 1 addition & 1 deletion flutter/example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ buildscript {
classpath 'io.sentry:sentry-android-gradle-plugin:4.12.0'
classpath 'com.android.tools.build:gradle:8.3.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'io.github.howardpang:androidNativeBundle:1.1.3'
classpath 'io.github.howardpang:androidNativeBundle:1.1.5'
}
}

Expand Down
2 changes: 2 additions & 0 deletions flutter/ios/Classes/SentryFlutterPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
#import <FlutterMacOS/FlutterMacOS.h>
#endif

#import <Sentry/SentryDebugImageProvider.h>

@interface SentryFlutterPlugin : NSObject<FlutterPlugin>
@end
31 changes: 27 additions & 4 deletions flutter/ios/Classes/SentryFlutterPluginApple.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
loadContexts(result: result)

case "loadImageList":
loadImageList(result: result)
loadImageList(call, result: result)

case "initNativeSdk":
initNativeSdk(call, result: result)
Expand Down Expand Up @@ -277,9 +277,32 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
}
}

private func loadImageList(result: @escaping FlutterResult) {
let debugImages = PrivateSentrySDKOnly.getDebugImages() as [DebugMeta]
result(debugImages.map { $0.serialize() })
private func loadImageList(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
var debugImages: [DebugMeta] = []

if let arguments = call.arguments as? [String], !arguments.isEmpty {
var imagesAddresses: Set<String> = []

for argument in arguments {
let hexDigits = argument.replacingOccurrences(of: "0x", with: "")
if let instructionAddress = UInt64(hexDigits, radix: 16) {
let image = SentryDependencyContainer.sharedInstance().binaryImageCache.image(
byAddress: instructionAddress)
if let image = image {
let imageAddress = sentry_formatHexAddressUInt64(image.address)!
imagesAddresses.insert(imageAddress)
}
}
}
debugImages =
SentryDependencyContainer.sharedInstance().debugImageProvider
.getDebugImagesForImageAddressesFromCache(imageAddresses: imagesAddresses) as [DebugMeta]
}
if debugImages.isEmpty {
debugImages = PrivateSentrySDKOnly.getDebugImages() as [DebugMeta]
}

result(debugImages.map { $0.serialize() })
}

private func initNativeSdk(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
Expand Down
19 changes: 9 additions & 10 deletions flutter/lib/src/frame_callback_handler.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/scheduler.dart';

/// Use instead of TimingsCallback as it is not available in the Flutter min version
typedef SentryTimingsCallback = void Function(List<FrameTiming> timings);

abstract class FrameCallbackHandler {
void addPostFrameCallback(FrameCallback callback);
void addPersistentFrameCallback(FrameCallback callback);
Future<void> get endOfFrame;
bool get hasScheduledFrame;
void removeTimingsCallback(SentryTimingsCallback callback);
void addTimingsCallback(SentryTimingsCallback callback);
}

class DefaultFrameCallbackHandler implements FrameCallbackHandler {
Expand All @@ -18,19 +20,16 @@ class DefaultFrameCallbackHandler implements FrameCallbackHandler {
}

@override
void addPersistentFrameCallback(FrameCallback callback) {
void addTimingsCallback(SentryTimingsCallback callback) {
try {
WidgetsBinding.instance.addPersistentFrameCallback(callback);
WidgetsBinding.instance.addTimingsCallback(callback);
} catch (_) {}
}

@override
Future<void> get endOfFrame async {
void removeTimingsCallback(SentryTimingsCallback callback) {
try {
await WidgetsBinding.instance.endOfFrame;
WidgetsBinding.instance.removeTimingsCallback(callback);
} catch (_) {}
}

@override
bool get hasScheduledFrame => WidgetsBinding.instance.hasScheduledFrame;
}
20 changes: 17 additions & 3 deletions flutter/lib/src/integrations/native_app_start_integration.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:ui';

import 'package:meta/meta.dart';

Expand Down Expand Up @@ -28,15 +29,24 @@ class NativeAppStartIntegration extends Integration<SentryFlutterOptions> {
}

final Completer<void> _appStartEndCompleter = Completer<void>();
bool _allowProcessing = true;

@override
void call(Hub hub, SentryFlutterOptions options) async {
_frameCallbackHandler.addPostFrameCallback((timeStamp) async {
void timingsCallback(List<FrameTiming> timings) async {
if (!_allowProcessing) {
return;
}
// Set immediately to prevent multiple executions
// we only care about the first frame
_allowProcessing = false;

try {
DateTime? appStartEnd;
if (options.autoAppStart) {
// ignore: invalid_use_of_internal_member
appStartEnd = options.clock();
appStartEnd = DateTime.fromMicrosecondsSinceEpoch(timings.first
.timestampInMicroseconds(FramePhase.rasterFinishWallTime));
} else if (_appStartEnd == null) {
await _appStartEndCompleter.future.timeout(
const Duration(seconds: 10),
Expand All @@ -62,8 +72,12 @@ class NativeAppStartIntegration extends Integration<SentryFlutterOptions> {
if (options.automatedTestMode) {
rethrow;
}
} finally {
_frameCallbackHandler.removeTimingsCallback(timingsCallback);
}
});
}

_frameCallbackHandler.addTimingsCallback(timingsCallback);
options.sdk.addIntegration('nativeAppStartIntegration');
}
}
11 changes: 9 additions & 2 deletions flutter/lib/src/native/sentry_native_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,15 @@ class SentryNativeChannel
@override
Future<List<DebugImage>?> loadDebugImages(SentryStackTrace stackTrace) =>
tryCatchAsync('loadDebugImages', () async {
final images = await channel
.invokeListMethod<Map<dynamic, dynamic>>('loadImageList');
Set<String> instructionAddresses = {};
for (final frame in stackTrace.frames) {
if (frame.instructionAddr != null) {
instructionAddresses.add(frame.instructionAddr!);
}
}

final images = await channel.invokeListMethod<Map<dynamic, dynamic>>(
'loadImageList', instructionAddresses.toList());
return images
?.map((e) => e.cast<String, dynamic>())
.map(DebugImage.fromJson)
Expand Down
14 changes: 6 additions & 8 deletions flutter/lib/src/sentry_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ mixin SentryFlutter {
}
integrations.add(LoadImageListIntegration(native));
integrations.add(FramesTrackingIntegration(native));
integrations.add(
NativeAppStartIntegration(
DefaultFrameCallbackHandler(),
NativeAppStartHandler(native),
),
);
options.enableDartSymbolication = false;
}

Expand All @@ -193,14 +199,6 @@ mixin SentryFlutter {
// in errors.
integrations.add(LoadReleaseIntegration());

if (native != null) {
integrations.add(
NativeAppStartIntegration(
DefaultFrameCallbackHandler(),
NativeAppStartHandler(native),
),
);
}
return integrations;
}

Expand Down
2 changes: 1 addition & 1 deletion flutter/sentry-native/CMakeCache.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# Basically, this is a properties file we use both in CMake and update-deps.yml to update dependencies.

repo=https://github.com/getsentry/sentry-native
version=0.7.12
version=0.7.13
34 changes: 17 additions & 17 deletions flutter/test/fake_frame_callback_handler.dart
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import 'package:flutter/scheduler.dart';
import 'package:sentry_flutter/src/frame_callback_handler.dart';

import 'mocks.dart';

class FakeFrameCallbackHandler implements FrameCallbackHandler {
final Duration finishAfterDuration;
FakeFrameCallbackHandler({this.postFrameCallbackDelay});

FakeFrameCallbackHandler(
{this.finishAfterDuration = const Duration(milliseconds: 50)});
/// If set, it automatically executes the callback after the delay
Duration? postFrameCallbackDelay;
FrameCallback? postFrameCallback;
TimingsCallback? timingsCallback;

@override
void addPostFrameCallback(FrameCallback callback) async {
// ignore: inference_failure_on_instance_creation
await Future.delayed(finishAfterDuration);
callback(Duration.zero);
}
postFrameCallback = callback;

@override
Future<void> addPersistentFrameCallback(FrameCallback callback) async {
for (final duration in fakeFrameDurations) {
// Let's wait a bit so the timestamp intervals are large enough
await Future<void>.delayed(Duration(milliseconds: 20));
callback(duration);
if (postFrameCallbackDelay != null) {
await Future<void>.delayed(postFrameCallbackDelay!);
callback(Duration.zero);
}
}

@override
bool hasScheduledFrame = true;
void addTimingsCallback(TimingsCallback callback) {
timingsCallback = callback;
}

@override
Future<void> get endOfFrame => Future<void>.value();
void removeTimingsCallback(TimingsCallback callback) {
if (timingsCallback == callback) {
timingsCallback = null;
}
}
}
Loading

0 comments on commit c135c54

Please sign in to comment.