Skip to content

Add a stackFrameFilter argument to SentryClient's capture #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 10, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
17 changes: 12 additions & 5 deletions lib/sentry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,10 @@ class SentryClient {
'${dsnUri.scheme}://${dsnUri.host}/api/$projectId/store/';

/// Reports an [event] to Sentry.io.
Future<SentryResponse> capture({@required Event event}) async {
Future<SentryResponse> capture({
@required Event event,
StackFrameFilter stackFrameFilter
}) async {
final DateTime now = _clock();
String authHeader = 'Sentry sentry_version=6, sentry_client=$sentryClient, '
'sentry_timestamp=${now.millisecondsSinceEpoch}, sentry_key=$publicKey';
Expand Down Expand Up @@ -192,7 +195,7 @@ class SentryClient {
if (userContext != null) {
mergeAttributes({'user': userContext.toJson()}, into: data);
}
mergeAttributes(event.toJson(), into: data);
mergeAttributes(event.toJson(stackFrameFilter: stackFrameFilter), into: data);

List<int> body = utf8.encode(json.encode(data));
if (compressPayload) {
Expand All @@ -216,15 +219,19 @@ class SentryClient {
}

/// Reports the [exception] and optionally its [stackTrace] to Sentry.io.
///
/// Optionally allows specifying a [stackFrameFilter] that receives the
/// list of stack frames just before sending to allow modifying it.
Future<SentryResponse> captureException({
@required dynamic exception,
dynamic stackTrace,
StackFrameFilter stackFrameFilter,
}) {
final Event event = new Event(
exception: exception,
stackTrace: stackTrace,
);
return capture(event: event);
return capture(event: event, stackFrameFilter: stackFrameFilter);
}

Future<Null> close() async {
Expand Down Expand Up @@ -376,7 +383,7 @@ class Event {
final List<String> fingerprint;

/// Serializes this event to JSON.
Map<String, dynamic> toJson() {
Map<String, dynamic> toJson({StackFrameFilter stackFrameFilter}) {
final Map<String, dynamic> json = <String, dynamic>{
'platform': sdkPlatform,
'sdk': {
Expand Down Expand Up @@ -406,7 +413,7 @@ class Event {

if (stackTrace != null) {
json['stacktrace'] = <String, dynamic>{
'frames': encodeStackTrace(stackTrace),
'frames': encodeStackTrace(stackTrace, stackFrameFilter: stackFrameFilter),
};
}

Expand Down
13 changes: 11 additions & 2 deletions lib/src/stack_trace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

import 'package:stack_trace/stack_trace.dart';

/// Used to filter or modify stack frames before sending stack trace.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's mention that the stack trace format given to the function uses the Senty.io format.

typedef StackFrameFilter =
List<Map<String,dynamic>> Function(List<Map<String,dynamic>>);

/// Sentry.io JSON encoding of a stack frame for the asynchronous suspension,
/// which is the gap between asynchronous calls.
const Map<String, dynamic> asynchronousGapFrameJson = const <String, dynamic>{
Expand All @@ -13,7 +17,8 @@ const Map<String, dynamic> asynchronousGapFrameJson = const <String, dynamic>{
/// Encodes [stackTrace] as JSON in the Sentry.io format.
///
/// [stackTrace] must be [String] or [StackTrace].
List<Map<String, dynamic>> encodeStackTrace(dynamic stackTrace) {
List<Map<String, dynamic>> encodeStackTrace(dynamic stackTrace,
{StackFrameFilter stackFrameFilter}) {
assert(stackTrace is String || stackTrace is StackTrace);
final Chain chain = stackTrace is StackTrace
? new Chain.forTrace(stackTrace)
Expand All @@ -24,7 +29,11 @@ List<Map<String, dynamic>> encodeStackTrace(dynamic stackTrace) {
frames.addAll(chain.traces[t].frames.map(encodeStackTraceFrame));
if (t < chain.traces.length - 1) frames.add(asynchronousGapFrameJson);
}
return frames.reversed.toList();

final jsonFrames = frames.reversed.toList();
return stackFrameFilter != null
? stackFrameFilter(jsonFrames)
: jsonFrames;
}

Map<String, dynamic> encodeStackTraceFrame(Frame frame) {
Expand Down
18 changes: 18 additions & 0 deletions test/stack_trace_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,23 @@ void main() {
},
]);
});

test('allows changing the stack frame list before sending', () {
StackFrameFilter filter = (list) =>
list.where((f) => f['abs_path'] != 'secret.dart').toList();

expect(encodeStackTrace('''
#0 baz (file:///pathto/test.dart:50:3)
#1 bar (file:///pathto/secret.dart:46:9)
''', stackFrameFilter: filter), [
{
'abs_path': 'test.dart',
'function': 'baz',
'lineno': 50,
'in_app': true,
'filename': 'test.dart'
},
]);
});
});
}