Skip to content

Commit d4641f9

Browse files
committed
Breadcrumb support
1 parent c40ff0c commit d4641f9

File tree

7 files changed

+224
-8
lines changed

7 files changed

+224
-8
lines changed

packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler
1515
import io.flutter.plugin.common.MethodChannel.Result
1616
import io.sentry.Breadcrumb
1717
import io.sentry.DateUtils
18+
import io.sentry.JsonObjectDeserializer
19+
import io.sentry.JsonObjectReader
20+
import io.sentry.ObjectReader
1821
import io.sentry.ScopesAdapter
1922
import io.sentry.Sentry
23+
import io.sentry.SentryOptions
2024
import io.sentry.android.core.InternalSentrySdk
2125
import io.sentry.android.core.SentryAndroid
2226
import io.sentry.android.core.SentryAndroidOptions
@@ -29,6 +33,7 @@ import io.sentry.protocol.User
2933
import io.sentry.transport.CurrentDateProvider
3034
import org.json.JSONObject
3135
import org.json.JSONArray
36+
import java.io.StringReader
3237
import java.lang.ref.WeakReference
3338
import kotlin.math.roundToInt
3439

@@ -66,7 +71,6 @@ class SentryFlutterPlugin :
6671
"removeContexts" -> removeContexts(call.argument("key"), result)
6772
"setUser" -> setUser(call.argument("user"), result)
6873
"addBreadcrumb" -> addBreadcrumb(call.argument("breadcrumb"), result)
69-
"clearBreadcrumbs" -> clearBreadcrumbs(result)
7074
"setExtra" -> setExtra(call.argument("key"), call.argument("value"), result)
7175
"removeExtra" -> removeExtra(call.argument("key"), result)
7276
"setTag" -> setTag(call.argument("key"), call.argument("value"), result)
@@ -202,12 +206,6 @@ class SentryFlutterPlugin :
202206
result.success("")
203207
}
204208

205-
private fun clearBreadcrumbs(result: Result) {
206-
Sentry.clearBreadcrumbs()
207-
208-
result.success("")
209-
}
210-
211209
private fun setExtra(
212210
key: String?,
213211
value: String?,
@@ -448,6 +446,22 @@ class SentryFlutterPlugin :
448446
return json.toByteArray(Charsets.UTF_8)
449447
}
450448

449+
@Suppress("unused") // Used by native/jni bindings
450+
@JvmStatic
451+
fun addBreadcrumbAsBytes(breadcrumbBytes: ByteArray) {
452+
val logger = ScopesAdapter.getInstance().options.logger
453+
val breadcrumbJson = breadcrumbBytes.toString(Charsets.UTF_8)
454+
val reader = JsonObjectReader(StringReader(breadcrumbJson))
455+
val breadcrumb = Breadcrumb.Deserializer().deserialize(reader, logger)
456+
Sentry.addBreadcrumb(breadcrumb)
457+
}
458+
459+
@Suppress("unused") // Used by native/jni bindings
460+
@JvmStatic
461+
fun clearBreadcrumbs() {
462+
Sentry.clearBreadcrumbs()
463+
}
464+
451465
internal fun setAutoPerformanceTracingEnabled(enabled: Boolean) {
452466
autoPerformanceTracingEnabled = enabled
453467
}

packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,22 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin {
646646
}
647647
return nil
648648
}
649+
650+
@objc public class func addBreadcrumbAsBytes(_ breadcrumbBytes: NSData) {
651+
guard let breadcrumbString = String(data: breadcrumbBytes as Data, encoding: .utf8),
652+
let jsonData = breadcrumbString.data(using: .utf8),
653+
let breadcrumbDict = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
654+
return
655+
}
656+
let breadcrumbInstance = PrivateSentrySDKOnly.breadcrumb(with: breadcrumbDict)
657+
SentrySDK.addBreadcrumb(breadcrumbInstance)
658+
}
659+
660+
@objc public class func clearBreadcrumbs() {
661+
SentrySDK.configureScope { scope in
662+
scope.clearBreadcrumbs()
663+
}
664+
}
649665
}
650666
// swiftlint:enable type_body_length
651667

packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
+ (nullable NSData *)fetchNativeAppStartAsBytes;
99
+ (nullable NSData *)loadContextsAsBytes;
1010
+ (nullable NSData *)loadDebugImagesAsBytes:(NSSet<NSString *> *)instructionAddresses;
11+
+ (void)addBreadcrumbAsBytes:(NSData *)breadcrumbBytes;
12+
+ (void)clearBreadcrumbs;
1113
+ (void)nativeCrash;
1214
+ (void)pauseAppHangTracking;
1315
+ (void)resumeAppHangTracking;

packages/flutter/lib/src/native/cocoa/binding.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1128,7 +1128,9 @@ late final _sel_fetchNativeAppStartAsBytes =
11281128
late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes");
11291129
late final _sel_loadDebugImagesAsBytes_ =
11301130
objc.registerName("loadDebugImagesAsBytes:");
1131-
late final _sel_nativeCrash = objc.registerName("nativeCrash");
1131+
late final _sel_addBreadcrumbAsBytes_ =
1132+
objc.registerName("addBreadcrumbAsBytes:");
1133+
late final _sel_clearBreadcrumbs = objc.registerName("clearBreadcrumbs");
11321134
final _objc_msgSend_1pl9qdv = objc.msgSendPointer
11331135
.cast<
11341136
ffi.NativeFunction<
@@ -1137,6 +1139,7 @@ final _objc_msgSend_1pl9qdv = objc.msgSendPointer
11371139
.asFunction<
11381140
void Function(
11391141
ffi.Pointer<objc.ObjCObject>, ffi.Pointer<objc.ObjCSelector>)>();
1142+
late final _sel_nativeCrash = objc.registerName("nativeCrash");
11401143
late final _sel_pauseAppHangTracking =
11411144
objc.registerName("pauseAppHangTracking");
11421145
late final _sel_resumeAppHangTracking =
@@ -1199,6 +1202,17 @@ class SentryFlutterPlugin extends objc.NSObject {
11991202
: objc.NSData.castFromPointer(_ret, retain: true, release: true);
12001203
}
12011204

1205+
/// addBreadcrumbAsBytes:
1206+
static void addBreadcrumbAsBytes(objc.NSData breadcrumbBytes) {
1207+
_objc_msgSend_xtuoz7(_class_SentryFlutterPlugin, _sel_addBreadcrumbAsBytes_,
1208+
breadcrumbBytes.ref.pointer);
1209+
}
1210+
1211+
/// clearBreadcrumbs
1212+
static void clearBreadcrumbs() {
1213+
_objc_msgSend_1pl9qdv(_class_SentryFlutterPlugin, _sel_clearBreadcrumbs);
1214+
}
1215+
12021216
/// nativeCrash
12031217
static void nativeCrash() {
12041218
_objc_msgSend_1pl9qdv(_class_SentryFlutterPlugin, _sel_nativeCrash);

packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:async';
2+
import 'dart:convert';
23
import 'dart:typed_data';
34
import 'package:meta/meta.dart';
45
import 'package:objective_c/objective_c.dart';
@@ -197,4 +198,36 @@ class SentryNativeCocoa extends SentryNativeChannel {
197198
cocoa.SentryFlutterPlugin.resumeAppHangTracking();
198199
});
199200
}
201+
202+
@override
203+
Future<void> addBreadcrumb(Breadcrumb breadcrumb) async {
204+
tryCatchSync('addBreadcrumb', () {
205+
// Normalize breadcrumb data like the method channel does
206+
final normalizedBreadcrumb = Breadcrumb(
207+
message: breadcrumb.message,
208+
category: breadcrumb.category,
209+
data: breadcrumb.data != null
210+
? Map<String, dynamic>.from(breadcrumb.data!)
211+
: null,
212+
level: breadcrumb.level,
213+
type: breadcrumb.type,
214+
timestamp: breadcrumb.timestamp,
215+
// ignore: invalid_use_of_internal_member
216+
unknown: breadcrumb.unknown,
217+
);
218+
219+
final jsonString = json.encode(normalizedBreadcrumb.toJson());
220+
final bytes = utf8.encode(jsonString);
221+
final nsData = bytes.toNSData();
222+
223+
cocoa.SentryFlutterPlugin.addBreadcrumbAsBytes(nsData);
224+
});
225+
}
226+
227+
@override
228+
Future<void> clearBreadcrumbs() async {
229+
tryCatchSync('clearBreadcrumbs', () {
230+
cocoa.SentryFlutterPlugin.clearBreadcrumbs();
231+
});
232+
}
200233
}

packages/flutter/lib/src/native/java/binding.dart

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,56 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject {
14561456
.object<jni$_.JByteArray?>(const jni$_.JByteArrayNullableType());
14571457
}
14581458

1459+
static final _id_addBreadcrumbAsBytes = _class.instanceMethodId(
1460+
r'addBreadcrumbAsBytes',
1461+
r'([B)V',
1462+
);
1463+
1464+
static final _addBreadcrumbAsBytes = jni$_.ProtectedJniExtensions.lookup<
1465+
jni$_.NativeFunction<
1466+
jni$_.JThrowablePtr Function(
1467+
jni$_.Pointer<jni$_.Void>,
1468+
jni$_.JMethodIDPtr,
1469+
jni$_.VarArgs<(jni$_.Pointer<jni$_.Void>,)>)>>(
1470+
'globalEnv_CallVoidMethod')
1471+
.asFunction<
1472+
jni$_.JThrowablePtr Function(jni$_.Pointer<jni$_.Void>,
1473+
jni$_.JMethodIDPtr, jni$_.Pointer<jni$_.Void>)>();
1474+
1475+
/// from: `public final void addBreadcrumbAsBytes(byte[] bs)`
1476+
void addBreadcrumbAsBytes(
1477+
jni$_.JByteArray bs,
1478+
) {
1479+
final _$bs = bs.reference;
1480+
_addBreadcrumbAsBytes(reference.pointer,
1481+
_id_addBreadcrumbAsBytes as jni$_.JMethodIDPtr, _$bs.pointer)
1482+
.check();
1483+
}
1484+
1485+
static final _id_clearBreadcrumbs = _class.instanceMethodId(
1486+
r'clearBreadcrumbs',
1487+
r'()V',
1488+
);
1489+
1490+
static final _clearBreadcrumbs = jni$_.ProtectedJniExtensions.lookup<
1491+
jni$_.NativeFunction<
1492+
jni$_.JThrowablePtr Function(
1493+
jni$_.Pointer<jni$_.Void>,
1494+
jni$_.JMethodIDPtr,
1495+
)>>('globalEnv_CallVoidMethod')
1496+
.asFunction<
1497+
jni$_.JThrowablePtr Function(
1498+
jni$_.Pointer<jni$_.Void>,
1499+
jni$_.JMethodIDPtr,
1500+
)>();
1501+
1502+
/// from: `public final void clearBreadcrumbs()`
1503+
void clearBreadcrumbs() {
1504+
_clearBreadcrumbs(
1505+
reference.pointer, _id_clearBreadcrumbs as jni$_.JMethodIDPtr)
1506+
.check();
1507+
}
1508+
14591509
static final _id_new$ = _class.constructorId(
14601510
r'(Lkotlin/jvm/internal/DefaultConstructorMarker;)V',
14611511
);
@@ -1990,6 +2040,56 @@ class SentryFlutterPlugin extends jni$_.JObject {
19902040
_id_loadDebugImagesAsBytes as jni$_.JMethodIDPtr, _$set.pointer)
19912041
.object<jni$_.JByteArray?>(const jni$_.JByteArrayNullableType());
19922042
}
2043+
2044+
static final _id_addBreadcrumbAsBytes = _class.staticMethodId(
2045+
r'addBreadcrumbAsBytes',
2046+
r'([B)V',
2047+
);
2048+
2049+
static final _addBreadcrumbAsBytes = jni$_.ProtectedJniExtensions.lookup<
2050+
jni$_.NativeFunction<
2051+
jni$_.JThrowablePtr Function(
2052+
jni$_.Pointer<jni$_.Void>,
2053+
jni$_.JMethodIDPtr,
2054+
jni$_.VarArgs<(jni$_.Pointer<jni$_.Void>,)>)>>(
2055+
'globalEnv_CallStaticVoidMethod')
2056+
.asFunction<
2057+
jni$_.JThrowablePtr Function(jni$_.Pointer<jni$_.Void>,
2058+
jni$_.JMethodIDPtr, jni$_.Pointer<jni$_.Void>)>();
2059+
2060+
/// from: `static public final void addBreadcrumbAsBytes(byte[] bs)`
2061+
static void addBreadcrumbAsBytes(
2062+
jni$_.JByteArray bs,
2063+
) {
2064+
final _$bs = bs.reference;
2065+
_addBreadcrumbAsBytes(_class.reference.pointer,
2066+
_id_addBreadcrumbAsBytes as jni$_.JMethodIDPtr, _$bs.pointer)
2067+
.check();
2068+
}
2069+
2070+
static final _id_clearBreadcrumbs = _class.staticMethodId(
2071+
r'clearBreadcrumbs',
2072+
r'()V',
2073+
);
2074+
2075+
static final _clearBreadcrumbs = jni$_.ProtectedJniExtensions.lookup<
2076+
jni$_.NativeFunction<
2077+
jni$_.JThrowablePtr Function(
2078+
jni$_.Pointer<jni$_.Void>,
2079+
jni$_.JMethodIDPtr,
2080+
)>>('globalEnv_CallStaticVoidMethod')
2081+
.asFunction<
2082+
jni$_.JThrowablePtr Function(
2083+
jni$_.Pointer<jni$_.Void>,
2084+
jni$_.JMethodIDPtr,
2085+
)>();
2086+
2087+
/// from: `static public final void clearBreadcrumbs()`
2088+
static void clearBreadcrumbs() {
2089+
_clearBreadcrumbs(_class.reference.pointer,
2090+
_id_clearBreadcrumbs as jni$_.JMethodIDPtr)
2091+
.check();
2092+
}
19932093
}
19942094

19952095
final class $SentryFlutterPlugin$NullableType

packages/flutter/lib/src/native/java/sentry_native_java.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:async';
2+
import 'dart:convert';
23
import 'dart:typed_data';
34

45
import 'package:jni/jni.dart';
@@ -220,4 +221,40 @@ class SentryNativeJava extends SentryNativeChannel {
220221
await _envelopeSender?.close();
221222
return super.close();
222223
}
224+
225+
@override
226+
Future<void> addBreadcrumb(Breadcrumb breadcrumb) async {
227+
JByteArray? breadcrumbBytes;
228+
229+
tryCatchSync('addBreadcrumb', () {
230+
final normalizedBreadcrumb = Breadcrumb(
231+
message: breadcrumb.message,
232+
category: breadcrumb.category,
233+
data: breadcrumb.data != null
234+
? Map<String, dynamic>.from(breadcrumb.data!)
235+
: null,
236+
level: breadcrumb.level,
237+
type: breadcrumb.type,
238+
timestamp: breadcrumb.timestamp,
239+
// ignore: invalid_use_of_internal_member
240+
unknown: breadcrumb.unknown,
241+
);
242+
243+
final jsonString = json.encode(normalizedBreadcrumb.toJson());
244+
final bytes = utf8.encode(jsonString);
245+
breadcrumbBytes = JByteArray.from(bytes);
246+
247+
native.SentryFlutterPlugin.Companion
248+
.addBreadcrumbAsBytes(breadcrumbBytes!);
249+
}, finallyFn: () {
250+
breadcrumbBytes?.release();
251+
});
252+
}
253+
254+
@override
255+
Future<void> clearBreadcrumbs() async {
256+
tryCatchSync('clearBreadcrumbs', () {
257+
native.SentryFlutterPlugin.Companion.clearBreadcrumbs();
258+
});
259+
}
223260
}

0 commit comments

Comments
 (0)