blob: 9bb95238262d98d1adfb72f08adcc9df50b35d0f [file] [log] [blame]
// Copyright 2019, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of firebase_crashlytics;
/// The entry point for accessing Crashlytics.
///
/// You can get an instance by calling `Crashlytics.instance`.
class Crashlytics {
static final Crashlytics instance = Crashlytics();
/// Set to true to have errors sent to Crashlytics while in debug mode. By
/// default this is false.
bool enableInDevMode = false;
/// Keys to be included with report.
final Map<String, dynamic> _keys = <String, dynamic>{};
/// Logs to be included with report.
final ListQueue<String> _logs = ListQueue<String>(15);
int _logSize = 0;
@visibleForTesting
static const MethodChannel channel =
MethodChannel('plugins.flutter.io/firebase_crashlytics');
/// Submits non-fatal crash report to Firebase Crashlytics.
Future<void> onError(FlutterErrorDetails details) async {
print('Error caught by Crashlytics plugin:');
bool inDebugMode = false;
if (!enableInDevMode) {
assert(inDebugMode = true);
}
if (inDebugMode && !enableInDevMode) {
print(Trace.format(details.stack));
} else {
// Report error
final List<String> stackTraceLines =
Trace.format(details.stack).trimRight().split('\n');
final List<Map<String, String>> stackTraceElements =
getStackTraceElements(stackTraceLines);
final dynamic result = await channel
.invokeMethod<dynamic>('Crashlytics#onError', <String, dynamic>{
'exception': details.exceptionAsString(),
// FlutterErrorDetails.context has been migrated from a String to a
// DiagnosticsNode. Coerce it to a String here in a way that will work
// on both Strings and the new DiagnosticsNode values. See https://groups.google.com/forum/#!topic/flutter-announce/hp1RNIgej38
'context': '${details.context}',
'stackTraceElements': stackTraceElements,
'logs': _logs.toList(),
'keys': _prepareKeys(),
});
print(result);
}
}
void crash() {
throw StateError('Error thrown by Crashlytics plugin');
}
/// Reports the global value for debug mode.
/// TODO(kroikie): Clarify what this means in context of both Android and iOS.
Future<bool> isDebuggable() async {
final bool result =
await channel.invokeMethod<bool>('Crashlytics#isDebuggable');
return result;
}
/// Returns Crashlytics SDK version.
Future<String> getVersion() async {
final String result =
await channel.invokeMethod<String>('Crashlytics#getVersion');
return result;
}
/// Add text logging that will be sent with your next report. `msg` will be
/// printed to the console when in debug mode. Each report has a rolling max
/// of 64k of logs, older logs are removed to allow newer logs to fit within
/// the limit.
void log(String msg) {
_logSize += Uint8List.fromList(msg.codeUnits).length;
_logs.add(msg);
// Remove oldest log till logSize is no more than 64K.
while (_logSize > 65536) {
final String first = _logs.removeFirst();
_logSize -= Uint8List.fromList(first.codeUnits).length;
}
}
void _setValue(String key, dynamic value) {
// Check that only 64 keys are set.
if (_keys.containsKey(key) || _keys.length <= 64) {
_keys[key] = value;
}
}
/// Sets a value to be associated with a given key for your crash data.
void setBool(String key, bool value) {
_setValue(key, value);
}
/// Sets a value to be associated with a given key for your crash data.
void setDouble(String key, double value) {
_setValue(key, value);
}
/// Sets a value to be associated with a given key for your crash data.
void setInt(String key, int value) {
_setValue(key, value);
}
/// Sets a value to be associated with a given key for your crash data.
void setString(String key, String value) {
_setValue(key, value);
}
/// Optionally set a end-user's name or username for display within the
/// Crashlytics UI. Please be mindful of end-user's privacy.
Future<void> setUserEmail(String email) async {
await channel.invokeMethod<void>(
'Crashlytics#setUserEmail', <String, dynamic>{'email': email});
}
/// Specify a user identifier which will be visible in the Crashlytics UI.
/// Please be mindful of end-user's privacy.
Future<void> setUserIdentifier(String identifier) async {
await channel.invokeMethod<void>('Crashlytics#setUserIdentifier',
<String, dynamic>{'identifier': identifier});
}
/// Specify a user name which will be visible in the Crashlytics UI. Please
/// be mindful of end-user's privacy.
Future<void> setUserName(String name) async {
await channel.invokeMethod<void>(
'Crashlytics#setUserName', <String, dynamic>{'name': name});
}
List<Map<String, dynamic>> _prepareKeys() {
final List<Map<String, dynamic>> crashlyticsKeys = <Map<String, dynamic>>[];
for (String key in _keys.keys) {
final dynamic value = _keys[key];
final Map<String, dynamic> crashlyticsKey = <String, dynamic>{
'key': key,
'value': value
};
if (value is int) {
crashlyticsKey['type'] = 'int';
} else if (value is double) {
crashlyticsKey['type'] = 'double';
} else if (value is String) {
crashlyticsKey['type'] = 'string';
} else if (value is bool) {
crashlyticsKey['type'] = 'boolean';
}
}
return crashlyticsKeys;
}
@visibleForTesting
List<Map<String, String>> getStackTraceElements(List<String> lines) {
final List<Map<String, String>> elements = <Map<String, String>>[];
for (String line in lines) {
final List<String> lineParts = line.split(RegExp('\\s+'));
try {
final String fileName = lineParts[0];
final String lineNumber = lineParts[1].contains(":")
? lineParts[1].substring(0, lineParts[1].indexOf(":")).trim()
: lineParts[1];
final String className =
lineParts[2].substring(0, lineParts[2].indexOf(".")).trim();
final String methodName =
lineParts[2].substring(lineParts[2].indexOf(".") + 1).trim();
elements.add(<String, String>{
'class': className,
'method': methodName,
'file': fileName,
'line': lineNumber,
});
} catch (e) {
print(e.toString());
}
}
return elements;
}
}