Skip to content

Commit 0662783

Browse files
authored
feat: Add exception recording. (#250)
Adds basic exception recording and updates example app. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds `Observe.recordException` and updates the example app to report Flutter and zone errors; includes a button to trigger an error. > > - **SDK / Observability**: > - **New API**: `Observe.recordException(exception, {stackTrace, attributes})` records exceptions by creating an error span `launchdarkly.error` and attaching optional metadata. > - **Example App (`example/lib/main.dart`)**: > - Reports Flutter framework errors via `FlutterError.onError` using `Observe.recordException`. > - Reports errors from `runZonedGuarded` handler via `Observe.recordException`. > - Adds a button to intentionally throw an exception to test unhandled error reporting. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit cde38af. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 3ed0671 commit 0662783

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

sdk/@launchdarkly/launchdarkly_flutter_observability/example/lib/main.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,18 @@ void main() {
3434

3535
LDSingleton.client!.start();
3636

37+
// Report any errors handled by flutter.
38+
FlutterError.onError = (FlutterErrorDetails details) {
39+
Observe.recordException(details.exception, stackTrace: details.stack);
40+
};
41+
3742
runApp(MyApp());
3843
},
3944
(err, stack) {
40-
// TODO: Record errors.
45+
// Report any errors reported from the guarded zone.
46+
Observe.recordException(err, stackTrace: stack);
47+
48+
// Any additional default error handling.
4149
},
4250
);
4351
}
@@ -148,6 +156,12 @@ class _MyHomePageState extends State<MyHomePage> {
148156
// wireframe for each widget.
149157
mainAxisAlignment: MainAxisAlignment.center,
150158
children: <Widget>[
159+
ElevatedButton(
160+
onPressed: () {
161+
throw Exception('This is an error!');
162+
},
163+
child: const Text('Trigger unhandled error'),
164+
),
151165
const Text('You have pushed the button this many times:'),
152166
Text(
153167
'$_counter',

sdk/@launchdarkly/launchdarkly_flutter_observability/lib/src/observe.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'api/span_kind.dart';
55
import 'otel/conversions.dart';
66

77
const _launchDarklyTracerName = 'launchdarkly-observability';
8+
const _launchDarklyErrorSpanName = 'launchdarkly.error';
89

910
/// Singleton used to access observability features.
1011
final class Observe {
@@ -26,4 +27,38 @@ final class Observe {
2627

2728
return wrapSpan(span, token);
2829
}
30+
31+
/// Record an exception with an optional stack trace and attributes.
32+
///
33+
/// In dart the stack trace is independent of the exception object and can
34+
/// be caught at the same time as an exception.
35+
/// ```dart
36+
/// try {
37+
/// // thing that throws
38+
/// catch(err, stack) {
39+
/// Observe.record(err, stacktrace: stack);
40+
/// }
41+
/// ```
42+
///
43+
/// In order to capture a stack trace that isn't at the origin of the catch
44+
/// the `StackTrace.current` method can be used.
45+
///
46+
/// The value of the [exception] object will be incorporated into traces
47+
/// using its `toString` method.
48+
static void recordException(
49+
dynamic exception, {
50+
StackTrace? stackTrace,
51+
Map<String, Attribute>? attributes,
52+
}) {
53+
// The OTEL library currently doesn't have a way to differentiate if there
54+
// is an active span or not. So currently we always create a span for
55+
// exceptions.
56+
final span = startSpan(_launchDarklyErrorSpanName);
57+
span.recordException(
58+
exception,
59+
attributes: attributes,
60+
stackTrace: stackTrace ?? StackTrace.empty,
61+
);
62+
span.end();
63+
}
2964
}

0 commit comments

Comments
 (0)