diff --git a/CHANGELOG.md b/CHANGELOG.md index bd9538085..05a687b22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ * Support ObjectId type. ([#468](https://github.com/realm/realm-dart/pull/468)) * Support Uuid type. ([#470](https://github.com/realm/realm-dart/pull/470)) * Support application login. ([#469](https://github.com/realm/realm-dart/pull/469)) +* Support app configuration log level and request timeout.([#566](https://github.com/realm/realm-dart/pull/566)) * Support EmailPassword register user. ([#452](https://github.com/realm/realm-dart/pull/452)) * Support EmailPassword confirm user. ([#478](https://github.com/realm/realm-dart/pull/478)) * Support EmailPassword resend user confirmation email. ([#479](https://github.com/realm/realm-dart/pull/479)) diff --git a/lib/src/app.dart b/lib/src/app.dart index 16c641995..86491417a 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -23,6 +23,42 @@ import 'credentials.dart'; import 'user.dart'; import 'configuration.dart'; +/// Specifies the criticality level above which messages will be logged +/// by the default sync client logger. +/// {@category Application} +enum LogLevel { + /// Log everything. This will seriously harm the performance of the + /// sync client and should never be used in production scenarios. + all, + + /// A version of 'debug' that allows for very high volume output. + /// This may seriously affect the performance of the sync client. + trace, + + /// Reveal information that can aid debugging, no longer paying + /// attention to efficiency. + debug, + + /// Same as 'Info', but prioritize completeness over minimalism. + detail, + + /// Log operational sync client messages, but in a minimalistic fashion to + /// avoid general overhead from logging and to keep volume down. + info, + + /// Log errors and warnings. + warn, + + /// Log errors only. + error, + + /// Log only fatal errors. + fatal, + + /// Log nothing. + off, +} + /// A class exposing configuration options for an [App] /// {@category Application} @immutable @@ -45,6 +81,12 @@ class AppConfiguration { /// The [defaultRequestTimeout] for HTTP requests. Defaults to 60 seconds. final Duration defaultRequestTimeout; + /// The maximum duration to allow for a connection to + /// become fully established. This includes the time to resolve the + /// network address, the TCP connect operation, the SSL handshake, and + /// the WebSocket handshake. Defaults to 2 minutes. + final Duration maxConnectionTimeout; + /// The [localAppName] is the friendly name identifying the current client application. /// /// This is typically used to differentiate between client applications that use the same @@ -69,6 +111,9 @@ class AppConfiguration { /// Setting this will not change the encryption key for individual Realms, which is set in the [Configuration]. final List? metadataEncryptionKey; + /// The [LogLevel] for sync operations. + final LogLevel logLevel; + /// The [HttpClient] that will be used for HTTP requests during authentication. /// /// You can use this to override the default http client handler and configure settings like proxies, @@ -87,6 +132,8 @@ class AppConfiguration { this.localAppVersion, this.metadataEncryptionKey, this.metadataPersistenceMode = MetadataPersistenceMode.plaintext, + this.logLevel = LogLevel.error, + this.maxConnectionTimeout = const Duration(minutes: 2), HttpClient? httpClient, }) : baseUrl = baseUrl ?? Uri.parse('https://realm.mongodb.com'), baseFilePath = baseFilePath ?? Directory(Configuration.filesPath), diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index a43eebf39..1ea2b584c 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1027,7 +1027,8 @@ class _RealmCore { _realmLib.realm_sync_client_config_set_base_file_path(handle._pointer, configuration.baseFilePath.path.toUtf8Ptr(arena)); _realmLib.realm_sync_client_config_set_metadata_mode(handle._pointer, configuration.metadataPersistenceMode.index); - + _realmLib.realm_sync_client_config_set_log_level(handle._pointer, configuration.logLevel.index); + _realmLib.realm_sync_client_config_set_connect_timeout(handle._pointer, configuration.maxConnectionTimeout.inMicroseconds); if (configuration.metadataEncryptionKey != null && configuration.metadataPersistenceMode == MetadataPersistenceMode.encrypted) { _realmLib.realm_sync_client_config_set_metadata_encryption_key(handle._pointer, configuration.metadataEncryptionKey!.toUint8Ptr(arena)); } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 01ffbbc09..d8d7e7c97 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -46,7 +46,7 @@ export 'package:realm_common/realm_common.dart' Uuid; // always expose with `show` to explicitly control the public API surface -export 'app.dart' show AppConfiguration, MetadataPersistenceMode, App; +export 'app.dart' show AppConfiguration, MetadataPersistenceMode, LogLevel, App; export "configuration.dart" show Configuration, diff --git a/test/app_test.dart b/test/app_test.dart index 5eb2a51c3..dae88b954 100644 --- a/test/app_test.dart +++ b/test/app_test.dart @@ -16,6 +16,7 @@ // //////////////////////////////////////////////////////////////////////////////// +import 'dart:convert'; import 'dart:io'; import 'package:test/expect.dart'; @@ -28,28 +29,74 @@ Future main([List? args]) async { await setupTests(args); - test('AppConfiguration can be created', () { - final a = AppConfiguration('myapp'); - expect(a.appId, 'myapp'); - expect(a.baseFilePath.path, Configuration.filesPath); - expect(a.baseUrl, Uri.parse('https://realm.mongodb.com')); - expect(a.defaultRequestTimeout, const Duration(minutes: 1)); + test('AppConfiguration can be initialized', () { + final defaultAppConfig = AppConfiguration('myapp'); + expect(defaultAppConfig.appId, 'myapp'); + expect(defaultAppConfig.baseFilePath.path, Configuration.filesPath); + expect(defaultAppConfig.baseUrl, Uri.parse('https://realm.mongodb.com')); + expect(defaultAppConfig.defaultRequestTimeout, const Duration(minutes: 1)); + expect(defaultAppConfig.logLevel, LogLevel.error); + expect(defaultAppConfig.metadataPersistenceMode, MetadataPersistenceMode.plaintext); + + final httpClient = HttpClient(context: SecurityContext(withTrustedRoots: false)); + final appConfig = AppConfiguration( + 'myapp1', + baseFilePath: Directory.systemTemp, + baseUrl: Uri.parse('https://not_re.al'), + defaultRequestTimeout: const Duration(seconds: 2), + localAppName: 'bar', + localAppVersion: "1.0.0", + metadataPersistenceMode: MetadataPersistenceMode.disabled, + logLevel: LogLevel.info, + maxConnectionTimeout: const Duration(minutes: 1), + httpClient: httpClient, + ); + expect(appConfig.appId, 'myapp1'); + expect(appConfig.baseFilePath.path, Directory.systemTemp.path); + expect(appConfig.baseUrl, Uri.parse('https://not_re.al')); + expect(appConfig.defaultRequestTimeout, const Duration(seconds: 2)); + expect(appConfig.logLevel, LogLevel.info); + expect(appConfig.metadataPersistenceMode, MetadataPersistenceMode.disabled); + expect(appConfig.maxConnectionTimeout, const Duration(minutes: 1)); + expect(appConfig.httpClient, httpClient); + }); + +test('AppConfiguration can be created with defaults', () { + final appConfig = AppConfiguration('myapp1'); + final app = App(appConfig); + expect(app.configuration.appId, 'myapp1'); + expect(app.configuration.baseUrl, Uri.parse('https://realm.mongodb.com')); + expect(app.configuration.defaultRequestTimeout, const Duration(minutes: 1)); + expect(app.configuration.logLevel, LogLevel.error); + expect(app.configuration.metadataPersistenceMode, MetadataPersistenceMode.plaintext); + expect(app.configuration.maxConnectionTimeout, const Duration(minutes: 2)); + expect(app.configuration.httpClient, isNotNull); + }); + test('AppConfiguration can be created', () { final httpClient = HttpClient(context: SecurityContext(withTrustedRoots: false)); - final b = AppConfiguration( + final appConfig = AppConfiguration( 'myapp1', baseFilePath: Directory.systemTemp, baseUrl: Uri.parse('https://not_re.al'), defaultRequestTimeout: const Duration(seconds: 2), localAppName: 'bar', localAppVersion: "1.0.0", + metadataPersistenceMode: MetadataPersistenceMode.encrypted, + metadataEncryptionKey: base64.decode("ekey"), + logLevel: LogLevel.info, + maxConnectionTimeout: const Duration(minutes: 1), httpClient: httpClient, ); - expect(b.appId, 'myapp1'); - expect(b.baseFilePath.path, Directory.systemTemp.path); - expect(b.baseUrl, Uri.parse('https://not_re.al')); - expect(b.defaultRequestTimeout, const Duration(seconds: 2)); - expect(b.httpClient, httpClient); + final app = App(appConfig); + expect(app.configuration.appId, 'myapp1'); + expect(app.configuration.baseFilePath.path, Directory.systemTemp.path); + expect(app.configuration.baseUrl, Uri.parse('https://not_re.al')); + expect(app.configuration.defaultRequestTimeout, const Duration(seconds: 2)); + expect(app.configuration.logLevel, LogLevel.info); + expect(app.configuration.metadataPersistenceMode, MetadataPersistenceMode.encrypted); + expect(app.configuration.maxConnectionTimeout, const Duration(minutes: 1)); + expect(app.configuration.httpClient, httpClient); }); test('App can be created', () async {