Skip to content

Commit

Permalink
Merge branch 'main' into v9
Browse files Browse the repository at this point in the history
  • Loading branch information
buenaflor authored Feb 19, 2025
2 parents 7c468df + 12ed940 commit 0f8bd48
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 37 deletions.
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,25 @@

## Unreleased

### Behavioral changes

- ⚠️ Auto IP assignment for `SentryUser` is now guarded by `sendDefaultPii` ([#2726](https://github.com/getsentry/sentry-dart/pull/2726))
- If you rely on Sentry automatically processing the IP address of the user, set `options.sendDefaultPii = true` or manually set the IP address of the `SentryUser` to `{{auto}}`

### Features

- Disable `ScreenshotIntegration`, `WidgetsBindingIntegration` and `SentryWidget` in multi-view apps #2366 ([#2366](https://github.com/getsentry/sentry-dart/pull/2366))

### Fixes
### Enhancements

- Use `loadDebugImagesForAddresses` API for Android ([#2706](https://github.com/getsentry/sentry-dart/pull/2706))
- This reduces the envelope size and data transferred across method channels
- If debug images received by `loadDebugImagesForAddresses` are empty, the SDK loads all debug images as fallback

### Fixes

- Reference to `SentryWidgetsFlutterBinding` in warning message in `FramesTrackingIntegration` ([#2704](https://github.com/getsentry/sentry-dart/pull/2704))
- Replay video interruption if a `navigation` breadcrumb is missing `to` route info ([#2720](https://github.com/getsentry/sentry-dart/pull/2720))

### Deprecations

Expand Down Expand Up @@ -72,6 +84,9 @@ final db = AppDatabase(executor);
- Bump Native SDK from v0.7.19 to v0.7.20 ([#2652](https://github.com/getsentry/sentry-dart/pull/2652))
- [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0720)
- [diff](https://github.com/getsentry/sentry-native/compare/0.7.19...0.7.20)
- Bump Cocoa SDK from v8.44.0 to v8.45.0 ([#2718](https://github.com/getsentry/sentry-dart/pull/2718))
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8450)
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.44.0...8.45.0)

## 8.13.0

Expand Down
19 changes: 14 additions & 5 deletions dart/lib/src/sentry_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ import 'version.dart';
/// to true.
const _defaultIpAddress = '{{auto}}';

@visibleForTesting
String get defaultIpAddress => _defaultIpAddress;

/// Logs crash reports and events to the Sentry.io service.
class SentryClient {
final SentryOptions _options;
Expand Down Expand Up @@ -303,12 +306,18 @@ class SentryClient {
}

SentryEvent _createUserOrSetDefaultIpAddress(SentryEvent event) {
var user = event.user;
if (user == null) {
return event.copyWith(user: SentryUser(ipAddress: _defaultIpAddress));
} else if (event.user?.ipAddress == null) {
return event.copyWith(user: user.copyWith(ipAddress: _defaultIpAddress));
final user = event.user;
final effectiveIpAddress =
user?.ipAddress ?? (_options.sendDefaultPii ? _defaultIpAddress : null);

if (effectiveIpAddress != null) {
final updatedUser = user == null
? SentryUser(ipAddress: effectiveIpAddress)
: user.copyWith(ipAddress: effectiveIpAddress);

return event.copyWith(user: updatedUser);
}

return event;
}

Expand Down
47 changes: 42 additions & 5 deletions dart/test/sentry_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1003,16 +1003,17 @@ void main() {
});
});

group('SentryClient: sets user & user ip', () {
group('SentryClient: user & user ip', () {
late Fixture fixture;

setUp(() {
fixture = Fixture();
});

test('event has no user', () async {
test('event has no user and sendDefaultPii = true', () async {
final client = fixture.getSut(sendDefaultPii: true);
var fakeEvent = SentryEvent();
expect(fakeEvent.user, isNull);

await client.captureEvent(fakeEvent);

Expand All @@ -1021,12 +1022,27 @@ void main() {

expect(fixture.transport.envelopes.length, 1);
expect(capturedEvent.user, isNotNull);
expect(capturedEvent.user?.ipAddress, '{{auto}}');
expect(capturedEvent.user?.ipAddress, defaultIpAddress);
});

test('event has no user and sendDefaultPii = false', () async {
final client = fixture.getSut(sendDefaultPii: false);
var fakeEvent = SentryEvent();
expect(fakeEvent.user, isNull);

await client.captureEvent(fakeEvent);

final capturedEnvelope = fixture.transport.envelopes.first;
final capturedEvent = await eventFromEnvelope(capturedEnvelope);

expect(fixture.transport.envelopes.length, 1);
expect(capturedEvent.user, isNull);
});

test('event has a user with IP address', () async {
final client = fixture.getSut(sendDefaultPii: true);

expect(fakeEvent.user?.ipAddress, isNotNull);
await client.captureEvent(fakeEvent);

final capturedEnvelope = fixture.transport.envelopes.first;
Expand All @@ -1040,10 +1056,31 @@ void main() {
expect(capturedEvent.user?.email, fakeEvent.user!.email);
});

test('event has a user without IP address', () async {
test('event has a user without IP address and sendDefaultPii = true',
() async {
final client = fixture.getSut(sendDefaultPii: true);

final event = fakeEvent.copyWith(user: fakeUser);
expect(event.user?.ipAddress, isNull);

await client.captureEvent(event);

final capturedEnvelope = fixture.transport.envelopes.first;
final capturedEvent = await eventFromEnvelope(capturedEnvelope);

expect(fixture.transport.envelopes.length, 1);
expect(capturedEvent.user, isNotNull);
expect(capturedEvent.user?.ipAddress, defaultIpAddress);
expect(capturedEvent.user?.id, fakeUser.id);
expect(capturedEvent.user?.email, fakeUser.email);
});

test('event has a user without IP address and sendDefaultPii = false',
() async {
final client = fixture.getSut(sendDefaultPii: false);

final event = fakeEvent.copyWith(user: fakeUser);
expect(event.user?.ipAddress, isNull);

await client.captureEvent(event);

Expand All @@ -1052,7 +1089,7 @@ void main() {

expect(fixture.transport.envelopes.length, 1);
expect(capturedEvent.user, isNotNull);
expect(capturedEvent.user?.ipAddress, '{{auto}}');
expect(capturedEvent.user?.ipAddress, isNull);
expect(capturedEvent.user?.id, fakeUser.id);
expect(capturedEvent.user?.email, fakeUser.email);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class SentryFlutterPlugin :
when (call.method) {
"initNativeSdk" -> initNativeSdk(call, result)
"captureEnvelope" -> captureEnvelope(call, result)
"loadImageList" -> loadImageList(result)
"loadImageList" -> loadImageList(call, result)
"closeNativeSdk" -> closeNativeSdk(result)
"fetchNativeAppStart" -> fetchNativeAppStart(result)
"beginNativeFrames" -> beginNativeFrames(result)
Expand Down Expand Up @@ -479,31 +479,43 @@ class SentryFlutterPlugin :
result.error("3", "Envelope is null or empty", null)
}

private fun loadImageList(result: Result) {
private fun loadImageList(
call: MethodCall,
result: Result,
) {
val options = HubAdapter.getInstance().options as SentryAndroidOptions

val newDebugImages = mutableListOf<Map<String, Any?>>()
val debugImages: List<DebugImage>? = options.debugImagesLoader.loadDebugImages()

debugImages?.let {
it.forEach { image ->
val item = mutableMapOf<String, Any?>()

item["image_addr"] = image.imageAddr
item["image_size"] = image.imageSize
item["code_file"] = image.codeFile
item["type"] = image.type
item["debug_id"] = image.debugId
item["code_id"] = image.codeId
item["debug_file"] = image.debugFile

newDebugImages.add(item)
val addresses = call.arguments() as List<String>? ?: listOf()
val debugImages =
if (addresses.isEmpty()) {
options.debugImagesLoader
.loadDebugImages()
?.toList()
.serialize()
} else {
options.debugImagesLoader
.loadDebugImagesForAddresses(addresses.toSet())
?.ifEmpty { options.debugImagesLoader.loadDebugImages() }
?.toList()
.serialize()
}
}

result.success(newDebugImages)
result.success(debugImages)
}

private fun List<DebugImage>?.serialize() = this?.map { it.serialize() }

private fun DebugImage.serialize() =
mapOf(
"image_addr" to imageAddr,
"image_size" to imageSize,
"code_file" to codeFile,
"type" to type,
"debug_id" to debugId,
"code_id" to codeId,
"debug_file" to debugFile,
)

private fun closeNativeSdk(result: Result) {
HubAdapter.getInstance().close()
framesTracker?.stop()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ class SentryFlutterReplayBreadcrumbConverter : DefaultReplayBreadcrumbConverter(
"sentry.event" -> null
"sentry.transaction" -> null
"http" -> convertNetworkBreadcrumb(breadcrumb)
"navigation" -> newRRWebBreadcrumb(breadcrumb)
"navigation" -> {
if (breadcrumb.data.containsKey("to") && breadcrumb.data["to"] is String) {
newRRWebBreadcrumb(breadcrumb)
} else {
null
}
}
"ui.click" ->
newRRWebBreadcrumb(breadcrumb).apply {
category = "ui.tap"
Expand Down
31 changes: 31 additions & 0 deletions flutter/example/integration_test/integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,37 @@ void main() {
await transaction.finish();
});

testWidgets('setup sentry and test loading debug image', (tester) async {
await restoreFlutterOnErrorAfter(() async {
await setupSentryAndApp(tester);
});

// By default it should load all debug images
final allDebugImages = await SentryFlutter.native
?.loadDebugImages(SentryStackTrace(frames: const []));
// Typically loading all images results in a larger numbers
expect(allDebugImages!.length > 100, isTrue);

// We can take any other random image for testing
final expectedImage = allDebugImages.first;
expect(expectedImage.imageAddr, isNotNull);
final imageAddr =
int.parse(expectedImage.imageAddr!.replaceAll('0x', ''), radix: 16);

// Use the base image address and increase by offset
// so the instructionAddress will be within the range of the image address
final imageOffset = (expectedImage.imageSize! / 2).toInt();
final instructionAddr = '0x${(imageAddr + imageOffset).toRadixString(16)}';
final sentryFrame = SentryStackFrame(instructionAddr: instructionAddr);

final debugImageByStacktrace = await SentryFlutter.native
?.loadDebugImages(SentryStackTrace(frames: [sentryFrame]));
expect(debugImageByStacktrace!.length, 1);
expect(debugImageByStacktrace.first.imageAddr, isNotNull);
expect(debugImageByStacktrace.first.imageAddr, isNotEmpty);
expect(debugImageByStacktrace.first.imageAddr, expectedImage.imageAddr);
});

group('e2e', () {
var output = find.byKey(const Key('output'));
late Fixture fixture;
Expand Down
2 changes: 1 addition & 1 deletion flutter/ios/sentry_flutter.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Sentry SDK for Flutter with support to native through sentry-cocoa.
:tag => s.version.to_s }
s.source_files = 'sentry_flutter/Sources/**/*'
s.public_header_files = 'sentry_flutter/Sources/**/*.h'
s.dependency 'Sentry/HybridSDK', '8.44.0'
s.dependency 'Sentry/HybridSDK', '8.45.0'
s.ios.dependency 'Flutter'
s.osx.dependency 'FlutterMacOS'
s.ios.deployment_target = '12.0'
Expand Down
2 changes: 1 addition & 1 deletion flutter/ios/sentry_flutter/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ let package = Package(
.library(name: "sentry-flutter", targets: ["sentry_flutter", "sentry_flutter_objc"])
],
dependencies: [
.package(url: "https://github.com/getsentry/sentry-cocoa", from: "8.44.0")
.package(url: "https://github.com/getsentry/sentry-cocoa", from: "8.45.0")
],
targets: [
.target(
Expand Down
Loading

0 comments on commit 0f8bd48

Please sign in to comment.