Skip to content

Commit 96be842

Browse files
tom95yjbanov
authored andcommitted
Add a stackFrameFilter argument to SentryClient's capture (#30)
This allows filtering sensitive frames on the client or applying custom truncation logic.
1 parent b01ebf8 commit 96be842

File tree

3 files changed

+40
-7
lines changed

3 files changed

+40
-7
lines changed

lib/sentry.dart

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ class SentryClient {
164164
'${dsnUri.scheme}://${dsnUri.host}/api/$projectId/store/';
165165

166166
/// Reports an [event] to Sentry.io.
167-
Future<SentryResponse> capture({@required Event event}) async {
167+
Future<SentryResponse> capture(
168+
{@required Event event, StackFrameFilter stackFrameFilter}) async {
168169
final DateTime now = _clock();
169170
String authHeader = 'Sentry sentry_version=6, sentry_client=$sentryClient, '
170171
'sentry_timestamp=${now.millisecondsSinceEpoch}, sentry_key=$publicKey';
@@ -192,7 +193,8 @@ class SentryClient {
192193
if (userContext != null) {
193194
mergeAttributes({'user': userContext.toJson()}, into: data);
194195
}
195-
mergeAttributes(event.toJson(), into: data);
196+
mergeAttributes(event.toJson(stackFrameFilter: stackFrameFilter),
197+
into: data);
196198

197199
List<int> body = utf8.encode(json.encode(data));
198200
if (compressPayload) {
@@ -216,15 +218,19 @@ class SentryClient {
216218
}
217219

218220
/// Reports the [exception] and optionally its [stackTrace] to Sentry.io.
221+
///
222+
/// Optionally allows specifying a [stackFrameFilter] that receives the
223+
/// list of stack frames just before sending to allow modifying it.
219224
Future<SentryResponse> captureException({
220225
@required dynamic exception,
221226
dynamic stackTrace,
227+
StackFrameFilter stackFrameFilter,
222228
}) {
223229
final Event event = new Event(
224230
exception: exception,
225231
stackTrace: stackTrace,
226232
);
227-
return capture(event: event);
233+
return capture(event: event, stackFrameFilter: stackFrameFilter);
228234
}
229235

230236
Future<Null> close() async {
@@ -376,7 +382,7 @@ class Event {
376382
final List<String> fingerprint;
377383

378384
/// Serializes this event to JSON.
379-
Map<String, dynamic> toJson() {
385+
Map<String, dynamic> toJson({StackFrameFilter stackFrameFilter}) {
380386
final Map<String, dynamic> json = <String, dynamic>{
381387
'platform': sdkPlatform,
382388
'sdk': {
@@ -406,7 +412,8 @@ class Event {
406412

407413
if (stackTrace != null) {
408414
json['stacktrace'] = <String, dynamic>{
409-
'frames': encodeStackTrace(stackTrace),
415+
'frames':
416+
encodeStackTrace(stackTrace, stackFrameFilter: stackFrameFilter),
410417
};
411418
}
412419

lib/src/stack_trace.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
import 'package:stack_trace/stack_trace.dart';
66

7+
/// Used to filter or modify stack frames before sending stack trace. The stack
8+
/// frames are in the Sentry.io format.
9+
typedef StackFrameFilter = List<Map<String, dynamic>> Function(
10+
List<Map<String, dynamic>>);
11+
712
/// Sentry.io JSON encoding of a stack frame for the asynchronous suspension,
813
/// which is the gap between asynchronous calls.
914
const Map<String, dynamic> asynchronousGapFrameJson = const <String, dynamic>{
@@ -13,7 +18,8 @@ const Map<String, dynamic> asynchronousGapFrameJson = const <String, dynamic>{
1318
/// Encodes [stackTrace] as JSON in the Sentry.io format.
1419
///
1520
/// [stackTrace] must be [String] or [StackTrace].
16-
List<Map<String, dynamic>> encodeStackTrace(dynamic stackTrace) {
21+
List<Map<String, dynamic>> encodeStackTrace(dynamic stackTrace,
22+
{StackFrameFilter stackFrameFilter}) {
1723
assert(stackTrace is String || stackTrace is StackTrace);
1824
final Chain chain = stackTrace is StackTrace
1925
? new Chain.forTrace(stackTrace)
@@ -24,7 +30,9 @@ List<Map<String, dynamic>> encodeStackTrace(dynamic stackTrace) {
2430
frames.addAll(chain.traces[t].frames.map(encodeStackTraceFrame));
2531
if (t < chain.traces.length - 1) frames.add(asynchronousGapFrameJson);
2632
}
27-
return frames.reversed.toList();
33+
34+
final jsonFrames = frames.reversed.toList();
35+
return stackFrameFilter != null ? stackFrameFilter(jsonFrames) : jsonFrames;
2836
}
2937

3038
Map<String, dynamic> encodeStackTraceFrame(Frame frame) {

test/stack_trace_test.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,23 @@ void main() {
7474
},
7575
]);
7676
});
77+
78+
test('allows changing the stack frame list before sending', () {
79+
StackFrameFilter filter =
80+
(list) => list.where((f) => f['abs_path'] != 'secret.dart').toList();
81+
82+
expect(encodeStackTrace('''
83+
#0 baz (file:///pathto/test.dart:50:3)
84+
#1 bar (file:///pathto/secret.dart:46:9)
85+
''', stackFrameFilter: filter), [
86+
{
87+
'abs_path': 'test.dart',
88+
'function': 'baz',
89+
'lineno': 50,
90+
'in_app': true,
91+
'filename': 'test.dart'
92+
},
93+
]);
94+
});
7795
});
7896
}

0 commit comments

Comments
 (0)