TF-4136 Add config Sentry

This commit is contained in:
dab246
2025-12-15 12:01:21 +07:00
committed by Dat H. Pham
parent ad768f1552
commit ec75bb3cab
46 changed files with 804 additions and 216 deletions
+74
View File
@@ -0,0 +1,74 @@
import 'package:core/utils/application_manager.dart';
import 'package:core/utils/build_utils.dart';
import 'package:core/utils/config/env_loader.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
/// Holds configuration values for initializing Sentry.
class SentryConfig {
// DSN (Data Source Name) endpoint for the Sentry project
final String dsn;
// Running environment (production/staging/dev)
final String environment;
// Current app release version
final String release;
// // Performance monitoring: Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing
final double tracesSampleRate;
// Optional profiling
final double profilesSampleRate;
// Enable logs to be sent to Sentry. To use Sentry.logger.fmt
final bool enableLogs;
// Debug logs during development
final bool isDebug;
// Automatically attaches a screenshot when capturing an error or exception.
final bool attachScreenshot;
// Check if Sentry is available
final bool isAvailable;
SentryConfig({
required this.dsn,
required this.environment,
required this.release,
this.tracesSampleRate = 1.0,
this.profilesSampleRate = 1.0,
this.enableLogs = true,
this.isDebug = BuildUtils.isDebugMode,
this.attachScreenshot = false,
this.isAvailable = false,
});
/// Load configuration from an env file.
static Future<SentryConfig> load() async {
await EnvLoader.loadConfigFromEnv();
final sentryAvailable = dotenv.get('SENTRY_ENABLED', fallback: 'false');
final isAvailable = sentryAvailable == 'true';
final sentryDSN = dotenv.get('SENTRY_DSN', fallback: '');
final sentryEnvironment = dotenv.get('SENTRY_ENVIRONMENT', fallback: '');
if (!isAvailable) {
throw Exception('Sentry is not available');
}
if (sentryDSN.trim().isEmpty || sentryEnvironment.trim().isEmpty) {
throw Exception('Sentry configuration is missing');
}
final appVersion = await ApplicationManager().getVersion();
return SentryConfig(
dsn: sentryDSN,
environment: sentryEnvironment,
release: appVersion,
isAvailable: isAvailable,
);
}
}
@@ -0,0 +1,45 @@
import 'dart:async';
import 'package:core/utils/sentry/sentry_config.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class SentryInitializer {
static Future<bool> init(FutureOr<void> Function() appRunner) async {
final config = await SentryConfig.load();
await SentryFlutter.init(
(options) {
options.dsn = config.dsn;
options.environment = config.environment;
options.release = config.release;
options.tracesSampleRate = config.tracesSampleRate;
options.profilesSampleRate = config.profilesSampleRate;
options.enableLogs = config.enableLogs;
options.debug = config.isDebug;
options.attachScreenshot = config.attachScreenshot;
options.maxRequestBodySize = MaxRequestBodySize.small;
// Automatically enable breadcrumbs that are appropriate for the current platform
options.enableBreadcrumbTrackingForCurrentPlatform();
// Assign the callback to process events before sending them to Sentry
options.beforeSend = _beforeSendHandler;
},
appRunner: appRunner,
);
return config.isAvailable;
}
/// Handler executed before sending an event to Sentry
static Future<SentryEvent?> _beforeSendHandler(
SentryEvent event,
Hint? hint,
) async {
// Ignore AssertionError events
if (event.throwable is AssertionError) {
return null;
}
return event;
}
}
+103
View File
@@ -0,0 +1,103 @@
import 'dart:async';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/sentry/sentry_initializer.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
/// Controls Sentry initialization and error reporting.
class SentryManager {
SentryManager._();
static final SentryManager instance = SentryManager._();
bool _isSentryAvailable = false;
bool get isSentryAvailable => _isSentryAvailable;
/// Initialize Sentry. App still runs if init fails.
Future<void> initialize({
required FutureOr<void> Function() appRunner,
required FutureOr<void> Function() fallBackRunner,
}) async {
try {
_isSentryAvailable = await SentryInitializer.init(appRunner);
log('[SentryManager] Sentry initialized: $_isSentryAvailable');
} catch (e, st) {
logError('[SentryManager] Init failed', exception: e, stackTrace: st);
await fallBackRunner();
}
}
/// Capture an exception. Metadata is attached as breadcrumbs.
Future<void> captureException(
dynamic exception, {
StackTrace? stackTrace,
String? message,
Map<String, dynamic>? extras,
}) async {
if (!_isSentryAvailable) return;
await Sentry.captureException(
exception,
stackTrace: stackTrace,
withScope: (scope) {
scope.addBreadcrumb(
Breadcrumb(
message: message ?? exception.toString(),
data: extras,
level: SentryLevel.error,
),
);
},
);
}
/// Capture a text message. Metadata also goes into breadcrumbs.
Future<void> captureMessage(
String message, {
SentryLevel level = SentryLevel.info,
Map<String, dynamic>? extras,
}) async {
if (!_isSentryAvailable) return;
await Sentry.captureMessage(
message,
level: level,
withScope: (scope) {
scope.addBreadcrumb(
Breadcrumb(
message: message,
data: extras,
level: SentryLevel.info,
),
);
},
);
}
Future<void> setUser(SentryUser user) async {
if (!_isSentryAvailable) return;
try {
await Sentry.configureScope((scope) => scope.setUser(user));
log('[SentryManager] User set: ${user.email}');
} catch (e, st) {
logError('[SentryManager] Set user failed', exception: e, stackTrace: st);
}
}
Future<void> clearUser() async {
if (!_isSentryAvailable) return;
try {
await Sentry.configureScope((scope) => scope.setUser(null));
log('[SentryManager] User cleared');
} catch (e, st) {
logError(
'[SentryManager] Clear user failed',
exception: e,
stackTrace: st,
);
}
}
}