|
@@ -1,5 +1,3 @@
|
|
|
-// @dart=2.9
|
|
|
-
|
|
|
library super_logging;
|
|
|
|
|
|
import 'dart:async';
|
|
@@ -8,6 +6,7 @@ import 'dart:core';
|
|
|
import 'dart:io';
|
|
|
|
|
|
import 'package:ente_auth/core/logging/tunneled_transport.dart';
|
|
|
+import 'package:ente_auth/models/typedefs.dart';
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
import 'package:flutter/widgets.dart';
|
|
|
import 'package:http/http.dart' as http;
|
|
@@ -20,8 +19,6 @@ import 'package:sentry_flutter/sentry_flutter.dart';
|
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
|
|
-typedef FutureOrVoidCallback = FutureOr<void> Function();
|
|
|
-
|
|
|
extension SuperString on String {
|
|
|
Iterable<String> chunked(int chunkSize) sync* {
|
|
|
var start = 0;
|
|
@@ -40,7 +37,7 @@ extension SuperString on String {
|
|
|
}
|
|
|
|
|
|
extension SuperLogRecord on LogRecord {
|
|
|
- String toPrettyString([String extraLines]) {
|
|
|
+ String toPrettyString([String? extraLines]) {
|
|
|
final header = "[$loggerName] [$level] [$time]";
|
|
|
|
|
|
var msg = "$header $message";
|
|
@@ -78,9 +75,9 @@ class LogConfig {
|
|
|
/// ```
|
|
|
///
|
|
|
/// If this is [null], Sentry logger is completely disabled (default).
|
|
|
- String sentryDsn;
|
|
|
+ String? sentryDsn;
|
|
|
|
|
|
- String tunnel;
|
|
|
+ String? tunnel;
|
|
|
|
|
|
/// A built-in retry mechanism for sending errors to sentry.
|
|
|
///
|
|
@@ -97,7 +94,7 @@ class LogConfig {
|
|
|
/// A non-empty string will be treated as an explicit path to a directory.
|
|
|
///
|
|
|
/// The chosen directory can be accessed using [SuperLogging.logFile.parent].
|
|
|
- String logDirPath;
|
|
|
+ String? logDirPath;
|
|
|
|
|
|
/// The maximum number of log files inside [logDirPath].
|
|
|
///
|
|
@@ -115,12 +112,12 @@ class LogConfig {
|
|
|
/// any uncaught errors during its execution will be reported.
|
|
|
///
|
|
|
/// Works by using [FlutterError.onError] and [runZoned].
|
|
|
- FutureOrVoidCallback body;
|
|
|
+ FutureOrVoidCallback? body;
|
|
|
|
|
|
/// The date format for storing log files.
|
|
|
///
|
|
|
/// `DateFormat('y-M-d')` by default.
|
|
|
- DateFormat dateFmt;
|
|
|
+ DateFormat? dateFmt;
|
|
|
|
|
|
String prefix;
|
|
|
|
|
@@ -144,77 +141,78 @@ class SuperLogging {
|
|
|
static final $ = Logger('ente_logging');
|
|
|
|
|
|
/// The current super logging configuration
|
|
|
- static LogConfig config;
|
|
|
+ static late LogConfig config;
|
|
|
|
|
|
- static SharedPreferences _preferences;
|
|
|
+ static late SharedPreferences _preferences;
|
|
|
|
|
|
static const keyShouldReportErrors = "should_report_errors";
|
|
|
|
|
|
static const keyAnonymousUserID = "anonymous_user_id";
|
|
|
|
|
|
- static Future<void> main([LogConfig config]) async {
|
|
|
- config ??= LogConfig();
|
|
|
-
|
|
|
- SuperLogging.config = config;
|
|
|
+ static Future<void> main([LogConfig? appConfig]) async {
|
|
|
+ appConfig ??= LogConfig();
|
|
|
+ SuperLogging.config = appConfig;
|
|
|
|
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
|
-
|
|
|
_preferences = await SharedPreferences.getInstance();
|
|
|
|
|
|
appVersion ??= await getAppVersion();
|
|
|
|
|
|
- final loggingEnabled = config.enableInDebugMode || kReleaseMode;
|
|
|
+ final enable = appConfig.enableInDebugMode || kReleaseMode;
|
|
|
sentryIsEnabled =
|
|
|
- loggingEnabled && config.sentryDsn != null && shouldReportErrors();
|
|
|
- fileIsEnabled = loggingEnabled && config.logDirPath != null;
|
|
|
+ enable && appConfig.sentryDsn != null && shouldReportErrors();
|
|
|
+ fileIsEnabled = enable && appConfig.logDirPath != null;
|
|
|
|
|
|
if (fileIsEnabled) {
|
|
|
await setupLogDir();
|
|
|
}
|
|
|
-
|
|
|
- Logger.root.level = Level.ALL;
|
|
|
- Logger.root.onRecord.listen(onLogRecord);
|
|
|
-
|
|
|
if (sentryIsEnabled) {
|
|
|
setupSentry();
|
|
|
- } else {
|
|
|
- $.info("Sentry is disabled");
|
|
|
}
|
|
|
|
|
|
- if (!loggingEnabled) {
|
|
|
+ Logger.root.level = Level.ALL;
|
|
|
+ Logger.root.onRecord.listen(onLogRecord);
|
|
|
+
|
|
|
+ if (!enable) {
|
|
|
$.info("detected debug mode; sentry & file logging disabled.");
|
|
|
}
|
|
|
if (fileIsEnabled) {
|
|
|
- $.info("log file for today: $logFile with prefix ${config.prefix}");
|
|
|
+ $.info("log file for today: $logFile with prefix ${appConfig.prefix}");
|
|
|
+ }
|
|
|
+ if (sentryIsEnabled) {
|
|
|
+ $.info("sentry uploader started");
|
|
|
}
|
|
|
|
|
|
- if (config.body == null) return;
|
|
|
+ if (appConfig.body == null) return;
|
|
|
|
|
|
- if (loggingEnabled && sentryIsEnabled) {
|
|
|
+ if (enable && sentryIsEnabled) {
|
|
|
await SentryFlutter.init(
|
|
|
(options) {
|
|
|
- options.dsn = config.sentryDsn;
|
|
|
+ options.dsn = appConfig!.sentryDsn;
|
|
|
options.httpClient = http.Client();
|
|
|
- if (config.tunnel != null) {
|
|
|
+ if (appConfig.tunnel != null) {
|
|
|
options.transport =
|
|
|
- TunneledTransport(Uri.parse(config.tunnel), options);
|
|
|
+ TunneledTransport(Uri.parse(appConfig.tunnel!), options);
|
|
|
}
|
|
|
},
|
|
|
- appRunner: () => config.body(),
|
|
|
+ appRunner: () => appConfig!.body!(),
|
|
|
);
|
|
|
} else {
|
|
|
- await config.body();
|
|
|
+ await appConfig.body!();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
static void setUserID(String userID) async {
|
|
|
- if (config?.sentryDsn != null) {
|
|
|
+ if (config.sentryDsn != null) {
|
|
|
Sentry.configureScope((scope) => scope.user = SentryUser(id: userID));
|
|
|
$.info("setting sentry user ID to: $userID");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- static Future<void> _sendErrorToSentry(Object error, StackTrace stack) async {
|
|
|
+ static Future<void> _sendErrorToSentry(
|
|
|
+ Object error,
|
|
|
+ StackTrace? stack,
|
|
|
+ ) async {
|
|
|
try {
|
|
|
await Sentry.captureException(
|
|
|
error,
|
|
@@ -230,14 +228,14 @@ class SuperLogging {
|
|
|
|
|
|
static Future onLogRecord(LogRecord rec) async {
|
|
|
// log misc info if it changed
|
|
|
- String extraLines = "app version: '$appVersion'\n";
|
|
|
+ String? extraLines = "app version: '$appVersion'\n";
|
|
|
if (extraLines != _lastExtraLines) {
|
|
|
_lastExtraLines = extraLines;
|
|
|
} else {
|
|
|
extraLines = null;
|
|
|
}
|
|
|
|
|
|
- final str = config.prefix + " " + rec.toPrettyString(extraLines);
|
|
|
+ final str = (config.prefix) + " " + rec.toPrettyString(extraLines);
|
|
|
|
|
|
// write to stdout
|
|
|
printLog(str);
|
|
@@ -251,16 +249,8 @@ class SuperLogging {
|
|
|
}
|
|
|
|
|
|
// add error to sentry queue
|
|
|
- if (sentryIsEnabled) {
|
|
|
- if (rec.error != null) {
|
|
|
- _sendErrorToSentry(rec.error, null);
|
|
|
- } else if (rec.level == Level.SEVERE || rec.level == Level.SHOUT) {
|
|
|
- if (rec.error != null) {
|
|
|
- _sendErrorToSentry(rec.error, null);
|
|
|
- } else {
|
|
|
- _sendErrorToSentry(rec.message, null);
|
|
|
- }
|
|
|
- }
|
|
|
+ if (sentryIsEnabled && rec.error != null) {
|
|
|
+ _sendErrorToSentry(rec.error!, null);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -268,12 +258,12 @@ class SuperLogging {
|
|
|
static bool isFlushing = false;
|
|
|
|
|
|
static void flushQueue() async {
|
|
|
- if (isFlushing) {
|
|
|
+ if (isFlushing || logFile == null) {
|
|
|
return;
|
|
|
}
|
|
|
isFlushing = true;
|
|
|
final entry = fileQueueEntries.removeFirst();
|
|
|
- await logFile.writeAsString(entry, mode: FileMode.append, flush: true);
|
|
|
+ await logFile!.writeAsString(entry, mode: FileMode.append, flush: true);
|
|
|
isFlushing = false;
|
|
|
if (fileQueueEntries.isNotEmpty) {
|
|
|
flushQueue();
|
|
@@ -292,7 +282,7 @@ class SuperLogging {
|
|
|
static final sentryQueueControl = StreamController<Error>();
|
|
|
|
|
|
/// Whether sentry logging is currently enabled or not.
|
|
|
- static bool sentryIsEnabled;
|
|
|
+ static bool sentryIsEnabled = false;
|
|
|
|
|
|
static Future<void> setupSentry() async {
|
|
|
$.info("Setting up sentry");
|
|
@@ -318,7 +308,7 @@ class SuperLogging {
|
|
|
|
|
|
static bool shouldReportErrors() {
|
|
|
if (_preferences.containsKey(keyShouldReportErrors)) {
|
|
|
- return _preferences.getBool(keyShouldReportErrors);
|
|
|
+ return _preferences.getBool(keyShouldReportErrors)!;
|
|
|
} else {
|
|
|
return false;
|
|
|
}
|
|
@@ -333,22 +323,22 @@ class SuperLogging {
|
|
|
//ignore: prefer_const_constructors
|
|
|
await _preferences.setString(keyAnonymousUserID, Uuid().v4());
|
|
|
}
|
|
|
- return _preferences.getString(keyAnonymousUserID);
|
|
|
+ return _preferences.getString(keyAnonymousUserID)!;
|
|
|
}
|
|
|
|
|
|
/// The log file currently in use.
|
|
|
- static File logFile;
|
|
|
+ static File? logFile;
|
|
|
|
|
|
/// Whether file logging is currently enabled or not.
|
|
|
- static bool fileIsEnabled;
|
|
|
+ static bool fileIsEnabled = false;
|
|
|
|
|
|
static Future<void> setupLogDir() async {
|
|
|
var dirPath = config.logDirPath;
|
|
|
|
|
|
// choose [logDir]
|
|
|
- if (dirPath.isEmpty) {
|
|
|
+ if (dirPath == null || dirPath.isEmpty) {
|
|
|
final root = await getExternalStorageDirectory();
|
|
|
- dirPath = '${root.path}/logs';
|
|
|
+ dirPath = '${root!.path}/logs';
|
|
|
}
|
|
|
|
|
|
// create [logDir]
|
|
@@ -361,16 +351,19 @@ class SuperLogging {
|
|
|
// collect all log files with valid names
|
|
|
await for (final file in dir.list()) {
|
|
|
try {
|
|
|
- final date = config.dateFmt.parse(basename(file.path));
|
|
|
+ final date = config.dateFmt!.parse(basename(file.path));
|
|
|
dates[file as File] = date;
|
|
|
files.add(file);
|
|
|
} on FormatException {}
|
|
|
}
|
|
|
+ final nowTime = DateTime.now();
|
|
|
|
|
|
// delete old log files, if [maxLogFiles] is exceeded.
|
|
|
if (files.length > config.maxLogFiles) {
|
|
|
// sort files based on ascending order of date (older first)
|
|
|
- files.sort((a, b) => dates[a].compareTo(dates[b]));
|
|
|
+ files.sort(
|
|
|
+ (a, b) => (dates[a] ?? nowTime).compareTo((dates[b] ?? nowTime)),
|
|
|
+ );
|
|
|
|
|
|
final extra = files.length - config.maxLogFiles;
|
|
|
final toDelete = files.sublist(0, extra);
|
|
@@ -385,13 +378,13 @@ class SuperLogging {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- logFile = File("$dirPath/${config.dateFmt.format(DateTime.now())}.txt");
|
|
|
+ logFile = File("$dirPath/${config.dateFmt!.format(DateTime.now())}.txt");
|
|
|
}
|
|
|
|
|
|
/// Current app version, obtained from package_info plugin.
|
|
|
///
|
|
|
/// See: [getAppVersion]
|
|
|
- static String appVersion;
|
|
|
+ static String? appVersion;
|
|
|
|
|
|
static Future<String> getAppVersion() async {
|
|
|
final pkgInfo = await PackageInfo.fromPlatform();
|