Files
worker/lib/core/services/sentry_service.dart
2025-12-11 16:39:25 +07:00

252 lines
7.2 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
/// Sentry service for error tracking and performance monitoring.
///
/// This service handles:
/// - Initializing Sentry SDK
/// - Capturing exceptions and errors
/// - Capturing custom messages
/// - Setting user context after login
/// - Performance monitoring
///
/// Usage:
/// ```dart
/// // Initialize in main.dart
/// await SentryService.init(
/// dsn: 'your-sentry-dsn',
/// appRunner: () => runApp(MyApp()),
/// );
///
/// // Capture exception
/// SentryService.captureException(error, stackTrace: stackTrace);
///
/// // Capture message
/// SentryService.captureMessage('User performed action X');
///
/// // Set user context after login
/// SentryService.setUser(userId: '123', email: 'user@example.com');
/// ```
class SentryService {
SentryService._();
/// Sentry DSN - Replace with your actual DSN from Sentry dashboard
static const String _dsn = 'https://2c5893508a29e5ea750b64d5ee31aeef@o4509632266436608.ingest.us.sentry.io/4510464530972672';
/// Initialize Sentry SDK
///
/// Must be called before runApp() in main.dart.
/// Wraps the app with Sentry error boundary.
///
/// [dsn] - Optional DSN override (uses default if not provided)
/// [appRunner] - The function that runs the app (typically runApp(MyApp()))
/// [environment] - Environment name (e.g., 'development', 'production')
static Future<void> init({
String? dsn,
required Future<void> Function() appRunner,
String? environment,
}) async {
// Get package info for release version
final packageInfo = await PackageInfo.fromPlatform();
final release = 'partner@${packageInfo.version}+${packageInfo.buildNumber}';
await SentryFlutter.init(
(options) {
options
..dsn = dsn ?? _dsn
// Release version: worker@1.0.1+29
..release = release
// Environment configuration
..environment = environment ?? (kReleaseMode ? 'production' : 'development')
// Performance monitoring
..tracesSampleRate = kReleaseMode ? 0.2 : 1.0 // 20% in prod, 100% in dev
..profilesSampleRate = kReleaseMode ? 0.2 : 1.0
// Enable automatic instrumentation
..enableAutoPerformanceTracing = true
// Capture failed requests
..captureFailedRequests = true
// Debug mode settings
..debug = kDebugMode
// Add app-specific tags
..beforeSend = (event, hint) {
// Filter out certain errors if needed
// Return null to drop the event
// return event;
return null;
};
},
appRunner: appRunner,
);
debugPrint('🔴 Sentry initialized (release: $release, enabled: ${!kDebugMode})');
}
/// Capture an exception with optional stack trace
///
/// [exception] - The exception to capture
/// [stackTrace] - Optional stack trace
/// [hint] - Optional hint with additional context
static Future<void> captureException(
dynamic exception, {
StackTrace? stackTrace,
Hint? hint,
}) async {
try {
await Sentry.captureException(
exception,
stackTrace: stackTrace,
hint: hint,
);
debugPrint('🔴 Sentry: Exception captured - ${exception.runtimeType}');
} catch (e) {
debugPrint('🔴 Sentry error: Failed to capture exception - $e');
}
}
/// Capture a custom message
///
/// [message] - The message to capture
/// [level] - Severity level (default: info)
/// [params] - Optional parameters to include
static Future<void> captureMessage(
String message, {
SentryLevel level = SentryLevel.info,
Map<String, dynamic>? params,
}) async {
try {
await Sentry.captureMessage(
message,
level: level,
withScope: params != null
? (scope) {
params.forEach((key, value) {
scope.setExtra(key, value);
});
}
: null,
);
debugPrint('🔴 Sentry: Message captured - $message');
} catch (e) {
debugPrint('🔴 Sentry error: Failed to capture message - $e');
}
}
/// Set user context for error tracking
///
/// Call this after successful login to associate errors with users.
///
/// [userId] - User's unique identifier
/// [email] - User's email (optional)
/// [username] - User's display name (optional)
/// [extras] - Additional user data (optional)
static Future<void> setUser({
required String userId,
String? email,
String? username,
Map<String, dynamic>? extras,
}) async {
try {
await Sentry.configureScope((scope) {
scope.setUser(SentryUser(
id: userId,
email: email,
username: username,
data: extras,
));
});
debugPrint('🔴 Sentry: User set - $userId');
} catch (e) {
debugPrint('🔴 Sentry error: Failed to set user - $e');
}
}
/// Clear user context on logout
static Future<void> clearUser() async {
try {
await Sentry.configureScope((scope) {
scope.setUser(null);
});
debugPrint('🔴 Sentry: User cleared');
} catch (e) {
debugPrint('🔴 Sentry error: Failed to clear user - $e');
}
}
/// Add a breadcrumb for tracking user actions
///
/// Breadcrumbs are used to track the sequence of events leading to an error.
///
/// [message] - Description of the action
/// [category] - Category of the breadcrumb (e.g., 'navigation', 'ui.click')
/// [data] - Additional data (optional)
static Future<void> addBreadcrumb({
required String message,
String? category,
Map<String, dynamic>? data,
SentryLevel level = SentryLevel.info,
}) async {
try {
await Sentry.addBreadcrumb(Breadcrumb(
message: message,
category: category,
data: data,
level: level,
timestamp: DateTime.now(),
));
} catch (e) {
debugPrint('🔴 Sentry error: Failed to add breadcrumb - $e');
}
}
/// Set a tag for filtering in Sentry dashboard
///
/// [key] - Tag name
/// [value] - Tag value
static Future<void> setTag(String key, String value) async {
try {
await Sentry.configureScope((scope) {
scope.setTag(key, value);
});
} catch (e) {
debugPrint('🔴 Sentry error: Failed to set tag - $e');
}
}
/// Set extra context data
///
/// [key] - Context key
/// [value] - Context value (will be serialized)
static Future<void> setExtra(String key, dynamic value) async {
try {
await Sentry.configureScope((scope) {
scope.setExtra(key, value);
});
} catch (e) {
debugPrint('🔴 Sentry error: Failed to set extra - $e');
}
}
/// Start a performance transaction
///
/// [name] - Transaction name
/// [operation] - Operation type (e.g., 'http.client', 'ui.load')
///
/// Returns the transaction to be finished later.
static ISentrySpan? startTransaction(String name, String operation) {
try {
return Sentry.startTransaction(name, operation);
} catch (e) {
debugPrint('🔴 Sentry error: Failed to start transaction - $e');
return null;
}
}
}