captureException static method
Captures a handled or unhandled exception with context and breadcrumbs.
Implementation
static Future<BloomTelemetryEvent?> captureException(
dynamic exception, {
dynamic stackTrace,
Map<String, dynamic>? context,
List<String>? fingerprint,
String? exceptionType,
BloomErrorLevel level = BloomErrorLevel.error,
}) async {
if (!_isInitialized || !_config.enabled) return null;
// Apply client-side sample rate evaluation
if (_config.sampleRate < 1.0 && _random.nextDouble() > _config.sampleRate) {
return null;
}
final effectiveType = exceptionType ?? exception.runtimeType.toString();
final message = exception.toString();
final stackStr = stackTrace?.toString() ?? StackTrace.current.toString();
// Compute deterministic crash fingerprint
final computedFingerprint = BloomCrashFingerprint.compute(
exceptionType: effectiveType,
message: message,
stackTrace: stackStr,
customFingerprint: fingerprint,
);
final fingerprintHash = BloomCrashFingerprint.hashTokens(computedFingerprint);
_eventCounter++;
final eventId = 'err_${DateTime.now().millisecondsSinceEpoch}_$_eventCounter';
// Assemble metadata payloads
final appPayload = {
'name': _config.appInfo['name'] ?? 'bloom_app',
'version': _config.bloomVersion ?? _config.appInfo['version'] ?? '1.0.0',
'buildNumber': _config.buildNumber ?? _config.appInfo['buildNumber'] ?? '1',
..._config.appInfo,
};
final runtimePayload = {
'bloomVersion': _config.bloomVersion ??
_config.appInfo['bloomVersion'] ??
_config.appInfo['version'] ??
'1.0.0',
'dartVersion': kIsWeb ? 'web' : getDartSdkVersion(),
'flutterVersion': _config.flutterVersion ?? _config.appInfo['flutterVersion'] ?? '3.27.0',
'runtimeFingerprint': _config.runtimeFingerprint ??
BloomRuntimeFingerprint.current().computeHash(),
'channel': _config.channel ?? _config.appInfo['channel'] ?? 'production',
if (_config.activePatchId != null || _config.appInfo['activePatchId'] != null)
'activePatchId': _config.activePatchId ?? _config.appInfo['activePatchId'],
};
final devicePayload = {
'isWeb': kIsWeb,
'platform': kIsWeb ? 'web' : getOperatingSystem(),
'osVersion': kIsWeb ? 'browser' : getOperatingSystemVersion(),
};
final eventContext = {
..._config.tags,
if (context != null) ...context,
};
var event = BloomTelemetryEvent(
eventId: eventId,
timestamp: DateTime.now().toUtc(),
level: level,
exceptionType: effectiveType,
message: message,
stackTrace: stackStr,
fingerprint: computedFingerprint,
fingerprintHash: fingerprintHash,
context: eventContext,
breadcrumbs: _ringBuffer.toList(),
app: appPayload,
runtime: runtimePayload,
device: devicePayload,
);
// Apply beforeSend mutation/filter hook
if (_config.beforeSend != null) {
final modifiedEvent = _config.beforeSend!(event);
if (modifiedEvent == null) {
logger.debug('BloomObservability: Event $eventId dropped by beforeSend filter.');
return null;
}
event = modifiedEvent;
}
// Transmit via configured transport
try {
await _config.transport.send(event);
logger.info(
'BloomObservability: Captured $effectiveType [$eventId] (${event.breadcrumbs.length} breadcrumbs)');
} catch (e, stack) {
logger.error('BloomObservability: Failed to transmit event: $e', stack);
}
return event;
}