Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
902dd1d
add options.attachStacktrace
rxlabz Nov 10, 2020
e1f347d
fix case
rxlabz Nov 10, 2020
07048d5
add a _exceptionFactory.stacktraceFactory getter
rxlabz Nov 10, 2020
a61f760
- auto-attach stacktrace to exception only if options.attachStackTrace
rxlabz Nov 10, 2020
0a5addb
fix test
rxlabz Nov 10, 2020
f7f2458
add client._stackTraceFactory
rxlabz Nov 10, 2020
a526771
fix import
rxlabz Nov 10, 2020
3c6cd02
attach stacktrace only if frames is not empty
rxlabz Nov 10, 2020
65c4d2b
attachStackTrace doc
rxlabz Nov 10, 2020
6d349fc
attachStackTrace only if event.stacktrace is null
rxlabz Nov 10, 2020
23738c2
init factories in client.ctor
rxlabz Nov 10, 2020
cbb7be8
feedback
rxlabz Nov 10, 2020
ee39da9
required SentryExceptionFactory.stacktraceFactory
rxlabz Nov 10, 2020
d901e26
type SentryEvent.stackTrace as SentryStackTrace
rxlabz Nov 11, 2020
490d3b7
serialize exception or stacktrace
rxlabz Nov 11, 2020
75d69a6
feedback
rxlabz Nov 11, 2020
b0c4a8e
feedbacks
rxlabz Nov 11, 2020
a027fe8
attach stacktrace only if stacktrace is null
rxlabz Nov 11, 2020
b92f661
event.stacktrace only if event.throwable AND event.exception are null
rxlabz Nov 11, 2020
7f4d18e
remove null conditional instanciation in exceptionFactory
rxlabz Nov 11, 2020
b3f546d
attach the passed stacktrace if attachStackTrace is true
rxlabz Nov 11, 2020
67c9ae8
fix & refactor client._prepareEvent
rxlabz Nov 11, 2020
c65800e
add stacktrace and exception.stacktrace tests
rxlabz Nov 11, 2020
ac1ee38
feedbacks
rxlabz Nov 11, 2020
3077703
fix frame.uri.pathSegments.isNotEmpty
rxlabz Nov 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 9 additions & 0 deletions dart/lib/src/protocol/sentry_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,15 @@ class SentryEvent {
json['exception'] = {
'values': [exception.toJson()].toList(growable: false)
};
} else if (stackTrace is SentryStackTrace) {
json['threads'] = {
'values': [
{
'id': 0,
'stacktrace': (stackTrace as SentryStackTrace).toJson(),
}
]
};
}

if (level != null) {
Expand Down
40 changes: 28 additions & 12 deletions dart/lib/src/sentry_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -20,22 +31,19 @@ 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<SentryId> captureEvent(
Expand Down Expand Up @@ -97,6 +105,14 @@ class SentryClient {
.getSentryException(event.throwable, stackTrace: event.stackTrace);

event = event.copyWith(exception: sentryException);
} else if (_options.attachStackTrace &&
event.stackTrace == null &&
event.exception == null) {
final frames = _stackTraceFactory.getStackFrames(StackTrace.current);

if (frames != null && frames.isNotEmpty) {
event = event.copyWith(stackTrace: SentryStackTrace(frames: frames));
}
}

return event;
Expand Down
26 changes: 17 additions & 9 deletions dart/lib/src/sentry_exception_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -26,13 +31,16 @@ class SentryExceptionFactory {
}) {
if (exception is Error) {
stackTrace ??= exception.stackTrace;
} else {
} else if (_options.attachStackTrace) {
stackTrace ??= StackTrace.current;
}

final sentryStackTrace = SentryStackTrace(
frames: _stacktraceFactory.getStackFrames(stackTrace),
);
SentryStackTrace sentryStackTrace;
if (stackTrace != null) {
sentryStackTrace = SentryStackTrace(
frames: _stacktraceFactory.getStackFrames(stackTrace),
);
}

final sentryException = SentryException(
type: '${exception.runtimeType}',
Expand Down
17 changes: 17 additions & 0 deletions dart/lib/src/sentry_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions dart/lib/src/sentry_stack_trace_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ class SentryStackTraceFactory {

final frames = <SentryStackFrame>[];
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);

Expand Down Expand Up @@ -101,14 +103,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;
}
}
Expand Down
20 changes: 18 additions & 2 deletions dart/test/exception_factory_test.dart
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -54,6 +57,19 @@ void main() {
});

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,
);
});
}
75 changes: 75 additions & 0 deletions dart/test/sentry_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,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);
});
});

Expand Down Expand Up @@ -141,6 +154,68 @@ void main() {
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)
<asynchronous suspension>
#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,
);
});
});

group('SentryClient : apply scope to the captured event', () {
Expand Down
23 changes: 23 additions & 0 deletions dart/test/sentry_event_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,29 @@ 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 = '#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 {
Expand Down