Skip to content

Commit

Permalink
Introduce static property Realm.logger
Browse files Browse the repository at this point in the history
* Remove logger and logLevel from AppConfiguration.
* New RealmLogLevel class define the realm specific levels.
  • Loading branch information
nielsenko committed May 23, 2022
1 parent 3d3bee5 commit 4d33723
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 111 deletions.
69 changes: 7 additions & 62 deletions lib/src/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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<int>? 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.
///
Expand All @@ -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.
Expand All @@ -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
Expand Down
32 changes: 29 additions & 3 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,7 @@ class _RealmCore {
static void _logCallback(Pointer<Void> userdata, int levelAsInt, Pointer<Int8> message) {
try {
final logger = userdata.toObject<Logger>(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)) {
Expand All @@ -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));
Expand Down Expand Up @@ -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;
}
}
62 changes: 62 additions & 0 deletions lib/src/realm_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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}
Expand All @@ -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;

Expand Down
56 changes: 10 additions & 46 deletions test/app_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ Future<void> main([List<String>? 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));
Expand All @@ -50,15 +49,13 @@ Future<void> main([List<String>? args]) async {
localAppName: 'bar',
localAppVersion: "1.0.0",
metadataPersistenceMode: MetadataPersistenceMode.disabled,
logLevel: LogLevel.info,
maxConnectionTimeout: const Duration(minutes: 1),
httpClient: httpClient,
);
expect(appConfig.appId, 'myapp1');
expect(appConfig.baseFilePath.path, Directory.systemTemp.path);
expect(appConfig.baseUrl, Uri.parse('https://not_re.al'));
expect(appConfig.defaultRequestTimeout, const Duration(seconds: 2));
expect(appConfig.logLevel, LogLevel.info);
expect(appConfig.metadataPersistenceMode, MetadataPersistenceMode.disabled);
expect(appConfig.maxConnectionTimeout, const Duration(minutes: 1));
expect(appConfig.httpClient, httpClient);
Expand All @@ -69,7 +66,6 @@ Future<void> main([List<String>? 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);
Expand All @@ -89,7 +85,6 @@ Future<void> main([List<String>? args]) async {
localAppVersion: "1.0.0",
metadataPersistenceMode: MetadataPersistenceMode.encrypted,
metadataEncryptionKey: base64.decode("ekey"),
logLevel: LogLevel.info,
maxConnectionTimeout: const Duration(minutes: 1),
httpClient: httpClient,
);
Expand All @@ -98,7 +93,6 @@ Future<void> main([List<String>? 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);
Expand Down Expand Up @@ -190,61 +184,31 @@ Future<void> main([List<String>? 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,
},
);
});
Expand Down

0 comments on commit 4d33723

Please sign in to comment.