From 769f1ffbbe11b2ef63bd7276d219b0240cbc55e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 13 Mar 2024 18:25:56 +0100 Subject: [PATCH 1/6] Update realm-core --- .../lib/src/native/realm_bindings.dart | 89 ++++++++++--------- packages/realm_dart/src/realm-core | 2 +- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/packages/realm_dart/lib/src/native/realm_bindings.dart b/packages/realm_dart/lib/src/native/realm_bindings.dart index ca058be2a..e46515e67 100644 --- a/packages/realm_dart/lib/src/native/realm_bindings.dart +++ b/packages/realm_dart/lib/src/native/realm_bindings.dart @@ -3343,6 +3343,20 @@ class RealmLibrary { ffi.Pointer, ffi.Pointer)>(); + void realm_dart_attach_logger( + int port, + ) { + return _realm_dart_attach_logger( + port, + ); + } + + late final _realm_dart_attach_loggerPtr = + _lookup>( + 'realm_dart_attach_logger'); + late final _realm_dart_attach_logger = + _realm_dart_attach_loggerPtr.asFunction(); + ffi.Pointer realm_dart_create_scheduler( int isolateId, int port, @@ -3636,6 +3650,20 @@ class RealmLibrary { _realm_dart_delete_persistent_handlePtr .asFunction)>(); + void realm_dart_detach_logger( + int port, + ) { + return _realm_dart_detach_logger( + port, + ); + } + + late final _realm_dart_detach_loggerPtr = + _lookup>( + 'realm_dart_detach_logger'); + late final _realm_dart_detach_logger = + _realm_dart_detach_loggerPtr.asFunction(); + /// implemented for Android only ffi.Pointer realm_dart_get_bundle_id() { return _realm_dart_get_bundle_id(); @@ -3769,21 +3797,23 @@ class RealmLibrary { void realm_dart_log_message_for_testing( int level, + ffi.Pointer category, ffi.Pointer message, ) { return _realm_dart_log_message_for_testing( level, + category, message, ); } late final _realm_dart_log_message_for_testingPtr = _lookup< - ffi - .NativeFunction)>>( - 'realm_dart_log_message_for_testing'); + ffi.NativeFunction< + ffi.Void Function(ffi.Int32, ffi.Pointer, + ffi.Pointer)>>('realm_dart_log_message_for_testing'); late final _realm_dart_log_message_for_testing = - _realm_dart_log_message_for_testingPtr - .asFunction)>(); + _realm_dart_log_message_for_testingPtr.asFunction< + void Function(int, ffi.Pointer, ffi.Pointer)>(); ffi.Pointer realm_dart_object_to_persistent_handle( Object handle, @@ -3815,20 +3845,6 @@ class RealmLibrary { _realm_dart_persistent_handle_to_objectPtr .asFunction)>(); - void realm_dart_release_logger( - int port, - ) { - return _realm_dart_release_logger( - port, - ); - } - - late final _realm_dart_release_loggerPtr = - _lookup>( - 'realm_dart_release_logger'); - late final _realm_dart_release_logger = - _realm_dart_release_loggerPtr.asFunction(); - void realm_dart_return_string_callback( ffi.Pointer userdata, ffi.Pointer serialized_ejson_response, @@ -3868,22 +3884,6 @@ class RealmLibrary { late final _realm_dart_scheduler_invoke = _realm_dart_scheduler_invokePtr .asFunction)>(); - void realm_dart_set_log_level( - int level, - int port, - ) { - return _realm_dart_set_log_level( - level, - port, - ); - } - - late final _realm_dart_set_log_levelPtr = - _lookup>( - 'realm_dart_set_log_level'); - late final _realm_dart_set_log_level = - _realm_dart_set_log_levelPtr.asFunction(); - bool realm_dart_sync_after_reset_handler_callback( ffi.Pointer userdata, ffi.Pointer before_realm, @@ -11631,6 +11631,8 @@ class _SymbolAddresses { ffi.Pointer)>> get realm_dart_async_open_task_callback => _library._realm_dart_async_open_task_callbackPtr; + ffi.Pointer> + get realm_dart_attach_logger => _library._realm_dart_attach_loggerPtr; ffi.Pointer< ffi.NativeFunction< ffi.Pointer Function(ffi.Uint64, Dart_Port)>> @@ -11708,6 +11710,8 @@ class _SymbolAddresses { ffi.Pointer)>> get realm_dart_delete_persistent_handle => _library._realm_dart_delete_persistent_handlePtr; + ffi.Pointer> + get realm_dart_detach_logger => _library._realm_dart_detach_loggerPtr; ffi.Pointer Function()>> get realm_dart_get_bundle_id => _library._realm_dart_get_bundle_idPtr; ffi.Pointer Function()>> @@ -11738,8 +11742,9 @@ class _SymbolAddresses { ffi.Pointer Function()>> get realm_dart_library_version => _library._realm_dart_library_versionPtr; ffi.Pointer< - ffi - .NativeFunction)>> + ffi.NativeFunction< + ffi.Void Function( + ffi.Int32, ffi.Pointer, ffi.Pointer)>> get realm_dart_log_message_for_testing => _library._realm_dart_log_message_for_testingPtr; ffi.Pointer Function(ffi.Handle)>> @@ -11748,8 +11753,6 @@ class _SymbolAddresses { ffi.Pointer)>> get realm_dart_persistent_handle_to_object => _library._realm_dart_persistent_handle_to_objectPtr; - ffi.Pointer> - get realm_dart_release_logger => _library._realm_dart_release_loggerPtr; ffi.Pointer< ffi.NativeFunction< ffi.Void Function(ffi.Pointer, ffi.Pointer, @@ -11761,8 +11764,6 @@ class _SymbolAddresses { .NativeFunction)>> get realm_dart_scheduler_invoke => _library._realm_dart_scheduler_invokePtr; - ffi.Pointer> - get realm_dart_set_log_level => _library._realm_dart_set_log_levelPtr; ffi.Pointer< ffi.NativeFunction< ffi.Bool Function( @@ -12456,10 +12457,14 @@ typedef realm_log_func_t = ffi.Pointer>; typedef realm_log_func_tFunction = ffi.Void Function( ffi.Pointer userdata, + ffi.Pointer category, ffi.Int32 level, ffi.Pointer message); typedef Dartrealm_log_func_tFunction = void Function( - ffi.Pointer userdata, int level, ffi.Pointer message); + ffi.Pointer userdata, + ffi.Pointer category, + int level, + ffi.Pointer message); /// Logging */ /// // equivalent to realm::util::Logger::Level in util/logger.hpp and must be kept in sync. diff --git a/packages/realm_dart/src/realm-core b/packages/realm_dart/src/realm-core index 95c6efce8..649184410 160000 --- a/packages/realm_dart/src/realm-core +++ b/packages/realm_dart/src/realm-core @@ -1 +1 @@ -Subproject commit 95c6efce8d573b00c97fa8cd447eabaceb3e6c75 +Subproject commit 6491844109bd299e82520f54d70101cb6a8fbae3 From 83cfcde1524108ce403c8b275ce160294fc781b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 13 Mar 2024 18:25:31 +0100 Subject: [PATCH 2/6] Implement and test new logger --- packages/realm_dart/lib/src/app.dart | 1 + .../realm_dart/lib/src/configuration.dart | 1 + packages/realm_dart/lib/src/logging.dart | 182 ++++++++++++++ .../lib/src/native/realm_bindings.dart | 42 +--- .../realm_dart/lib/src/native/realm_core.dart | 97 ++------ packages/realm_dart/lib/src/realm_class.dart | 89 +------ packages/realm_dart/lib/src/scheduler.dart | 10 +- packages/realm_dart/src/realm_dart_logger.cpp | 72 ++---- packages/realm_dart/src/realm_dart_logger.h | 9 +- .../realm_dart/src/realm_dart_scheduler.cpp | 2 +- packages/realm_dart/src/realm_dart_sync.cpp | 8 - packages/realm_dart/src/realm_dart_sync.h | 2 - packages/realm_dart/test/app_test.dart | 21 +- packages/realm_dart/test/baas_helper.dart | 2 +- .../realm_dart/test/dynamic_realm_test.dart | 2 +- .../realm_dart/test/realm_logger_test.dart | 230 ++++-------------- packages/realm_dart/test/test.dart | 12 +- pubspec.yaml | 2 +- 18 files changed, 319 insertions(+), 465 deletions(-) create mode 100644 packages/realm_dart/lib/src/logging.dart diff --git a/packages/realm_dart/lib/src/app.dart b/packages/realm_dart/lib/src/app.dart index 76757ac59..5cd0a2d3b 100644 --- a/packages/realm_dart/lib/src/app.dart +++ b/packages/realm_dart/lib/src/app.dart @@ -11,6 +11,7 @@ import 'package:path/path.dart' as _path; import '../realm.dart'; import 'credentials.dart'; +import 'logging.dart'; import 'native/realm_core.dart'; import 'user.dart'; diff --git a/packages/realm_dart/lib/src/configuration.dart b/packages/realm_dart/lib/src/configuration.dart index da92dd551..995fcd482 100644 --- a/packages/realm_dart/lib/src/configuration.dart +++ b/packages/realm_dart/lib/src/configuration.dart @@ -7,6 +7,7 @@ import 'dart:io'; // ignore: no_leading_underscores_for_library_prefixes import 'package:path/path.dart' as _path; +import 'logging.dart'; import 'native/realm_core.dart'; import 'realm_class.dart'; import 'init.dart'; diff --git a/packages/realm_dart/lib/src/logging.dart b/packages/realm_dart/lib/src/logging.dart new file mode 100644 index 000000000..917771715 --- /dev/null +++ b/packages/realm_dart/lib/src/logging.dart @@ -0,0 +1,182 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:async'; + +import 'package:logging/logging.dart'; + +import 'native/realm_core.dart'; + +// Using classes to make a fancy hierarchical enum +sealed class RealmLogCategory { + /// All possible log categories. + static final values = [ + realm, + realm.app, + realm.sdk, + realm.storage, + realm.storage.notification, + realm.storage.object, + realm.storage.query, + realm.storage.transaction, + realm.sync, + realm.sync.client, + realm.sync.client.changeset, + realm.sync.client.network, + realm.sync.client.reset, + realm.sync.client.session, + realm.sync.server, + ]; + + /// The root category for all log messages from the Realm SDK. + static final realm = _RealmLogCategory(); + + final String _name; + final RealmLogCategory? _parent; + + RealmLogCategory._(this._name, this._parent); + + bool contains(RealmLogCategory category) { + var current = category; + while (current != this) { + if (current == realm) { // root + return false; + } + current = current._parent!; + } + return true; + } + + // cache the toString result + late final String _toString = (_parent == null ? _name : '$_parent.$_name'); + @override + String toString() => _toString; + + static final _map = {for (final category in RealmLogCategory.values) category.toString(): category}; + factory RealmLogCategory.fromString(String message) => _map[message]!; +} + +class _LeafLogCategory extends RealmLogCategory { + _LeafLogCategory(super.name, RealmLogCategory super.parent) : super._(); +} + +class _RealmLogCategory extends RealmLogCategory { + _RealmLogCategory() : super._('Realm', null); + + late final app = _LeafLogCategory('App', this); + late final sdk = _LeafLogCategory('SDK', this); + late final storage = _StorageLogCategory(this); + late final sync = _SyncLogCategory(this); +} + +class _SyncLogCategory extends RealmLogCategory { + _SyncLogCategory(RealmLogCategory parent) : super._('Sync', parent); + + late final client = _ClientLogCategory(this); + late final server = _LeafLogCategory('Server', this); +} + +class _ClientLogCategory extends RealmLogCategory { + _ClientLogCategory(RealmLogCategory parent) : super._('Client', parent); + + late final changeset = _LeafLogCategory('Changeset', this); + late final network = _LeafLogCategory('Network', this); + late final reset = _LeafLogCategory('Reset', this); + late final session = _LeafLogCategory('Session', this); +} + +class _StorageLogCategory extends RealmLogCategory { + _StorageLogCategory(RealmLogCategory parent) : super._('Storage', parent); + + late final notification = _LeafLogCategory('Notification', this); + late final object = _LeafLogCategory('Object', this); + late final query = _LeafLogCategory('Query', this); + late final transaction = _LeafLogCategory('Transaction', this); +} + +/// Specifies the criticality level above which messages will be logged +/// by the default sync client logger. +/// {@category Realm} +enum RealmLogLevel { + /// 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 minimalist 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), + + /// Turn off logging. + off(Level.OFF), + ; + + static const logToValues = [ + RealmLogLevel.trace, + RealmLogLevel.debug, + RealmLogLevel.detail, + RealmLogLevel.info, + RealmLogLevel.warn, + RealmLogLevel.error, + RealmLogLevel.fatal, + ]; + + final Level level; + const RealmLogLevel(this.level); + + static final _valuesMap = {for (var value in RealmLogLevel.values) value.index: value}; + factory RealmLogLevel.fromInt(int code) => _valuesMap[code]!; +} + +/// A record of a log message from the Realm SDK. +typedef RealmLogRecord = ({RealmLogCategory category, RealmLogLevel level, String message}); + +/// A logger that logs messages from the Realm SDK. +class RealmLogger { + static final _controller = StreamController.broadcast( + onListen: () => realmCore.loggerAttach(), + onCancel: () => realmCore.loggerDetach(), + ); + + const RealmLogger(); + + void setLogLevel(RealmLogLevel level, {RealmLogCategory? category}) { + category ??= RealmLogCategory.realm; + realmCore.setLogLevel(level, category: category); + } + + Stream get onRecord => _controller.stream; + + void _raise(RealmLogRecord record) { + _controller.add(record); + } + + void _log(RealmLogLevel level, Object message, {RealmLogCategory? category}) { + category ??= RealmLogCategory.realm.sdk; + realmCore.logMessage(RealmLogCategory.realm.sdk, level, message.toString()); + } +} + +extension RealmLoggerInternal on RealmLogger { + void raise(RealmLogRecord record) => _raise(record); + void log(RealmLogLevel level, Object message, {RealmLogCategory? category}) => _log(level, message, category: category); +} diff --git a/packages/realm_dart/lib/src/native/realm_bindings.dart b/packages/realm_dart/lib/src/native/realm_bindings.dart index e46515e67..8be7ae4bd 100644 --- a/packages/realm_dart/lib/src/native/realm_bindings.dart +++ b/packages/realm_dart/lib/src/native/realm_bindings.dart @@ -3795,25 +3795,24 @@ class RealmLibrary { late final _realm_dart_library_version = _realm_dart_library_versionPtr .asFunction Function()>(); - void realm_dart_log_message_for_testing( + void realm_dart_log( int level, ffi.Pointer category, ffi.Pointer message, ) { - return _realm_dart_log_message_for_testing( + return _realm_dart_log( level, category, message, ); } - late final _realm_dart_log_message_for_testingPtr = _lookup< + late final _realm_dart_logPtr = _lookup< ffi.NativeFunction< ffi.Void Function(ffi.Int32, ffi.Pointer, - ffi.Pointer)>>('realm_dart_log_message_for_testing'); - late final _realm_dart_log_message_for_testing = - _realm_dart_log_message_for_testingPtr.asFunction< - void Function(int, ffi.Pointer, ffi.Pointer)>(); + ffi.Pointer)>>('realm_dart_log'); + late final _realm_dart_log = _realm_dart_logPtr.asFunction< + void Function(int, ffi.Pointer, ffi.Pointer)>(); ffi.Pointer realm_dart_object_to_persistent_handle( Object handle, @@ -3928,26 +3927,6 @@ class RealmLibrary { _realm_dart_sync_before_reset_handler_callbackPtr.asFunction< bool Function(ffi.Pointer, ffi.Pointer)>(); - void realm_dart_sync_client_log_callback( - ffi.Pointer userdata, - int level, - ffi.Pointer message, - ) { - return _realm_dart_sync_client_log_callback( - userdata, - level, - message, - ); - } - - late final _realm_dart_sync_client_log_callbackPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer, ffi.Int32, - ffi.Pointer)>>('realm_dart_sync_client_log_callback'); - late final _realm_dart_sync_client_log_callback = - _realm_dart_sync_client_log_callbackPtr.asFunction< - void Function(ffi.Pointer, int, ffi.Pointer)>(); - void realm_dart_sync_connection_state_changed_callback( ffi.Pointer userdata, int old_state, @@ -11745,8 +11724,7 @@ class _SymbolAddresses { ffi.NativeFunction< ffi.Void Function( ffi.Int32, ffi.Pointer, ffi.Pointer)>> - get realm_dart_log_message_for_testing => - _library._realm_dart_log_message_for_testingPtr; + get realm_dart_log => _library._realm_dart_logPtr; ffi.Pointer Function(ffi.Handle)>> get realm_dart_object_to_persistent_handle => _library._realm_dart_object_to_persistent_handlePtr; @@ -11777,12 +11755,6 @@ class _SymbolAddresses { ffi.Bool Function(ffi.Pointer, ffi.Pointer)>> get realm_dart_sync_before_reset_handler_callback => _library._realm_dart_sync_before_reset_handler_callbackPtr; - ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, ffi.Int32, ffi.Pointer)>> - get realm_dart_sync_client_log_callback => - _library._realm_dart_sync_client_log_callbackPtr; ffi.Pointer< ffi.NativeFunction< ffi.Void Function(ffi.Pointer, ffi.Int32, ffi.Int32)>> diff --git a/packages/realm_dart/lib/src/native/realm_core.dart b/packages/realm_dart/lib/src/native/realm_core.dart index 49f28d502..71835398d 100644 --- a/packages/realm_dart/lib/src/native/realm_core.dart +++ b/packages/realm_dart/lib/src/native/realm_core.dart @@ -10,15 +10,16 @@ import 'dart:io'; import 'dart:isolate'; import 'dart:typed_data'; -import 'package:crypto/crypto.dart'; -import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:cancellation_token/cancellation_token.dart'; +import 'package:crypto/crypto.dart'; // 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 'package:path/path.dart' as path; -import 'package:realm_common/realm_common.dart' hide Decimal128; +import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:realm_common/realm_common.dart' as common show Decimal128; +import 'package:realm_common/realm_common.dart' hide Decimal128; +import 'package:realm_dart/src/logging.dart'; import '../app.dart'; import '../collections.dart'; @@ -33,9 +34,9 @@ import '../realm_object.dart'; import '../results.dart'; import '../scheduler.dart'; import '../session.dart'; +import '../set.dart'; import '../subscription.dart'; import '../user.dart'; -import '../set.dart'; import 'realm_bindings.dart'; part 'decimal128.dart'; @@ -91,8 +92,6 @@ class _RealmCore { static const int RLM_INVALID_OBJECT_KEY = -1; final encryptionKeySize = 64; - late final Logger defaultRealmLogger; - late StreamSubscription realmLoggerLevelChangedSubscription; // ignore: unused_field static late final _RealmCore _instance; @@ -102,21 +101,21 @@ class _RealmCore { // This prevents reentrance if `realmCore` global variable is accessed during _RealmCore construction realmCore = this; - defaultRealmLogger = _initDefaultLogger(scheduler); + _initDefaultLogger(); } - Logger _initDefaultLogger(Scheduler scheduler) { - final logger = Logger.detached('Realm')..level = Level.INFO; - realmLoggerLevelChangedSubscription = logger.onLevelChanged.listen((logLevel) => loggerSetLogLevel(logLevel ?? RealmLogLevel.off, scheduler.nativePort)); - - bool isDefaultLogger = _realmLib.realm_dart_init_core_logger(logger.level.toInt()); - if (isDefaultLogger) { - logger.onRecord.listen((event) => print('${event.time.toIso8601String()}: $event')); + void _initDefaultLogger() { + if (_realmLib.realm_dart_init_core_logger(RealmLogLevel.info.index)) { + loggerAttach(); // TODO: Should we do this, or should we let the user attach the logger? } + } - loggerSetLogLevel(logger.level, scheduler.nativePort); + void loggerAttach() { + _realmLib.realm_dart_attach_logger(scheduler.nativePort); + } - return logger; + void loggerDetach() { + _realmLib.realm_dart_detach_logger(scheduler.nativePort); } // for debugging only. Enable in realm_dart.cpp @@ -2084,13 +2083,9 @@ class _RealmCore { }); } - void loggerSetLogLevel(Level logLevel, int schedulerPort) { - _realmLib.realm_dart_set_log_level(logLevel.toInt(), schedulerPort); - } - - void logMessageForTesting(Level logLevel, String message) { + void logMessage(RealmLogCategory category, RealmLogLevel logLevel, String message) { return using((arena) { - _realmLib.realm_dart_log_message_for_testing(logLevel.toInt(), message.toCharPtr(arena)); + _realmLib.realm_dart_log(logLevel.index, category.toString().toCharPtr(arena), message.toCharPtr(arena)); }); } @@ -3075,6 +3070,12 @@ class _RealmCore { collectionHandle?.release(); } } + + void setLogLevel(RealmLogLevel level, {required RealmLogCategory category}) { + using((arena) { + _realmLib.realm_set_log_level_category(category.toString().toCharPtr(arena), level.index); + }); + } } class LastError { @@ -3823,58 +3824,6 @@ extension on realm_app_user_apikey { ); } -extension LevelExt on Level { - int toInt() { - if (this == Level.ALL) { - return 0; - } else if (name == "TRACE") { - return 1; - } else if (name == "DEBUG") { - return 2; - } else if (name == "DETAIL") { - return 3; - } else if (this == Level.INFO) { - return 4; - } else if (this == Level.WARNING) { - return 5; - } else if (name == "ERROR") { - return 6; - } else if (name == "FATAL") { - return 7; - } else if (this == Level.OFF) { - return 8; - } else { - // if unknown logging is off - return 8; - } - } - - static Level fromInt(int value) { - switch (value) { - case 0: - return RealmLogLevel.all; - case 1: - return RealmLogLevel.trace; - case 2: - return RealmLogLevel.debug; - case 3: - return RealmLogLevel.detail; - case 4: - return RealmLogLevel.info; - case 5: - return RealmLogLevel.warn; - case 6: - return RealmLogLevel.error; - case 7: - return RealmLogLevel.fatal; - case 8: - default: - // if unknown logging is off - return RealmLogLevel.off; - } - } -} - extension PlatformEx on Platform { static String fromEnvironment(String name, {String defaultValue = ""}) { final result = Platform.environment[name]; diff --git a/packages/realm_dart/lib/src/realm_class.dart b/packages/realm_dart/lib/src/realm_class.dart index d63294b60..c0d0a7eb9 100644 --- a/packages/realm_dart/lib/src/realm_class.dart +++ b/packages/realm_dart/lib/src/realm_class.dart @@ -7,19 +7,19 @@ import 'dart:io'; import 'package:cancellation_token/cancellation_token.dart'; import 'package:collection/collection.dart'; -import 'package:logging/logging.dart'; import 'package:realm_common/realm_common.dart'; import 'configuration.dart'; import 'list.dart'; +import 'logging.dart'; +import 'map.dart'; import 'native/realm_core.dart'; import 'realm_object.dart'; import 'results.dart'; import 'scheduler.dart'; import 'session.dart'; -import 'subscription.dart'; import 'set.dart'; -import 'map.dart'; +import 'subscription.dart'; export 'package:cancellation_token/cancellation_token.dart' show CancellationToken, TimeoutCancellationToken, CancelledException; export 'package:realm_common/realm_common.dart' @@ -80,9 +80,9 @@ export "configuration.dart" SyncErrorHandler; export 'credentials.dart' show AuthProviderType, Credentials, EmailPasswordAuthProvider; export 'list.dart' show RealmList, RealmListOfObject, RealmListChanges, ListExtension; -export 'set.dart' show RealmSet, RealmSetChanges, RealmSetOfObject; export 'map.dart' show RealmMap, RealmMapChanges, RealmMapOfObject; export 'migration.dart' show Migration; +export 'native/realm_core.dart' show Decimal128; export 'realm_object.dart' show AsymmetricObject, @@ -98,9 +98,9 @@ export 'realm_object.dart' export 'realm_property.dart'; export 'results.dart' show RealmResultsOfObject, RealmResultsChanges, RealmResults, WaitForSyncMode, RealmResultsOfRealmObject; export 'session.dart' show ConnectionStateChange, SyncProgress, ProgressDirection, ProgressMode, ConnectionState, Session, SessionState, SyncErrorCode; +export 'set.dart' show RealmSet, RealmSetChanges, RealmSetOfObject; export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetState, MutableSubscriptionSet; export 'user.dart' show User, UserState, ApiKeyClient, UserIdentity, ApiKey, FunctionsClient, UserChanges; -export 'native/realm_core.dart' show Decimal128; /// A [Realm] instance represents a `Realm` database. /// @@ -510,26 +510,9 @@ class Realm implements Finalizable { return realmCore.realmEquals(this, other); } - static Logger _logger = realmCore.defaultRealmLogger; - - /// The logger to use for Realm logging in this `Isolate` + /// The logger to use for Realm logging in all [Isolate]s /// The default log level is [RealmLogLevel.info]. - static Logger get logger { - return _logger; - } - - static set logger(Logger value) { - if (_logger == value) { - return; - } - - _logger.clearListeners(); - realmCore.realmLoggerLevelChangedSubscription.cancel(); - _logger = value; - realmCore.loggerSetLogLevel(_logger.level, scheduler.nativePort); - realmCore.realmLoggerLevelChangedSubscription = - _logger.onLevelChanged.listen((logLevel) => realmCore.loggerSetLogLevel(logLevel ?? RealmLogLevel.off, scheduler.nativePort)); - } + static const logger = RealmLogger(); /// Used to shutdown Realm and allow the process to correctly release native resources and exit. /// @@ -855,10 +838,6 @@ extension RealmInternal on Realm { } } - static void logMessageForTesting(Level logLevel, String message) { - realmCore.logMessageForTesting(logLevel, message); - } - void updateSchema() { _updateSchema(); } @@ -891,60 +870,6 @@ abstract class NotificationsController implements Finalizable { } } -/// 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. - /// - /// Same as [Level.ALL] - 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 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] - 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. - /// - /// 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. - /// - /// Same as [Level.SEVERE]; - static const error = Level('ERROR', 1000); - - /// Log only fatal errors. - /// - /// Same as [Level.SHOUT]; - static const fatal = Level('FATAL', 1200); - - /// Turn off logging. - /// - /// Same as [Level.OFF]; - static const off = Level.OFF; -} - /// @nodoc class RealmMetadata { final _typeMap = {}; diff --git a/packages/realm_dart/lib/src/scheduler.dart b/packages/realm_dart/lib/src/scheduler.dart index a7d282a45..99d456db1 100644 --- a/packages/realm_dart/lib/src/scheduler.dart +++ b/packages/realm_dart/lib/src/scheduler.dart @@ -3,6 +3,8 @@ import 'dart:ffi'; import 'dart:isolate'; +import 'package:realm_dart/src/logging.dart'; + import 'native/realm_core.dart'; import 'realm_class.dart'; @@ -22,9 +24,11 @@ class Scheduler { _receivePort.handler = (dynamic message) { if (message is List) { // currently the only `message as List` is from the logger. - final level = message[0] as int; - final text = message[1] as String; - Realm.logger.log(LevelExt.fromInt(level), text); + final category = RealmLogCategory.fromString(message[0] as String); + // category is a string to avoid converting back and forth between RealmLogCategory and String + final level = RealmLogLevel.fromInt(message[1] as int); + final text = message[2] as String; + Realm.logger.raise((category: category, level: level, message: text)); } else if (message is int) { realmCore.invokeScheduler(message); } else { diff --git a/packages/realm_dart/src/realm_dart_logger.cpp b/packages/realm_dart/src/realm_dart_logger.cpp index a5f5124b2..8aab35191 100644 --- a/packages/realm_dart/src/realm_dart_logger.cpp +++ b/packages/realm_dart/src/realm_dart_logger.cpp @@ -22,39 +22,31 @@ #include #include #include +#include #include "realm_dart_logger.h" -std::recursive_mutex dart_logger_mutex; +using namespace realm::util; + +std::mutex dart_logger_mutex; bool is_core_logger_callback_set = false; -std::map dart_send_ports; -realm_log_level_e current_core_log_level; - -realm_log_level_e calculate_minimum_log_level() { - std::lock_guard lock(dart_logger_mutex); - auto min_element = std::min_element(dart_send_ports.begin(), dart_send_ports.end(), - [](std::pair const& prev, std::pair const& next) { - return prev.second < next.second; - }); - if (min_element != dart_send_ports.end()) { - return min_element->second; - } +std::set dart_send_ports; - return current_core_log_level; +RLM_API void realm_dart_detach_logger(Dart_Port port) { + std::lock_guard lock(dart_logger_mutex); + dart_send_ports.erase(port); } -RLM_API void realm_dart_release_logger(Dart_Port port) { - std::lock_guard lock(dart_logger_mutex); - - if (dart_send_ports.find(port) != dart_send_ports.end()) - { - dart_send_ports.erase(port); - auto minimum_level = calculate_minimum_log_level(); - realm_set_log_level(minimum_level); - } +RLM_API void realm_dart_attach_logger(Dart_Port port) { + std::lock_guard lock(dart_logger_mutex); + dart_send_ports.insert(port); } -bool send_message_to_scheduler(Dart_Port port, realm_log_level_e level, const char* message) { +bool send_message_to_scheduler(Dart_Port port, const char* category, realm_log_level_e level, const char* message) { + Dart_CObject c_category; + c_category.type = Dart_CObject_kString; + c_category.value.as_string = const_cast(category); + Dart_CObject c_level; c_level.type = Dart_CObject_kInt32; c_level.value.as_int32 = level; @@ -63,7 +55,7 @@ bool send_message_to_scheduler(Dart_Port port, realm_log_level_e level, const ch c_message.type = Dart_CObject_kString; c_message.value.as_string = const_cast(message); - Dart_CObject* c_request_arr[] = { &c_level , &c_message }; + Dart_CObject* c_request_arr[] = { &c_category, &c_level, &c_message }; Dart_CObject c_request; c_request.type = Dart_CObject_kArray; c_request.value.as_array.values = c_request_arr; @@ -72,40 +64,26 @@ bool send_message_to_scheduler(Dart_Port port, realm_log_level_e level, const ch return Dart_PostCObject_DL(port, &c_request); } -void realm_dart_logger_callback(realm_userdata_t userData, realm_log_level_e level, const char* message) { - std::lock_guard lock(dart_logger_mutex); - +void realm_dart_logger_callback(realm_userdata_t userData, const char* category, realm_log_level_e level, const char* message) { + std::lock_guard lock(dart_logger_mutex); for (auto itr = dart_send_ports.begin(); itr != dart_send_ports.end(); ++itr) { - Dart_Port port = itr->first; - send_message_to_scheduler(port, level, message); + Dart_Port port = *itr; + send_message_to_scheduler(port, category, level, message); } } RLM_API bool realm_dart_init_core_logger(realm_log_level_e level) { - std::lock_guard lock(dart_logger_mutex); - + std::lock_guard lock(dart_logger_mutex); if (is_core_logger_callback_set) { return false; } - current_core_log_level = level; - realm_set_log_callback(realm_dart_logger_callback, current_core_log_level, nullptr, nullptr); + realm_set_log_callback(realm_dart_logger_callback, level, nullptr, nullptr); is_core_logger_callback_set = true; return is_core_logger_callback_set; } -RLM_API void realm_dart_set_log_level(realm_log_level_e level, Dart_Port port) { - std::lock_guard lock(dart_logger_mutex); - - auto port_item = dart_send_ports.find(port); - if (port_item == dart_send_ports.end() || port_item->second != level) { - dart_send_ports[port] = level; - auto minimum_level = calculate_minimum_log_level(); - realm_set_log_level(minimum_level); - } -} - -RLM_API void realm_dart_log_message_for_testing(realm_log_level_e level, const char* message) { - realm_dart_logger_callback(nullptr, level, message); +RLM_API void realm_dart_log(realm_log_level_e level, const char* category, const char* message) { + Logger::get_default_logger()->log(LogCategory::get_category(category), Logger::Level(level), message); } \ No newline at end of file diff --git a/packages/realm_dart/src/realm_dart_logger.h b/packages/realm_dart/src/realm_dart_logger.h index 7ceaee5a1..b783678f8 100644 --- a/packages/realm_dart/src/realm_dart_logger.h +++ b/packages/realm_dart/src/realm_dart_logger.h @@ -22,16 +22,15 @@ #include #include - -RLM_API void realm_dart_release_logger(Dart_Port port); - /** * Returns `true` if Realm Core logger was initialized. */ RLM_API bool realm_dart_init_core_logger(realm_log_level_e level); -RLM_API void realm_dart_set_log_level(realm_log_level_e level, Dart_Port port); +RLM_API void realm_dart_attach_logger(Dart_Port port); + +RLM_API void realm_dart_detach_logger(Dart_Port port); -RLM_API void realm_dart_log_message_for_testing(realm_log_level_e level, const char* message); +RLM_API void realm_dart_log(realm_log_level_e level, const char* category, const char* message); #endif // REALM_DART_LOGGER_H \ No newline at end of file diff --git a/packages/realm_dart/src/realm_dart_scheduler.cpp b/packages/realm_dart/src/realm_dart_scheduler.cpp index 5c75e2e9a..f3863f440 100644 --- a/packages/realm_dart/src/realm_dart_scheduler.cpp +++ b/packages/realm_dart/src/realm_dart_scheduler.cpp @@ -41,7 +41,7 @@ struct SchedulerData { //This can be invoked on any thread void realm_dart_scheduler_free_userData(void* userData) { SchedulerData* schedulerData = static_cast(userData); - realm_dart_release_logger(schedulerData->port); + realm_dart_detach_logger(schedulerData->port); //delete the scheduler delete schedulerData; } diff --git a/packages/realm_dart/src/realm_dart_sync.cpp b/packages/realm_dart/src/realm_dart_sync.cpp index 867ee08bd..88166c74f 100644 --- a/packages/realm_dart/src/realm_dart_sync.cpp +++ b/packages/realm_dart/src/realm_dart_sync.cpp @@ -50,14 +50,6 @@ RLM_API void realm_dart_http_request_callback(realm_userdata_t userdata, realm_h }); } -RLM_API void realm_dart_sync_client_log_callback(realm_userdata_t userdata, realm_log_level_e level, const char* message) -{ - auto ud = reinterpret_cast(userdata); - ud->scheduler->invoke([ud, level, message = std::string(message)]() { - (reinterpret_cast(ud->dart_callback))(ud->handle, level, message.c_str()); - }); -} - RLM_API void realm_dart_sync_error_handler_callback(realm_userdata_t userdata, realm_sync_session_t* session, realm_sync_error_t error) { // the pointers in error are to stack values, we need to make copies and move them into the scheduler invocation diff --git a/packages/realm_dart/src/realm_dart_sync.h b/packages/realm_dart/src/realm_dart_sync.h index 87680cdd3..aae8707db 100644 --- a/packages/realm_dart/src/realm_dart_sync.h +++ b/packages/realm_dart/src/realm_dart_sync.h @@ -25,8 +25,6 @@ typedef void (*realm_sync_after_client_reset_begin_func_t)(realm_userdata_t user RLM_API void realm_dart_http_request_callback(realm_userdata_t userdata, realm_http_request_t request, void* request_context); -RLM_API void realm_dart_sync_client_log_callback(realm_userdata_t userdata, realm_log_level_e level, const char* message); - RLM_API void realm_dart_sync_error_handler_callback(realm_userdata_t userdata, realm_sync_session_t* session, realm_sync_error_t error); RLM_API void realm_dart_sync_wait_for_completion_callback(realm_userdata_t userdata, realm_error_t* error); diff --git a/packages/realm_dart/test/app_test.dart b/packages/realm_dart/test/app_test.dart index 1f29a6039..dbc449e1a 100644 --- a/packages/realm_dart/test/app_test.dart +++ b/packages/realm_dart/test/app_test.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'dart:io'; import 'dart:isolate'; import 'package:logging/logging.dart'; +import 'package:realm_dart/src/logging.dart'; import 'package:test/expect.dart' hide throws; import 'package:path/path.dart' as path; import 'package:crypto/crypto.dart'; @@ -359,22 +360,18 @@ void main() { }); baasTest('App(AppConfiguration) on background isolate logs warning', (appConfig) async { - final receivePort = ReceivePort(); - await Isolate.spawn((args) { - final logger = Logger.detached('foo'); - final sb = StringBuffer(); - logger.onRecord.listen((event) { - sb.writeln('${event.level}: ${event.message}'); - }); + Realm.logger.setLogLevel(RealmLogLevel.warn); - Realm.logger = logger; + final sb = StringBuffer(); + Realm.logger.onRecord.listen((event) { + sb.writeln('${event.category} ${event.level}: ${event.message}'); + }); - final sendPort = args[0]; + await Isolate.run(() { App(AppConfiguration('abc')); - Isolate.exit(sendPort, sb.toString()); - }, [receivePort.sendPort]); + }); - final log = await receivePort.first as String; + final log = sb.toString(); expect(log, contains('App constructor called on Isolate')); }); diff --git a/packages/realm_dart/test/baas_helper.dart b/packages/realm_dart/test/baas_helper.dart index cf7f31336..f6145bf19 100644 --- a/packages/realm_dart/test/baas_helper.dart +++ b/packages/realm_dart/test/baas_helper.dart @@ -232,7 +232,7 @@ class BaasHelper { try { final result = await config.user.functions.call('triggerClientResetOnSyncServer', [userId, appId]) as Map; if (result['status'] != 'success') { - throw 'Unsuccesful status: ${result['status']}'; + throw 'Unsuccessful status: ${result['status']}'; } break; } catch (e) { diff --git a/packages/realm_dart/test/dynamic_realm_test.dart b/packages/realm_dart/test/dynamic_realm_test.dart index c70b36163..08008d4db 100644 --- a/packages/realm_dart/test/dynamic_realm_test.dart +++ b/packages/realm_dart/test/dynamic_realm_test.dart @@ -632,7 +632,7 @@ void main() { expect(dynamicObj2.link, obj1); expect(dynamicObj2.link.id, uuid1); - assertSchemaMatches(dynamicObj2.link.objectSchema, LinksClass.schema); + assertSchemaMatches(dynamicObj2.link.objectSchema as SchemaObject, LinksClass.schema); }); test('fails with non-existent property', () { diff --git a/packages/realm_dart/test/realm_logger_test.dart b/packages/realm_dart/test/realm_logger_test.dart index dfa1afc3d..bd64d99b2 100644 --- a/packages/realm_dart/test/realm_logger_test.dart +++ b/packages/realm_dart/test/realm_logger_test.dart @@ -1,211 +1,67 @@ // Copyright 2023 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -// ignore_for_file: unused_local_variable, avoid_relative_lib_imports - -import 'dart:async'; import 'dart:isolate'; -import 'package:logging/logging.dart'; +import 'package:realm_dart/src/logging.dart'; import 'package:test/test.dart' hide test, throws; -import 'package:realm_dart/src/realm_class.dart' show RealmInternal; import 'package:realm_dart/realm.dart'; import 'test.dart'; -const logLevels = [ - RealmLogLevel.all, - RealmLogLevel.trace, - RealmLogLevel.debug, - RealmLogLevel.detail, - RealmLogLevel.info, - RealmLogLevel.fatal, - RealmLogLevel.error, - RealmLogLevel.warn -]; - -class LoggedMessage { - final Level level; - final String message; - - const LoggedMessage(this.level, this.message); - - factory LoggedMessage.empty() => const LoggedMessage(RealmLogLevel.off, ""); - - @override - // ignore: hash_and_equals - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other is! LoggedMessage) return false; - return level == other.level && message == other.message; - } - - @override - String toString() => "level:$level message:$message"; -} - -void openARealm() { - Configuration.defaultRealmName = generateRandomRealmPath(); - final config = Configuration.inMemory([Car.schema]); - Realm(config).close(); - tryDeleteRealm(Configuration.defaultRealmName); -} - void main() { setupTests(); - for (var level in logLevels) { - test('Realm.logger supports log level $level', () async { - LoggedMessage actual = await Isolate.run(() async { - final completer = Completer(); - - Realm.logger.level = level; - await Future.delayed(const Duration(milliseconds: 10)); - Realm.logger.onRecord.listen((event) { - completer.complete(LoggedMessage(event.level, event.message)); - }); - - RealmInternal.logMessageForTesting(level, "123"); - - return await completer.future; + group('All levels', () { + Realm.logger.setLogLevel(RealmLogLevel.all); + for (var level in RealmLogLevel.values) { + test('Realm.logger supports log level $level', () { + final tag = Uuid.v4(); + expectLater( + Realm.logger.onRecord, + emits((category: RealmLogCategory.realm.sdk, level: level, message: '$level $tag')), + ); + Realm.logger.log(level, '$level $tag'); }); + } + }); - expect(actual, LoggedMessage(level, "123")); - }); - } - - test('Realm.logger supports changing log level', () async { - List actual = await Isolate.run(() async { - final result = []; - - var completer = Completer(); - Realm.logger.level = RealmLogLevel.trace; - Realm.logger.onRecord.listen((event) { - if (event.level != Realm.logger.level) { - return; + group('Match levels', () { + for (var level in RealmLogLevel.values) { + final expectedLevels = RealmLogLevel.logToValues.where((l) => l.index >= level.index); + test('$level matches $expectedLevels', () { + Realm.logger.setLogLevel(level); + expectLater(Realm.logger.onRecord, emitsInOrder(expectedLevels.map((l) => isA().having((r) => r.level, '$l', l)))); + for (var sendLevel in RealmLogLevel.logToValues) { + Realm.logger.log(sendLevel, '$sendLevel'); } - - completer.complete(LoggedMessage(event.level, event.message)); }); - openARealm(); - result.add(await completer.future); - - Realm.logger.level = RealmLogLevel.debug; - completer = Completer(); - openARealm(); - result.add(await completer.future); - - Realm.logger.level = RealmLogLevel.trace; - completer = Completer(); - openARealm(); - result.add(await completer.future); - - //increase log verbosity - Realm.logger.level = RealmLogLevel.debug; - completer = Completer(); - openARealm(); - result.add(await completer.future); - - return result; - }); - - expect(actual[0].level, RealmLogLevel.trace); - expect(actual[1].level, RealmLogLevel.debug); - expect(actual[2].level, RealmLogLevel.trace); - expect(actual[3].level, RealmLogLevel.debug); + } }); - test('Realm.logger supports custom logger', () async { - LoggedMessage actual = await Isolate.run(() async { - final completer = Completer(); - - Realm.logger.onRecord.listen((event) { - throw RealmError("Default logger should not log messages if custom logger is set"); - }); - - Realm.logger = Logger.detached("custom logger")..level = RealmLogLevel.detail; - - Realm.logger.onRecord.listen((event) { - completer.complete(LoggedMessage(event.level, event.message)); - }); - - RealmInternal.logMessageForTesting(RealmLogLevel.detail, "123"); + group('RealmLogCategory.contains', () { + for (final outer in RealmLogCategory.values) { + for (final inner in RealmLogCategory.values) { + test('$outer contains $inner', () { + expect(outer.contains(inner), inner.toString().startsWith(outer.toString())); + }); + } + } + }); - return await completer.future; + test('Trace in subisolate seen in parent', () { + Realm.logger.setLogLevel(RealmLogLevel.all); + expectLater(Realm.logger.onRecord, emits(isA().having((r) => r.message, '', 'Hey'))); + Isolate.run(() { + Realm.logger.log(RealmLogLevel.trace, 'Hey'); }); - - expect(actual, LoggedMessage(RealmLogLevel.detail, "123")); }); - test('Realm.logger supports logging from multiple isolates', () async { - List actual = await Isolate.run(() async { - var completer = Completer(); - final result = []; - - Realm.logger.level = RealmLogLevel.all; - Realm.logger.onRecord.listen((event) { - if (event.message == "stop") { - return completer.complete(); - } - - result.add(LoggedMessage(event.level, event.message)); - }); - - // run a second isolate to listen logging specific log level messages - await Isolate.run(() async { - var completer2 = Completer(); - Realm.logger.level = RealmLogLevel.error; - Realm.logger.onRecord.listen((event) { - if ((event.level == RealmLogLevel.error && event.message != "2") || (event.level == RealmLogLevel.trace && event.message != "3")) { - throw RealmError("Unexpected message ${LoggedMessage(event.level, event.message)}"); - } - - completer2.complete(); - }); - - Future logTestMessage(Level level, String message) async { - completer2 = Completer(); - RealmInternal.logMessageForTesting(level, message); - if (Realm.logger.level == RealmLogLevel.off) { - Future.delayed(const Duration(milliseconds: 10)).then((value) { - completer2.complete(); - }).ignore(); - } - await completer2.future; - } - - await logTestMessage(RealmLogLevel.error, "2"); - await logTestMessage(RealmLogLevel.error, "2"); - - // turn off second isolate - Realm.logger.level = RealmLogLevel.off; - - // log another message. second isoalte should not get it - await logTestMessage(RealmLogLevel.error, "first only"); - - // turn on second isolate - Realm.logger.level = RealmLogLevel.trace; - - // log a another message - await logTestMessage(RealmLogLevel.trace, "3"); - - // stop all isolates signal - await logTestMessage(RealmLogLevel.detail, "stop"); - - return await completer2.future; - }); - - await completer.future; - - return result; + test('Trace in root isolate seen in subisolate', () async { + Realm.logger.setLogLevel(RealmLogLevel.all); + final trace = Isolate.run(() async { + return (await Realm.logger.onRecord.first).message; }); - - final expected = [ - const LoggedMessage(RealmLogLevel.error, "2"), - const LoggedMessage(RealmLogLevel.error, "2"), - const LoggedMessage(RealmLogLevel.error, "first only"), - const LoggedMessage(RealmLogLevel.trace, "3") - ]; - - //first isolate should have collected all the messages - expect(actual, containsAllInOrder(expected)); + await Future.delayed(const Duration(microseconds: 1)); // yield + expectLater(trace, completion('Hey')); + Realm.logger.log(RealmLogLevel.trace, 'Hey'); }); } diff --git a/packages/realm_dart/test/test.dart b/packages/realm_dart/test/test.dart index 11877de1d..c65902415 100644 --- a/packages/realm_dart/test/test.dart +++ b/packages/realm_dart/test/test.dart @@ -10,6 +10,7 @@ import 'dart:typed_data'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as _path; +import 'package:realm_dart/src/logging.dart'; import 'package:test/test.dart' hide test; import 'package:test/test.dart' as testing; import 'package:realm_dart/realm.dart'; @@ -415,11 +416,10 @@ void setupTests() { setUpAll(() async { baasHelper = await BaasHelper.setupBaas(); - Realm.logger = Logger.detached('test run') - ..level = Level.ALL - ..onRecord.listen((record) { - testing.printOnFailure('${record.time} ${record.level.name}: ${record.message}'); - }); + Realm.logger.setLogLevel(RealmLogLevel.all); + Realm.logger.onRecord.listen((record) { + testing.printOnFailure('${record.category} ${record.level.name}: ${record.message}'); + }); // Enable this to print platform info, including current PID _printPlatformInfo(); @@ -553,7 +553,7 @@ Future tryDeleteRealm(String path) async { return; } catch (e) { - Realm.logger.info('Failed to delete realm at path $path. Trying again in ${duration.inMilliseconds}ms'); + Realm.logger.log(RealmLogLevel.info, 'Failed to delete realm at path $path. Trying again in ${duration.inMilliseconds}ms'); await Future.delayed(duration); } } diff --git a/pubspec.yaml b/pubspec.yaml index 23e781959..65d461c09 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,5 +3,5 @@ name: my_project_workspace environment: sdk: '>=3.0.0 <4.0.0' dev_dependencies: - melos: ^4.1.0 + melos: ^5.1.0 From ae514fd4c1d5bf7018421d78c7a93df6e52bfb27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 15 Mar 2024 19:36:09 +0100 Subject: [PATCH 3/6] Add a bit of delay (ugly) --- packages/realm_dart/test/realm_logger_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/realm_dart/test/realm_logger_test.dart b/packages/realm_dart/test/realm_logger_test.dart index bd64d99b2..f7113a2a9 100644 --- a/packages/realm_dart/test/realm_logger_test.dart +++ b/packages/realm_dart/test/realm_logger_test.dart @@ -60,7 +60,7 @@ void main() { final trace = Isolate.run(() async { return (await Realm.logger.onRecord.first).message; }); - await Future.delayed(const Duration(microseconds: 1)); // yield + await Future.delayed(const Duration(seconds: 1)); // yield expectLater(trace, completion('Hey')); Realm.logger.log(RealmLogLevel.trace, 'Hey'); }); From 7493cfca750ccbf03bd748a5e619d0859b5fa385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 18 Mar 2024 12:29:22 +0100 Subject: [PATCH 4/6] Test integration with package logging --- .../realm_dart/test/realm_logger_test.dart | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/realm_dart/test/realm_logger_test.dart b/packages/realm_dart/test/realm_logger_test.dart index f7113a2a9..b35ef7095 100644 --- a/packages/realm_dart/test/realm_logger_test.dart +++ b/packages/realm_dart/test/realm_logger_test.dart @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import 'dart:isolate'; +import 'dart:math'; +import 'package:logging/logging.dart'; import 'package:realm_dart/src/logging.dart'; import 'package:test/test.dart' hide test, throws; import 'package:realm_dart/realm.dart'; @@ -64,4 +66,27 @@ void main() { expectLater(trace, completion('Hey')); Realm.logger.log(RealmLogLevel.trace, 'Hey'); }); + + test('RealmLogger hookup logging', () async { + final logger = Logger.detached('Test'); + Realm.logger.onRecord.forEach((r) => logger.log(r.level.level, r.message)); + logger.level = Level.ALL; + Realm.logger.setLogLevel(RealmLogLevel.error); + + expectLater(logger.onRecord, emits(isA().having((r) => r.level, '==', Level.SEVERE).having((r) => r.message, '==', 'error'))); + + Realm.logger.log(RealmLogLevel.error, 'error', category: RealmLogCategory.realm.sdk); + }); + + test('RealmLogger hookup hierarchical logging', () async { + hierarchicalLoggingEnabled = true; + Realm.logger.onRecord.forEach((r) => Logger(r.category.toString()).log(r.level.level, r.message)); + Logger.root.level = Level.ALL; + Realm.logger.setLogLevel(RealmLogLevel.error); + + expectLater(Logger('Realm').onRecord, emits(isA().having((r) => r.level, '==', Level.SEVERE).having((r) => r.message, '==', 'error'))); + expectLater(Logger('Realm.SDK').onRecord, emits(isA().having((r) => r.level, '==', Level.SEVERE).having((r) => r.message, '==', 'error'))); + + Realm.logger.log(RealmLogLevel.error, 'error', category: RealmLogCategory.realm.sdk); + }); } From b4d8b7560f8eb9978d6fc9d098863f4f22384278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 18 Mar 2024 12:29:34 +0100 Subject: [PATCH 5/6] Update CHANGELOG --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d100b90c2..ea278f42d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,40 @@ ``` * Removed `SchemaObject.properties` - instead, `SchemaObject` is now an iterable collection of `Property`. (Issue [#1449](https://github.com/realm/realm-dart/issues/1449)) * `SyncProgress.transferredBytes` and `SyncProgress.transferableBytes` have been consolidated into `SyncProgress.progressEstimate`. The values reported previously were incorrect and did not accurately represent bytes either. The new field better conveys the uncertainty around the progress being reported. With this release, we're reporting accurate estimates for upload progress, but estimating downloads is still unreliable. A future server and SDK release will add better estimations for download progress. (Issue [#1562](https://github.com/realm/realm-dart/issues/1562)) +* `Realm.logger` is no longer settable, and no longer implements `Logger` from package `logging`. In particular you can no longer call `Realm.logger.level =`. Instead you should call `Realm.logger.setLogLevel(RealmLogLevel level, {RealmLogCategory? category})` that takes an optional category. If no category is exlicitly given, then `RealmLogCategory.realm` is assumed. + + Also, note that setting a level is no longer local to the current isolate, but shared between accross all isolates. Maintaining the illution that there exists a logger per isolate, became untennable with the new API. At the core level there is just one process wide logger. + + Categories form a hirarchy and setting the log level of a parent category will override the level of the current children. The hierarchy is exposed in a type safe manner with: + ```dart + sealed class RealmLogCategory { + /// All possible log categories. + static final values = [ + realm, + realm.app, + realm.sdk, + realm.storage, + realm.storage.notification, + realm.storage.object, + realm.storage.query, + realm.storage.transaction, + realm.sync, + realm.sync.client, + realm.sync.client.changeset, + realm.sync.client.network, + realm.sync.client.reset, + realm.sync.client.session, + realm.sync.server, + ... + ``` + the `onRecord` stream now pumps `RealmLogRecord`s that includes the category the message was logged to. + + If you want to hook up realm logging with conventional dart logging you can do: + ```dart + Realm.logger.onRecord.forEach((r) => Logger(r.category.toString()).log(r.level.level, r.message)); + ``` + If no isolate subscribes to `Realm.logger.onRecord` then the logs will by default be send to stdout. + ### Enhancements * Realm objects can now be serialized as [EJSON](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/) From bd49967b1d1babf33b677a853d88877201dac0d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 18 Mar 2024 15:58:58 +0100 Subject: [PATCH 6/6] Fallback to native StderrLogger --- .../lib/src/native/realm_bindings.dart | 92 +++++++++++++++---- .../realm_dart/lib/src/native/realm_core.dart | 8 +- packages/realm_dart/src/realm-core | 2 +- packages/realm_dart/src/realm_dart_logger.cpp | 54 ++++++----- packages/realm_dart/src/realm_dart_logger.h | 5 +- .../realm_dart/src/realm_dart_scheduler.cpp | 2 - .../realm_dart/test/realm_logger_test.dart | 13 +-- 7 files changed, 112 insertions(+), 64 deletions(-) diff --git a/packages/realm_dart/lib/src/native/realm_bindings.dart b/packages/realm_dart/lib/src/native/realm_bindings.dart index 8be7ae4bd..55b29cdc8 100644 --- a/packages/realm_dart/lib/src/native/realm_bindings.dart +++ b/packages/realm_dart/lib/src/native/realm_bindings.dart @@ -3737,20 +3737,15 @@ class RealmLibrary { void Function(ffi.Pointer, realm_http_request_t, ffi.Pointer)>(); - /// Returns `true` if Realm Core logger was initialized. - bool realm_dart_init_core_logger( - int level, - ) { - return _realm_dart_init_core_logger( - level, - ); + void realm_dart_init_debug_logger() { + return _realm_dart_init_debug_logger(); } - late final _realm_dart_init_core_loggerPtr = - _lookup>( - 'realm_dart_init_core_logger'); - late final _realm_dart_init_core_logger = - _realm_dart_init_core_loggerPtr.asFunction(); + late final _realm_dart_init_debug_loggerPtr = + _lookup>( + 'realm_dart_init_debug_logger'); + late final _realm_dart_init_debug_logger = + _realm_dart_init_debug_loggerPtr.asFunction(); void realm_dart_initializeDartApiDL( ffi.Pointer data, @@ -9040,13 +9035,11 @@ class RealmLibrary { /// Install the default logger void realm_set_log_callback( realm_log_func_t arg0, - int arg1, ffi.Pointer userdata, realm_free_userdata_func_t userdata_free, ) { return _realm_set_log_callback( arg0, - arg1, userdata, userdata_free, ); @@ -9054,10 +9047,10 @@ class RealmLibrary { late final _realm_set_log_callbackPtr = _lookup< ffi.NativeFunction< - ffi.Void Function(realm_log_func_t, ffi.Int32, ffi.Pointer, + ffi.Void Function(realm_log_func_t, ffi.Pointer, realm_free_userdata_func_t)>>('realm_set_log_callback'); late final _realm_set_log_callback = _realm_set_log_callbackPtr.asFunction< - void Function(realm_log_func_t, int, ffi.Pointer, + void Function(realm_log_func_t, ffi.Pointer, realm_free_userdata_func_t)>(); void realm_set_log_level( @@ -9390,6 +9383,26 @@ class RealmLibrary { _realm_sync_client_config_set_fast_reconnect_limitPtr.asFunction< void Function(ffi.Pointer, int)>(); + void realm_sync_client_config_set_max_resumption_delay_interval( + ffi.Pointer arg0, + int arg1, + ) { + return _realm_sync_client_config_set_max_resumption_delay_interval( + arg0, + arg1, + ); + } + + late final _realm_sync_client_config_set_max_resumption_delay_intervalPtr = + _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, ffi.Uint64)>>( + 'realm_sync_client_config_set_max_resumption_delay_interval'); + late final _realm_sync_client_config_set_max_resumption_delay_interval = + _realm_sync_client_config_set_max_resumption_delay_intervalPtr.asFunction< + void Function(ffi.Pointer, int)>(); + void realm_sync_client_config_set_metadata_encryption_key( ffi.Pointer arg0, ffi.Pointer arg1, @@ -9502,6 +9515,47 @@ class RealmLibrary { _realm_sync_client_config_set_reconnect_modePtr.asFunction< void Function(ffi.Pointer, int)>(); + void realm_sync_client_config_set_resumption_delay_backoff_multiplier( + ffi.Pointer arg0, + int arg1, + ) { + return _realm_sync_client_config_set_resumption_delay_backoff_multiplier( + arg0, + arg1, + ); + } + + late final _realm_sync_client_config_set_resumption_delay_backoff_multiplierPtr = + _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, ffi.Int)>>( + 'realm_sync_client_config_set_resumption_delay_backoff_multiplier'); + late final _realm_sync_client_config_set_resumption_delay_backoff_multiplier = + _realm_sync_client_config_set_resumption_delay_backoff_multiplierPtr + .asFunction< + void Function(ffi.Pointer, int)>(); + + void realm_sync_client_config_set_resumption_delay_interval( + ffi.Pointer arg0, + int arg1, + ) { + return _realm_sync_client_config_set_resumption_delay_interval( + arg0, + arg1, + ); + } + + late final _realm_sync_client_config_set_resumption_delay_intervalPtr = + _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, ffi.Uint64)>>( + 'realm_sync_client_config_set_resumption_delay_interval'); + late final _realm_sync_client_config_set_resumption_delay_interval = + _realm_sync_client_config_set_resumption_delay_intervalPtr.asFunction< + void Function(ffi.Pointer, int)>(); + void realm_sync_client_config_set_sync_socket( ffi.Pointer arg0, ffi.Pointer arg1, @@ -11707,9 +11761,9 @@ class _SymbolAddresses { ffi.Void Function(ffi.Pointer, realm_http_request_t, ffi.Pointer)>> get realm_dart_http_request_callback => _library._realm_dart_http_request_callbackPtr; - ffi.Pointer> - get realm_dart_init_core_logger => - _library._realm_dart_init_core_loggerPtr; + ffi.Pointer> + get realm_dart_init_debug_logger => + _library._realm_dart_init_debug_loggerPtr; ffi.Pointer)>> get realm_dart_initializeDartApiDL => _library._realm_dart_initializeDartApiDLPtr; diff --git a/packages/realm_dart/lib/src/native/realm_core.dart b/packages/realm_dart/lib/src/native/realm_core.dart index 71835398d..5ed99f242 100644 --- a/packages/realm_dart/lib/src/native/realm_core.dart +++ b/packages/realm_dart/lib/src/native/realm_core.dart @@ -14,7 +14,6 @@ import 'package:cancellation_token/cancellation_token.dart'; import 'package:crypto/crypto.dart'; // 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 'package:path/path.dart' as path; import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:realm_common/realm_common.dart' as common show Decimal128; @@ -101,13 +100,8 @@ class _RealmCore { // This prevents reentrance if `realmCore` global variable is accessed during _RealmCore construction realmCore = this; - _initDefaultLogger(); - } - void _initDefaultLogger() { - if (_realmLib.realm_dart_init_core_logger(RealmLogLevel.info.index)) { - loggerAttach(); // TODO: Should we do this, or should we let the user attach the logger? - } + _realmLib.realm_dart_init_debug_logger(); } void loggerAttach() { diff --git a/packages/realm_dart/src/realm-core b/packages/realm_dart/src/realm-core index 649184410..90d55ea67 160000 --- a/packages/realm_dart/src/realm-core +++ b/packages/realm_dart/src/realm-core @@ -1 +1 @@ -Subproject commit 6491844109bd299e82520f54d70101cb6a8fbae3 +Subproject commit 90d55ea67aaa2dfca253d33f251d032100d0a157 diff --git a/packages/realm_dart/src/realm_dart_logger.cpp b/packages/realm_dart/src/realm_dart_logger.cpp index 8aab35191..d0f84cfd9 100644 --- a/packages/realm_dart/src/realm_dart_logger.cpp +++ b/packages/realm_dart/src/realm_dart_logger.cpp @@ -16,32 +16,18 @@ // //////////////////////////////////////////////////////////////////////////////// -#include -#include +#include +#include #include +#include +#include #include -#include -#include #include #include "realm_dart_logger.h" using namespace realm::util; -std::mutex dart_logger_mutex; -bool is_core_logger_callback_set = false; -std::set dart_send_ports; - -RLM_API void realm_dart_detach_logger(Dart_Port port) { - std::lock_guard lock(dart_logger_mutex); - dart_send_ports.erase(port); -} - -RLM_API void realm_dart_attach_logger(Dart_Port port) { - std::lock_guard lock(dart_logger_mutex); - dart_send_ports.insert(port); -} - bool send_message_to_scheduler(Dart_Port port, const char* category, realm_log_level_e level, const char* message) { Dart_CObject c_category; c_category.type = Dart_CObject_kString; @@ -64,6 +50,20 @@ bool send_message_to_scheduler(Dart_Port port, const char* category, realm_log_l return Dart_PostCObject_DL(port, &c_request); } +std::mutex dart_logger_mutex; +std::set dart_send_ports; +std::shared_ptr default_debug_logger; +bool default_debug_logger_initialized = false; + +RLM_API void realm_dart_init_debug_logger() { + if (default_debug_logger_initialized) { + return; + } + default_debug_logger = std::make_shared(); + Logger::set_default_logger(default_debug_logger); + default_debug_logger_initialized = true; +} + void realm_dart_logger_callback(realm_userdata_t userData, const char* category, realm_log_level_e level, const char* message) { std::lock_guard lock(dart_logger_mutex); for (auto itr = dart_send_ports.begin(); itr != dart_send_ports.end(); ++itr) { @@ -72,16 +72,20 @@ void realm_dart_logger_callback(realm_userdata_t userData, const char* category, } } -RLM_API bool realm_dart_init_core_logger(realm_log_level_e level) { +RLM_API void realm_dart_attach_logger(Dart_Port port) { std::lock_guard lock(dart_logger_mutex); - if (is_core_logger_callback_set) { - return false; + if (dart_send_ports.empty()) { + realm_set_log_callback(realm_dart_logger_callback, nullptr, nullptr); } + dart_send_ports.insert(port); +} - realm_set_log_callback(realm_dart_logger_callback, level, nullptr, nullptr); - is_core_logger_callback_set = true; - - return is_core_logger_callback_set; +RLM_API void realm_dart_detach_logger(Dart_Port port) { + std::lock_guard lock(dart_logger_mutex); + dart_send_ports.erase(port); + if (dart_send_ports.empty()) { + Logger::set_default_logger(default_debug_logger); + } } RLM_API void realm_dart_log(realm_log_level_e level, const char* category, const char* message) { diff --git a/packages/realm_dart/src/realm_dart_logger.h b/packages/realm_dart/src/realm_dart_logger.h index b783678f8..a11001796 100644 --- a/packages/realm_dart/src/realm_dart_logger.h +++ b/packages/realm_dart/src/realm_dart_logger.h @@ -22,10 +22,7 @@ #include #include -/** - * Returns `true` if Realm Core logger was initialized. - */ -RLM_API bool realm_dart_init_core_logger(realm_log_level_e level); +RLM_API void realm_dart_init_debug_logger(); RLM_API void realm_dart_attach_logger(Dart_Port port); diff --git a/packages/realm_dart/src/realm_dart_scheduler.cpp b/packages/realm_dart/src/realm_dart_scheduler.cpp index f3863f440..f885667a7 100644 --- a/packages/realm_dart/src/realm_dart_scheduler.cpp +++ b/packages/realm_dart/src/realm_dart_scheduler.cpp @@ -41,8 +41,6 @@ struct SchedulerData { //This can be invoked on any thread void realm_dart_scheduler_free_userData(void* userData) { SchedulerData* schedulerData = static_cast(userData); - realm_dart_detach_logger(schedulerData->port); - //delete the scheduler delete schedulerData; } diff --git a/packages/realm_dart/test/realm_logger_test.dart b/packages/realm_dart/test/realm_logger_test.dart index b35ef7095..764d6ca1e 100644 --- a/packages/realm_dart/test/realm_logger_test.dart +++ b/packages/realm_dart/test/realm_logger_test.dart @@ -1,8 +1,8 @@ // Copyright 2023 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 +import 'dart:async'; import 'dart:isolate'; -import 'dart:math'; import 'package:logging/logging.dart'; import 'package:realm_dart/src/logging.dart'; import 'package:test/test.dart' hide test, throws; @@ -51,7 +51,7 @@ void main() { test('Trace in subisolate seen in parent', () { Realm.logger.setLogLevel(RealmLogLevel.all); - expectLater(Realm.logger.onRecord, emits(isA().having((r) => r.message, '', 'Hey'))); + expectLater(Realm.logger.onRecord, emits(isA().having((r) => r.message, 'message', 'Hey'))); Isolate.run(() { Realm.logger.log(RealmLogLevel.trace, 'Hey'); }); @@ -62,7 +62,7 @@ void main() { final trace = Isolate.run(() async { return (await Realm.logger.onRecord.first).message; }); - await Future.delayed(const Duration(seconds: 1)); // yield + await Future.delayed(const Duration(milliseconds: 1)); // yield expectLater(trace, completion('Hey')); Realm.logger.log(RealmLogLevel.trace, 'Hey'); }); @@ -73,7 +73,7 @@ void main() { logger.level = Level.ALL; Realm.logger.setLogLevel(RealmLogLevel.error); - expectLater(logger.onRecord, emits(isA().having((r) => r.level, '==', Level.SEVERE).having((r) => r.message, '==', 'error'))); + expectLater(logger.onRecord, emits(isA().having((r) => r.level, 'level', Level.SEVERE).having((r) => r.message, 'message', 'error'))); Realm.logger.log(RealmLogLevel.error, 'error', category: RealmLogCategory.realm.sdk); }); @@ -84,8 +84,9 @@ void main() { Logger.root.level = Level.ALL; Realm.logger.setLogLevel(RealmLogLevel.error); - expectLater(Logger('Realm').onRecord, emits(isA().having((r) => r.level, '==', Level.SEVERE).having((r) => r.message, '==', 'error'))); - expectLater(Logger('Realm.SDK').onRecord, emits(isA().having((r) => r.level, '==', Level.SEVERE).having((r) => r.message, '==', 'error'))); + expectLater(Logger('Realm').onRecord, emits(isA().having((r) => r.level, 'level', Level.SEVERE).having((r) => r.message, 'message', 'error'))); + expectLater( + Logger('Realm.SDK').onRecord, emits(isA().having((r) => r.level, 'level', Level.SEVERE).having((r) => r.message, 'message', 'error'))); Realm.logger.log(RealmLogLevel.error, 'error', category: RealmLogCategory.realm.sdk); });