diff --git a/bin/test.dart b/bin/test.dart index 0e90193e61..bbeadbe1fc 100644 --- a/bin/test.dart +++ b/bin/test.dart @@ -19,8 +19,10 @@ Future main(List rawArgs) async { final SentryClient client = new SentryClient(dsn: dsn); try { - throw new StateError('This is a test error'); + await foo(); } catch (error, stackTrace) { + print('Reporting the following stack trace: '); + print(stackTrace); final SentryResponse response = await client.captureException( exception: error, stackTrace: stackTrace, @@ -35,3 +37,15 @@ Future main(List rawArgs) async { await client.close(); } } + +Future foo() async { + await bar(); +} + +Future bar() async { + await baz(); +} + +Future baz() async { + throw new StateError('This is a test error'); +} diff --git a/lib/sentry.dart b/lib/sentry.dart index 9e3ab16501..a2d4343bbd 100644 --- a/lib/sentry.dart +++ b/lib/sentry.dart @@ -12,7 +12,6 @@ import 'dart:io'; import 'package:http/http.dart'; import 'package:meta/meta.dart'; import 'package:quiver/time.dart'; -import 'package:stack_trace/stack_trace.dart'; import 'package:usage/uuid/uuid.dart'; import 'src/stack_trace.dart'; @@ -377,13 +376,8 @@ class Event { } if (stackTrace != null) { - assert(stackTrace is String || stackTrace is StackTrace); - final Trace trace = stackTrace is StackTrace - ? new Trace.from(stackTrace) - : new Trace.parse(stackTrace); - json['stacktrace'] = { - 'frames': trace.frames.map(stackTraceFrameToJsonFrame).toList(), + 'frames': encodeStackTrace(stackTrace), }; } diff --git a/lib/src/stack_trace.dart b/lib/src/stack_trace.dart index 2f7d73d028..13b1b122ef 100644 --- a/lib/src/stack_trace.dart +++ b/lib/src/stack_trace.dart @@ -4,7 +4,30 @@ import 'package:stack_trace/stack_trace.dart'; -Map stackTraceFrameToJsonFrame(Frame frame) { +/// Sentry.io JSON encoding of a stack frame for the asynchronous suspension, +/// which is the gap between asynchronous calls. +const Map asynchronousGapFrameJson = const { + 'abs_path': '', +}; + +/// Encodes [strackTrace] as JSON in the Sentry.io format. +/// +/// [stackTrace] must be [String] or [StackTrace]. +List> encodeStackTrace(dynamic stackTrace) { + assert(stackTrace is String || stackTrace is StackTrace); + final Chain chain = stackTrace is StackTrace + ? new Chain.forTrace(stackTrace) + : new Chain.parse(stackTrace); + + final List> frames = >[]; + for (int t = 0; t < chain.traces.length; t += 1) { + frames.addAll(chain.traces[t].frames.map(encodeStackTraceFrame)); + if (t < chain.traces.length - 1) frames.add(asynchronousGapFrameJson); + } + return frames; +} + +Map encodeStackTraceFrame(Frame frame) { final Map json = { 'abs_path': _absolutePathForCrashReport(frame), 'function': frame.member, diff --git a/lib/src/version.dart b/lib/src/version.dart index faf41d4d2a..1462cfc1ed 100644 --- a/lib/src/version.dart +++ b/lib/src/version.dart @@ -9,7 +9,7 @@ library version; /// The SDK version reported to Sentry.io in the submitted events. -const String sdkVersion = '0.0.3'; +const String sdkVersion = '0.0.4'; /// The SDK name reported to Sentry.io in the submitted events. const String sdkName = 'dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 19ea6e25f4..8ec03bbf18 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sentry -version: 0.0.3 +version: 0.0.4 description: A pure Dart Sentry.io client. author: Yegor Jbanov homepage: https://github.com/yjbanov/sentry diff --git a/test/stack_trace_test.dart b/test/stack_trace_test.dart index 248b115ea2..eb5ab06f85 100644 --- a/test/stack_trace_test.dart +++ b/test/stack_trace_test.dart @@ -7,10 +7,10 @@ import 'package:stack_trace/stack_trace.dart'; import 'package:test/test.dart'; void main() { - group('stackTraceFrameToJsonFrame', () { + group('encodeStackTraceFrame', () { test('marks dart: frames as not app frames', () { final Frame frame = new Frame(Uri.parse('dart:core'), 1, 2, 'buzz'); - expect(stackTraceFrameToJsonFrame(frame), { + expect(encodeStackTraceFrame(frame), { 'abs_path': 'dart:core', 'function': 'buzz', 'lineno': 1, @@ -22,7 +22,57 @@ void main() { test('cleanses absolute paths', () { final Frame frame = new Frame(Uri.parse('file://foo/bar/baz.dart'), 1, 2, 'buzz'); - expect(stackTraceFrameToJsonFrame(frame)['abs_path'], 'baz.dart'); + expect(encodeStackTraceFrame(frame)['abs_path'], 'baz.dart'); + }); + }); + + group('encodeStackTrace', () { + test('encodes a simple stack trace', () { + expect(encodeStackTrace(''' +#0 baz (file:///pathto/test.dart:50:3) +#1 bar (file:///pathto/test.dart:46:9) + '''), [ + { + 'abs_path': 'test.dart', + 'function': 'baz', + 'lineno': 50, + 'in_app': true, + 'filename': 'test.dart' + }, + { + 'abs_path': 'test.dart', + 'function': 'bar', + 'lineno': 46, + 'in_app': true, + 'filename': 'test.dart' + } + ]); + }); + + test('encodes an asynchronous stack trace', () { + expect(encodeStackTrace(''' +#0 baz (file:///pathto/test.dart:50:3) + +#1 bar (file:///pathto/test.dart:46:9) + '''), [ + { + 'abs_path': 'test.dart', + 'function': 'baz', + 'lineno': 50, + 'in_app': true, + 'filename': 'test.dart' + }, + { + 'abs_path': '', + }, + { + 'abs_path': 'test.dart', + 'function': 'bar', + 'lineno': 46, + 'in_app': true, + 'filename': 'test.dart' + } + ]); }); }); }