diff --git a/CHANGELOG.md b/CHANGELOG.md index 03f2000ebb..df026aba05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - feat: add missing protocol classes - fix: logger method and refactoring little things - fix: sentry protocol is v7 +- feat: add an attachStackTrace options ## 4.0.0-alpha.1 diff --git a/dart/example_web/web/main.dart b/dart/example_web/web/main.dart index 1d25121f17..dce2e37fcf 100644 --- a/dart/example_web/web/main.dart +++ b/dart/example_web/web/main.dart @@ -92,9 +92,9 @@ void captureException() async { } Future captureCompleteExampleEvent() async { + print('\nReporting a complete event example: ${sdkName}'); final sentryId = await Sentry.captureEvent(event); - print('\nReporting a complete event example: ${sdkName}'); print('Response SentryId: ${sentryId}'); if (sentryId != SentryId.empty()) { diff --git a/dart/lib/src/hub.dart b/dart/lib/src/hub.dart index 9933eba304..c87ea0cb15 100644 --- a/dart/lib/src/hub.dart +++ b/dart/lib/src/hub.dart @@ -54,7 +54,11 @@ class Hub { SentryId get lastEventId => _lastEventId; /// Captures the event. - Future captureEvent(SentryEvent event, {dynamic hint}) async { + Future captureEvent( + SentryEvent event, { + dynamic stackTrace, + dynamic hint, + }) async { var sentryId = SentryId.empty(); if (!_isEnabled) { @@ -73,6 +77,7 @@ class Hub { try { sentryId = await item.client.captureEvent( event, + stackTrace: stackTrace, scope: item.scope, hint: hint, ); diff --git a/dart/lib/src/hub_adapter.dart b/dart/lib/src/hub_adapter.dart index 64b37daba9..78cf368d40 100644 --- a/dart/lib/src/hub_adapter.dart +++ b/dart/lib/src/hub_adapter.dart @@ -23,7 +23,11 @@ class HubAdapter implements Hub { void bindClient(SentryClient client) => Sentry.bindClient(client); @override - Future captureEvent(SentryEvent event, {dynamic hint}) => + Future captureEvent( + SentryEvent event, { + dynamic stackTrace, + dynamic hint, + }) => Sentry.captureEvent(event, hint: hint); @override diff --git a/dart/lib/src/noop_hub.dart b/dart/lib/src/noop_hub.dart index 36a93bcb8c..4f7da67e2f 100644 --- a/dart/lib/src/noop_hub.dart +++ b/dart/lib/src/noop_hub.dart @@ -17,7 +17,11 @@ class NoOpHub implements Hub { void bindClient(SentryClient client) {} @override - Future captureEvent(SentryEvent event, {dynamic hint}) => + Future captureEvent( + SentryEvent event, { + dynamic stackTrace, + dynamic hint, + }) => Future.value(SentryId.empty()); @override diff --git a/dart/lib/src/noop_sentry_client.dart b/dart/lib/src/noop_sentry_client.dart index 1b5be04a8c..9481a1ee6e 100644 --- a/dart/lib/src/noop_sentry_client.dart +++ b/dart/lib/src/noop_sentry_client.dart @@ -16,6 +16,7 @@ class NoOpSentryClient implements SentryClient { @override Future captureEvent( SentryEvent event, { + dynamic stackTrace, Scope scope, dynamic hint, }) => diff --git a/dart/lib/src/protocol/sentry_event.dart b/dart/lib/src/protocol/sentry_event.dart index 3501dfeb2b..acef5c3ed6 100644 --- a/dart/lib/src/protocol/sentry_event.dart +++ b/dart/lib/src/protocol/sentry_event.dart @@ -83,10 +83,9 @@ class SentryEvent { /// If this behavior is undesirable, consider using a custom formatted [message] instead. final dynamic throwable; - /// The stack trace corresponding to the thrown [exception]. - /// - /// Can be `null`, a [String], or a [StackTrace]. - final dynamic stackTrace; + /// an optional attached StackTrace + /// used when event has no throwable or exception, see [SentryOptions.attachStackTrace] + final SentryStackTrace stackTrace; /// an exception or error that occurred in a program /// TODO more doc @@ -262,6 +261,17 @@ class SentryEvent { json['exception'] = { 'values': [exception.toJson()].toList(growable: false) }; + } else if (stackTrace != null) { + json['threads'] = { + 'values': [ + { + 'id': 0, + 'stacktrace': stackTrace.toJson(), + 'crashed': true, + 'name': 'Current Isolate', + } + ] + }; } if (level != null) { diff --git a/dart/lib/src/protocol/sentry_exception.dart b/dart/lib/src/protocol/sentry_exception.dart index 065bf2a2b1..47b93471c2 100644 --- a/dart/lib/src/protocol/sentry_exception.dart +++ b/dart/lib/src/protocol/sentry_exception.dart @@ -14,7 +14,7 @@ class SentryException { final String module; /// An optional stack trace object - final SentryStackTrace stacktrace; + final SentryStackTrace stackTrace; /// An optional object describing the [Mechanism] that created this exception final Mechanism mechanism; @@ -26,7 +26,7 @@ class SentryException { @required this.type, @required this.value, this.module, - this.stacktrace, + this.stackTrace, this.mechanism, this.threadId, }); @@ -46,8 +46,8 @@ class SentryException { json['module'] = module; } - if (stacktrace != null) { - json['stacktrace'] = stacktrace.toJson(); + if (stackTrace != null) { + json['stacktrace'] = stackTrace.toJson(); } if (mechanism != null) { diff --git a/dart/lib/src/sentry.dart b/dart/lib/src/sentry.dart index 8da191834d..00a4b8f1ed 100644 --- a/dart/lib/src/sentry.dart +++ b/dart/lib/src/sentry.dart @@ -59,9 +59,10 @@ class Sentry { /// Reports an [event] to Sentry.io. static Future captureEvent( SentryEvent event, { + dynamic stackTrace, dynamic hint, }) async => - currentHub.captureEvent(event, hint: hint); + currentHub.captureEvent(event, stackTrace: stackTrace, hint: hint); /// Reports the [throwable] and optionally its [stackTrace] to Sentry.io. static Future captureException( diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index 344c7cd718..e49b892140 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -5,12 +5,23 @@ import 'protocol.dart'; import 'scope.dart'; import 'sentry_exception_factory.dart'; import 'sentry_options.dart'; +import 'sentry_stack_trace_factory.dart'; import 'transport/http_transport.dart'; import 'transport/noop_transport.dart'; import 'version.dart'; /// Logs crash reports and events to the Sentry.io service. class SentryClient { + final SentryOptions _options; + + final Random _random; + + static final _sentryId = Future.value(SentryId.empty()); + + SentryExceptionFactory _exceptionFactory; + + SentryStackTraceFactory _stackTraceFactory; + /// Instantiates a client using [SentryOptions] factory SentryClient(SentryOptions options) { if (options == null) { @@ -20,27 +31,25 @@ class SentryClient { if (options.transport is NoOpTransport) { options.transport = HttpTransport(options); } + return SentryClient._(options); } - final SentryOptions _options; - - final Random _random; - - final SentryExceptionFactory _exceptionFactory; - - static final _sentryId = Future.value(SentryId.empty()); - /// Instantiates a client using [SentryOptions] - SentryClient._(this._options, {SentryExceptionFactory exceptionFactory}) - : _exceptionFactory = - exceptionFactory ?? SentryExceptionFactory(options: _options), - _random = _options.sampleRate == null ? null : Random(); + SentryClient._(this._options) + : _random = _options.sampleRate == null ? null : Random() { + _stackTraceFactory = SentryStackTraceFactory(_options); + _exceptionFactory = SentryExceptionFactory( + options: _options, + stacktraceFactory: _stackTraceFactory, + ); + } /// Reports an [event] to Sentry.io. Future captureEvent( SentryEvent event, { Scope scope, + dynamic stackTrace, dynamic hint, }) async { event = _processEvent(event, eventProcessors: _options.eventProcessors); @@ -61,7 +70,7 @@ class SentryClient { return _sentryId; } - event = _prepareEvent(event); + event = _prepareEvent(event, stackTrace: stackTrace); if (_options.beforeSend != null) { try { @@ -81,7 +90,7 @@ class SentryClient { return _options.transport.send(event); } - SentryEvent _prepareEvent(SentryEvent event) { + SentryEvent _prepareEvent(SentryEvent event, {dynamic stackTrace}) { event = event.copyWith( serverName: event.serverName ?? _options.serverName, dist: event.dist ?? _options.dist, @@ -92,11 +101,22 @@ class SentryClient { platform: event.platform ?? sdkPlatform, ); - if (event.throwable != null && event.exception == null) { + if (event.exception != null) return event; + + if (event.throwable != null) { final sentryException = _exceptionFactory - .getSentryException(event.throwable, stackTrace: event.stackTrace); + .getSentryException(event.throwable, stackTrace: stackTrace); + + return event.copyWith(exception: sentryException); + } - event = event.copyWith(exception: sentryException); + if (stackTrace != null || _options.attachStackTrace) { + stackTrace ??= StackTrace.current; + final frames = _stackTraceFactory.getStackFrames(stackTrace); + + if (frames != null && frames.isNotEmpty) { + event = event.copyWith(stackTrace: SentryStackTrace(frames: frames)); + } } return event; @@ -111,10 +131,15 @@ class SentryClient { }) { final event = SentryEvent( throwable: throwable, - stackTrace: stackTrace, timestamp: _options.clock(), ); - return captureEvent(event, scope: scope, hint: hint); + + return captureEvent( + event, + stackTrace: stackTrace, + scope: scope, + hint: hint, + ); } /// Reports the [template] diff --git a/dart/lib/src/sentry_exception_factory.dart b/dart/lib/src/sentry_exception_factory.dart index c7560cce82..7a62047d12 100644 --- a/dart/lib/src/sentry_exception_factory.dart +++ b/dart/lib/src/sentry_exception_factory.dart @@ -6,17 +6,22 @@ import 'sentry_stack_trace_factory.dart'; /// class to convert Dart Error and exception to SentryException class SentryExceptionFactory { - SentryStackTraceFactory _stacktraceFactory; + final SentryOptions _options; + + final SentryStackTraceFactory _stacktraceFactory; SentryExceptionFactory({ - SentryStackTraceFactory stacktraceFactory, + @required SentryStackTraceFactory stacktraceFactory, @required SentryOptions options, - }) { - if (options == null) { + }) : _options = options, + _stacktraceFactory = stacktraceFactory { + if (_options == null) { throw ArgumentError('SentryOptions is required.'); } - _stacktraceFactory = stacktraceFactory ?? SentryStackTraceFactory(options); + if (_stacktraceFactory == null) { + throw ArgumentError('SentryStackTraceFactory is required.'); + } } SentryException getSentryException( @@ -24,21 +29,26 @@ class SentryExceptionFactory { dynamic stackTrace, Mechanism mechanism, }) { - if (exception is Error) { - stackTrace ??= exception.stackTrace; - } else { - stackTrace ??= StackTrace.current; + SentryStackTrace sentryStackTrace; + if (stackTrace != null) { + sentryStackTrace = SentryStackTrace( + frames: _stacktraceFactory.getStackFrames(stackTrace), + ); + } else if (exception is Error && exception.stackTrace != null) { + sentryStackTrace = SentryStackTrace( + frames: _stacktraceFactory.getStackFrames(exception.stackTrace), + ); + } else if (_options.attachStackTrace) { + sentryStackTrace = SentryStackTrace( + frames: _stacktraceFactory.getStackFrames(StackTrace.current), + ); } - final sentryStackTrace = SentryStackTrace( - frames: _stacktraceFactory.getStackFrames(stackTrace), - ); - final sentryException = SentryException( type: '${exception.runtimeType}', value: '$exception', mechanism: mechanism, - stacktrace: sentryStackTrace, + stackTrace: sentryStackTrace, ); return sentryException; diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index 1a4736f8b4..e152f37c09 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -162,6 +162,23 @@ class SentryOptions { _sdk = sdk ?? _sdk; } + bool _attachStackTrace = true; + + /// When enabled, stack traces are automatically attached to all messages logged. + /// Stack traces are always attached to exceptions; + /// however, when this option is set, stack traces are also sent with messages. + /// This option, for instance, means that stack traces appear next to all log messages. + /// + /// This option is true` by default. + /// + /// Grouping in Sentry is different for events with stack traces and without. + /// As a result, you will get new groups as you enable or disable this flag for certain events. + bool get attachStackTrace => _attachStackTrace; + + set attachStackTrace(bool attachStacktrace) { + _attachStackTrace = attachStacktrace ?? _attachStackTrace; + } + // TODO: Scope observers, enableScopeSync // TODO: sendDefaultPii diff --git a/dart/lib/src/sentry_stack_trace_factory.dart b/dart/lib/src/sentry_stack_trace_factory.dart index 3c6076aa18..b7df056c1d 100644 --- a/dart/lib/src/sentry_stack_trace_factory.dart +++ b/dart/lib/src/sentry_stack_trace_factory.dart @@ -36,8 +36,10 @@ class SentryStackTraceFactory { final frames = []; for (var t = 0; t < chain.traces.length; t += 1) { - final encodedFrames = - chain.traces[t].frames.map((f) => encodeStackTraceFrame(f)); + final encodedFrames = chain.traces[t].frames + // we don't want to add our own frames + .where((frame) => frame.package != 'sentry') + .map((f) => encodeStackTraceFrame(f)); frames.addAll(encodedFrames); @@ -84,7 +86,9 @@ class SentryStackTraceFactory { /// "dart:" and "package:" imports are always relative and are OK to send in /// full. String _absolutePathForCrashReport(Frame frame) { - if (frame.uri.scheme != 'dart' && frame.uri.scheme != 'package') { + if (frame.uri.scheme != 'dart' && + frame.uri.scheme != 'package' && + frame.uri.pathSegments.isNotEmpty) { return frame.uri.pathSegments.last; } @@ -101,14 +105,14 @@ class SentryStackTraceFactory { if (_inAppIncludes != null) { for (final include in _inAppIncludes) { - if (frame.package != null && frame.package.startsWith(include)) { + if (frame.package != null && frame.package == include) { return true; } } } if (_inAppExcludes != null) { for (final exclude in _inAppExcludes) { - if (frame.package != null && frame.package.startsWith(exclude)) { + if (frame.package != null && frame.package == exclude) { return false; } } diff --git a/dart/test/exception_factory_test.dart b/dart/test/exception_factory_test.dart index fa46dc3327..d75340d265 100644 --- a/dart/test/exception_factory_test.dart +++ b/dart/test/exception_factory_test.dart @@ -1,10 +1,13 @@ import 'package:sentry/sentry.dart'; import 'package:sentry/src/sentry_exception_factory.dart'; +import 'package:sentry/src/sentry_stack_trace_factory.dart'; import 'package:test/test.dart'; void main() { group('Exception factory', () { - final exceptionFactory = SentryExceptionFactory(options: SentryOptions()); + final options = SentryOptions(); + final exceptionFactory = SentryExceptionFactory( + options: options, stacktraceFactory: SentryStackTraceFactory(options)); test('exceptionFactory.getSentryException', () { SentryException sentryException; @@ -23,7 +26,7 @@ void main() { } expect(sentryException.type, 'StateError'); - expect(sentryException.stacktrace.frames, isNotEmpty); + expect(sentryException.stackTrace.frames, isNotEmpty); }); test('should not override event.stacktrace', () { @@ -35,25 +38,39 @@ void main() { type: 'example', description: 'a mechanism', ); - sentryException = exceptionFactory.getSentryException( - err, - mechanism: mechanism, - stackTrace: ''' + final stackTrace = ''' #0 baz (file:///pathto/test.dart:50:3) #1 bar (file:///pathto/test.dart:46:9) - ''', + '''; + sentryException = exceptionFactory.getSentryException( + err, + mechanism: mechanism, + stackTrace: stackTrace, ); } expect(sentryException.type, 'StateError'); - expect(sentryException.stacktrace.frames.first.lineNo, 46); - expect(sentryException.stacktrace.frames.first.colNo, 9); - expect(sentryException.stacktrace.frames.first.fileName, 'test.dart'); + expect(sentryException.stackTrace.frames.first.lineNo, 46); + expect(sentryException.stackTrace.frames.first.colNo, 9); + expect(sentryException.stackTrace.frames.first.fileName, 'test.dart'); }); }); test("options can't be null", () { - expect(() => SentryExceptionFactory(options: null), throwsArgumentError); + expect( + () => SentryExceptionFactory( + options: null, + stacktraceFactory: SentryStackTraceFactory(SentryOptions()), + ), + throwsArgumentError); + }); + + test("stacktraceFactory can't be null", () { + expect( + () => SentryExceptionFactory( + options: SentryOptions(), stacktraceFactory: null), + throwsArgumentError, + ); }); } diff --git a/dart/test/protocol/sentry_exception_test.dart b/dart/test/protocol/sentry_exception_test.dart index b55a8ad4aa..77b1a3a08a 100644 --- a/dart/test/protocol/sentry_exception_test.dart +++ b/dart/test/protocol/sentry_exception_test.dart @@ -41,7 +41,7 @@ void main() { type: 'StateError', value: 'Bad state: error', module: 'example.module', - stacktrace: stacktrace, + stackTrace: stacktrace, mechanism: mechanism, threadId: 123456, ); diff --git a/dart/test/sentry_client_test.dart b/dart/test/sentry_client_test.dart index 768a4089a7..13232699c7 100644 --- a/dart/test/sentry_client_test.dart +++ b/dart/test/sentry_client_test.dart @@ -1,5 +1,6 @@ import 'package:mockito/mockito.dart'; import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_stack_trace_factory.dart'; import 'package:test/test.dart'; import 'mocks.dart'; @@ -13,6 +14,94 @@ void main() { options.transport = MockTransport(); }); + test('should capture event stacktrace', () async { + final client = SentryClient(options..attachStackTrace = false); + final event = SentryEvent(); + await client.captureEvent( + event, + stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', + ); + + final capturedEvent = (verify( + options.transport.send(captureAny), + ).captured.first) as SentryEvent; + + expect(capturedEvent.stackTrace is SentryStackTrace, true); + }); + + test('should attach event stacktrace', () async { + final client = SentryClient(options); + final event = SentryEvent(); + await client.captureEvent(event); + + final capturedEvent = (verify( + options.transport.send(captureAny), + ).captured.first) as SentryEvent; + + expect(capturedEvent.stackTrace is SentryStackTrace, true); + }); + + test('should not attach event stacktrace', () async { + final client = SentryClient(options..attachStackTrace = false); + final event = SentryEvent(); + await client.captureEvent(event); + + final capturedEvent = (verify( + options.transport.send(captureAny), + ).captured.first) as SentryEvent; + + expect(capturedEvent.stackTrace, isNull); + }); + + test('should not attach event stacktrace if event has throwable', () async { + final client = SentryClient(options); + + SentryEvent event; + try { + throw StateError('Error'); + } on Error catch (err) { + event = SentryEvent(throwable: err); + } + + await client.captureEvent( + event, + stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', + ); + + final capturedEvent = (verify( + options.transport.send(captureAny), + ).captured.first) as SentryEvent; + + expect(capturedEvent.stackTrace, isNull); + expect(capturedEvent.exception.stackTrace, isNotNull); + }); + + test('should not attach event stacktrace if event has exception', () async { + final client = SentryClient(options); + + final exception = SentryException( + type: 'Exception', + value: 'an exception', + stackTrace: SentryStackTrace( + frames: SentryStackTraceFactory(options) + .getStackFrames('#0 baz (file:///pathto/test.dart:50:3)'), + ), + ); + final event = SentryEvent(exception: exception); + + await client.captureEvent( + event, + stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', + ); + + final capturedEvent = (verify( + options.transport.send(captureAny), + ).captured.first) as SentryEvent; + + expect(capturedEvent.stackTrace, isNull); + expect(capturedEvent.exception.stackTrace, isNotNull); + }); + test('should capture message', () async { final client = SentryClient(options); await client.captureMessage( @@ -29,6 +118,19 @@ void main() { expect(capturedEvent.message.formatted, 'simple message 1'); expect(capturedEvent.message.template, 'simple message %d'); expect(capturedEvent.message.params, [1]); + + expect(capturedEvent.stackTrace is SentryStackTrace, true); + }); + + test('should capture message without stacktrace', () async { + final client = SentryClient(options..attachStackTrace = false); + await client.captureMessage('message', level: SentryLevel.error); + + final capturedEvent = (verify( + options.transport.send(captureAny), + ).captured.first) as SentryEvent; + + expect(capturedEvent.stackTrace, isNull); }); }); @@ -60,7 +162,7 @@ void main() { expect(capturedEvent.throwable, error); expect(capturedEvent.exception is SentryException, true); - expect(capturedEvent.exception.stacktrace, isNotNull); + expect(capturedEvent.exception.stackTrace, isNotNull); }); }); @@ -96,11 +198,11 @@ void main() { expect(capturedEvent.throwable, error); expect(capturedEvent.exception is SentryException, true); - expect(capturedEvent.exception.stacktrace, isNotNull); - expect(capturedEvent.exception.stacktrace.frames.first.fileName, + expect(capturedEvent.exception.stackTrace, isNotNull); + expect(capturedEvent.exception.stackTrace.frames.first.fileName, 'test.dart'); - expect(capturedEvent.exception.stacktrace.frames.first.lineNo, 46); - expect(capturedEvent.exception.stacktrace.frames.first.colNo, 9); + expect(capturedEvent.exception.stackTrace.frames.first.lineNo, 46); + expect(capturedEvent.exception.stackTrace.frames.first.colNo, 9); }); }); @@ -136,10 +238,72 @@ void main() { expect(capturedEvent.throwable, exception); expect(capturedEvent.exception is SentryException, true); - expect(capturedEvent.exception.stacktrace.frames.first.fileName, + expect(capturedEvent.exception.stackTrace.frames.first.fileName, 'test.dart'); - expect(capturedEvent.exception.stacktrace.frames.first.lineNo, 46); - expect(capturedEvent.exception.stacktrace.frames.first.colNo, 9); + expect(capturedEvent.exception.stackTrace.frames.first.lineNo, 46); + expect(capturedEvent.exception.stackTrace.frames.first.colNo, 9); + }); + + test('should capture exception with Stackframe.current', () async { + try { + throw Exception('Error'); + } catch (err) { + exception = err; + } + + final client = SentryClient(options); + await client.captureException(exception); + + final capturedEvent = (verify( + options.transport.send(captureAny), + ).captured.first) as SentryEvent; + + expect(capturedEvent.exception.stackTrace, isNotNull); + }); + + test('should capture exception without Stackframe.current', () async { + try { + throw Exception('Error'); + } catch (err) { + exception = err; + } + + final client = SentryClient(options..attachStackTrace = false); + await client.captureException(exception); + + final capturedEvent = (verify( + options.transport.send(captureAny), + ).captured.first) as SentryEvent; + + expect(capturedEvent.exception.stackTrace, isNull); + }); + + test('should not capture sentry frames exception', () async { + try { + throw Exception('Error'); + } catch (err) { + exception = err; + } + + final stacktrace = ''' +#0 init (package:sentry/sentry.dart:46:9) +#1 bar (file:///pathto/test.dart:46:9) + +#2 capture (package:sentry/sentry.dart:46:9) + '''; + + final client = SentryClient(options); + await client.captureException(exception, stackTrace: stacktrace); + + final capturedEvent = (verify( + options.transport.send(captureAny), + ).captured.first) as SentryEvent; + + expect( + capturedEvent.exception.stackTrace.frames + .every((frame) => frame.package != 'sentry'), + true, + ); }); }); diff --git a/dart/test/sentry_event_test.dart b/dart/test/sentry_event_test.dart index 314aac18e3..a80737fd91 100644 --- a/dart/test/sentry_event_test.dart +++ b/dart/test/sentry_event_test.dart @@ -4,6 +4,7 @@ import 'package:sentry/sentry.dart'; import 'package:sentry/src/protocol/request.dart'; +import 'package:sentry/src/sentry_stack_trace_factory.dart'; import 'package:sentry/src/utils.dart'; import 'package:test/test.dart'; @@ -188,6 +189,32 @@ void main() { expect(serialized['exception'], null); }); + test('should serialize stacktrace if SentryStacktrace', () { + final stacktrace = + SentryStackTrace(frames: [SentryStackFrame(function: 'main')]); + final serialized = SentryEvent(stackTrace: stacktrace).toJson(); + expect(serialized['threads']['values'].first['stacktrace'], isNotNull); + }); + + test('should not serialize event.stacktrace if event.exception is set', () { + final stacktrace = + SentryStackTrace(frames: [SentryStackFrame(function: 'main')]); + final serialized = SentryEvent( + exception: SentryException(value: 'Bad state', type: 'StateError'), + stackTrace: stacktrace, + ).toJson(); + expect(serialized['stacktrace'], isNull); + }); + + test('should not serialize stacktrace if not SentryStacktrace', () { + final stacktrace = SentryStackTrace( + frames: SentryStackTraceFactory(SentryOptions()) + .getStackFrames('#0 baz (file:///pathto/test.dart:50:3)'), + ); + final serialized = SentryEvent(stackTrace: stacktrace).toJson(); + expect(serialized['stacktrace'], isNull); + }); + test('serializes to JSON with sentryException', () { var sentryException; try { diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 52b8fdcad9..472f0796ac 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -38,13 +38,12 @@ Future main() async { print('Capture from runZonedGuarded $error'); final event = SentryEvent( throwable: error, - stackTrace: stackTrace, // release is required on Web to match the source maps release: _release, // sdk: const Sdk(name: sdkName, version: sdkVersion), ); - await _sentry.captureEvent(event); + await _sentry.captureEvent(event, stackTrace: stackTrace); }); }