Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App config log level and request timeout #566

Merged
merged 11 commits into from
May 17, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
47 changes: 47 additions & 0 deletions lib/src/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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<int>? 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,
Expand All @@ -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),
Expand Down
3 changes: 2 additions & 1 deletion lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/realm_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
71 changes: 59 additions & 12 deletions test/app_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//
////////////////////////////////////////////////////////////////////////////////

import 'dart:convert';
import 'dart:io';

import 'package:test/expect.dart';
Expand All @@ -28,28 +29,74 @@ Future<void> main([List<String>? 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 {
Expand Down