-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathsentry_setup.dart
More file actions
337 lines (294 loc) · 12.1 KB
/
sentry_setup.dart
File metadata and controls
337 lines (294 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:dio/dio.dart';
import 'package:sentry_dio/sentry_dio.dart';
import 'package:sentry_file/sentry_file.dart';
import 'package:sentry_logging/sentry_logging.dart';
import 'package:logging/logging.dart';
import 'se_config.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
/// Generates a random customer type based on specified distribution
/// - 40% enterprise
/// - 20% small-plan
/// - 20% medium-plan
/// - 20% large-plan
String getRandomCustomerType() {
final random = Random();
final value = random.nextDouble(); // 0.0 to 1.0
if (value < 0.40) {
return 'enterprise';
} else if (value < 0.60) {
return 'small-plan';
} else if (value < 0.80) {
return 'medium-plan';
} else {
return 'large-plan';
}
}
FutureOr<SentryEvent?> beforeSend(SentryEvent event, Hint? hint) async {
// Add the se tag for engineer separation
final tags = <String, String>{};
if (event.tags != null) tags.addAll(event.tags!);
tags['se'] = se;
event.tags = tags;
// Add the se tag to the fingerprint for grouping by engineer
event.fingerprint = ['{{ default }}', 'se:$se'];
return event;
}
Future<void> initSentry({required VoidCallback appRunner}) async {
// Get configuration from dart-define (for release builds) or dotenv (for debug)
const dartDefineDsn = String.fromEnvironment('SENTRY_DSN', defaultValue: '');
const dartDefineRelease = String.fromEnvironment('SENTRY_RELEASE', defaultValue: '');
const dartDefineEnvironment = String.fromEnvironment('SENTRY_ENVIRONMENT', defaultValue: '');
final String? sentryDsn = dartDefineDsn.isNotEmpty ? dartDefineDsn : dotenv.env['SENTRY_DSN'];
final String? sentryRelease = dartDefineRelease.isNotEmpty ? dartDefineRelease : dotenv.env['SENTRY_RELEASE'];
final String? sentryEnvironment = dartDefineEnvironment.isNotEmpty ? dartDefineEnvironment : dotenv.env['SENTRY_ENVIRONMENT'];
await SentryFlutter.init((options) {
// ========================================
// Core Configuration
// ========================================
options.dsn = sentryDsn;
// Release must match exactly with uploaded debug symbols
// CRITICAL: This must be in format "appname@version+build" (e.g., "empower_flutter@9.14.0+2")
options.release = sentryRelease;
options.environment = sentryEnvironment;
// Set distribution to match build number for better symbol matching
options.dist = '2'; // Matches version 9.14.0+2
// Debug settings (disabled for production to ensure proper symbol resolution)
// Set to true only when debugging Sentry configuration issues
options.debug = false;
options.diagnosticLevel = SentryLevel.error;
// ========================================
// Sampling Configuration
// ========================================
// Capture 100% of errors (recommended for demo, adjust in production)
options.sampleRate = 1.0;
// Capture 100% of performance traces (adjust in production based on volume)
options.tracesSampleRate = 1.0;
// Enable profiling (relative to tracesSampleRate)
// Required for JSON Decoding, Image Decoding, and Frame Drop detection
// Profiling is available on iOS, macOS, and Android
options.profilesSampleRate = 1.0;
// ========================================
// Session Replay Configuration
// ========================================
// Capture 100% of error sessions with replay
options.replay.onErrorSampleRate = 1.0;
// Capture 100% of normal sessions with replay (adjust in production)
options.replay.sessionSampleRate = 1.0;
// ========================================
// Performance Tracking
// ========================================
// Enable automatic performance tracking
options.enableAutoPerformanceTracing = true;
// Enable Time to Initial Display (TTID) and Time to Full Display (TTFD) tracking
options.enableTimeToFullDisplayTracing = true;
// Enable user interaction tracing (tap, swipe, etc.)
options.enableUserInteractionTracing = true;
options.enableUserInteractionBreadcrumbs = true;
// ========================================
// Breadcrumbs & Context
// ========================================
options.maxBreadcrumbs = 100;
options.enableAutoNativeBreadcrumbs = true;
// ========================================
// Attachments & Screenshots
// ========================================
options.attachStacktrace = true;
options.attachScreenshot = false; // Disabled for demo
// options.screenshotQuality = SentryScreenshotQuality.high; // Not needed when screenshots are disabled
options.attachViewHierarchy = true;
options.attachThreads = true;
// ========================================
// Crash & Error Handling
// ========================================
options.enableNativeCrashHandling = true;
options.enableNdkScopeSync = true;
options.reportSilentFlutterErrors = true;
// ANR (Application Not Responding) detection for Android
options.anrEnabled = true;
options.anrTimeoutInterval = const Duration(seconds: 5);
// App Hang tracking for iOS/macOS
// Enabled by default in Sentry Flutter SDK 9.0.0+
// Watchdog termination tracking (iOS/macOS)
options.enableWatchdogTerminationTracking = true;
// Configure App Hang tracking for iOS/macOS
if (Platform.isIOS || Platform.isMacOS) {
// App hang timeout interval (default is 2 seconds)
options.appHangTimeoutInterval = const Duration(seconds: 2);
// Note: App Hang Tracking V2 is enabled by default in SDK 9.0.0+
// It automatically measures duration and differentiates between
// fully-blocking and non-fully-blocking app hangs
}
// ========================================
// Session Tracking
// ========================================
options.enableAutoSessionTracking = true;
options.enableScopeSync = true;
// ========================================
// HTTP Request Tracking
// ========================================
options.captureFailedRequests = true;
options.maxRequestBodySize = MaxRequestBodySize.medium;
// ========================================
// Logging Integration
// ========================================
// Enable structured logs to be sent to Sentry
options.enableLogs = true;
// Integrate with dart logging package to capture Logger() calls
options.addIntegration(LoggingIntegration());
// ========================================
// Metrics Configuration
// ========================================
// Enable metrics to track counters, gauges, and distributions
options.enableMetrics = true;
// ========================================
// Privacy & PII
// ========================================
// Set to true to capture personally identifiable information
// (user IP, request headers, user.id, user.name, user.email in logs/metrics)
// Enable for maximum telemetry in demo environment
options.sendDefaultPii = true;
// ========================================
// Session Replay Privacy Configuration
// ========================================
// Disable default masking - everything is visible in session replays
// WARNING: Only use this for demo environments without sensitive data
options.privacy.maskAllText = false;
options.privacy.maskAllImages = false;
// To enable masking again, set the above to true and add custom rules:
// options.privacy.mask<YourWidget>();
// options.privacy.unmask<YourWidget>();
// options.privacy.maskCallback<Text>(
// (element, widget) {
// final text = widget.data?.toLowerCase() ?? '';
// // Add your masking logic here
// return SentryMaskingDecision.continueProcessing;
// },
// );
// ========================================
// Additional Configuration
// ========================================
options.maxCacheItems = 30;
options.sendClientReports = true;
options.reportPackages = true;
// Sends the envelope to both Sentry and Spotlight which is helpful for debugging
// https://spotlightjs.com/setup/headless/
options.spotlight = Spotlight(enabled: kDebugMode);
// ========================================
// Custom Hooks
// ========================================
// Ensure the se tag is added to all events for engineer separation
options.beforeSend = beforeSend;
// options.beforeBreadcrumb = yourBeforeBreadcrumbFunction;
}, appRunner: appRunner);
// ========================================
// Customer Type Tag Configuration
// ========================================
// Set a random customer type tag for all events to demonstrate tag filtering
// Distribution: 40% enterprise, 20% small-plan, 20% medium-plan, 20% large-plan
final customerType = getRandomCustomerType();
Sentry.configureScope((scope) {
scope.setTag('customerType', customerType);
if (kDebugMode) {
print('Sentry: Set customerType tag to: $customerType');
}
});
// ========================================
// HTTP Client Integration (Dio)
// ========================================
// Create a global Dio instance with Sentry integration
final dio = Dio();
dio.addSentry();
// You can now use this dio instance throughout your app for HTTP requests
}
// Show user feedback dialog from a global error handler
void showUserFeedbackDialog(BuildContext context, SentryId eventId) async {
final nameController = TextEditingController();
final descController = TextEditingController();
final result = await showDialog<Map<String, String>>(
context: context,
builder: (context) => AlertDialog(
title: Text('Send Feedback'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
decoration: InputDecoration(hintText: 'Your Name'),
),
SizedBox(height: 8),
TextField(
controller: descController,
decoration: InputDecoration(hintText: 'Describe what happened'),
maxLines: 4,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.of(
context,
).pop({'name': nameController.text, 'desc': descController.text}),
child: Text('Send'),
),
],
),
);
if (result != null &&
result['desc'] != null &&
result['desc']!.trim().isNotEmpty) {
await Sentry.captureFeedback(
SentryFeedback(
message: result['desc']!,
associatedEventId: eventId,
name: result['name'],
),
);
}
}
// Sentry file I/O instrumentation example
// Use this to automatically track file operations performance
Future<void> sentryFileExample() async {
final file = File('my_file.txt');
final sentryFile = file.sentryTrace();
final transaction = Sentry.startTransaction(
'file_operations_example',
'file.io',
bindToScope: true,
);
try {
await sentryFile.create();
await sentryFile.writeAsString('Hello World');
final text = await sentryFile.readAsString();
if (kDebugMode) {
print(text);
}
await sentryFile.delete();
await transaction.finish(status: SpanStatus.ok());
} catch (error, stackTrace) {
transaction.throwable = error;
transaction.status = SpanStatus.internalError();
await Sentry.captureException(error, stackTrace: stackTrace);
await transaction.finish();
}
}
// Example logger usage
final log = Logger('EmpowerPlantLogger');
void testSentryLogging() {
log.info('Sentry logging breadcrumb!');
try {
throw StateError('Intentional error for Sentry logging test');
} catch (error, stackTrace) {
log.severe('Sentry logging error!', error, stackTrace);
}
}