diff --git a/lib/core/logging/super_logging.dart b/lib/core/logging/super_logging.dart index bccc2cf852..514e97e9a1 100644 --- a/lib/core/logging/super_logging.dart +++ b/lib/core/logging/super_logging.dart @@ -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 Function(); - extension SuperString on String { Iterable 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 main([LogConfig config]) async { - config ??= LogConfig(); - - SuperLogging.config = config; + static Future 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(); } + if (sentryIsEnabled) { + setupSentry(); + } Logger.root.level = Level.ALL; Logger.root.onRecord.listen(onLogRecord); - if (sentryIsEnabled) { - setupSentry(); - } else { - $.info("Sentry is disabled"); - } - - if (!loggingEnabled) { + 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 _sendErrorToSentry(Object error, StackTrace stack) async { + static Future _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(); /// Whether sentry logging is currently enabled or not. - static bool sentryIsEnabled; + static bool sentryIsEnabled = false; static Future 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 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 getAppVersion() async { final pkgInfo = await PackageInfo.fromPlatform(); diff --git a/lib/core/logging/tunneled_transport.dart b/lib/core/logging/tunneled_transport.dart index 3234b88f96..38f3ca1008 100644 --- a/lib/core/logging/tunneled_transport.dart +++ b/lib/core/logging/tunneled_transport.dart @@ -1,5 +1,3 @@ -// @dart=2.9 - import 'dart:convert'; import 'package:http/http.dart'; @@ -10,9 +8,9 @@ class TunneledTransport implements Transport { final Uri _tunnel; final SentryOptions _options; - final Dsn _dsn; + final Dsn? _dsn; - _CredentialBuilder _credentialBuilder; + _CredentialBuilder? _credentialBuilder; final Map _headers; @@ -21,7 +19,7 @@ class TunneledTransport implements Transport { } TunneledTransport._(this._tunnel, this._options) - : _dsn = Dsn.parse(_options.dsn), + : _dsn = _options.dsn != null ? Dsn.parse(_options.dsn!) : null, _headers = _buildHeaders( _options.platformChecker.isWeb, _options.sdk.identifier, @@ -34,7 +32,7 @@ class TunneledTransport implements Transport { } @override - Future send(SentryEnvelope envelope) async { + Future send(SentryEnvelope envelope) async { final streamedRequest = await _createStreamedRequest(envelope); final response = await _options.httpClient .send(streamedRequest) @@ -47,7 +45,7 @@ class TunneledTransport implements Transport { _options.logger( SentryLevel.error, 'API returned an error, statusCode = ${response.statusCode}, ' - 'body = ${response.body}', + 'body = ${response.body}', ); } return const SentryId.empty(); @@ -66,15 +64,15 @@ class TunneledTransport implements Transport { } Future _createStreamedRequest( - SentryEnvelope envelope, - ) async { + SentryEnvelope envelope, + ) async { final streamedRequest = StreamedRequest('POST', _tunnel); envelope .envelopeStream(_options) .listen(streamedRequest.sink.add) .onDone(streamedRequest.sink.close); - streamedRequest.headers.addAll(_credentialBuilder.configure(_headers)); + streamedRequest.headers.addAll(_credentialBuilder!.configure(_headers)); return streamedRequest; } @@ -92,13 +90,13 @@ class _CredentialBuilder { _clock = clock; factory _CredentialBuilder( - Dsn dsn, - String sdkIdentifier, - ClockProvider clock, - ) { + Dsn? dsn, + String sdkIdentifier, + ClockProvider clock, + ) { final authHeader = _buildAuthHeader( - publicKey: dsn.publicKey, - secretKey: dsn.secretKey, + publicKey: dsn?.publicKey, + secretKey: dsn?.secretKey, sdkIdentifier: sdkIdentifier, ); @@ -106,9 +104,9 @@ class _CredentialBuilder { } static String _buildAuthHeader({ - String publicKey, - String secretKey, - String sdkIdentifier, + String? publicKey, + String? secretKey, + String? sdkIdentifier, }) { var header = 'Sentry sentry_version=7, sentry_client=$sdkIdentifier, ' 'sentry_key=$publicKey';