From f81ed9530ba8d1b3efb3d492aab1486bc542bd3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 17 May 2022 15:42:41 +0200 Subject: [PATCH 01/12] Add Dart scheduler trampoline for realm_sync_client_config_set_log_callback --- ffigen/sync_client_config.h | 1 + lib/src/native/realm_bindings.dart | 54 ++++++++++++++++++++++--- src/CMakeLists.txt | 2 + src/subscription_set.h | 5 +-- src/sync_client_config.cpp | 63 ++++++++++++++++++++++++++++++ src/sync_client_config.h | 30 ++++++++++++++ 6 files changed, 145 insertions(+), 10 deletions(-) create mode 120000 ffigen/sync_client_config.h create mode 100644 src/sync_client_config.cpp create mode 100644 src/sync_client_config.h diff --git a/ffigen/sync_client_config.h b/ffigen/sync_client_config.h new file mode 120000 index 000000000..9da3f2db8 --- /dev/null +++ b/ffigen/sync_client_config.h @@ -0,0 +1 @@ +../src/sync_client_config.h \ No newline at end of file diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index 3eedef146..eeb5b1664 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -3134,6 +3134,40 @@ class RealmLibrary { late final _realm_dart_scheduler_invoke = _realm_dart_scheduler_invokePtr .asFunction)>(); + void realm_dart_sync_client_config_set_log_callback( + ffi.Pointer config, + realm_log_func_t callback, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, + ffi.Pointer scheduler, + ) { + return _realm_dart_sync_client_config_set_log_callback( + config, + callback, + userdata, + userdata_free, + scheduler, + ); + } + + late final _realm_dart_sync_client_config_set_log_callbackPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + realm_log_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>>( + 'realm_dart_sync_client_config_set_log_callback'); + late final _realm_dart_sync_client_config_set_log_callback = + _realm_dart_sync_client_config_set_log_callbackPtr.asFunction< + void Function( + ffi.Pointer, + realm_log_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>(); + /// Register a handler in order to be notified when subscription set is equal to the one passed as parameter /// This is an asynchronous operation. /// @@ -3145,7 +3179,7 @@ class RealmLibrary { bool realm_dart_sync_on_subscription_set_state_change_async( ffi.Pointer subscription_set, int notify_when, - realm_dart_sync_on_subscription_state_changed callback, + realm_sync_on_subscription_state_changed_t callback, ffi.Pointer userdata, realm_free_userdata_func_t userdata_free, ffi.Pointer scheduler, @@ -3167,7 +3201,7 @@ class RealmLibrary { ffi.Uint8 Function( ffi.Pointer, ffi.Int32, - realm_dart_sync_on_subscription_state_changed, + realm_sync_on_subscription_state_changed_t, ffi.Pointer, realm_free_userdata_func_t, ffi.Pointer)>>( @@ -3177,7 +3211,7 @@ class RealmLibrary { int Function( ffi.Pointer, int, - realm_dart_sync_on_subscription_state_changed, + realm_sync_on_subscription_state_changed_t, ffi.Pointer, realm_free_userdata_func_t, ffi.Pointer)>(); @@ -9097,12 +9131,22 @@ class _SymbolAddresses { ffi.Void Function(ffi.Uint64, ffi.Pointer)>> get realm_dart_scheduler_invoke => _library._realm_dart_scheduler_invokePtr; + ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + realm_log_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>> + get realm_dart_sync_client_config_set_log_callback => + _library._realm_dart_sync_client_config_set_log_callbackPtr; ffi.Pointer< ffi.NativeFunction< ffi.Uint8 Function( ffi.Pointer, ffi.Int32, - realm_dart_sync_on_subscription_state_changed, + realm_sync_on_subscription_state_changed_t, ffi.Pointer, realm_free_userdata_func_t, ffi.Pointer)>> @@ -9470,8 +9514,6 @@ abstract class realm_column_attr { class realm_config extends ffi.Opaque {} typedef realm_config_t = realm_config; -typedef realm_dart_sync_on_subscription_state_changed = ffi.Pointer< - ffi.NativeFunction, ffi.Int32)>>; typedef realm_data_initialization_func_t = ffi.Pointer< ffi.NativeFunction< ffi.Uint8 Function(ffi.Pointer, ffi.Pointer)>>; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1be774df4..10d91d03d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,6 +12,7 @@ set(SOURCES realm_dart_scheduler.cpp subscription_set.cpp sync_session.cpp + sync_client_config.cpp ) set(HEADERS @@ -20,6 +21,7 @@ set(HEADERS realm-core/src/realm.h subscription_set.h sync_session.h + sync_client_config.h ) add_library(realm_dart SHARED ${SOURCES} ${HEADERS}) diff --git a/src/subscription_set.h b/src/subscription_set.h index 0ef00ffb9..438c45e90 100644 --- a/src/subscription_set.h +++ b/src/subscription_set.h @@ -21,9 +21,6 @@ #include -typedef void (*realm_dart_sync_on_subscription_state_changed)(void* userdata, - realm_flx_sync_subscription_set_state_e state); - /** * Register a handler in order to be notified when subscription set is equal to the one passed as parameter * This is an asynchronous operation. @@ -37,7 +34,7 @@ typedef void (*realm_dart_sync_on_subscription_state_changed)(void* userdata, RLM_API bool realm_dart_sync_on_subscription_set_state_change_async(const realm_flx_sync_subscription_set_t* subscription_set, realm_flx_sync_subscription_set_state_e notify_when, - realm_dart_sync_on_subscription_state_changed callback, + realm_sync_on_subscription_state_changed_t callback, void* userdata, realm_free_userdata_func_t userdata_free, realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; diff --git a/src/sync_client_config.cpp b/src/sync_client_config.cpp new file mode 100644 index 000000000..a0b4bacef --- /dev/null +++ b/src/sync_client_config.cpp @@ -0,0 +1,63 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "sync_client_config.h" + +#include +#include + +#include "event_loop_dispatcher.hpp" + +namespace realm::c_api { +namespace { + +using namespace realm::sync; + +using FreeT = std::function; +using CallbackT = std::function; // Differs per callback +using UserdataT = std::tuple; + +void _callback(void* userdata, realm_log_level_e level, const char* message ) { + auto u = reinterpret_cast(userdata); + // we need to copy the message + auto len = strlen(message) + 1; + auto buffer = (char*)malloc(len); + strncpy(buffer, message, len); + std::get<0>(*u)(level, buffer); +} + +void _userdata_free(void* userdata) { + auto u = reinterpret_cast(userdata); + std::get<1>(*u)(); + delete u; +} + +RLM_API void realm_dart_sync_client_config_set_log_callback( + realm_sync_client_config_t* config, + realm_log_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) noexcept +{ + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1, std::placeholders::_2), + std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + return realm_sync_client_config_set_log_callback(config, _callback, u, _userdata_free); +} + +} // anonymous namespace +} // namespace realm::c_api diff --git a/src/sync_client_config.h b/src/sync_client_config.h new file mode 100644 index 000000000..0da18ed82 --- /dev/null +++ b/src/sync_client_config.h @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_DART_SYNC_CLIENT_CONFIG_H +#define REALM_DART_SYNC_CLIENT_CONFIG_H + +#include "realm.h" + +RLM_API void realm_dart_sync_client_config_set_log_callback(realm_sync_client_config_t* config, + realm_log_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; + +#endif // REALM_DART_SYNC_CLIENT_CONFIG_H From 19aca76f5d372227a543e71767bd56dea33662bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 19 May 2022 15:06:11 +0200 Subject: [PATCH 02/12] Wire up Dart side Logger --- lib/src/app.dart | 36 ++++++++++++++++++++++++---------- lib/src/native/realm_core.dart | 22 +++++++++++++++++++++ lib/src/realm_class.dart | 2 ++ 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index 944ff7672..da5b5de6e 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -17,6 +17,7 @@ //////////////////////////////////////////////////////////////////////////////// import 'dart:io'; +import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'native/realm_core.dart'; import 'credentials.dart'; @@ -29,36 +30,46 @@ import 'configuration.dart'; enum LogLevel { /// Log everything. This will seriously harm the performance of the /// sync client and should never be used in production scenarios. - all, + all(Level.ALL), /// A version of 'debug' that allows for very high volume output. /// This may seriously affect the performance of the sync client. - trace, + trace(Level.FINEST), /// Reveal information that can aid debugging, no longer paying /// attention to efficiency. - debug, + debug(Level.FINER), /// Same as 'Info', but prioritize completeness over minimalism. - detail, + detail(Level.FINE), /// Log operational sync client messages, but in a minimalistic fashion to /// avoid general overhead from logging and to keep volume down. - info, + info(Level.INFO), /// Log errors and warnings. - warn, + warn(Level.WARNING), /// Log errors only. - error, + error(Level.SEVERE), /// Log only fatal errors. - fatal, + fatal(Level.SHOUT), /// Log nothing. - off, + off(Level.OFF); + + /// The corresponding [Logger] [Level] + final Level loggerLevel; + const LogLevel(this.loggerLevel); } +late final _defaultLogger = Logger.detached('realm') + ..level = Level.ALL + ..onRecord.listen((event) { + print(event.message); + }); + /// A class exposing configuration options for an [App] /// {@category Application} @immutable @@ -114,6 +125,9 @@ class AppConfiguration { /// The [LogLevel] for sync operations. final LogLevel logLevel; + /// The [Logger] to be used. + final Logger logger; + /// 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, @@ -133,11 +147,13 @@ class AppConfiguration { this.metadataEncryptionKey, this.metadataPersistenceMode = MetadataPersistenceMode.plaintext, this.logLevel = LogLevel.error, + Logger? logger, this.maxConnectionTimeout = const Duration(minutes: 2), HttpClient? httpClient, }) : baseUrl = baseUrl ?? Uri.parse('https://realm.mongodb.com'), baseFilePath = baseFilePath ?? Directory(ConfigurationInternal.defaultStorageFolder), - httpClient = httpClient ?? HttpClient(); + httpClient = httpClient ?? HttpClient(), + logger = logger ?? _defaultLogger; } /// An [App] is the main client-side entry point for interacting with a MongoDB Realm App. diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 5285b4b87..41b3838ac 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -23,6 +23,7 @@ import 'dart:typed_data'; // Hide StringUtf8Pointer.toNativeUtf8 and StringUtf16Pointer since these allows silently allocating memory. Use toUtf8Ptr instead import 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer; +import 'package:logging/logging.dart'; import '../app.dart'; import '../collections.dart'; @@ -1026,6 +1027,20 @@ class _RealmCore { }); } + static void _logCallback(Pointer userdata, int levelAsInt, Pointer message) { + try { + final logger = userdata.toObject(isPersistent: true)!; + final level = LogLevel.values[levelAsInt].loggerLevel; + + // Don't do expensive utf8 to utf16 conversion unless we have to.. + if (logger.isLoggable(level)) { + logger.log(level, message.cast().toDartString()); + } + } finally { + _realmLib.realm_free(message.cast()); // .. but always free the message + } + } + SyncClientConfigHandle _createSyncClientConfig(AppConfiguration configuration) { return using((arena) { final handle = SyncClientConfigHandle._(_realmLib.realm_sync_client_config_new()); @@ -1033,6 +1048,13 @@ 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_dart_sync_client_config_set_log_callback( + handle._pointer, + Pointer.fromFunction(_logCallback), + configuration.logger.toPersistentHandle(), + _realmLib.addresses.realm_dart_delete_persistent_handle, + scheduler.handle._pointer, + ); _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 5d9b1d06e..2b2a76469 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -342,6 +342,8 @@ class Scheduler { } } +late final scheduler = Scheduler(() {}); + /// @nodoc class Transaction { Realm? _realm; From 8cccea9fca93933590f48ad574e78e4ad2ed717b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 20 May 2022 10:33:43 +0200 Subject: [PATCH 03/12] Allow user to specify default logger --- lib/src/app.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index da5b5de6e..f1751c6d5 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -153,7 +153,7 @@ class AppConfiguration { }) : baseUrl = baseUrl ?? Uri.parse('https://realm.mongodb.com'), baseFilePath = baseFilePath ?? Directory(ConfigurationInternal.defaultStorageFolder), httpClient = httpClient ?? HttpClient(), - logger = logger ?? _defaultLogger; + logger = logger ?? App.defaultLogger; } /// An [App] is the main client-side entry point for interacting with a MongoDB Realm App. @@ -163,6 +163,11 @@ class AppConfiguration { /// * Synchronize data between the local device and a remote Realm App with Synchronized Realms /// {@category Application} class App { + /// The [Logger] to use by default, when creating Apps. + /// + /// The logger used can also be customized by specifying it on the [AppConfiguration]. + static late var defaultLogger = _defaultLogger; + final AppHandle _handle; /// The id of this application. This is the same as the appId in the [AppConfiguration] used to From e5fa51615d1a6ee37f04c85ad6e86bbafa06fa38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 20 May 2022 08:59:13 +0200 Subject: [PATCH 04/12] Add tests --- test/app_test.dart | 84 ++++++++++++++++++++++++++++++++++++++++++++++ test/test.dart | 2 ++ 2 files changed, 86 insertions(+) diff --git a/test/app_test.dart b/test/app_test.dart index faf1a9035..040a358a4 100644 --- a/test/app_test.dart +++ b/test/app_test.dart @@ -19,10 +19,12 @@ import 'dart:convert'; import 'dart:io'; +import 'package:logging/logging.dart'; import 'package:test/expect.dart'; import '../lib/src/configuration.dart'; import '../lib/realm.dart'; +import 'realm_object_test.dart'; import 'test.dart'; Future main([List? args]) async { @@ -187,4 +189,86 @@ Future main([List? args]) async { final user1 = await app.logIn(Credentials.emailPassword(testUsername, testPassword)); expect(app.users, [user1, user]); }); + + baasTest('AppConfiguration.logger', (configuration) async { + final logger = Logger.detached(generateRandomString(10))..level = LogLevel.all.loggerLevel; + configuration = AppConfiguration(configuration.appId, logLevel: LogLevel.all, logger: logger); + + await testLogger( + configuration, + logger, + maxExpectedCounts: { + // No problems expected! + LogLevel.fatal.loggerLevel: 0, + LogLevel.error.loggerLevel: 0, + LogLevel.warn.loggerLevel: 0, + }, + minExpectedCounts: { + // these are set low (roughly half of what was seen when test was created), + // so that changes to core are less likely to break the test + LogLevel.trace.loggerLevel: 10, + LogLevel.debug.loggerLevel: 20, + LogLevel.detail.loggerLevel: 2, + LogLevel.info.loggerLevel: 1, + }, + ); + }); + + baasTest('App.defaultLogger', (configuration) async { + App.defaultLogger = Logger.detached(generateRandomString(10))..level = LogLevel.all.loggerLevel; + configuration = AppConfiguration(configuration.appId, logLevel: LogLevel.all); // uses App.defaultLogger + + await testLogger( + configuration, + App.defaultLogger, + maxExpectedCounts: { + // No problems expected! + LogLevel.fatal.loggerLevel: 0, + LogLevel.error.loggerLevel: 0, + LogLevel.warn.loggerLevel: 0, + }, + minExpectedCounts: { + // these are set low (roughly half of what was seen when test was created), + // so that changes to core are less likely to break the test + LogLevel.trace.loggerLevel: 10, + LogLevel.debug.loggerLevel: 20, + LogLevel.detail.loggerLevel: 2, + LogLevel.info.loggerLevel: 1, + }, + ); + }); +} + +Future testLogger( + AppConfiguration configuration, + Logger logger, { + Map minExpectedCounts = const {}, + Map maxExpectedCounts = const {}, +}) async { + // To see the trace, add this: + /* + logger.onRecord.listen((event) { + print('${event.sequenceNumber} ${event.level} ${event.message}'); + }); + */ + + // Setup + clearCachedApps(); + final app = App(configuration); + final realm = await getIntegrationRealm(app: app); + + // Prepare to capture trace + final counts = {}; + logger.onRecord.listen((r) { + counts[r.level] = (counts[r.level] ?? 0) + 1; + }); + + // Trigger trace + await realm.syncSession.waitForDownload(); + + // Check count of various levels + for (final e in counts.entries) { + expect(e.value, lessThanOrEqualTo(maxExpectedCounts[e.key] ?? maxInt)); + expect(e.value, greaterThanOrEqualTo(minExpectedCounts[e.key] ?? minInt)); + } } diff --git a/test/test.dart b/test/test.dart index 993975862..0085d3055 100644 --- a/test/test.dart +++ b/test/test.dart @@ -378,3 +378,5 @@ Future loginWithRetry(App app, Credentials credentials, {int retryCount = extension RealmObjectTest on RealmObject { String toJson() => realmCore.objectToString(this); } + +void clearCachedApps() => realmCore.clearCachedApps(); From 98dda19697ebf9b65a07845b0ac07a104be5c134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 20 May 2022 09:42:29 +0200 Subject: [PATCH 05/12] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 305d89dd7..5ed7e5eda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ * Support user profile data. ([#570](https://github.com/realm/realm-dart/pull/570)) * Support flexible synchronization. ([#496](https://github.com/realm/realm-dart/pull/496)) * Added support for DateTime properties. ([#569](https://github.com/realm/realm-dart/pull/569)) +* Support setting logger on AppConfiguration. ([#583](https://github.com/realm/realm-dart/pull/583)) +* Support setting default logger on App class. Default of default is to print to console. ([#583](https://github.com/realm/realm-dart/pull/583)) ### Fixed * Fixed an issue that would result in the wrong transaction being rolled back if you start a write transaction inside a write transaction. ([#442](https://github.com/realm/realm-dart/issues/442)) From af29d8c702f991874296e9e6d99d87631b27adbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 20 May 2022 12:11:30 +0200 Subject: [PATCH 06/12] Fix tests --- test/app_test.dart | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/app_test.dart b/test/app_test.dart index 040a358a4..bfb5ac781 100644 --- a/test/app_test.dart +++ b/test/app_test.dart @@ -192,7 +192,13 @@ Future main([List? args]) async { baasTest('AppConfiguration.logger', (configuration) async { final logger = Logger.detached(generateRandomString(10))..level = LogLevel.all.loggerLevel; - configuration = AppConfiguration(configuration.appId, logLevel: LogLevel.all, logger: logger); + configuration = AppConfiguration( + configuration.appId, + logLevel: LogLevel.all, + logger: logger, + baseFilePath: configuration.baseFilePath, + baseUrl: configuration.baseUrl, + ); await testLogger( configuration, @@ -216,7 +222,12 @@ Future main([List? args]) async { baasTest('App.defaultLogger', (configuration) async { App.defaultLogger = Logger.detached(generateRandomString(10))..level = LogLevel.all.loggerLevel; - configuration = AppConfiguration(configuration.appId, logLevel: LogLevel.all); // uses App.defaultLogger + configuration = AppConfiguration( + configuration.appId, + logLevel: LogLevel.all, + baseFilePath: configuration.baseFilePath, + baseUrl: configuration.baseUrl, + ); // uses App.defaultLogger await testLogger( configuration, From ef6e1e724077fb8242e3c9295f1e96c71b272e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 23 May 2022 14:49:17 +0200 Subject: [PATCH 07/12] config.yaml + symlinks are back :-/ --- ffigen/config.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ffigen/config.yaml b/ffigen/config.yaml index d329fbb1c..256c0ae95 100644 --- a/ffigen/config.yaml +++ b/ffigen/config.yaml @@ -10,6 +10,7 @@ headers: - 'realm_android_platform.h' - 'subscription_set.h' - 'sync_session.h' + - 'sync_client_config.h' include-directives: #generate only for these headers - 'realm.h' - 'realm_dart.h' @@ -17,6 +18,7 @@ headers: - 'realm_android_platform.h' - 'subscription_set.h' - 'sync_session.h' + - 'sync_client_config.h' compiler-opts: - '-DRLM_NO_ANON_UNIONS' - '-DFFI_GEN' From cf24eb0b0eb49d8cd99a061a5d3437b281e6412a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 23 May 2022 17:29:24 +0200 Subject: [PATCH 08/12] Introduce static property Realm.logger * Remove logger and logLevel from AppConfiguration. * New RealmLogLevel class define the realm specific levels. --- lib/src/app.dart | 69 ++++------------------------------ lib/src/native/realm_core.dart | 32 ++++++++++++++-- lib/src/realm_class.dart | 62 ++++++++++++++++++++++++++++++ test/app_test.dart | 56 +++++---------------------- 4 files changed, 108 insertions(+), 111 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index f1751c6d5..d9f63500f 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -15,60 +15,16 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// - import 'dart:io'; + import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; -import 'native/realm_core.dart'; + +import '../realm.dart'; +import 'configuration.dart'; import 'credentials.dart'; +import 'native/realm_core.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(Level.ALL), - - /// A version of 'debug' that allows for very high volume output. - /// This may seriously affect the performance of the sync client. - trace(Level.FINEST), - - /// Reveal information that can aid debugging, no longer paying - /// attention to efficiency. - debug(Level.FINER), - - /// Same as 'Info', but prioritize completeness over minimalism. - detail(Level.FINE), - - /// Log operational sync client messages, but in a minimalistic fashion to - /// avoid general overhead from logging and to keep volume down. - info(Level.INFO), - - /// Log errors and warnings. - warn(Level.WARNING), - - /// Log errors only. - error(Level.SEVERE), - - /// Log only fatal errors. - fatal(Level.SHOUT), - - /// Log nothing. - off(Level.OFF); - - /// The corresponding [Logger] [Level] - final Level loggerLevel; - const LogLevel(this.loggerLevel); -} - -late final _defaultLogger = Logger.detached('realm') - ..level = Level.ALL - ..onRecord.listen((event) { - print(event.message); - }); /// A class exposing configuration options for an [App] /// {@category Application} @@ -122,11 +78,7 @@ 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 [Logger] to be used. - final Logger logger; + final Logger _logger; /// The [HttpClient] that will be used for HTTP requests during authentication. /// @@ -146,14 +98,12 @@ class AppConfiguration { this.localAppVersion, this.metadataEncryptionKey, this.metadataPersistenceMode = MetadataPersistenceMode.plaintext, - this.logLevel = LogLevel.error, - Logger? logger, this.maxConnectionTimeout = const Duration(minutes: 2), HttpClient? httpClient, }) : baseUrl = baseUrl ?? Uri.parse('https://realm.mongodb.com'), baseFilePath = baseFilePath ?? Directory(ConfigurationInternal.defaultStorageFolder), httpClient = httpClient ?? HttpClient(), - logger = logger ?? App.defaultLogger; + _logger = Realm.logger; } /// An [App] is the main client-side entry point for interacting with a MongoDB Realm App. @@ -163,11 +113,6 @@ class AppConfiguration { /// * Synchronize data between the local device and a remote Realm App with Synchronized Realms /// {@category Application} class App { - /// The [Logger] to use by default, when creating Apps. - /// - /// The logger used can also be customized by specifying it on the [AppConfiguration]. - static late var defaultLogger = _defaultLogger; - final AppHandle _handle; /// The id of this application. This is the same as the appId in the [AppConfiguration] used to diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 41b3838ac..a8e03aea6 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1030,7 +1030,7 @@ class _RealmCore { static void _logCallback(Pointer userdata, int levelAsInt, Pointer message) { try { final logger = userdata.toObject(isPersistent: true)!; - final level = LogLevel.values[levelAsInt].loggerLevel; + final level = _LogLevel.values[levelAsInt].loggerLevel; // Don't do expensive utf8 to utf16 conversion unless we have to.. if (logger.isLoggable(level)) { @@ -1047,14 +1047,17 @@ 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); + + final logger = Realm.logger; + _realmLib.realm_sync_client_config_set_log_level(handle._pointer, _LogLevel.fromLevel(logger.level).index); _realmLib.realm_dart_sync_client_config_set_log_callback( handle._pointer, Pointer.fromFunction(_logCallback), - configuration.logger.toPersistentHandle(), + logger.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, scheduler.handle._pointer, ); + _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)); @@ -1992,3 +1995,26 @@ extension on realm_object_id { return ObjectId.fromBytes(buffer); } } + +// Helper enum for converting Level +enum _LogLevel { + all(RealmLogLevel.all), + trace(RealmLogLevel.trace), + debug(RealmLogLevel.debug), + detail(RealmLogLevel.detail), + info(RealmLogLevel.info), + warn(RealmLogLevel.warn), + error(RealmLogLevel.error), + fatal(RealmLogLevel.fatal), + off(RealmLogLevel.off); + + final Level loggerLevel; + const _LogLevel(this.loggerLevel); + + factory _LogLevel.fromLevel(Level level) { + for (final candidate in _LogLevel.values) { + if (level.value > candidate.loggerLevel.value) return candidate; + } + return _LogLevel.off; + } +} diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 2b2a76469..dd00ccad3 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -21,6 +21,7 @@ import 'dart:ffi'; import 'dart:io'; import 'dart:isolate'; +import 'package:logging/logging.dart'; import 'package:realm_common/realm_common.dart'; import 'configuration.dart'; @@ -70,6 +71,60 @@ export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetSt export 'user.dart' show User, UserState, UserIdentity; export 'session.dart' show Session, SessionState, ConnectionState, ProgressDirection, ProgressMode, SyncProgress; +/// Specifies the criticality level above which messages will be logged +/// by the default sync client logger. +/// {@category Realm} +class RealmLogLevel { + /// Log everything. This will seriously harm the performance of the + /// sync client and should never be used in production scenarios. + static const all = Level.ALL; + + /// A version of 'debug' that allows for very high volume output. + /// This may seriously affect the performance of the sync client. + /// + /// Same Level.FINEST + static const trace = Level('TRACE', 300); + + /// Reveal information that can aid debugging, no longer paying + /// attention to efficiency. + /// + /// Same as Level.FINER + static const debug = Level('DEBUG', 400); + + /// Same as 'Info', but prioritize completeness over minimalism. + /// + /// Same as Level.FINE; + static const detail = Level('DETAIL', 500); + + /// Log operational sync client messages, but in a minimalist fashion to + /// avoid general overhead from logging and to keep volume down. + static const info = Level.INFO; + + /// Log errors and warnings. + static const warn = Level.WARNING; + + /// Log errors only. + static const error = Level('ERROR', 1000); // Same as Level.SEVERE; + + /// Log only fatal errors. + static const fatal = Level('FATAL', 1200); // Same as Level.SHOUT; + + /// Log nothing. + static const off = Level.OFF; + + static const levels = [ + all, + trace, + debug, + detail, + info, + warn, + error, + fatal, + off, + ]; +} + /// A [Realm] instance represents a `Realm` database. /// /// {@category Realm} @@ -79,6 +134,13 @@ class Realm { late final RealmHandle _handle; late final Scheduler _scheduler; + /// The logger to use. + /// + /// Defaults to printing info or worse to the console + static late var logger = Logger.detached('Realm') + ..level = RealmLogLevel.info + ..onRecord.listen((event) => print(event)); + /// The [Configuration] object used to open this [Realm] Configuration get config => _config; diff --git a/test/app_test.dart b/test/app_test.dart index bfb5ac781..87ad58b37 100644 --- a/test/app_test.dart +++ b/test/app_test.dart @@ -38,7 +38,6 @@ Future main([List? args]) async { expect(defaultAppConfig.baseFilePath.path, ConfigurationInternal.defaultStorageFolder); 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)); @@ -50,7 +49,6 @@ Future main([List? args]) async { localAppName: 'bar', localAppVersion: "1.0.0", metadataPersistenceMode: MetadataPersistenceMode.disabled, - logLevel: LogLevel.info, maxConnectionTimeout: const Duration(minutes: 1), httpClient: httpClient, ); @@ -58,7 +56,6 @@ Future main([List? args]) async { 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); @@ -69,7 +66,6 @@ Future main([List? args]) async { expect(appConfig.appId, 'myapp1'); expect(appConfig.baseUrl, Uri.parse('https://realm.mongodb.com')); expect(appConfig.defaultRequestTimeout, const Duration(minutes: 1)); - expect(appConfig.logLevel, LogLevel.error); expect(appConfig.metadataPersistenceMode, MetadataPersistenceMode.plaintext); expect(appConfig.maxConnectionTimeout, const Duration(minutes: 2)); expect(appConfig.httpClient, isNotNull); @@ -89,7 +85,6 @@ Future main([List? args]) async { localAppVersion: "1.0.0", metadataPersistenceMode: MetadataPersistenceMode.encrypted, metadataEncryptionKey: base64.decode("ekey"), - logLevel: LogLevel.info, maxConnectionTimeout: const Duration(minutes: 1), httpClient: httpClient, ); @@ -98,7 +93,6 @@ Future main([List? args]) async { 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.encrypted); expect(appConfig.maxConnectionTimeout, const Duration(minutes: 1)); expect(appConfig.httpClient, httpClient); @@ -190,61 +184,31 @@ Future main([List? args]) async { expect(app.users, [user1, user]); }); - baasTest('AppConfiguration.logger', (configuration) async { - final logger = Logger.detached(generateRandomString(10))..level = LogLevel.all.loggerLevel; - configuration = AppConfiguration( - configuration.appId, - logLevel: LogLevel.all, - logger: logger, - baseFilePath: configuration.baseFilePath, - baseUrl: configuration.baseUrl, - ); - - await testLogger( - configuration, - logger, - maxExpectedCounts: { - // No problems expected! - LogLevel.fatal.loggerLevel: 0, - LogLevel.error.loggerLevel: 0, - LogLevel.warn.loggerLevel: 0, - }, - minExpectedCounts: { - // these are set low (roughly half of what was seen when test was created), - // so that changes to core are less likely to break the test - LogLevel.trace.loggerLevel: 10, - LogLevel.debug.loggerLevel: 20, - LogLevel.detail.loggerLevel: 2, - LogLevel.info.loggerLevel: 1, - }, - ); - }); - baasTest('App.defaultLogger', (configuration) async { - App.defaultLogger = Logger.detached(generateRandomString(10))..level = LogLevel.all.loggerLevel; + baasTest('Realm.logger', (configuration) async { + Realm.logger = Logger.detached(generateRandomString(10))..level = RealmLogLevel.all; configuration = AppConfiguration( configuration.appId, - logLevel: LogLevel.all, baseFilePath: configuration.baseFilePath, baseUrl: configuration.baseUrl, ); // uses App.defaultLogger await testLogger( configuration, - App.defaultLogger, + Realm.logger, maxExpectedCounts: { // No problems expected! - LogLevel.fatal.loggerLevel: 0, - LogLevel.error.loggerLevel: 0, - LogLevel.warn.loggerLevel: 0, + RealmLogLevel.fatal: 0, + RealmLogLevel.error: 0, + RealmLogLevel.warn: 0, }, minExpectedCounts: { // these are set low (roughly half of what was seen when test was created), // so that changes to core are less likely to break the test - LogLevel.trace.loggerLevel: 10, - LogLevel.debug.loggerLevel: 20, - LogLevel.detail.loggerLevel: 2, - LogLevel.info.loggerLevel: 1, + RealmLogLevel.trace: 10, + RealmLogLevel.debug: 20, + RealmLogLevel.detail: 2, + RealmLogLevel.info: 1, }, ); }); From 414d86fa0aa1bdd5aaeec404f79335c11a2f1018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 23 May 2022 17:57:47 +0200 Subject: [PATCH 09/12] Remove dead code (_logger) --- lib/src/app.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index d9f63500f..2ab397b71 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -78,8 +78,6 @@ class AppConfiguration { /// Setting this will not change the encryption key for individual Realms, which is set in the [Configuration]. final List? metadataEncryptionKey; - final Logger _logger; - /// 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, @@ -102,8 +100,7 @@ class AppConfiguration { HttpClient? httpClient, }) : baseUrl = baseUrl ?? Uri.parse('https://realm.mongodb.com'), baseFilePath = baseFilePath ?? Directory(ConfigurationInternal.defaultStorageFolder), - httpClient = httpClient ?? HttpClient(), - _logger = Realm.logger; + httpClient = httpClient ?? HttpClient(); } /// An [App] is the main client-side entry point for interacting with a MongoDB Realm App. From f47cbe5140910ff8924a18e14213b32b40a05f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 23 May 2022 18:00:47 +0200 Subject: [PATCH 10/12] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ed7e5eda..aa33bb458 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,7 @@ * Support flexible synchronization. ([#496](https://github.com/realm/realm-dart/pull/496)) * Added support for DateTime properties. ([#569](https://github.com/realm/realm-dart/pull/569)) * Support setting logger on AppConfiguration. ([#583](https://github.com/realm/realm-dart/pull/583)) -* Support setting default logger on App class. Default of default is to print to console. ([#583](https://github.com/realm/realm-dart/pull/583)) +* Support setting logger on Realm class. Default is to print info message or worse to the console. ([#583](https://github.com/realm/realm-dart/pull/583)) ### Fixed * Fixed an issue that would result in the wrong transaction being rolled back if you start a write transaction inside a write transaction. ([#442](https://github.com/realm/realm-dart/issues/442)) From 53bdbca176e7ede553adfc12c769e95f00c876f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 24 May 2022 08:47:51 +0200 Subject: [PATCH 11/12] Update doc comments on RealmLogLevel members --- lib/src/realm_class.dart | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index dd00ccad3..f09e04808 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -77,39 +77,51 @@ export 'session.dart' show Session, SessionState, ConnectionState, ProgressDirec class RealmLogLevel { /// Log everything. This will seriously harm the performance of the /// sync client and should never be used in production scenarios. + /// + /// Same as [Level.ALL] static const all = Level.ALL; - /// A version of 'debug' that allows for very high volume output. + /// A version of [debug] that allows for very high volume output. /// This may seriously affect the performance of the sync client. /// - /// Same Level.FINEST + /// Same as [Level.FINEST] static const trace = Level('TRACE', 300); /// Reveal information that can aid debugging, no longer paying /// attention to efficiency. /// - /// Same as Level.FINER + /// Same as [Level.FINER] static const debug = Level('DEBUG', 400); - /// Same as 'Info', but prioritize completeness over minimalism. + /// Same as [info], but prioritize completeness over minimalism. /// - /// Same as Level.FINE; + /// Same as [Level.FINE]; static const detail = Level('DETAIL', 500); /// Log operational sync client messages, but in a minimalist fashion to /// avoid general overhead from logging and to keep volume down. + /// + /// Same as [Level.INFO]; static const info = Level.INFO; /// Log errors and warnings. + /// + /// Same as [Level.WARNING]; static const warn = Level.WARNING; /// Log errors only. - static const error = Level('ERROR', 1000); // Same as Level.SEVERE; + /// + /// Same as [Level.SEVERE]; + static const error = Level('ERROR', 1000); /// Log only fatal errors. - static const fatal = Level('FATAL', 1200); // Same as Level.SHOUT; + /// + /// Same as [Level.SHOUT]; + static const fatal = Level('FATAL', 1200); /// Log nothing. + /// + /// Same as [Level.OFF]; static const off = Level.OFF; static const levels = [ From 47203e6de58114459303cace56dc85a51713738b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 24 May 2022 08:47:18 +0200 Subject: [PATCH 12/12] No need to round-trip logger through native code, now that it is a static member on Realm class --- lib/src/native/realm_core.dart | 9 ++++----- src/sync_client_config.cpp | 4 +++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index a8e03aea6..33787f6ef 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1029,7 +1029,7 @@ class _RealmCore { static void _logCallback(Pointer userdata, int levelAsInt, Pointer message) { try { - final logger = userdata.toObject(isPersistent: true)!; + final logger = Realm.logger; final level = _LogLevel.values[levelAsInt].loggerLevel; // Don't do expensive utf8 to utf16 conversion unless we have to.. @@ -1048,13 +1048,12 @@ 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); - final logger = Realm.logger; - _realmLib.realm_sync_client_config_set_log_level(handle._pointer, _LogLevel.fromLevel(logger.level).index); + _realmLib.realm_sync_client_config_set_log_level(handle._pointer, _LogLevel.fromLevel(Realm.logger.level).index); _realmLib.realm_dart_sync_client_config_set_log_callback( handle._pointer, Pointer.fromFunction(_logCallback), - logger.toPersistentHandle(), - _realmLib.addresses.realm_dart_delete_persistent_handle, + nullptr, + nullptr, scheduler.handle._pointer, ); diff --git a/src/sync_client_config.cpp b/src/sync_client_config.cpp index a0b4bacef..aed410f2f 100644 --- a/src/sync_client_config.cpp +++ b/src/sync_client_config.cpp @@ -47,6 +47,8 @@ void _userdata_free(void* userdata) { delete u; } +void _no_op() {} + RLM_API void realm_dart_sync_client_config_set_log_callback( realm_sync_client_config_t* config, realm_log_func_t callback, @@ -55,7 +57,7 @@ RLM_API void realm_dart_sync_client_config_set_log_callback( realm_scheduler_t* scheduler) noexcept { auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1, std::placeholders::_2), - std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + userdata_free != nullptr ? std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata) : std::function(_no_op)); return realm_sync_client_config_set_log_callback(config, _callback, u, _userdata_free); }