diff --git a/lib/core/database/mongodb_connection.dart b/lib/core/database/mongodb_connection.dart index 8b3205dc..b20ca1a9 100644 --- a/lib/core/database/mongodb_connection.dart +++ b/lib/core/database/mongodb_connection.dart @@ -9,6 +9,7 @@ import 'package:querya_desktop/core/security/ssh_tunnel_config.dart'; import 'package:querya_desktop/core/security/ssl_certificate_support.dart'; import 'package:querya_desktop/core/storage/connection_secrets_store.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; /// MongoDB connection configuration and state. class MongoConnection with SshTunnelHost { @@ -368,7 +369,9 @@ class MongoConnection with SshTunnelHost { _openedDbs.remove(databaseName); try { await existing.close(); - } catch (_) {} + } catch (e) { + logIgnored('mongodb_connection', e); + } } return _openingDbs.putIfAbsent(databaseName, () async { try { @@ -379,7 +382,9 @@ class MongoConnection with SshTunnelHost { } catch (_) { try { await db.close(); - } catch (_) {} + } catch (e) { + logIgnored('mongodb_connection', e); + } rethrow; } _openedDbs[databaseName] = db; @@ -471,7 +476,9 @@ class MongoConnection with SshTunnelHost { _openedDbs.remove(databaseName); try { await db.close(); - } catch (_) {} + } catch (e) { + logIgnored('mongodb_connection', e); + } if (identical(_db, db)) { _db = null; for (final other in _openedDbs.values) { diff --git a/lib/core/database/mysql_connection.dart b/lib/core/database/mysql_connection.dart index 06f3c0d0..afd23671 100644 --- a/lib/core/database/mysql_connection.dart +++ b/lib/core/database/mysql_connection.dart @@ -13,6 +13,7 @@ import 'package:querya_desktop/core/security/ssh_tunnel_manager.dart'; import 'package:querya_desktop/core/security/ssl_certificate_support.dart'; import 'package:querya_desktop/core/storage/connection_secrets_store.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; /// TLS policy from `ssl-mode` / `sslmode` / `ssl` on a MySQL URI. enum MysqlSslMode { @@ -212,7 +213,9 @@ class MysqlConnection with SshTunnelHost { if (_conn != null && !_conn!.connected) { try { await _conn?.close(); - } catch (_) {} + } catch (e) { + logIgnored('mysql_connection', e); + } _conn = null; } final generation = _generation; @@ -305,10 +308,14 @@ class MysqlConnection with SshTunnelHost { if (identical(_conn, opened)) _conn = null; try { if (opened != null && opened.connected) await opened.close(); - } catch (_) {} + } catch (e) { + logIgnored('mysql_connection', e); + } try { await openedHandle?.release(); - } catch (_) {} + } catch (e) { + logIgnored('mysql_connection', e); + } forgetSshTunnel(openedHandle); throw StateError('The connection was closed while it was being opened'); } @@ -560,7 +567,9 @@ class MysqlConnection with SshTunnelHost { if (!join) { try { await execute('ROLLBACK'); - } catch (_) {} + } catch (e) { + logIgnored('mysql_connection', e); + } } rethrow; } diff --git a/lib/core/database/redis_connection.dart b/lib/core/database/redis_connection.dart index 316d52c3..fe8c1bd6 100644 --- a/lib/core/database/redis_connection.dart +++ b/lib/core/database/redis_connection.dart @@ -12,6 +12,7 @@ import 'package:querya_desktop/core/storage/connection_secrets_store.dart'; import 'package:querya_desktop/core/database/redis_bulk.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:redis/redis.dart' as redis; +import 'package:querya_desktop/core/util/log_ignored.dart'; /// First page / load-more size for hash, list, set, and zset editors. const redisCollectionPageSize = 200; @@ -181,7 +182,9 @@ class RedisConnection with SshTunnelHost { if (_socket != null) { try { _socket?.destroy(); - } catch (_) {} + } catch (e) { + logIgnored('redis_connection', e); + } _socket = null; _command = null; _conn = null; @@ -276,7 +279,9 @@ class RedisConnection with SshTunnelHost { // a silent server): close it, or every failed try leaves one behind. try { socket?.destroy(); - } catch (_) {} + } catch (e) { + logIgnored('redis_connection', e); + } await releaseSshTunnel(); rethrow; } @@ -306,7 +311,9 @@ class RedisConnection with SshTunnelHost { _socket = null; try { sock?.destroy(); - } catch (_) {} + } catch (e) { + logIgnored('redis_connection', e); + } final c = _conn; _conn = null; if (c != null && wasConnected) { @@ -363,7 +370,9 @@ class RedisConnection with SshTunnelHost { if (!isConnected) return; try { await sendCommand([readOnly ? 'READONLY' : 'READWRITE']); - } catch (_) {} + } catch (e) { + logIgnored('redis_connection', e); + } } void _assertWritable() { @@ -927,7 +936,9 @@ class RedisConnectionTestFake extends RedisConnection { if (c != null) { try { await c.close(); - } catch (_) {} + } catch (e) { + logIgnored('redis_connection', e); + } } } diff --git a/lib/core/database/sqlite_connection.dart b/lib/core/database/sqlite_connection.dart index 9a857bc6..3b549994 100644 --- a/lib/core/database/sqlite_connection.dart +++ b/lib/core/database/sqlite_connection.dart @@ -7,6 +7,7 @@ import 'package:querya_desktop/core/database/sql_table_target_extractor.dart'; import 'package:querya_desktop/core/database/sqlite_sql.dart'; import 'package:querya_desktop/core/database/table_schema_meta.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; /// In-memory paths (`:memory:`) skip the missing-file check. bool sqlitePathIsInMemory(String path) => @@ -275,7 +276,9 @@ class SqliteConnection { if (!join) { try { await execute('ROLLBACK'); - } catch (_) {} + } catch (e) { + logIgnored('sqlite_connection', e); + } } rethrow; } @@ -353,7 +356,9 @@ class SqliteConnection { } catch (_) { try { await _db!.execute('DROP VIEW IF EXISTS $viewName'); - } catch (_) {} + } catch (e) { + logIgnored('sqlite_connection', e); + } } } @@ -362,7 +367,9 @@ class SqliteConnection { if (target != null) { try { return await listColumnNames(table: target.tableName); - } catch (_) {} + } catch (e) { + logIgnored('sqlite_connection', e); + } } return const []; diff --git a/lib/core/extensions/extension_driver_session.dart b/lib/core/extensions/extension_driver_session.dart index a0378953..b2946c1c 100644 --- a/lib/core/extensions/extension_driver_session.dart +++ b/lib/core/extensions/extension_driver_session.dart @@ -19,6 +19,7 @@ import 'package:querya_desktop/core/sdui/sdui_tree_schema.dart'; import 'package:querya_desktop/core/storage/app_settings.dart'; import 'package:querya_desktop/core/storage/connection_secrets_store.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; /// Owns [PluginRpcBridge] sessions for extension-backed connections. class ExtensionDriverSession { @@ -111,7 +112,9 @@ class ExtensionDriverSession { } catch (e) { try { await bridge.shutdown(); - } catch (_) {} + } catch (e) { + logIgnored('extension_driver_session', e); + } rethrow; } @@ -142,12 +145,16 @@ class ExtensionDriverSession { } try { await bridge.sendRequest('db.disconnect', {'connectionId': tempId}); - } catch (_) {} + } catch (e) { + logIgnored('extension_driver_session', e); + } return version; } finally { try { await bridge.shutdown(); - } catch (_) {} + } catch (e) { + logIgnored('extension_driver_session', e); + } } } diff --git a/lib/core/extensions/harness/extension_harness.dart b/lib/core/extensions/harness/extension_harness.dart index 7244abb8..03bafca4 100644 --- a/lib/core/extensions/harness/extension_harness.dart +++ b/lib/core/extensions/harness/extension_harness.dart @@ -9,6 +9,7 @@ import 'package:querya_desktop/core/extensions/harness/extension_protocol_profil import 'package:querya_desktop/core/extensions/rpc/json_rpc_stdio_client.dart'; import 'package:querya_desktop/core/extensions/sandbox/sandbox_launch_command.dart'; import 'package:querya_desktop/core/extensions/sandbox/sandbox_scratch_directory.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; /// How to start the plugin process. final class HarnessLaunchRequest { @@ -133,7 +134,9 @@ final class _OsPluginProcess implements HarnessPluginProcess { _process.kill(ProcessSignal.sigkill); try { await _process.exitCode.timeout(const Duration(seconds: 2)); - } catch (_) {} + } catch (e) { + logIgnored('extension_harness', e); + } } @override @@ -537,6 +540,8 @@ Future _rssKb(int pid) async { final r = await Process.run('ps', ['-o', 'rss=', '-p', '$pid']); return r.exitCode == 0 ? int.tryParse('${r.stdout}'.trim()) : null; } - } catch (_) {} + } catch (e) { + logIgnored('extension_harness', e); + } return null; } diff --git a/lib/core/extensions/local_extension_installer.dart b/lib/core/extensions/local_extension_installer.dart index f2ac7112..2a4df220 100644 --- a/lib/core/extensions/local_extension_installer.dart +++ b/lib/core/extensions/local_extension_installer.dart @@ -14,6 +14,7 @@ import 'package:querya_desktop/core/market/marketplace_repository.dart'; import 'package:querya_desktop/core/security/archive_path_guard.dart'; import 'package:querya_desktop/core/security/safe_zip_extractor.dart'; import 'package:querya_desktop/core/updater/sha256_checksums.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; /// Installs an extension package from a local `.zip` / `.qext` archive (issue #316). /// @@ -206,7 +207,9 @@ class LocalExtensionInstaller { // The new version is in place; a leftover backup is only clutter. try { await backup.delete(recursive: true); - } catch (_) {} + } catch (e) { + logIgnored('local_extension_installer', e); + } } await _reloadRegistry(); @@ -218,7 +221,9 @@ class LocalExtensionInstaller { if (!swapped && await staging.exists()) { await staging.delete(recursive: true); } - } catch (_) {} + } catch (e) { + logIgnored('local_extension_installer', e); + } if (e is MarketplaceException) rethrow; throw MarketplaceException('Failed to install extension: $e'); } diff --git a/lib/core/extensions/sandbox/sandbox_process_runner.dart b/lib/core/extensions/sandbox/sandbox_process_runner.dart index 979e78ca..07a2f405 100644 --- a/lib/core/extensions/sandbox/sandbox_process_runner.dart +++ b/lib/core/extensions/sandbox/sandbox_process_runner.dart @@ -7,6 +7,7 @@ import 'package:querya_desktop/core/extensions/sandbox/sandbox_launch_command.da import 'package:querya_desktop/core/extensions/sandbox/sandbox_os_isolation.dart'; import 'package:querya_desktop/core/extensions/sandbox/sandbox_scratch_directory.dart'; import 'package:querya_desktop/core/extensions/sandbox/sandbox_secret_guard.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; /// Live handle for a sandboxed OS process (Block E Level 2). class SandboxProcessHandle { @@ -57,8 +58,12 @@ class SandboxProcessHandle { process.kill(ProcessSignal.sigkill); try { await process.exitCode.timeout(const Duration(seconds: 1)); - } catch (_) {} - } catch (_) {} + } catch (e) { + logIgnored('sandbox_process_runner', e); + } + } catch (e) { + logIgnored('sandbox_process_runner', e); + } } catch (e) { debugPrint('SandboxProcessHandle.dispose($pluginId) kill: $e'); } diff --git a/lib/core/mcp/mcp_access_store.dart b/lib/core/mcp/mcp_access_store.dart index 217b2c38..b737080d 100644 --- a/lib/core/mcp/mcp_access_store.dart +++ b/lib/core/mcp/mcp_access_store.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:querya_desktop/core/storage/local_db.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; /// Which connections MCP clients may read. Opt-in per connection: a /// connection that was never enabled is invisible to every MCP tool. @@ -29,7 +30,9 @@ class McpAccessStore implements McpAccessSettings { try { final decoded = jsonDecode(raw); if (decoded is List) return {for (final v in decoded) if (v is int) v}; - } catch (_) {} + } catch (e) { + logIgnored('mcp_access_store', e); + } return {}; } diff --git a/lib/core/mcp/mcp_mongo_service.dart b/lib/core/mcp/mcp_mongo_service.dart index 212189bd..b8256d8c 100644 --- a/lib/core/mcp/mcp_mongo_service.dart +++ b/lib/core/mcp/mcp_mongo_service.dart @@ -6,6 +6,7 @@ import 'package:querya_desktop/core/mcp/mcp_access_store.dart'; import 'package:querya_desktop/core/mcp/mcp_query_service.dart'; import 'package:querya_desktop/core/mcp/mcp_redaction.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; /// Why [McpMongoGuard] refused a filter or sort: a stable rule id /// for the audit log and the message the model reads. @@ -287,7 +288,9 @@ class McpMongoService { } finally { try { await session?.close(); - } catch (_) {} + } catch (e) { + logIgnored('mcp_mongo_service', e); + } } } diff --git a/lib/core/mcp/mcp_server_controller.dart b/lib/core/mcp/mcp_server_controller.dart index 428b1fcd..1f9b0c08 100644 --- a/lib/core/mcp/mcp_server_controller.dart +++ b/lib/core/mcp/mcp_server_controller.dart @@ -11,6 +11,7 @@ import 'package:querya_desktop/core/mcp/mcp_query_service.dart'; import 'package:querya_desktop/core/mcp/mcp_socket_host.dart'; import 'package:querya_desktop/core/mcp/querya_mcp_server.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; /// Owns the in-app MCP server: starts it when enabled in settings, stops it /// on shutdown, and reports status and tool calls to the UI. @@ -163,7 +164,9 @@ class McpServerController { try { name = (await LocalDb.instance.getConnectionById(r.connectionId!)) ?.name; - } catch (_) {} + } catch (e) { + logIgnored('mcp_server_controller', e); + } } final sql = r.sql; await LocalDb.instance.recordMcpActivity(McpActivityEntry( diff --git a/lib/core/security/connection_environment.dart b/lib/core/security/connection_environment.dart index 7e703d8e..bcc75867 100644 --- a/lib/core/security/connection_environment.dart +++ b/lib/core/security/connection_environment.dart @@ -1,3 +1,4 @@ +import 'package:querya_desktop/core/util/log_ignored.dart'; import 'dart:convert'; /// Which kind of server a connection points at. Drives the color coding in the @@ -40,7 +41,9 @@ enum ConnectionEnvironment { try { final decoded = jsonDecode(driverOptions); if (decoded is Map) return fromStorageValue(decoded[optionsKey]); - } catch (_) {} + } catch (e) { + logIgnored('connection_environment', e); + } return null; } @@ -56,7 +59,9 @@ enum ConnectionEnvironment { try { final decoded = jsonDecode(driverOptions); if (decoded is Map) options = Map.from(decoded); - } catch (_) {} + } catch (e) { + logIgnored('connection_environment', e); + } } if (environment == null) { options.remove(optionsKey); diff --git a/lib/core/security/ssh_tunnel_manager.dart b/lib/core/security/ssh_tunnel_manager.dart index eadd657c..85a754d0 100644 --- a/lib/core/security/ssh_tunnel_manager.dart +++ b/lib/core/security/ssh_tunnel_manager.dart @@ -6,6 +6,7 @@ import 'package:crypto/crypto.dart'; import 'package:dartssh2/dartssh2.dart'; import 'package:flutter/foundation.dart'; import 'package:querya_desktop/core/security/ssh_tunnel_config.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; /// Result of an SSH connection diagnostic test. class SshTestResult { @@ -79,13 +80,19 @@ class _PooledTunnelSession { keepAliveTimer = null; try { await serverSocket.close(); - } catch (_) {} + } catch (e) { + logIgnored('ssh_tunnel_manager', e); + } try { unawaited(client.close()); - } catch (_) {} + } catch (e) { + logIgnored('ssh_tunnel_manager', e); + } try { unawaited(jumpClient?.close()); - } catch (_) {} + } catch (e) { + logIgnored('ssh_tunnel_manager', e); + } } } @@ -495,19 +502,29 @@ class SshTunnelManager { keepAliveTimer?.cancel(); try { await serverSocket?.close(); - } catch (_) {} + } catch (e) { + logIgnored('ssh_tunnel_manager', e); + } try { unawaited(client?.close()); - } catch (_) {} + } catch (e) { + logIgnored('ssh_tunnel_manager', e); + } try { bastionSocket?.destroy(); - } catch (_) {} + } catch (e) { + logIgnored('ssh_tunnel_manager', e); + } try { unawaited(jumpClient?.close()); - } catch (_) {} + } catch (e) { + logIgnored('ssh_tunnel_manager', e); + } try { jumpSocket?.destroy(); - } catch (_) {} + } catch (e) { + logIgnored('ssh_tunnel_manager', e); + } rethrow; } } diff --git a/lib/core/security/ssl_certificate_support.dart b/lib/core/security/ssl_certificate_support.dart index aa4f46d9..b51022f4 100644 --- a/lib/core/security/ssl_certificate_support.dart +++ b/lib/core/security/ssl_certificate_support.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:file_selector/file_selector.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; /// Querya-standard SSL certificate query parameters (aligned with PostgreSQL). const kSslRootCertParam = 'sslrootcert'; @@ -55,7 +56,8 @@ SslCertificatePaths extractSslCertificatePathsFromString(String? raw) { clientCert: params[kSslCertParam], clientKey: params[kSslKeyParam], ); - } catch (_) { + } catch (e) { + logIgnored('ssl_certificate_support.paths', e); return const SslCertificatePaths(); } } @@ -169,7 +171,9 @@ Future resolveMongoTlsCertificateKeyFile({ if (!Platform.isWindows) { try { await Process.run('chmod', ['700', dir.path]); - } catch (_) {} + } catch (e) { + logIgnored('ssl_certificate_support', e); + } } final merged = File('${dir.path}/client.pem'); await merged.writeAsString('$certBytes\n$keyBytes\n', flush: true); @@ -211,7 +215,9 @@ Future cleanupMongoTlsTempFile(String? filePath) async { if (await parent.exists()) { await parent.delete(recursive: true); } - } catch (_) {} + } catch (e) { + logIgnored('ssl_certificate_support', e); + } } /// Cleans up any stale temporary MongoDB TLS directories left from previous sessions. @@ -226,11 +232,15 @@ Future cleanupStaleMongoTlsTempFiles() async { if (name.startsWith(kMongoTlsTempPrefix)) { try { await entity.delete(recursive: true); - } catch (_) {} + } catch (e) { + logIgnored('ssl_certificate_support', e); + } } } } - } catch (_) {} + } catch (e) { + logIgnored('ssl_certificate_support', e); + } } String buildRedisConnectionUri({ diff --git a/lib/core/storage/connection_secrets_store.dart b/lib/core/storage/connection_secrets_store.dart index 7fa31211..bb49b1bd 100644 --- a/lib/core/storage/connection_secrets_store.dart +++ b/lib/core/storage/connection_secrets_store.dart @@ -1,5 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; /// Backend for reading/writing per-connection secrets (password, connection string). /// In unit/widget tests, set [ConnectionSecretsStore.backend] to a memory implementation. @@ -226,12 +227,16 @@ class ConnectionSecretsStore { if (legacyPassword != null) { try { await backend.delete(_legacyPasswordKey(connectionId)); - } catch (_) {} + } catch (e) { + logIgnored('connection_secrets_store', e); + } } if (legacyConnectionString != null) { try { await backend.delete(_legacyConnectionStringKey(connectionId)); - } catch (_) {} + } catch (e) { + logIgnored('connection_secrets_store', e); + } } password = effectivePassword; connectionString = effectiveConnectionString; @@ -253,7 +258,9 @@ class ConnectionSecretsStore { try { await backend.delete(_legacyPasswordKey(connectionId)); await backend.delete(_legacyConnectionStringKey(connectionId)); - } catch (_) {} + } catch (e) { + logIgnored('connection_secrets_store', e); + } } /// One-time migration (issue #986): before keyring keys were namespaced by diff --git a/lib/core/storage/local_db.dart b/lib/core/storage/local_db.dart index a378e903..516e5590 100644 --- a/lib/core/storage/local_db.dart +++ b/lib/core/storage/local_db.dart @@ -10,6 +10,7 @@ import 'package:querya_desktop/core/storage/app_data_root.dart'; import 'package:querya_desktop/core/storage/connection_secrets_store.dart'; import 'package:querya_desktop/core/storage/update_backup.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; const _dbName = 'querya.db'; const _dbVersion = 13; @@ -940,17 +941,23 @@ class LocalDb { if (await walFile.exists()) { try { await walFile.delete(); - } catch (_) {} + } catch (e) { + logIgnored('local_db', e); + } } if (await shmFile.exists()) { try { await shmFile.delete(); - } catch (_) {} + } catch (e) { + logIgnored('local_db', e); + } } if (await lockFile.exists()) { try { await lockFile.delete(); - } catch (_) {} + } catch (e) { + logIgnored('local_db', e); + } } } @@ -986,7 +993,9 @@ class LocalDb { if (await walFile.exists()) { try { await walFile.copy('$backupPath-wal'); - } catch (_) {} + } catch (e) { + logIgnored('local_db', e); + } } return backupPath; } @@ -1000,10 +1009,14 @@ class LocalDb { if (await dbFile.exists()) { try { await backupDatabaseFile(); - } catch (_) {} + } catch (e) { + logIgnored('local_db', e); + } try { await dbFile.delete(); - } catch (_) {} + } catch (e) { + logIgnored('local_db', e); + } } await removeWalAndLockFiles(); } @@ -1229,7 +1242,9 @@ class ConnectionRow { return SshTunnelConfig.fromMap(sshMap); } } - } catch (_) {} + } catch (e) { + logIgnored('local_db', e); + } return null; } @@ -1256,7 +1271,9 @@ class ConnectionRow { if (decoded is Map) { opts = Map.from(decoded); } - } catch (_) {} + } catch (e) { + logIgnored('local_db', e); + } } if (config == null || !config.enabled) { opts.remove('ssh_tunnel'); diff --git a/lib/core/updater/installers/macos_sparkle_installer.dart b/lib/core/updater/installers/macos_sparkle_installer.dart index f731adb1..1b1db156 100644 --- a/lib/core/updater/installers/macos_sparkle_installer.dart +++ b/lib/core/updater/installers/macos_sparkle_installer.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; +import 'package:querya_desktop/core/util/log_ignored.dart'; import '../app_updater_service.dart'; import 'update_install_context.dart'; @@ -65,7 +66,9 @@ class MacosSparkleInstaller { // The app keeps running: do not leave the extracted update behind. try { await extractDir.delete(recursive: true); - } catch (_) {} + } catch (e) { + logIgnored('macos_sparkle_installer', e); + } rethrow; } diff --git a/lib/core/util/log_ignored.dart b/lib/core/util/log_ignored.dart new file mode 100644 index 00000000..b7bd8de7 --- /dev/null +++ b/lib/core/util/log_ignored.dart @@ -0,0 +1,10 @@ +import 'package:flutter/foundation.dart'; + +/// Records an error that the caller deliberately goes on from: a best-effort +/// close or delete, a cache that can be rebuilt. [where] names the place +/// (`'LocalExtensionInstaller.backup'`), so a failure that matters later has a +/// trace in the debug log instead of none. +void logIgnored(String where, Object error, [StackTrace? stack]) { + debugPrint('[$where] ignored: $error'); + if (stack != null && kDebugMode) debugPrint('$stack'); +}