From a4bf132468a2e6738fc8d6521e71ddd2717047a4 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Wed, 26 Jun 2024 12:33:17 +0200 Subject: [PATCH 01/43] Implement 'LogCategory' classes. --- Realm/Realm/Logging/LogCategory.cs | 132 +++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 Realm/Realm/Logging/LogCategory.cs diff --git a/Realm/Realm/Logging/LogCategory.cs b/Realm/Realm/Logging/LogCategory.cs new file mode 100644 index 0000000000..d60c532ede --- /dev/null +++ b/Realm/Realm/Logging/LogCategory.cs @@ -0,0 +1,132 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 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. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using Realms.Helpers; + +namespace Realms.Logging +{ + // TODO(lj): Remove 'abstract' and use this class instead of 'LeafLogCategory'? + public abstract class LogCategory + { + public string Name { get; } + + public static RealmLogCategory Realm { get; } = new(); + + private static readonly Dictionary _nameToCategory = new() + { + { Realm.Name, Realm }, + { Realm.Storage.Name, Realm.Storage }, + { Realm.Storage.Transaction.Name, Realm.Storage.Transaction }, + { Realm.Storage.Query.Name, Realm.Storage.Query }, + { Realm.Storage.Object.Name, Realm.Storage.Object }, + { Realm.Storage.Notification.Name, Realm.Storage.Notification }, + { Realm.Sync.Name, Realm.Sync }, + { Realm.Sync.Client.Name, Realm.Sync.Client }, + { Realm.Sync.Client.Session.Name, Realm.Sync.Client.Session }, + { Realm.Sync.Client.Changeset.Name, Realm.Sync.Client.Changeset }, + { Realm.Sync.Client.Network.Name, Realm.Sync.Client.Network }, + { Realm.Sync.Client.Reset.Name, Realm.Sync.Client.Reset }, + { Realm.Sync.Server.Name, Realm.Sync.Server }, + { Realm.App.Name, Realm.App }, + { Realm.SDK.Name, Realm.SDK }, + }; + + private LogCategory(string name) => Name = name; + + internal static LogCategory FromName(string name) + { + Argument.Ensure(_nameToCategory.TryGetValue(name, out var category), $"Unexpected category name: '{name}'", nameof(name)); + + return category; + } + + /// + /// Returns a string that represents the category, equivalent to its name. + /// + /// A string that represents the category, equivalent to its name. + public override string ToString() => Name; + + // TODO(lj): Passing entire category name path for now, can update later to + // pass the current-level name (e.g. "Storage") and the parent. + + public class RealmLogCategory : LogCategory + { + public StorageLogCategory Storage { get; } = new(); + + public SyncLogCategory Sync { get; } = new(); + + public LeafLogCategory App { get; } = new("Realm.App"); + + public LeafLogCategory SDK { get; } = new("Realm.SDK"); + + internal RealmLogCategory() : base("Realm") + { + } + } + + public class StorageLogCategory : LogCategory + { + public LeafLogCategory Transaction { get; } = new("Realm.Storage.Transaction"); + + public LeafLogCategory Query { get; } = new("Realm.Storage.Query"); + + public LeafLogCategory Object { get; } = new("Realm.Storage.Object"); + + public LeafLogCategory Notification { get; } = new("Realm.Storage.Notification"); + + internal StorageLogCategory() : base("Realm.Storage") + { + } + } + + public class SyncLogCategory : LogCategory + { + public ClientLogCategory Client { get; } = new(); + + public LeafLogCategory Server { get; } = new("Realm.Sync.Server"); + + internal SyncLogCategory() : base("Realm.Sync") + { + } + } + + public class ClientLogCategory : LogCategory + { + public LeafLogCategory Session { get; } = new("Realm.Sync.Client.Session"); + + public LeafLogCategory Changeset { get; } = new("Realm.Sync.Client.Changeset"); + + public LeafLogCategory Network { get; } = new("Realm.Sync.Client.Network"); + + public LeafLogCategory Reset { get; } = new("Realm.Sync.Client.Reset"); + + internal ClientLogCategory() : base("Realm.Sync.Client") + { + } + } + + public class LeafLogCategory : LogCategory + { + internal LeafLogCategory(string name) : base(name) + { + } + } + } +} From 0f6508188710742c6eee851fcbd8a0b2da701a96 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Wed, 26 Jun 2024 12:37:27 +0200 Subject: [PATCH 02/43] Update Core wrapper for setting log level. --- wrappers/src/shared_realm_cs.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index 8b1d6b9061..6357b41f87 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -48,7 +48,8 @@ using namespace realm::util; using OpenRealmCallbackT = void(void* task_completion_source, ThreadSafeReference* ref, NativeException::Marshallable ex); using RealmChangedT = void(void* managed_state_handle); using ReleaseGCHandleT = void(void* managed_handle); -using LogMessageT = void(realm_string_t message, util::Logger::Level level); +// TODO(lj): Update arg order to be the same across the SDK. +using LogMessageT = void(realm_string_t message, util::Logger::Level level, realm_string_t category_name); using MigrationCallbackT = void*(realm::SharedRealm* old_realm, realm::SharedRealm* new_realm, Schema* migration_schema, MarshaledVector, uint64_t schema_version, void* managed_migration_handle); using HandleTaskCompletionCallbackT = void(void* tcs_ptr, bool invoke_async, NativeException::Marshallable ex); using SharedSyncSession = std::shared_ptr; @@ -99,7 +100,7 @@ namespace binding { protected: void do_log(const LogCategory& category, Level level, const std::string& message) override final { - s_log_message(to_capi(message), level); + s_log_message(to_capi(message), level, to_capi(category.get_name())); } }; } @@ -295,8 +296,9 @@ REALM_EXPORT void shared_realm_install_callbacks( LogCategory::realm.set_default_level_threshold(Logger::Level::info); } -REALM_EXPORT void shared_realm_set_log_level(Logger::Level level) { - LogCategory::realm.set_default_level_threshold(level); +REALM_EXPORT void shared_realm_set_log_level(Logger::Level level, uint16_t* category_name_buf, size_t category_name_len) { + Utf16StringAccessor category_name(category_name_buf, category_name_len); + LogCategory::get_category(category_name).set_default_level_threshold(level); } REALM_EXPORT SharedRealm* shared_realm_open(Configuration configuration, NativeException::Marshallable& ex) From 7dfb38009e56d396de36a3ad78c5cd3ed77bbc48 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Wed, 26 Jun 2024 12:41:51 +0200 Subject: [PATCH 03/43] Update 'SharedRealmHandle' for logging and setting log level and category. --- Realm/Realm/Handles/SharedRealmHandle.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index 9b438f2247..a82588b259 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -63,8 +63,9 @@ private static class NativeMethods [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void DisposeGCHandleCallback(IntPtr handle); + // TODO(lj): Update arg order to be the same across the SDK. [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void LogMessageCallback(StringValue message, LogLevel level); + public delegate void LogMessageCallback(StringValue message, LogLevel level, StringValue categoryName); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void HandleTaskCompletionCallback(IntPtr tcs_ptr, [MarshalAs(UnmanagedType.U1)] bool invoke_async, NativeException ex); @@ -224,7 +225,7 @@ public static extern void rename_property(SharedRealmHandle sharedRealm, public static extern bool refresh_async(SharedRealmHandle realm, IntPtr tcs_handle, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_set_log_level", CallingConvention = CallingConvention.Cdecl)] - public static extern bool set_log_level(LogLevel level); + public static extern void set_log_level(LogLevel level, [MarshalAs(UnmanagedType.LPWStr)] string category_name, IntPtr category_name_len); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_operating_system", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_operating_system(IntPtr buffer, IntPtr buffer_length); @@ -271,7 +272,7 @@ public static unsafe void Initialize() notifyObject, notifyDictionary, onMigration, shouldCompact, handleTaskCompletion, onInitialization); } - public static void SetLogLevel(LogLevel level) => NativeMethods.set_log_level(level); + public static void SetLogLevel(LogLevel level, LogCategory category) => NativeMethods.set_log_level(level, category.Name, (IntPtr)category.Name.Length); [Preserve] protected SharedRealmHandle(IntPtr handle) : base(handle) @@ -822,9 +823,9 @@ private static void OnDisposeGCHandle(IntPtr handle) } [MonoPInvokeCallback(typeof(NativeMethods.LogMessageCallback))] - private static void LogMessage(StringValue message, LogLevel level) + private static void LogMessage(StringValue message, LogLevel level, StringValue categoryName) { - Logger.LogDefault(level, message!); + Logger.LogDefault(level, LogCategory.FromName(categoryName!), message!); } [MonoPInvokeCallback(typeof(NativeMethods.MigrationCallback))] From b33aba15798a2ae23a852468df5b499cd89089c4 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Wed, 26 Jun 2024 12:56:43 +0200 Subject: [PATCH 04/43] Update 'Logger' classes to account for category. --- Realm/Realm/Logging/Logger.cs | 85 +++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 24 deletions(-) diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index a255969a39..e409d8cbd8 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -35,8 +35,11 @@ public abstract class Logger { private readonly Lazy _gcHandle; + private static readonly LogCategory _defaultLogCategory = LogCategory.Realm; private static Logger? _defaultLogger; + // TODO(lj): Remove since it's not one level anymore. (Get it from Core) private static LogLevel _logLevel = LogLevel.Info; + private static LogCategory _logCategory = _defaultLogCategory; /// /// Gets a that outputs messages to the default console. For most project types, that will be @@ -64,6 +67,17 @@ public abstract class Logger /// A that doesn't output any messages. public static Logger Null { get; } = new NullLogger(); + /// + /// Gets a that proxies Log calls to the supplied function. The message will + /// already be formatted with the default message formatting that includes a timestamp. + /// + /// Function to proxy log calls to. + /// + /// A instance that will invoke for each message. + /// + public static Logger Function(Action logFunction) => new FunctionLogger((level, category, message) => logFunction(FormatLog(level, category, message))); + + // TODO(lj): Deprecate /// /// Gets a that proxies Log calls to the supplied function. /// @@ -71,17 +85,16 @@ public abstract class Logger /// /// A instance that will invoke for each message. /// - public static Logger Function(Action logFunction) => new FunctionLogger(logFunction); + public static Logger Function(Action logFunction) => new FunctionLogger((level, _, message) => logFunction(level, message)); /// - /// Gets a that proxies Log calls to the supplied function. The message will - /// already be formatted with the default message formatting that includes a timestamp. + /// Gets a that proxies Log calls to the supplied function. /// /// Function to proxy log calls to. /// /// A instance that will invoke for each message. /// - public static Logger Function(Action logFunction) => new FunctionLogger((level, message) => logFunction(FormatLog(level, message))); + public static Logger Function(Action logFunction) => new FunctionLogger(logFunction); /// /// Gets or sets the verbosity of log messages. @@ -90,13 +103,29 @@ public abstract class Logger public static LogLevel LogLevel { get => _logLevel; + // TODO(lj): Deprecate and refer to `SetLogLevel`. set { - _logLevel = value; - SharedRealmHandle.SetLogLevel(value); + SetLogLevel(value); } } + public static LogCategory LogCategory + { + get => _logCategory; + } + + public static void SetLogLevel(LogLevel level, LogCategory? category = null) + { + category ??= _defaultLogCategory; + SharedRealmHandle.SetLogLevel(level, category); + // TODO(lj): Remove setting `_logLevel` as we should get the level at the current category. + _logLevel = level; + _logCategory = category; + } + + // TODO(lj): Would it make sense to also deprecate the Default setter + // and provide e.g. `SetDefaultLogger()`? /// /// Gets or sets a custom implementation that will be used by /// Realm whenever information must be logged. @@ -120,12 +149,19 @@ protected Logger() internal static void LogDefault(LogLevel level, string message) => Default?.Log(level, message); + internal static void LogDefault(LogLevel level, LogCategory category, string message) => Default?.Log(level, category, message); + /// /// Log a message at the supplied level. /// /// The criticality level for the message. /// The message to log. public void Log(LogLevel level, string message) + { + Log(level, LogCategory, message); + } + + public void Log(LogLevel level, LogCategory category, string message) { if (level < LogLevel) { @@ -134,11 +170,11 @@ public void Log(LogLevel level, string message) try { - LogImpl(level, message); + LogImpl(level, category, message); } catch (Exception ex) { - Console.Log(LogLevel.Error, $"An exception occurred while trying to log the message: '{message}' at level: {level}. Error: {ex}"); + Console.Log(LogLevel.Error, $"An exception occurred while trying to log the message: '{message}' at level: {level} in category: '{category}'. Error: {ex}"); } } @@ -146,22 +182,23 @@ public void Log(LogLevel level, string message) /// The internal implementation being called from . /// /// The criticality level for the message. + /// TODO(lj). /// The message to log. - protected abstract void LogImpl(LogLevel level, string message); + protected abstract void LogImpl(LogLevel level, LogCategory category, string message); - internal static string FormatLog(LogLevel level, string message) => $"{DateTimeOffset.UtcNow:yyyy-MM-dd HH:mm:ss.fff} {level}: {message}"; + internal static string FormatLog(LogLevel level, LogCategory category, string message) => $"{DateTimeOffset.UtcNow:yyyy-MM-dd HH:mm:ss.fff} {category} {level}: {message}"; private class ConsoleLogger : Logger { - protected override void LogImpl(LogLevel level, string message) + protected override void LogImpl(LogLevel level, LogCategory category, string message) { - System.Console.WriteLine(FormatLog(level, message)); + System.Console.WriteLine(FormatLog(level, category, message)); } } private class FileLogger : Logger { - private readonly object locker = new(); + private readonly object _locker = new(); private readonly string _filePath; private readonly Encoding _encoding; @@ -171,30 +208,30 @@ public FileLogger(string filePath, Encoding? encoding = null) _encoding = encoding ?? Encoding.UTF8; } - protected override void LogImpl(LogLevel level, string message) + protected override void LogImpl(LogLevel level, LogCategory category, string message) { - lock (locker) + lock (_locker) { - System.IO.File.AppendAllText(_filePath, FormatLog(level, message) + Environment.NewLine, _encoding); + System.IO.File.AppendAllText(_filePath, FormatLog(level, category, message) + Environment.NewLine, _encoding); } } } private class FunctionLogger : Logger { - private readonly Action _logFunction; + private readonly Action _logFunction; - public FunctionLogger(Action logFunction) + public FunctionLogger(Action logFunction) { _logFunction = logFunction; } - protected override void LogImpl(LogLevel level, string message) => _logFunction(level, message); + protected override void LogImpl(LogLevel level, LogCategory category, string message) => _logFunction(level, category, message); } private class NullLogger : Logger { - protected override void LogImpl(LogLevel level, string message) + protected override void LogImpl(LogLevel level, LogCategory category, string message) { } } @@ -203,11 +240,11 @@ internal class InMemoryLogger : Logger { private readonly StringBuilder _builder = new(); - protected override void LogImpl(LogLevel level, string message) + protected override void LogImpl(LogLevel level, LogCategory category, string message) { lock (_builder) { - _builder.AppendLine(FormatLog(level, message)); + _builder.AppendLine(FormatLog(level, category, message)); } } @@ -255,11 +292,11 @@ public void Dispose() _flush.Dispose(); } - protected override void LogImpl(LogLevel level, string message) + protected override void LogImpl(LogLevel level, LogCategory category, string message) { if (!_isFlushing) { - _queue.Enqueue(FormatLog(level, message)); + _queue.Enqueue(FormatLog(level, category, message)); _hasNewItems.Set(); } } From a2fbca79c0e51180567540607536098306970595 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:51:39 +0200 Subject: [PATCH 05/43] Replace static '_logLevel' member with call into Core. --- Realm/Realm/Handles/SharedRealmHandle.cs | 5 +++++ Realm/Realm/Logging/Logger.cs | 20 ++++++++++++++------ wrappers/src/shared_realm_cs.cpp | 10 ++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index a82588b259..14af2ec71b 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -224,6 +224,9 @@ public static extern void rename_property(SharedRealmHandle sharedRealm, [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_refresh_async", CallingConvention = CallingConvention.Cdecl)] public static extern bool refresh_async(SharedRealmHandle realm, IntPtr tcs_handle, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_log_level", CallingConvention = CallingConvention.Cdecl)] + public static extern LogLevel get_log_level([MarshalAs(UnmanagedType.LPWStr)] string category_name, IntPtr category_name_len); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_set_log_level", CallingConvention = CallingConvention.Cdecl)] public static extern void set_log_level(LogLevel level, [MarshalAs(UnmanagedType.LPWStr)] string category_name, IntPtr category_name_len); @@ -272,6 +275,8 @@ public static unsafe void Initialize() notifyObject, notifyDictionary, onMigration, shouldCompact, handleTaskCompletion, onInitialization); } + public static LogLevel GetLogLevel(LogCategory category) => NativeMethods.get_log_level(category.Name, (IntPtr)category.Name.Length); + public static void SetLogLevel(LogLevel level, LogCategory category) => NativeMethods.set_log_level(level, category.Name, (IntPtr)category.Name.Length); [Preserve] diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index e409d8cbd8..8a0078594a 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -35,11 +35,11 @@ public abstract class Logger { private readonly Lazy _gcHandle; + // TODO(lj): Use this default log level? + private static readonly LogLevel _defaultLogLevel = LogLevel.Info; private static readonly LogCategory _defaultLogCategory = LogCategory.Realm; - private static Logger? _defaultLogger; - // TODO(lj): Remove since it's not one level anymore. (Get it from Core) - private static LogLevel _logLevel = LogLevel.Info; private static LogCategory _logCategory = _defaultLogCategory; + private static Logger? _defaultLogger; /// /// Gets a that outputs messages to the default console. For most project types, that will be @@ -102,7 +102,8 @@ public abstract class Logger /// The log level for Realm-originating messages. public static LogLevel LogLevel { - get => _logLevel; + // TODO(lj): Do we want to deprecate the getter as well? + get => GetLogLevel(); // TODO(lj): Deprecate and refer to `SetLogLevel`. set { @@ -115,12 +116,19 @@ public static LogCategory LogCategory get => _logCategory; } + public static LogLevel GetLogLevel(LogCategory? category = null) + { + // TODO(lj): Perhaps we should grab the current category (`_logCategory`) + // instead of the default here? If there hasn't been a category + // explicitly set, it will still be the default. + category ??= _defaultLogCategory; + return SharedRealmHandle.GetLogLevel(category); + } + public static void SetLogLevel(LogLevel level, LogCategory? category = null) { category ??= _defaultLogCategory; SharedRealmHandle.SetLogLevel(level, category); - // TODO(lj): Remove setting `_logLevel` as we should get the level at the current category. - _logLevel = level; _logCategory = category; } diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index 6357b41f87..45f857fec9 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -296,6 +296,16 @@ REALM_EXPORT void shared_realm_install_callbacks( LogCategory::realm.set_default_level_threshold(Logger::Level::info); } +REALM_EXPORT Logger::Level shared_realm_get_log_level(uint16_t* category_name_buf, size_t category_name_len) { + Utf16StringAccessor category_name(category_name_buf, category_name_len); + // TODO(lj): Usage in Core: + auto& category = LogCategory::get_category(category_name); + return Logger::get_default_logger()->get_level_threshold(category); + + // TODO(lj): But this seems to work as well: + // return LogCategory::get_category(category_name).get_default_level_threshold(); +} + REALM_EXPORT void shared_realm_set_log_level(Logger::Level level, uint16_t* category_name_buf, size_t category_name_len) { Utf16StringAccessor category_name(category_name_buf, category_name_len); LogCategory::get_category(category_name).set_default_level_threshold(level); From c0d9aa47088c17ad4f371588a7298dedd55e2736 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Wed, 26 Jun 2024 16:22:57 +0200 Subject: [PATCH 06/43] Update signature in 'UnityLogger'. --- Realm/Realm.UnityUtils/UnityLogger.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Realm/Realm.UnityUtils/UnityLogger.cs b/Realm/Realm.UnityUtils/UnityLogger.cs index f580f35a01..57972d0638 100644 --- a/Realm/Realm.UnityUtils/UnityLogger.cs +++ b/Realm/Realm.UnityUtils/UnityLogger.cs @@ -22,9 +22,9 @@ namespace UnityUtils { public class UnityLogger : Logger { - protected override void LogImpl(LogLevel level, string message) + protected override void LogImpl(LogLevel level, LogCategory category, string message) { - var toLog = FormatLog(level, message); + var toLog = FormatLog(level, category, message); switch (level) { case LogLevel.Fatal: From 596e7f585042d942f02e03c637132b661e2997a8 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Fri, 28 Jun 2024 06:56:52 +0200 Subject: [PATCH 07/43] Replace use of 'LeafLogCategory' with the base. --- Realm/Realm/Logging/LogCategory.cs | 32 +++++++++++------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/Realm/Realm/Logging/LogCategory.cs b/Realm/Realm/Logging/LogCategory.cs index d60c532ede..cfa9a2d679 100644 --- a/Realm/Realm/Logging/LogCategory.cs +++ b/Realm/Realm/Logging/LogCategory.cs @@ -22,8 +22,7 @@ namespace Realms.Logging { - // TODO(lj): Remove 'abstract' and use this class instead of 'LeafLogCategory'? - public abstract class LogCategory + public class LogCategory { public string Name { get; } @@ -72,9 +71,9 @@ public class RealmLogCategory : LogCategory public SyncLogCategory Sync { get; } = new(); - public LeafLogCategory App { get; } = new("Realm.App"); + public LogCategory App { get; } = new("Realm.App"); - public LeafLogCategory SDK { get; } = new("Realm.SDK"); + public LogCategory SDK { get; } = new("Realm.SDK"); internal RealmLogCategory() : base("Realm") { @@ -83,13 +82,13 @@ internal RealmLogCategory() : base("Realm") public class StorageLogCategory : LogCategory { - public LeafLogCategory Transaction { get; } = new("Realm.Storage.Transaction"); + public LogCategory Transaction { get; } = new("Realm.Storage.Transaction"); - public LeafLogCategory Query { get; } = new("Realm.Storage.Query"); + public LogCategory Query { get; } = new("Realm.Storage.Query"); - public LeafLogCategory Object { get; } = new("Realm.Storage.Object"); + public LogCategory Object { get; } = new("Realm.Storage.Object"); - public LeafLogCategory Notification { get; } = new("Realm.Storage.Notification"); + public LogCategory Notification { get; } = new("Realm.Storage.Notification"); internal StorageLogCategory() : base("Realm.Storage") { @@ -100,7 +99,7 @@ public class SyncLogCategory : LogCategory { public ClientLogCategory Client { get; } = new(); - public LeafLogCategory Server { get; } = new("Realm.Sync.Server"); + public LogCategory Server { get; } = new("Realm.Sync.Server"); internal SyncLogCategory() : base("Realm.Sync") { @@ -109,24 +108,17 @@ internal SyncLogCategory() : base("Realm.Sync") public class ClientLogCategory : LogCategory { - public LeafLogCategory Session { get; } = new("Realm.Sync.Client.Session"); + public LogCategory Session { get; } = new("Realm.Sync.Client.Session"); - public LeafLogCategory Changeset { get; } = new("Realm.Sync.Client.Changeset"); + public LogCategory Changeset { get; } = new("Realm.Sync.Client.Changeset"); - public LeafLogCategory Network { get; } = new("Realm.Sync.Client.Network"); + public LogCategory Network { get; } = new("Realm.Sync.Client.Network"); - public LeafLogCategory Reset { get; } = new("Realm.Sync.Client.Reset"); + public LogCategory Reset { get; } = new("Realm.Sync.Client.Reset"); internal ClientLogCategory() : base("Realm.Sync.Client") { } } - - public class LeafLogCategory : LogCategory - { - internal LeafLogCategory(string name) : base(name) - { - } - } } } From f4c9574c7ce67b5eab059a50b3bbf5dda8b1d870 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Fri, 28 Jun 2024 07:28:17 +0200 Subject: [PATCH 08/43] Remove 'LogCategory' getter. --- Realm/Realm/Logging/Logger.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index 8a0078594a..3acc251f16 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -35,10 +35,7 @@ public abstract class Logger { private readonly Lazy _gcHandle; - // TODO(lj): Use this default log level? - private static readonly LogLevel _defaultLogLevel = LogLevel.Info; private static readonly LogCategory _defaultLogCategory = LogCategory.Realm; - private static LogCategory _logCategory = _defaultLogCategory; private static Logger? _defaultLogger; /// @@ -111,16 +108,8 @@ public static LogLevel LogLevel } } - public static LogCategory LogCategory - { - get => _logCategory; - } - public static LogLevel GetLogLevel(LogCategory? category = null) { - // TODO(lj): Perhaps we should grab the current category (`_logCategory`) - // instead of the default here? If there hasn't been a category - // explicitly set, it will still be the default. category ??= _defaultLogCategory; return SharedRealmHandle.GetLogLevel(category); } @@ -129,7 +118,6 @@ public static void SetLogLevel(LogLevel level, LogCategory? category = null) { category ??= _defaultLogCategory; SharedRealmHandle.SetLogLevel(level, category); - _logCategory = category; } // TODO(lj): Would it make sense to also deprecate the Default setter @@ -166,7 +154,8 @@ protected Logger() /// The message to log. public void Log(LogLevel level, string message) { - Log(level, LogCategory, message); + // TODO(lj): See if `LogCategory.Realm.SDK` should be preferred. + Log(level, _defaultLogCategory, message); } public void Log(LogLevel level, LogCategory category, string message) From 5f8cbf4d0f912e58503b690935f7ef9aaa5acb83 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Fri, 28 Jun 2024 17:22:10 +0200 Subject: [PATCH 09/43] Update todo comments. --- Realm/Realm/Logging/Logger.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index 3acc251f16..a8c87c25b0 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -93,15 +93,14 @@ public abstract class Logger /// public static Logger Function(Action logFunction) => new FunctionLogger(logFunction); + // TODO(lj): Deprecate /// /// Gets or sets the verbosity of log messages. /// /// The log level for Realm-originating messages. public static LogLevel LogLevel { - // TODO(lj): Do we want to deprecate the getter as well? get => GetLogLevel(); - // TODO(lj): Deprecate and refer to `SetLogLevel`. set { SetLogLevel(value); @@ -120,8 +119,6 @@ public static void SetLogLevel(LogLevel level, LogCategory? category = null) SharedRealmHandle.SetLogLevel(level, category); } - // TODO(lj): Would it make sense to also deprecate the Default setter - // and provide e.g. `SetDefaultLogger()`? /// /// Gets or sets a custom implementation that will be used by /// Realm whenever information must be logged. @@ -147,6 +144,7 @@ protected Logger() internal static void LogDefault(LogLevel level, LogCategory category, string message) => Default?.Log(level, category, message); + // TODO(lj): Deprecate /// /// Log a message at the supplied level. /// @@ -158,9 +156,10 @@ public void Log(LogLevel level, string message) Log(level, _defaultLogCategory, message); } + // TODO(lj): Use category as optional 3rd param. public void Log(LogLevel level, LogCategory category, string message) { - if (level < LogLevel) + if (level < GetLogLevel(category)) { return; } From 7e909c2215e28ea172c806fc104f3dd9b8d4754d Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Fri, 28 Jun 2024 17:39:31 +0200 Subject: [PATCH 10/43] Add first tests. --- Realm/Realm/Logging/LogCategory.cs | 4 +- Tests/Realm.Tests/Database/LoggerTests.cs | 83 ++++++++++++++++++----- 2 files changed, 67 insertions(+), 20 deletions(-) diff --git a/Realm/Realm/Logging/LogCategory.cs b/Realm/Realm/Logging/LogCategory.cs index cfa9a2d679..9e5aedb89f 100644 --- a/Realm/Realm/Logging/LogCategory.cs +++ b/Realm/Realm/Logging/LogCategory.cs @@ -28,7 +28,7 @@ public class LogCategory public static RealmLogCategory Realm { get; } = new(); - private static readonly Dictionary _nameToCategory = new() + internal static readonly Dictionary NameToCategory = new() { { Realm.Name, Realm }, { Realm.Storage.Name, Realm.Storage }, @@ -51,7 +51,7 @@ public class LogCategory internal static LogCategory FromName(string name) { - Argument.Ensure(_nameToCategory.TryGetValue(name, out var category), $"Unexpected category name: '{name}'", nameof(name)); + Argument.Ensure(NameToCategory.TryGetValue(name, out var category), $"Unexpected category name: '{name}'", nameof(name)); return category; } diff --git a/Tests/Realm.Tests/Database/LoggerTests.cs b/Tests/Realm.Tests/Database/LoggerTests.cs index 3c122dc907..1cd473c6ca 100644 --- a/Tests/Realm.Tests/Database/LoggerTests.cs +++ b/Tests/Realm.Tests/Database/LoggerTests.cs @@ -27,21 +27,29 @@ namespace Realms.Tests.Database [TestFixture, Preserve(AllMembers = true)] public class LoggerTests { + private readonly LogCategory _originalLogCategory = LogCategory.Realm; + private readonly LogLevel _originalLogLevel = Logger.GetLogLevel(LogCategory.Realm); private Logger _originalLogger = null!; - private LogLevel _originalLogLevel; [SetUp] public void Setup() { _originalLogger = Logger.Default; - _originalLogLevel = Logger.LogLevel; } [TearDown] public void TearDown() { Logger.Default = _originalLogger; - Logger.LogLevel = _originalLogLevel; + Logger.SetLogLevel(_originalLogLevel, _originalLogCategory); + } + + private void AssertLogMessageContains(string actual, LogLevel level, LogCategory category, string message) + { + Assert.That(actual, Does.Contain(level.ToString())); + Assert.That(actual, Does.Contain(category.Name)); + Assert.That(actual, Does.Contain(message)); + Assert.That(actual, Does.Contain(DateTimeOffset.UtcNow.ToString("yyyy-MM-dd"))); } [Test] @@ -50,12 +58,10 @@ public void Logger_CanSetDefaultLogger() var messages = new List(); Logger.Default = Logger.Function(message => messages.Add(message)); - Logger.LogDefault(LogLevel.Warn, "This is very dangerous!"); + Logger.LogDefault(LogLevel.Warn, LogCategory.Realm.SDK, "This is very dangerous!"); Assert.That(messages.Count, Is.EqualTo(1)); - Assert.That(messages[0], Does.Contain(LogLevel.Warn.ToString())); - Assert.That(messages[0], Does.Contain(DateTimeOffset.UtcNow.ToString("yyyy-MM-dd"))); - Assert.That(messages[0], Does.Contain("This is very dangerous!")); + AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "This is very dangerous!"); } [Test] @@ -69,28 +75,69 @@ public void Logger_SkipsDebugMessagesByDefault() Assert.That(messages.Count, Is.EqualTo(0)); } + [Test] + public void Logger_SetsLogLevelAtGivenCategory() + { + var categories = LogCategory.NameToCategory.Values; + foreach (var category in categories) + { + Logger.SetLogLevel(LogLevel.All, category); + Assert.That(Logger.GetLogLevel(category), Is.EqualTo(LogLevel.All)); + } + } + [TestCase(LogLevel.Error)] [TestCase(LogLevel.Info)] [TestCase(LogLevel.Debug)] public void Logger_WhenLevelIsSet_LogsOnlyExpectedLevels(LogLevel level) { - var messages = new List(); - Logger.Default = Logger.Function(message => messages.Add(message)); - Logger.LogLevel = level; + var categories = LogCategory.NameToCategory.Values; + foreach (var category in categories) + { + var messages = new List(); + Logger.Default = Logger.Function(message => messages.Add(message)); + Logger.SetLogLevel(level, category); + + Logger.LogDefault(level - 1, category, "This is at level - 1"); + Logger.LogDefault(level, category, "This is at the same level"); + Logger.LogDefault(level + 1, category, "This is at level + 1"); + + Assert.That(messages.Count, Is.EqualTo(2)); + AssertLogMessageContains(messages[0], level, category, "This is at the same level"); + AssertLogMessageContains(messages[1], level + 1, category, "This is at level + 1"); + } + } - Logger.LogDefault(level - 1, "This is at level - 1"); - Logger.LogDefault(level, "This is at the same level"); - Logger.LogDefault(level + 1, "This is at level + 1"); + [Test] + public void Logger_LogsAtGivenCategory() + { + var categories = LogCategory.NameToCategory.Values; + foreach (var category in categories) + { + var messages = new List(); + Logger.Default = Logger.Function((message) => messages.Add(message)); + + Logger.LogDefault(LogLevel.Warn, category, "A log message"); + + Assert.That(messages.Count, Is.EqualTo(1)); + AssertLogMessageContains(messages[0], LogLevel.Warn, category, "A log message"); + } + } - Assert.That(messages.Count, Is.EqualTo(2)); + [Test] + public void Logger_CallsCustomFunction() + { + var messages = new List(); + Logger.Default = Logger.Function((level, category, message) => messages.Add(Logger.FormatLog(level, category, message))); - Assert.That(messages[0], Does.Contain(level.ToString())); - Assert.That(messages[0], Does.Contain("This is at the same level")); + Logger.LogDefault(LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); - Assert.That(messages[1], Does.Contain((level + 1).ToString())); - Assert.That(messages[1], Does.Contain("This is at level + 1")); + Assert.That(messages.Count, Is.EqualTo(1)); + AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); } + // TODO(lj): Test that all Core categories are being used and matches names. + [Test] public void FileLogger() { From 84535af891c6f3e0922b2e84cd0ec91fc9dfc5e4 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Fri, 28 Jun 2024 17:45:40 +0200 Subject: [PATCH 11/43] Log with SDK category if not provided. --- Realm/Realm/Logging/Logger.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index a8c87c25b0..aaf3f042a8 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -152,8 +152,7 @@ protected Logger() /// The message to log. public void Log(LogLevel level, string message) { - // TODO(lj): See if `LogCategory.Realm.SDK` should be preferred. - Log(level, _defaultLogCategory, message); + Log(level, LogCategory.Realm.SDK, message); } // TODO(lj): Use category as optional 3rd param. From e728ff2b6dc7fdb5bf1ac7936cd7e2cc334ea714 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:55:25 +0200 Subject: [PATCH 12/43] Pass parent category to create name. --- Realm/Realm/Logging/LogCategory.cs | 56 ++++++++++++++++++------------ 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/Realm/Realm/Logging/LogCategory.cs b/Realm/Realm/Logging/LogCategory.cs index 9e5aedb89f..535abea2a5 100644 --- a/Realm/Realm/Logging/LogCategory.cs +++ b/Realm/Realm/Logging/LogCategory.cs @@ -47,7 +47,7 @@ public class LogCategory { Realm.SDK.Name, Realm.SDK }, }; - private LogCategory(string name) => Name = name; + private LogCategory(string name, LogCategory? parent) => Name = parent == null ? name : $"{parent}.{name}"; internal static LogCategory FromName(string name) { @@ -62,62 +62,74 @@ internal static LogCategory FromName(string name) /// A string that represents the category, equivalent to its name. public override string ToString() => Name; - // TODO(lj): Passing entire category name path for now, can update later to - // pass the current-level name (e.g. "Storage") and the parent. - public class RealmLogCategory : LogCategory { - public StorageLogCategory Storage { get; } = new(); + public StorageLogCategory Storage { get; } - public SyncLogCategory Sync { get; } = new(); + public SyncLogCategory Sync { get; } - public LogCategory App { get; } = new("Realm.App"); + public LogCategory App { get; } - public LogCategory SDK { get; } = new("Realm.SDK"); + // TODO(lj): Prefer `SDK` or `Sdk` for c#? + public LogCategory SDK { get; } - internal RealmLogCategory() : base("Realm") + internal RealmLogCategory() : base("Realm", null) { + Storage = new StorageLogCategory(this); + Sync = new SyncLogCategory(this); + App = new LogCategory("App", this); + SDK = new LogCategory("SDK", this); } } public class StorageLogCategory : LogCategory { - public LogCategory Transaction { get; } = new("Realm.Storage.Transaction"); + public LogCategory Transaction { get; } - public LogCategory Query { get; } = new("Realm.Storage.Query"); + public LogCategory Query { get; } - public LogCategory Object { get; } = new("Realm.Storage.Object"); + public LogCategory Object { get; } - public LogCategory Notification { get; } = new("Realm.Storage.Notification"); + public LogCategory Notification { get; } - internal StorageLogCategory() : base("Realm.Storage") + internal StorageLogCategory(LogCategory parent) : base("Storage", parent) { + Transaction = new LogCategory("Transaction", this); + Query = new LogCategory("Query", this); + Object = new LogCategory("Object", this); + Notification = new LogCategory("Notification", this); } } public class SyncLogCategory : LogCategory { - public ClientLogCategory Client { get; } = new(); + public ClientLogCategory Client { get; } - public LogCategory Server { get; } = new("Realm.Sync.Server"); + public LogCategory Server { get; } - internal SyncLogCategory() : base("Realm.Sync") + internal SyncLogCategory(LogCategory parent) : base("Sync", parent) { + Client = new ClientLogCategory(this); + Server = new LogCategory("Server", this); } } public class ClientLogCategory : LogCategory { - public LogCategory Session { get; } = new("Realm.Sync.Client.Session"); + public LogCategory Session { get; } - public LogCategory Changeset { get; } = new("Realm.Sync.Client.Changeset"); + public LogCategory Changeset { get; } - public LogCategory Network { get; } = new("Realm.Sync.Client.Network"); + public LogCategory Network { get; } - public LogCategory Reset { get; } = new("Realm.Sync.Client.Reset"); + public LogCategory Reset { get; } - internal ClientLogCategory() : base("Realm.Sync.Client") + internal ClientLogCategory(LogCategory parent) : base("Client", parent) { + Session = new LogCategory("Session", this); + Changeset = new LogCategory("Changeset", this); + Network = new LogCategory("Network", this); + Reset = new LogCategory("Reset", this); } } } From dc7af0a8a1008227548f726544d6b52ad59cbc86 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:43:26 +0200 Subject: [PATCH 13/43] Marshal log category names from Core. --- Realm/Realm/Handles/SharedRealmHandle.cs | 9 +++++++++ wrappers/realm-core | 2 +- wrappers/src/shared_realm_cs.cpp | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index 14af2ec71b..cb9a293429 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -230,6 +231,9 @@ public static extern void rename_property(SharedRealmHandle sharedRealm, [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_set_log_level", CallingConvention = CallingConvention.Cdecl)] public static extern void set_log_level(LogLevel level, [MarshalAs(UnmanagedType.LPWStr)] string category_name, IntPtr category_name_len); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_log_category_names", CallingConvention = CallingConvention.Cdecl)] + public static extern MarshaledVector get_log_category_names(); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_operating_system", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_operating_system(IntPtr buffer, IntPtr buffer_length); @@ -279,6 +283,11 @@ public static unsafe void Initialize() public static void SetLogLevel(LogLevel level, LogCategory category) => NativeMethods.set_log_level(level, category.Name, (IntPtr)category.Name.Length); + public static string[] GetLogCategoryNames() => NativeMethods.get_log_category_names() + .ToEnumerable() + .Select(name => name.ToDotnetString()!) + .ToArray(); + [Preserve] protected SharedRealmHandle(IntPtr handle) : base(handle) { diff --git a/wrappers/realm-core b/wrappers/realm-core index f66e24d035..61060ee931 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit f66e24d035c987e35183cb8f0bcb864a8df8697c +Subproject commit 61060ee93136545a9b36b71451854127567bc4be diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index 45f857fec9..c74c063a7c 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -311,6 +311,20 @@ REALM_EXPORT void shared_realm_set_log_level(Logger::Level level, uint16_t* cate LogCategory::get_category(category_name).set_default_level_threshold(level); } +REALM_EXPORT MarshaledVector shared_realm_get_log_category_names() { + const auto names = LogCategory::get_category_names(); + // Use heap allocation in order to keep the vector alive beyond this call. + // This will cause a memory leak 😢; however, this is only used for the tests. + const auto result = new std::vector(); + result->reserve(names.size()); + + for (const auto name : names) { + result->push_back(to_capi(name)); + } + + return *result; +} + REALM_EXPORT SharedRealm* shared_realm_open(Configuration configuration, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { From dedf349e4027e0463afeb9e11d2ffa5d78b36718 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:50:26 +0200 Subject: [PATCH 14/43] Test matching Core category names. --- Tests/Realm.Tests/Database/LoggerTests.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Tests/Realm.Tests/Database/LoggerTests.cs b/Tests/Realm.Tests/Database/LoggerTests.cs index 1cd473c6ca..10cc65f2fa 100644 --- a/Tests/Realm.Tests/Database/LoggerTests.cs +++ b/Tests/Realm.Tests/Database/LoggerTests.cs @@ -136,7 +136,20 @@ public void Logger_CallsCustomFunction() AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); } - // TODO(lj): Test that all Core categories are being used and matches names. + [Test] + public void Logger_MatchesCoreCategoryNames() + { + var coreCategoryNames = SharedRealmHandle.GetLogCategoryNames(); + var sdkCategoriesMap = LogCategory.NameToCategory; + + Assert.That(sdkCategoriesMap.Count, Is.EqualTo(coreCategoryNames.Length)); + foreach (var name in coreCategoryNames) + { + Assert.That(sdkCategoriesMap.TryGetValue(name!, out var category), Is.True); + Assert.That(category!.Name, Is.EqualTo(name)); + Assert.That(LogCategory.FromName(name!), Is.SameAs(category)); + } + } [Test] public void FileLogger() From cc68acb1aef4655a7cd74c8a62b0e41cceeacece Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:58:20 +0200 Subject: [PATCH 15/43] Add API docs. --- Realm/Realm/Logging/LogCategory.cs | 70 +++++++++++++++++++++++++++++- Realm/Realm/Logging/Logger.cs | 24 +++++++++- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/Realm/Realm/Logging/LogCategory.cs b/Realm/Realm/Logging/LogCategory.cs index 535abea2a5..14015765d7 100644 --- a/Realm/Realm/Logging/LogCategory.cs +++ b/Realm/Realm/Logging/LogCategory.cs @@ -16,16 +16,27 @@ // //////////////////////////////////////////////////////////////////////////// -using System; using System.Collections.Generic; using Realms.Helpers; namespace Realms.Logging { + /// + /// Specifies the category to receive log messages for when logged by the default + /// logger. The will always be set for a specific category. + /// Setting the log level for one category will automatically set the same level + /// for all of its subcategories. + /// public class LogCategory { + /// + /// Gets the name of the category. + /// public string Name { get; } + /// + /// Gets the top-level category for receiving log messages for all categories. + /// public static RealmLogCategory Realm { get; } = new(); internal static readonly Dictionary NameToCategory = new() @@ -62,15 +73,30 @@ internal static LogCategory FromName(string name) /// A string that represents the category, equivalent to its name. public override string ToString() => Name; + /// + /// The top-level category for receiving log messages for all categories. + /// public class RealmLogCategory : LogCategory { + /// + /// Gets the category for receiving log messages pertaining to database events. + /// public StorageLogCategory Storage { get; } + /// + /// Gets the category for receiving log messages pertaining to Atlas Device Sync. + /// public SyncLogCategory Sync { get; } + /// + /// Gets the category for receiving log messages pertaining to Atlas App. + /// public LogCategory App { get; } // TODO(lj): Prefer `SDK` or `Sdk` for c#? + /// + /// Gets the category for receiving log messages pertaining to the SDK. + /// public LogCategory SDK { get; } internal RealmLogCategory() : base("Realm", null) @@ -82,14 +108,31 @@ internal RealmLogCategory() : base("Realm", null) } } + /// + /// The category for receiving log messages pertaining to database events. + /// public class StorageLogCategory : LogCategory { + /// + /// Gets the category for receiving log messages when creating, advancing, and + /// committing transactions. + /// public LogCategory Transaction { get; } + /// + /// Gets the category for receiving log messages when querying the database. + /// public LogCategory Query { get; } + /// + /// Gets the category for receiving log messages when mutating the database. + /// public LogCategory Object { get; } + /// + /// Gets the category for receiving log messages when there are notifications + /// of changes to the database. + /// public LogCategory Notification { get; } internal StorageLogCategory(LogCategory parent) : base("Storage", parent) @@ -101,10 +144,19 @@ internal StorageLogCategory(LogCategory parent) : base("Storage", parent) } } + /// + /// The category for receiving log messages pertaining to Atlas Device Sync. + /// public class SyncLogCategory : LogCategory { + /// + /// Gets the category for receiving log messages pertaining to sync client operations. + /// public ClientLogCategory Client { get; } + /// + /// Gets the category for receiving log messages pertaining to sync server operations. + /// public LogCategory Server { get; } internal SyncLogCategory(LogCategory parent) : base("Sync", parent) @@ -114,14 +166,30 @@ internal SyncLogCategory(LogCategory parent) : base("Sync", parent) } } + /// + /// The category for receiving log messages pertaining to sync client operations. + /// public class ClientLogCategory : LogCategory { + /// + /// Gets the category for receiving log messages pertaining to the sync session. + /// public LogCategory Session { get; } + /// + /// Gets the category for receiving log messages when receiving, uploading, and + /// integrating changesets. + /// public LogCategory Changeset { get; } + /// + /// Gets the category for receiving log messages pertaining to low-level network activity. + /// public LogCategory Network { get; } + /// + /// Gets the category for receiving log messages when there are client reset operations. + /// public LogCategory Reset { get; } internal ClientLogCategory(LogCategory parent) : base("Client", parent) diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index aaf3f042a8..36372721b0 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -95,7 +95,7 @@ public abstract class Logger // TODO(lj): Deprecate /// - /// Gets or sets the verbosity of log messages. + /// Gets or sets the verbosity of log messages for all log categories via . /// /// The log level for Realm-originating messages. public static LogLevel LogLevel @@ -107,12 +107,24 @@ public static LogLevel LogLevel } } + /// + /// Gets the verbosity of log messages for the given category. + /// + /// The category to get the level for. Defaults to if not specified. + /// + /// The log level used. + /// public static LogLevel GetLogLevel(LogCategory? category = null) { category ??= _defaultLogCategory; return SharedRealmHandle.GetLogLevel(category); } + /// + /// Sets the verbosity of log messages for the given category. + /// + /// The log level to use for messages. + /// The category to set the level for. Defaults to if not specified. public static void SetLogLevel(LogLevel level, LogCategory? category = null) { category ??= _defaultLogCategory; @@ -156,6 +168,12 @@ public void Log(LogLevel level, string message) } // TODO(lj): Use category as optional 3rd param. + /// + /// Log a message at the supplied level and category. + /// + /// The criticality level for the message. + /// The category for the message. + /// The message to log. public void Log(LogLevel level, LogCategory category, string message) { if (level < GetLogLevel(category)) @@ -173,11 +191,13 @@ public void Log(LogLevel level, LogCategory category, string message) } } + // TODO(lj): Set category as optional 3rd arg as we'll do for `Log`, + // so that we don't break the API. /// /// The internal implementation being called from . /// /// The criticality level for the message. - /// TODO(lj). + /// The category for the message. /// The message to log. protected abstract void LogImpl(LogLevel level, LogCategory category, string message); From 69bd80923c150aa1079e6e80c7c6cf5701f16497 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:13:45 +0200 Subject: [PATCH 16/43] Add more tests. --- Tests/Realm.Tests/Database/LoggerTests.cs | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Tests/Realm.Tests/Database/LoggerTests.cs b/Tests/Realm.Tests/Database/LoggerTests.cs index 10cc65f2fa..1c091760ac 100644 --- a/Tests/Realm.Tests/Database/LoggerTests.cs +++ b/Tests/Realm.Tests/Database/LoggerTests.cs @@ -86,6 +86,39 @@ public void Logger_SetsLogLevelAtGivenCategory() } } + [Test] + public void Logger_SetsLogLevelAtSubcategories() + { + var storageCategories = new[] + { + LogCategory.Realm.Storage.Transaction, + LogCategory.Realm.Storage.Query, + LogCategory.Realm.Storage.Object, + LogCategory.Realm.Storage.Notification + }; + foreach (var category in storageCategories) + { + Assert.That(Logger.GetLogLevel(category), Is.Not.EqualTo(LogLevel.Error)); + } + + Logger.SetLogLevel(LogLevel.Error, LogCategory.Realm.Storage); + foreach (var category in storageCategories) + { + Assert.That(Logger.GetLogLevel(category), Is.EqualTo(LogLevel.Error)); + } + } + + [Test] + public void Logger_WhenUsingLogLevelSetter_OverwritesCategory() + { + var category = LogCategory.Realm.Storage; + Logger.SetLogLevel(LogLevel.Error, category); + Assert.That(Logger.GetLogLevel(category), Is.EqualTo(LogLevel.Error)); + + Logger.LogLevel = LogLevel.All; + Assert.That(Logger.GetLogLevel(category), Is.EqualTo(LogLevel.All)); + } + [TestCase(LogLevel.Error)] [TestCase(LogLevel.Info)] [TestCase(LogLevel.Debug)] @@ -151,6 +184,16 @@ public void Logger_MatchesCoreCategoryNames() } } + [Test] + public void Logger_WhenNonExistentCategoryName_FromNameThrows() + { + var nonExistentNames = new[] { "realm", "Realm.app", string.Empty }; + foreach (var name in nonExistentNames) + { + Assert.That(() => LogCategory.FromName(name), Throws.TypeOf().And.Message.Contains($"Unexpected category name: '{name}'")); + } + } + [Test] public void FileLogger() { From 2919b03aa90acdae9ef9f84759080c2a2b39abcf Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:36:36 +0200 Subject: [PATCH 17/43] Show category hierarchy in API docs. --- Realm/Realm/Logging/LogCategory.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Realm/Realm/Logging/LogCategory.cs b/Realm/Realm/Logging/LogCategory.cs index 14015765d7..141c0ca811 100644 --- a/Realm/Realm/Logging/LogCategory.cs +++ b/Realm/Realm/Logging/LogCategory.cs @@ -26,7 +26,29 @@ namespace Realms.Logging /// logger. The will always be set for a specific category. /// Setting the log level for one category will automatically set the same level /// for all of its subcategories. + /// + /// Realm + /// ├─► Storage + /// │ ├─► Transaction + /// │ ├─► Query + /// │ ├─► Object + /// │ └─► Notification + /// ├─► Sync + /// │ ├─► Client + /// │ │ ├─► Session + /// │ │ ├─► Changeset + /// │ │ ├─► Network + /// │ │ └─► Reset + /// │ └─► Server + /// ├─► App + /// └─► Sdk + /// /// + /// + /// + /// LogCategory.Realm.Sync.Client + /// + /// public class LogCategory { /// From 84e873305bd1d0b03db9564b3599c624850b88b5 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:14:52 +0200 Subject: [PATCH 18/43] Add CHANGELOG entry. --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2bc3f226c..f704cf075b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,24 @@ ## vNext (TBD) +### Deprecations +* TODO(lj): Add deprecated APIs + ### Enhancements * Allow `ShouldCompactOnLaunch` to be set on `SyncConfiguration`, not only `RealmConfiguration`. (Issue [#3617](https://github.com/realm/realm-dotnet/issues/3617)) * Reduce the size of the local transaction log produced by creating objects, improving the performance of insertion-heavy transactions (Core 14.10.0). * Performance has been improved for range queries on integers and timestamps. Requires that you use the "BETWEEN" operation in `Realm.All().Filter(...)`. (Core 14.10.1) +* Allowed `ShouldCompactOnLaunch` to be set on `SyncConfiguration`, not only `RealmConfiguration`. (Issue [#3617](https://github.com/realm/realm-dotnet/issues/3617)) +* Allowed setting and getting a `LogLevel` for a given `LogCategory`, enabling more control over which category of messages should be logged and at what criticality level. The hierarchy of categories starts at `LogCategory.Realm`. + ```csharp + Logger.SetLogLevel(LogLevel.Warn, LogCategory.Realm.Sync); + Logger.GetLogLevel(LogCategory.Realm.Sync.Client.Session); // LogLevel.Warn + ``` + (PR [#3634](https://github.com/realm/realm-dotnet/pull/3634)) +* Added a function logger that accepts a callback that will receive the `LogLevel`, `LogCategory`, and the message when invoked. + ```csharp + Logger.Default = Logger.Function((level, category, message) => /* custom implementation */); + ``` + (PR [#3634](https://github.com/realm/realm-dotnet/pull/3634)) ### Fixed * A `ForCurrentlyOutstandingWork` progress notifier would not immediately call its callback after registration. Instead you would have to wait for some data to be received to get your first update - if you were already caught up when you registered the notifier you could end up waiting a long time for the server to deliver a download that would call/expire your notifier. (Core 14.8.0) From 52325770a811dbb70b66fd5303ce4dda5724fc48 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:18:27 +0200 Subject: [PATCH 19/43] Change arg. order to not break 'LogImpl()' API. --- Realm/Realm.UnityUtils/UnityLogger.cs | 4 +- Realm/Realm/Handles/SharedRealmHandle.cs | 2 +- Realm/Realm/Logging/Logger.cs | 55 +++++++++-------------- Tests/Realm.Tests/Database/LoggerTests.cs | 26 ++++++++--- 4 files changed, 43 insertions(+), 44 deletions(-) diff --git a/Realm/Realm.UnityUtils/UnityLogger.cs b/Realm/Realm.UnityUtils/UnityLogger.cs index 57972d0638..6801a7136f 100644 --- a/Realm/Realm.UnityUtils/UnityLogger.cs +++ b/Realm/Realm.UnityUtils/UnityLogger.cs @@ -22,9 +22,9 @@ namespace UnityUtils { public class UnityLogger : Logger { - protected override void LogImpl(LogLevel level, LogCategory category, string message) + protected override void LogImpl(LogLevel level, string message, LogCategory? category = null) { - var toLog = FormatLog(level, category, message); + var toLog = FormatLog(level, message, category!); switch (level) { case LogLevel.Fatal: diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index cb9a293429..b1615eec8b 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -839,7 +839,7 @@ private static void OnDisposeGCHandle(IntPtr handle) [MonoPInvokeCallback(typeof(NativeMethods.LogMessageCallback))] private static void LogMessage(StringValue message, LogLevel level, StringValue categoryName) { - Logger.LogDefault(level, LogCategory.FromName(categoryName!), message!); + Logger.LogDefault(level, message!, LogCategory.FromName(categoryName!)); } [MonoPInvokeCallback(typeof(NativeMethods.MigrationCallback))] diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index 36372721b0..1e0a508f6e 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -72,7 +72,7 @@ public abstract class Logger /// /// A instance that will invoke for each message. /// - public static Logger Function(Action logFunction) => new FunctionLogger((level, category, message) => logFunction(FormatLog(level, category, message))); + public static Logger Function(Action logFunction) => new FunctionLogger((level, category, message) => logFunction(FormatLog(level, message, category))); // TODO(lj): Deprecate /// @@ -152,30 +152,17 @@ protected Logger() _gcHandle = new Lazy(() => GCHandle.Alloc(this)); } - internal static void LogDefault(LogLevel level, string message) => Default?.Log(level, message); + internal static void LogDefault(LogLevel level, string message, LogCategory? category = null) => Default?.Log(level, message, category); - internal static void LogDefault(LogLevel level, LogCategory category, string message) => Default?.Log(level, category, message); - - // TODO(lj): Deprecate - /// - /// Log a message at the supplied level. - /// - /// The criticality level for the message. - /// The message to log. - public void Log(LogLevel level, string message) - { - Log(level, LogCategory.Realm.SDK, message); - } - - // TODO(lj): Use category as optional 3rd param. /// /// Log a message at the supplied level and category. /// /// The criticality level for the message. - /// The category for the message. /// The message to log. - public void Log(LogLevel level, LogCategory category, string message) + /// The category for the message. Defaults to if not specified. + public void Log(LogLevel level, string message, LogCategory? category = null) { + category ??= LogCategory.Realm.SDK; if (level < GetLogLevel(category)) { return; @@ -183,7 +170,7 @@ public void Log(LogLevel level, LogCategory category, string message) try { - LogImpl(level, category, message); + LogImpl(level, message, category); } catch (Exception ex) { @@ -191,23 +178,23 @@ public void Log(LogLevel level, LogCategory category, string message) } } - // TODO(lj): Set category as optional 3rd arg as we'll do for `Log`, - // so that we don't break the API. /// /// The internal implementation being called from . /// /// The criticality level for the message. - /// The category for the message. /// The message to log. - protected abstract void LogImpl(LogLevel level, LogCategory category, string message); + /// The category for the message. + protected abstract void LogImpl(LogLevel level, string message, LogCategory? category = null); - internal static string FormatLog(LogLevel level, LogCategory category, string message) => $"{DateTimeOffset.UtcNow:yyyy-MM-dd HH:mm:ss.fff} {category} {level}: {message}"; + internal static string FormatLog(LogLevel level, string message, LogCategory category) => $"{DateTimeOffset.UtcNow:yyyy-MM-dd HH:mm:ss.fff} {category} {level}: {message}"; private class ConsoleLogger : Logger { - protected override void LogImpl(LogLevel level, LogCategory category, string message) + protected override void LogImpl(LogLevel level, string message, LogCategory? category = null) { - System.Console.WriteLine(FormatLog(level, category, message)); + // TODO(lj): Currently using `!` since calls always go thru `Log()`; however, we + // could do `category ??= LogCategory.Realm.SDK;` in all logger classes. + System.Console.WriteLine(FormatLog(level, message, category!)); } } @@ -223,11 +210,11 @@ public FileLogger(string filePath, Encoding? encoding = null) _encoding = encoding ?? Encoding.UTF8; } - protected override void LogImpl(LogLevel level, LogCategory category, string message) + protected override void LogImpl(LogLevel level, string message, LogCategory? category = null) { lock (_locker) { - System.IO.File.AppendAllText(_filePath, FormatLog(level, category, message) + Environment.NewLine, _encoding); + System.IO.File.AppendAllText(_filePath, FormatLog(level, message, category!) + Environment.NewLine, _encoding); } } } @@ -241,12 +228,12 @@ public FunctionLogger(Action logFunction) _logFunction = logFunction; } - protected override void LogImpl(LogLevel level, LogCategory category, string message) => _logFunction(level, category, message); + protected override void LogImpl(LogLevel level, string message, LogCategory? category = null) => _logFunction(level, category!, message); } private class NullLogger : Logger { - protected override void LogImpl(LogLevel level, LogCategory category, string message) + protected override void LogImpl(LogLevel level, string message, LogCategory? category = null) { } } @@ -255,11 +242,11 @@ internal class InMemoryLogger : Logger { private readonly StringBuilder _builder = new(); - protected override void LogImpl(LogLevel level, LogCategory category, string message) + protected override void LogImpl(LogLevel level, string message, LogCategory? category = null) { lock (_builder) { - _builder.AppendLine(FormatLog(level, category, message)); + _builder.AppendLine(FormatLog(level, message, category!)); } } @@ -307,11 +294,11 @@ public void Dispose() _flush.Dispose(); } - protected override void LogImpl(LogLevel level, LogCategory category, string message) + protected override void LogImpl(LogLevel level, string message, LogCategory? category = null) { if (!_isFlushing) { - _queue.Enqueue(FormatLog(level, category, message)); + _queue.Enqueue(FormatLog(level, message, category!)); _hasNewItems.Set(); } } diff --git a/Tests/Realm.Tests/Database/LoggerTests.cs b/Tests/Realm.Tests/Database/LoggerTests.cs index 1c091760ac..ce3458224c 100644 --- a/Tests/Realm.Tests/Database/LoggerTests.cs +++ b/Tests/Realm.Tests/Database/LoggerTests.cs @@ -58,7 +58,7 @@ public void Logger_CanSetDefaultLogger() var messages = new List(); Logger.Default = Logger.Function(message => messages.Add(message)); - Logger.LogDefault(LogLevel.Warn, LogCategory.Realm.SDK, "This is very dangerous!"); + Logger.LogDefault(LogLevel.Warn, "This is very dangerous!", LogCategory.Realm.SDK); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "This is very dangerous!"); @@ -131,9 +131,9 @@ public void Logger_WhenLevelIsSet_LogsOnlyExpectedLevels(LogLevel level) Logger.Default = Logger.Function(message => messages.Add(message)); Logger.SetLogLevel(level, category); - Logger.LogDefault(level - 1, category, "This is at level - 1"); - Logger.LogDefault(level, category, "This is at the same level"); - Logger.LogDefault(level + 1, category, "This is at level + 1"); + Logger.LogDefault(level - 1, "This is at level - 1", category); + Logger.LogDefault(level, "This is at the same level", category); + Logger.LogDefault(level + 1, "This is at level + 1", category); Assert.That(messages.Count, Is.EqualTo(2)); AssertLogMessageContains(messages[0], level, category, "This is at the same level"); @@ -150,20 +150,32 @@ public void Logger_LogsAtGivenCategory() var messages = new List(); Logger.Default = Logger.Function((message) => messages.Add(message)); - Logger.LogDefault(LogLevel.Warn, category, "A log message"); + Logger.LogDefault(LogLevel.Warn, "A log message", category); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, category, "A log message"); } } + [Test] + public void Logger_LogsSdkCategoryByDefault() + { + var messages = new List(); + Logger.Default = Logger.Function((message) => messages.Add(message)); + + Logger.LogDefault(LogLevel.Warn, "A log message"); + + Assert.That(messages.Count, Is.EqualTo(1)); + AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); + } + [Test] public void Logger_CallsCustomFunction() { var messages = new List(); - Logger.Default = Logger.Function((level, category, message) => messages.Add(Logger.FormatLog(level, category, message))); + Logger.Default = Logger.Function((level, category, message) => messages.Add(Logger.FormatLog(level, message, category))); - Logger.LogDefault(LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); + Logger.LogDefault(LogLevel.Warn, "A log message", LogCategory.Realm.SDK); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); From d1977ee32ac48d6982338881e4f58c38dbc221e2 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:23:50 +0200 Subject: [PATCH 20/43] Deprecate old APIs. --- Realm/Realm/Logging/Logger.cs | 4 ++-- Tests/Realm.Tests/Database/LoggerTests.cs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index 1e0a508f6e..83e4e83db8 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -74,7 +74,6 @@ public abstract class Logger /// public static Logger Function(Action logFunction) => new FunctionLogger((level, category, message) => logFunction(FormatLog(level, message, category))); - // TODO(lj): Deprecate /// /// Gets a that proxies Log calls to the supplied function. /// @@ -82,6 +81,7 @@ public abstract class Logger /// /// A instance that will invoke for each message. /// + [Obsolete("Use Function(Action logFunction).")] public static Logger Function(Action logFunction) => new FunctionLogger((level, _, message) => logFunction(level, message)); /// @@ -93,11 +93,11 @@ public abstract class Logger /// public static Logger Function(Action logFunction) => new FunctionLogger(logFunction); - // TODO(lj): Deprecate /// /// Gets or sets the verbosity of log messages for all log categories via . /// /// The log level for Realm-originating messages. + [Obsolete("Use GetLogLevel() and SetLogLevel().")] public static LogLevel LogLevel { get => GetLogLevel(); diff --git a/Tests/Realm.Tests/Database/LoggerTests.cs b/Tests/Realm.Tests/Database/LoggerTests.cs index ce3458224c..249c107db9 100644 --- a/Tests/Realm.Tests/Database/LoggerTests.cs +++ b/Tests/Realm.Tests/Database/LoggerTests.cs @@ -109,6 +109,7 @@ public void Logger_SetsLogLevelAtSubcategories() } [Test] + [Obsolete("Using LogLevel setter.")] public void Logger_WhenUsingLogLevelSetter_OverwritesCategory() { var category = LogCategory.Realm.Storage; From c2dfcfa3bbb3b3fada9e4c4de52bd211878c829d Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:39:47 +0200 Subject: [PATCH 21/43] Update CHANGELOG. --- CHANGELOG.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f704cf075b..2e76bcd293 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,24 +1,29 @@ ## vNext (TBD) ### Deprecations -* TODO(lj): Add deprecated APIs +* The `Logger.LogLevel` `set` and `get` accessors have been deprecated. Please use `Logger.SetLogLevel()` and `Logger.GetLogLevel()` (see **Enhancements** below). +* The `Logger.Function(Action logFunction)` have been deprecated. Please use `Logger.Function(Action logFunction)` (see **Enhancements** below). ### Enhancements * Allow `ShouldCompactOnLaunch` to be set on `SyncConfiguration`, not only `RealmConfiguration`. (Issue [#3617](https://github.com/realm/realm-dotnet/issues/3617)) * Reduce the size of the local transaction log produced by creating objects, improving the performance of insertion-heavy transactions (Core 14.10.0). * Performance has been improved for range queries on integers and timestamps. Requires that you use the "BETWEEN" operation in `Realm.All().Filter(...)`. (Core 14.10.1) * Allowed `ShouldCompactOnLaunch` to be set on `SyncConfiguration`, not only `RealmConfiguration`. (Issue [#3617](https://github.com/realm/realm-dotnet/issues/3617)) -* Allowed setting and getting a `LogLevel` for a given `LogCategory`, enabling more control over which category of messages should be logged and at what criticality level. The hierarchy of categories starts at `LogCategory.Realm`. - ```csharp - Logger.SetLogLevel(LogLevel.Warn, LogCategory.Realm.Sync); - Logger.GetLogLevel(LogCategory.Realm.Sync.Client.Session); // LogLevel.Warn - ``` - (PR [#3634](https://github.com/realm/realm-dotnet/pull/3634)) -* Added a function logger that accepts a callback that will receive the `LogLevel`, `LogCategory`, and the message when invoked. - ```csharp - Logger.Default = Logger.Function((level, category, message) => /* custom implementation */); - ``` - (PR [#3634](https://github.com/realm/realm-dotnet/pull/3634)) +* Introduced a `LogCategory` and allowed for more control over which category of messages should be logged and at which criticality level: + * Allowed setting and getting a `LogLevel` for a given `LogCategory`. The hierarchy of categories starts at `LogCategory.Realm`. + ```csharp + Logger.SetLogLevel(LogLevel.Warn, LogCategory.Realm.Sync); + Logger.GetLogLevel(LogCategory.Realm.Sync.Client.Session); // LogLevel.Warn + ``` + * Added a function logger that accepts a callback that will receive the `LogLevel`, `LogCategory`, and the message when invoked. + ```csharp + Logger.Default = Logger.Function((level, category, message) => /* custom implementation */); + ``` + * Added an optional category as a last parameter to `Logger.Log()`. If unset, `LogCategory.Realm.SDK` will be used. + ```csharp + Logger.Default.Log(LogLevel.Warn, "A warning message", LogCategory.Realm); + ``` + (PR [#3634](https://github.com/realm/realm-dotnet/pull/3634)) ### Fixed * A `ForCurrentlyOutstandingWork` progress notifier would not immediately call its callback after registration. Instead you would have to wait for some data to be received to get your first update - if you were already caught up when you registered the notifier you could end up waiting a long time for the server to deliver a download that would call/expire your notifier. (Core 14.8.0) From b4dcaadd54d75a7873cfd0ccc14a02535e3a0c7f Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:52:25 +0200 Subject: [PATCH 22/43] Point to updated Core. --- wrappers/realm-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/realm-core b/wrappers/realm-core index 61060ee931..40c95c585f 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit 61060ee93136545a9b36b71451854127567bc4be +Subproject commit 40c95c585f335fa1ccbff5afe47ce9334bc27090 From 61f05a3bd2f331acbf29f8c457ae5dcef8168895 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:36:47 +0200 Subject: [PATCH 23/43] Change marshaled vector from heap-allocated to global. --- wrappers/src/shared_realm_cs.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index c74c063a7c..ceccffb1db 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -313,16 +313,19 @@ REALM_EXPORT void shared_realm_set_log_level(Logger::Level level, uint16_t* cate REALM_EXPORT MarshaledVector shared_realm_get_log_category_names() { const auto names = LogCategory::get_category_names(); - // Use heap allocation in order to keep the vector alive beyond this call. - // This will cause a memory leak 😢; however, this is only used for the tests. - const auto result = new std::vector(); - result->reserve(names.size()); - - for (const auto name : names) { - result->push_back(to_capi(name)); + // Declare the vector as static in order to make it a globally allocated + // and keep the vector alive beyond this call. + static std::vector result; + + // Check if it is empty before populating the result to prevent appending + // names on each invocation since the vector is global. + if (result.empty()) { + for (const auto name : names) { + result.push_back(to_capi(name)); + } } - return *result; + return result; } REALM_EXPORT SharedRealm* shared_realm_open(Configuration configuration, NativeException::Marshallable& ex) From a86513cc6d880c0b2986336533963743d0779ec4 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:38:47 +0200 Subject: [PATCH 24/43] Update 'shared_realm_get_log_level'. --- wrappers/src/shared_realm_cs.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index ceccffb1db..4577888e2b 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -298,12 +298,7 @@ REALM_EXPORT void shared_realm_install_callbacks( REALM_EXPORT Logger::Level shared_realm_get_log_level(uint16_t* category_name_buf, size_t category_name_len) { Utf16StringAccessor category_name(category_name_buf, category_name_len); - // TODO(lj): Usage in Core: - auto& category = LogCategory::get_category(category_name); - return Logger::get_default_logger()->get_level_threshold(category); - - // TODO(lj): But this seems to work as well: - // return LogCategory::get_category(category_name).get_default_level_threshold(); + return LogCategory::get_category(category_name).get_default_level_threshold(); } REALM_EXPORT void shared_realm_set_log_level(Logger::Level level, uint16_t* category_name_buf, size_t category_name_len) { From 461e090c839f70177ea8df10024bd4a741610441 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 19:07:06 +0200 Subject: [PATCH 25/43] Update usage of now-deprecated APIs. --- .../Native/SyncSocketProvider.WebSocket.cs | 4 +++- Tests/Realm.Tests/Database/InstanceTests.cs | 4 ++-- Tests/Realm.Tests/Database/LoggerTests.cs | 17 +++++++++++++++-- Tests/Realm.Tests/RealmTest.cs | 6 +++--- Tests/Realm.Tests/Sync/AppTests.cs | 2 +- Tests/Realm.Tests/Sync/SyncTestHelpers.cs | 2 +- .../Sync/SynchronizedInstanceTests.cs | 6 +++--- 7 files changed, 28 insertions(+), 13 deletions(-) diff --git a/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs b/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs index 4990a356e5..43cc8c09aa 100644 --- a/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs +++ b/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs @@ -28,6 +28,7 @@ namespace Realms.Native; +// TODO(lj): For the logs happening in this class, should we log for `LogCategory.Realm.Sync.Client`? internal partial class SyncSocketProvider { private class Socket : IDisposable @@ -185,7 +186,8 @@ private static void FormatExceptionForLogging(Exception ex, StringBuilder builde builder.AppendFormat("{0}: {1}", ex.GetType().FullName, ex.Message); builder.AppendLine(); - if (Logger.LogLevel >= LogLevel.Trace && !string.IsNullOrEmpty(ex.StackTrace)) + // TODO(lj): May update to log for a specific category. + if (Logger.GetLogLevel() >= LogLevel.Trace && !string.IsNullOrEmpty(ex.StackTrace)) { builder.Append(indentation); var indentedTrace = ex.StackTrace.Replace(Environment.NewLine, Environment.NewLine + indentation); diff --git a/Tests/Realm.Tests/Database/InstanceTests.cs b/Tests/Realm.Tests/Database/InstanceTests.cs index e920e1c9e7..5c982313a9 100644 --- a/Tests/Realm.Tests/Database/InstanceTests.cs +++ b/Tests/Realm.Tests/Database/InstanceTests.cs @@ -1281,14 +1281,14 @@ public void Logger_ChangeLevel_ReflectedImmediately() // We're at info level, so we don't expect any statements. WriteAndVerifyLogs(); - Logger.LogLevel = LogLevel.Debug; + Logger.SetLogLevel(LogLevel.Debug); // We're at Debug level now, so we should see the write message. var expectedWriteLog = new Regex("Debug: DB: .* Commit of size [^ ]* done in [^ ]* us"); WriteAndVerifyLogs(expectedWriteLog); // Revert back to Info level and make sure we don't log anything - Logger.LogLevel = LogLevel.Info; + Logger.SetLogLevel(LogLevel.Info); WriteAndVerifyLogs(); void WriteAndVerifyLogs(Regex? expectedRegex = null) diff --git a/Tests/Realm.Tests/Database/LoggerTests.cs b/Tests/Realm.Tests/Database/LoggerTests.cs index 249c107db9..705e73bd32 100644 --- a/Tests/Realm.Tests/Database/LoggerTests.cs +++ b/Tests/Realm.Tests/Database/LoggerTests.cs @@ -109,7 +109,7 @@ public void Logger_SetsLogLevelAtSubcategories() } [Test] - [Obsolete("Using LogLevel setter.")] + [Obsolete("Using LogLevel set accessor.")] public void Logger_WhenUsingLogLevelSetter_OverwritesCategory() { var category = LogCategory.Realm.Storage; @@ -182,6 +182,19 @@ public void Logger_CallsCustomFunction() AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); } + [Test] + [Obsolete("Using function not accepting category.")] + public void Logger_CallsObsoleteCustomFunction() + { + var messages = new List(); + Logger.Default = Logger.Function((level, message) => messages.Add(Logger.FormatLog(level, message, LogCategory.Realm.SDK))); + + Logger.LogDefault(LogLevel.Warn, "A log message"); + + Assert.That(messages.Count, Is.EqualTo(1)); + AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); + } + [Test] public void Logger_MatchesCoreCategoryNames() { @@ -212,7 +225,7 @@ public void FileLogger() { var tempFilePath = Path.GetTempFileName(); - Logger.LogLevel = LogLevel.All; + Logger.SetLogLevel(LogLevel.All); Logger.Default = Logger.File(tempFilePath); var warnMessage = "This is very dangerous!"; diff --git a/Tests/Realm.Tests/RealmTest.cs b/Tests/Realm.Tests/RealmTest.cs index aa5955c48b..27550349e6 100644 --- a/Tests/Realm.Tests/RealmTest.cs +++ b/Tests/Realm.Tests/RealmTest.cs @@ -32,8 +32,9 @@ namespace Realms.Tests public abstract class RealmTest { private readonly ConcurrentQueue> _realms = new(); + private readonly LogCategory _originalLogCategory = LogCategory.Realm; + private readonly LogLevel _originalLogLevel = Logger.GetLogLevel(LogCategory.Realm); private Logger _originalLogger = null!; - private LogLevel _originalLogLevel; private bool _isSetup; @@ -58,7 +59,6 @@ public void SetUp() if (!_isSetup) { _originalLogger = Logger.Default; - _originalLogLevel = Logger.LogLevel; if (OverrideDefaultConfig) { @@ -87,7 +87,7 @@ public void TearDown() CustomTearDown(); Logger.Default = _originalLogger; - Logger.LogLevel = _originalLogLevel; + Logger.SetLogLevel(_originalLogLevel, _originalLogCategory); #pragma warning disable CS0618 // Type or member is obsolete Realm.UseLegacyGuidRepresentation = false; diff --git a/Tests/Realm.Tests/Sync/AppTests.cs b/Tests/Realm.Tests/Sync/AppTests.cs index 4b89bd8926..0763774bb5 100644 --- a/Tests/Realm.Tests/Sync/AppTests.cs +++ b/Tests/Realm.Tests/Sync/AppTests.cs @@ -167,7 +167,7 @@ public void RealmConfiguration_WithCustomLogger_LogsSyncOperations(LogLevel logL { SyncTestHelpers.RunBaasTestAsync(async () => { - Logger.LogLevel = logLevel; + Logger.SetLogLevel(logLevel); var logger = new Logger.InMemoryLogger(); Logger.Default = logger; diff --git a/Tests/Realm.Tests/Sync/SyncTestHelpers.cs b/Tests/Realm.Tests/Sync/SyncTestHelpers.cs index 3e9dcdfec1..2cb11f7873 100644 --- a/Tests/Realm.Tests/Sync/SyncTestHelpers.cs +++ b/Tests/Realm.Tests/Sync/SyncTestHelpers.cs @@ -142,7 +142,7 @@ public static (string[] RemainingArgs, IDisposable? Logger) SetLoggerFromArgs(st var logLevel = (LogLevel)Enum.Parse(typeof(LogLevel), extracted.RealmLogLevel!); TestHelpers.Output.WriteLine($"Setting log level to {logLevel}"); - Logger.LogLevel = logLevel; + Logger.SetLogLevel(logLevel); } Logger.AsyncFileLogger? logger = null; diff --git a/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs b/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs index adadf62221..1c1b199da7 100644 --- a/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs +++ b/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs @@ -728,7 +728,7 @@ public void SyncTimeouts_ArePassedCorrectlyToCore() { var logger = new Logger.InMemoryLogger(); Logger.Default = logger; - Logger.LogLevel = LogLevel.Debug; + Logger.SetLogLevel(LogLevel.Debug); SyncTestHelpers.RunBaasTestAsync(async () => { @@ -811,7 +811,7 @@ public void SyncLogger_WhenLevelChanges_LogsAtNewLevel() } }); - Logger.LogLevel = LogLevel.Info; + Logger.SetLogLevel(LogLevel.Info); Logger.Default = logger; SyncTestHelpers.RunBaasTestAsync(async () => @@ -824,7 +824,7 @@ public void SyncLogger_WhenLevelChanges_LogsAtNewLevel() Assert.That(initialInfoLogs, Is.GreaterThan(0)); Assert.That(logs[LogLevel.Debug].Count, Is.EqualTo(0)); - Logger.LogLevel = LogLevel.Debug; + Logger.SetLogLevel(LogLevel.Debug); realm.Write(() => { From b19f081164df1a15a033e15a9e2d90b163cbc9bb Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Fri, 5 Jul 2024 12:25:52 +0200 Subject: [PATCH 26/43] Update API doc. --- Realm/Realm/Logging/Logger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index 83e4e83db8..d6e581b389 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -112,7 +112,7 @@ public static LogLevel LogLevel /// /// The category to get the level for. Defaults to if not specified. /// - /// The log level used. + /// The log level used for the given category. /// public static LogLevel GetLogLevel(LogCategory? category = null) { From 8fafa127c5f4996dc180fdae40017331077a7bd7 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Fri, 5 Jul 2024 12:27:52 +0200 Subject: [PATCH 27/43] Use braced initializer when returning marshaled vector. --- wrappers/src/shared_realm_cs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index 4577888e2b..29abe079a0 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -320,7 +320,7 @@ REALM_EXPORT MarshaledVector shared_realm_get_log_category_names } } - return result; + return {result}; } REALM_EXPORT SharedRealm* shared_realm_open(Configuration configuration, NativeException::Marshallable& ex) From f09ae91ed7f538933f3b396d0ceab0d942aab9b0 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Fri, 5 Jul 2024 15:30:37 +0200 Subject: [PATCH 28/43] Try to fix Windows marshaling. --- wrappers/src/marshalling.hpp | 12 ++++++++++++ wrappers/src/shared_realm_cs.cpp | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/wrappers/src/marshalling.hpp b/wrappers/src/marshalling.hpp index b1bff56675..e976a18cb4 100644 --- a/wrappers/src/marshalling.hpp +++ b/wrappers/src/marshalling.hpp @@ -86,6 +86,18 @@ struct MarshaledVector iterator begin() const noexcept { return {items}; } iterator end() const noexcept { return {items + count}; } + + struct TopLevelMarshallable + { + const T* items; + size_t count; + }; + + /// Needed for MSVC when returning a `MarshaledVector` from CPP + /// directly, as compared to when nested within another struct. + TopLevelMarshallable for_top_level_marshalling() const { + return {items, count}; + } }; enum class realm_value_type : uint8_t { diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index 29abe079a0..f7aaf47ed9 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -306,7 +306,7 @@ REALM_EXPORT void shared_realm_set_log_level(Logger::Level level, uint16_t* cate LogCategory::get_category(category_name).set_default_level_threshold(level); } -REALM_EXPORT MarshaledVector shared_realm_get_log_category_names() { +REALM_EXPORT MarshaledVector::TopLevelMarshallable shared_realm_get_log_category_names() { const auto names = LogCategory::get_category_names(); // Declare the vector as static in order to make it a globally allocated // and keep the vector alive beyond this call. @@ -320,7 +320,7 @@ REALM_EXPORT MarshaledVector shared_realm_get_log_category_names } } - return {result}; + return MarshaledVector(result).for_top_level_marshalling(); } REALM_EXPORT SharedRealm* shared_realm_open(Configuration configuration, NativeException::Marshallable& ex) From 28455dd8e3d7abac15ef6b4215c07d439e8ab3db Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:01:58 +0200 Subject: [PATCH 29/43] Make marshallable struct unnested. --- wrappers/src/marshalling.hpp | 17 ++++++++++------- wrappers/src/shared_realm_cs.cpp | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/wrappers/src/marshalling.hpp b/wrappers/src/marshalling.hpp index e976a18cb4..9f16bfdc74 100644 --- a/wrappers/src/marshalling.hpp +++ b/wrappers/src/marshalling.hpp @@ -28,6 +28,15 @@ namespace realm::binding { +/// Needed for MSVC when returning a `MarshaledVector` from CPP +/// directly, as compared to when nested within another struct. +/// Returned via `MarshaledVector::for_top_level_marshalling()`. +struct AnyTopLevelMarshallable +{ + const void* items; + size_t count; +}; + template struct MarshaledVector { @@ -87,15 +96,9 @@ struct MarshaledVector iterator begin() const noexcept { return {items}; } iterator end() const noexcept { return {items + count}; } - struct TopLevelMarshallable - { - const T* items; - size_t count; - }; - /// Needed for MSVC when returning a `MarshaledVector` from CPP /// directly, as compared to when nested within another struct. - TopLevelMarshallable for_top_level_marshalling() const { + AnyTopLevelMarshallable for_top_level_marshalling() const { return {items, count}; } }; diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index f7aaf47ed9..94ea14b20c 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -306,7 +306,7 @@ REALM_EXPORT void shared_realm_set_log_level(Logger::Level level, uint16_t* cate LogCategory::get_category(category_name).set_default_level_threshold(level); } -REALM_EXPORT MarshaledVector::TopLevelMarshallable shared_realm_get_log_category_names() { +REALM_EXPORT AnyTopLevelMarshallable shared_realm_get_log_category_names() { const auto names = LogCategory::get_category_names(); // Declare the vector as static in order to make it a globally allocated // and keep the vector alive beyond this call. From 7c91e71e9a3351871cf3522e35303cb16a9964a7 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:32:46 +0200 Subject: [PATCH 30/43] Make marshalable nested. --- wrappers/src/marshalling.hpp | 17 +++++++---------- wrappers/src/shared_realm_cs.cpp | 2 +- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/wrappers/src/marshalling.hpp b/wrappers/src/marshalling.hpp index 9f16bfdc74..e87446b9a9 100644 --- a/wrappers/src/marshalling.hpp +++ b/wrappers/src/marshalling.hpp @@ -28,15 +28,6 @@ namespace realm::binding { -/// Needed for MSVC when returning a `MarshaledVector` from CPP -/// directly, as compared to when nested within another struct. -/// Returned via `MarshaledVector::for_top_level_marshalling()`. -struct AnyTopLevelMarshallable -{ - const void* items; - size_t count; -}; - template struct MarshaledVector { @@ -96,9 +87,15 @@ struct MarshaledVector iterator begin() const noexcept { return {items}; } iterator end() const noexcept { return {items + count}; } + struct AnyMarshallable + { + const void* items; + size_t count; + }; + /// Needed for MSVC when returning a `MarshaledVector` from CPP /// directly, as compared to when nested within another struct. - AnyTopLevelMarshallable for_top_level_marshalling() const { + AnyMarshallable for_top_level_marshalling() const { return {items, count}; } }; diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index 94ea14b20c..29bac7c9d7 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -306,7 +306,7 @@ REALM_EXPORT void shared_realm_set_log_level(Logger::Level level, uint16_t* cate LogCategory::get_category(category_name).set_default_level_threshold(level); } -REALM_EXPORT AnyTopLevelMarshallable shared_realm_get_log_category_names() { +REALM_EXPORT MarshaledVector::AnyMarshallable shared_realm_get_log_category_names() { const auto names = LogCategory::get_category_names(); // Declare the vector as static in order to make it a globally allocated // and keep the vector alive beyond this call. From e5a6a8e4ab217cc48a51c98a506a5bd0a70ee67f Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Fri, 5 Jul 2024 18:05:19 +0200 Subject: [PATCH 31/43] Fix Windows marshaling once and for all. --- wrappers/src/marshalling.hpp | 17 ++++++++++------- wrappers/src/shared_realm_cs.cpp | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/wrappers/src/marshalling.hpp b/wrappers/src/marshalling.hpp index e87446b9a9..96b1b7abc4 100644 --- a/wrappers/src/marshalling.hpp +++ b/wrappers/src/marshalling.hpp @@ -28,6 +28,15 @@ namespace realm::binding { +/// A struct used when marshaling of `MarshaledVector` cannot be +/// compiled, e.g. for MSVC when returning a `MarshaledVector` from +/// CPP directly, as compared to when nested within another struct. +struct AnyMarshaledVector +{ + const void* items; + size_t count; +}; + template struct MarshaledVector { @@ -87,15 +96,9 @@ struct MarshaledVector iterator begin() const noexcept { return {items}; } iterator end() const noexcept { return {items + count}; } - struct AnyMarshallable - { - const void* items; - size_t count; - }; - /// Needed for MSVC when returning a `MarshaledVector` from CPP /// directly, as compared to when nested within another struct. - AnyMarshallable for_top_level_marshalling() const { + AnyMarshaledVector for_any_type_marshalling() const { return {items, count}; } }; diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index 29bac7c9d7..fd65e31302 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -306,7 +306,7 @@ REALM_EXPORT void shared_realm_set_log_level(Logger::Level level, uint16_t* cate LogCategory::get_category(category_name).set_default_level_threshold(level); } -REALM_EXPORT MarshaledVector::AnyMarshallable shared_realm_get_log_category_names() { +REALM_EXPORT AnyMarshaledVector shared_realm_get_log_category_names() { const auto names = LogCategory::get_category_names(); // Declare the vector as static in order to make it a globally allocated // and keep the vector alive beyond this call. @@ -320,7 +320,7 @@ REALM_EXPORT MarshaledVector::AnyMarshallable shared_realm_get_l } } - return MarshaledVector(result).for_top_level_marshalling(); + return MarshaledVector(result).for_any_type_marshalling(); } REALM_EXPORT SharedRealm* shared_realm_open(Configuration configuration, NativeException::Marshallable& ex) From 40195c37ad28fc6d8b3013c57d5a07f2e66a0b1b Mon Sep 17 00:00:00 2001 From: nirinchev Date: Wed, 10 Jul 2024 02:06:24 +0200 Subject: [PATCH 32/43] Fix Windows marshaling twice and for all --- Realm/Realm/Handles/SharedRealmHandle.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index b1615eec8b..0205b86011 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -49,6 +49,17 @@ internal class SharedRealmHandle : StandaloneHandle private static class NativeMethods { + // This is a wrapper struct around MarshaledVector since P/Invoke doesn't like it + // when the MarshaledVector is returned as the top-level return value from a native + // function. This only manifests in .NET Framework and is not an issue with Mono/.NET. + // The native return value is MarshaledVector without the wrapper because they are binary + // compatible. + [StructLayout(LayoutKind.Sequential)] + public struct CategoryNamesContainer + { + public MarshaledVector CategoryNames; + } + #pragma warning disable IDE0049 // Use built-in type alias #pragma warning disable SA1121 // Use built-in type alias @@ -232,7 +243,7 @@ public static extern void rename_property(SharedRealmHandle sharedRealm, public static extern void set_log_level(LogLevel level, [MarshalAs(UnmanagedType.LPWStr)] string category_name, IntPtr category_name_len); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_log_category_names", CallingConvention = CallingConvention.Cdecl)] - public static extern MarshaledVector get_log_category_names(); + public static extern CategoryNamesContainer get_log_category_names(); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_operating_system", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_operating_system(IntPtr buffer, IntPtr buffer_length); @@ -284,6 +295,7 @@ public static unsafe void Initialize() public static void SetLogLevel(LogLevel level, LogCategory category) => NativeMethods.set_log_level(level, category.Name, (IntPtr)category.Name.Length); public static string[] GetLogCategoryNames() => NativeMethods.get_log_category_names() + .CategoryNames .ToEnumerable() .Select(name => name.ToDotnetString()!) .ToArray(); From 55f074d0624d37de1be54c18d3c7f31b97f8d57c Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Thu, 11 Jul 2024 11:26:28 +0200 Subject: [PATCH 33/43] Deprecate 'Logger' and use its base class. --- CHANGELOG.md | 18 +-- Realm/Realm.UnityUtils/Initializer.cs | 2 +- Realm/Realm.UnityUtils/UnityLogger.cs | 6 +- Realm/Realm/Handles/RealmHandle.cs | 2 +- Realm/Realm/Handles/SessionHandle.cs | 8 +- Realm/Realm/Handles/SharedRealmHandle.cs | 2 +- Realm/Realm/Handles/SyncUserHandle.cs | 2 +- Realm/Realm/Helpers/Argument.cs | 2 +- Realm/Realm/Logging/Logger.cs | 117 ++++++++++------- Realm/Realm/Native/NativeCommon.cs | 6 +- .../Native/SyncSocketProvider.EventLoop.cs | 18 +-- .../Native/SyncSocketProvider.WebSocket.cs | 18 +-- Realm/Realm/Native/SyncSocketProvider.cs | 4 +- Realm/Realm/Realm.cs | 2 +- .../ProgressNotificationToken.cs | 2 +- .../GuidRepresentationMigrationTests.cs | 28 ++-- Tests/Realm.Tests/Database/InstanceTests.cs | 8 +- Tests/Realm.Tests/Database/LoggerTests.cs | 124 ++++++++++++------ .../Realm.Tests/Database/NotificationTests.cs | 4 +- Tests/Realm.Tests/RealmTest.cs | 10 +- Tests/Realm.Tests/Sync/AppTests.cs | 6 +- Tests/Realm.Tests/Sync/SyncTestHelpers.cs | 8 +- .../Sync/SynchronizedInstanceTests.cs | 18 +-- 23 files changed, 246 insertions(+), 169 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1db9b8d12..e0e706ae73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,29 @@ ## vNext (TBD) ### Deprecations -* The `Logger.LogLevel` `set` and `get` accessors have been deprecated. Please use `Logger.SetLogLevel()` and `Logger.GetLogLevel()` (see **Enhancements** below). -* The `Logger.Function(Action logFunction)` have been deprecated. Please use `Logger.Function(Action logFunction)` (see **Enhancements** below). +* The `Logger` has been deprecated in favor of `RealmLogger`, which `Logger` currently derives from. (PR [#3634](https://github.com/realm/realm-dotnet/pull/3634)) + * The `Logger.LogLevel` `set` and `get` accessors have been deprecated. Please use `RealmLogger.SetLogLevel()` and `RealmLogger.GetLogLevel()` (see **Enhancements** below). + * The `Logger.Function(Action logFunction)` have been deprecated. Please use `RealmLogger.Function(Action logFunction)` (see **Enhancements** below). ### Enhancements * Allow `ShouldCompactOnLaunch` to be set on `SyncConfiguration`, not only `RealmConfiguration`. (Issue [#3617](https://github.com/realm/realm-dotnet/issues/3617)) * Reduce the size of the local transaction log produced by creating objects, improving the performance of insertion-heavy transactions (Core 14.10.0). * Performance has been improved for range queries on integers and timestamps. Requires that you use the "BETWEEN" operation in `Realm.All().Filter(...)`. (Core 14.10.1) * Allowed `ShouldCompactOnLaunch` to be set on `SyncConfiguration`, not only `RealmConfiguration`. (Issue [#3617](https://github.com/realm/realm-dotnet/issues/3617)) -* Introduced a `LogCategory` and allowed for more control over which category of messages should be logged and at which criticality level: +* Introduced a `LogCategory` and allowed for more control over which category of messages should be logged and at which criticality level. (PR [#3634](https://github.com/realm/realm-dotnet/pull/3634)) * Allowed setting and getting a `LogLevel` for a given `LogCategory`. The hierarchy of categories starts at `LogCategory.Realm`. ```csharp - Logger.SetLogLevel(LogLevel.Warn, LogCategory.Realm.Sync); - Logger.GetLogLevel(LogCategory.Realm.Sync.Client.Session); // LogLevel.Warn + RealmLogger.SetLogLevel(LogLevel.Warn, LogCategory.Realm.Sync); + RealmLogger.GetLogLevel(LogCategory.Realm.Sync.Client.Session); // LogLevel.Warn ``` * Added a function logger that accepts a callback that will receive the `LogLevel`, `LogCategory`, and the message when invoked. ```csharp - Logger.Default = Logger.Function((level, category, message) => /* custom implementation */); + RealmLogger.Default = RealmLogger.Function((level, category, message) => /* custom implementation */); ``` - * Added an optional category as a last parameter to `Logger.Log()`. If unset, `LogCategory.Realm.SDK` will be used. + * Added a `RealmLogger.Log()` overload taking a category. If unset, `LogCategory.Realm.SDK` will be used. ```csharp - Logger.Default.Log(LogLevel.Warn, "A warning message", LogCategory.Realm); + RealmLogger.Default.Log(LogLevel.Warn, LogCategory.Realm, "A warning message"); ``` - (PR [#3634](https://github.com/realm/realm-dotnet/pull/3634)) ### Fixed * A `ForCurrentlyOutstandingWork` progress notifier would not immediately call its callback after registration. Instead you would have to wait for some data to be received to get your first update - if you were already caught up when you registered the notifier you could end up waiting a long time for the server to deliver a download that would call/expire your notifier. (Core 14.8.0) diff --git a/Realm/Realm.UnityUtils/Initializer.cs b/Realm/Realm.UnityUtils/Initializer.cs index e7f317b93f..f8dd575389 100644 --- a/Realm/Realm.UnityUtils/Initializer.cs +++ b/Realm/Realm.UnityUtils/Initializer.cs @@ -37,7 +37,7 @@ public static void Initialize() Platform.DeviceInfo = new UnityDeviceInfo(); Platform.BundleId = Application.productName; InteropConfig.AddPotentialStorageFolder(FileHelper.GetStorageFolder()); - Realms.Logging.Logger.Console = new UnityLogger(); + Realms.Logging.RealmLogger.Console = new UnityLogger(); Application.quitting += () => { NativeCommon.CleanupNativeResources("Application is exiting"); diff --git a/Realm/Realm.UnityUtils/UnityLogger.cs b/Realm/Realm.UnityUtils/UnityLogger.cs index 6801a7136f..7be96ce55b 100644 --- a/Realm/Realm.UnityUtils/UnityLogger.cs +++ b/Realm/Realm.UnityUtils/UnityLogger.cs @@ -20,11 +20,11 @@ namespace UnityUtils { - public class UnityLogger : Logger + public class UnityLogger : RealmLogger { - protected override void LogImpl(LogLevel level, string message, LogCategory? category = null) + protected override void LogImpl(LogLevel level, LogCategory category, string message) { - var toLog = FormatLog(level, message, category!); + var toLog = FormatLog(level, category!, message); switch (level) { case LogLevel.Fatal: diff --git a/Realm/Realm/Handles/RealmHandle.cs b/Realm/Realm/Handles/RealmHandle.cs index 6aa61ede0f..02c5b7a7aa 100644 --- a/Realm/Realm/Handles/RealmHandle.cs +++ b/Realm/Realm/Handles/RealmHandle.cs @@ -148,7 +148,7 @@ protected override bool ReleaseHandle() } catch(Exception ex) { - Logger.Default.Log(LogLevel.Error, $"An error occurred while closing native handle. Please file an issue at https://github.com/realm/realm-dotnet/issues. Error: {ex}"); + RealmLogger.Default.Log(LogLevel.Error, $"An error occurred while closing native handle. Please file an issue at https://github.com/realm/realm-dotnet/issues. Error: {ex}"); Debug.Fail($"Failed to close native handle: {ex}"); // it would be really bad if we got an exception in here. We must not pass it on, but have to return false diff --git a/Realm/Realm/Handles/SessionHandle.cs b/Realm/Realm/Handles/SessionHandle.cs index 6068ce6102..060f5daf9a 100644 --- a/Realm/Realm/Handles/SessionHandle.cs +++ b/Realm/Realm/Handles/SessionHandle.cs @@ -325,7 +325,7 @@ private static void HandleSessionError(IntPtr sessionHandlePtr, SyncError error, } catch (Exception ex) { - Logger.Default.Log(LogLevel.Warn, $"An error has occurred while handling a session error: {ex}"); + RealmLogger.Default.Log(LogLevel.Warn, $"An error has occurred while handling a session error: {ex}"); } } @@ -359,7 +359,7 @@ private static IntPtr NotifyBeforeClientReset(IntPtr beforeFrozen, IntPtr manage catch (Exception ex) { var handlerType = syncConfig is null ? "ClientResetHandler" : syncConfig.ClientResetHandler.GetType().Name; - Logger.Default.Log(LogLevel.Error, $"An error has occurred while executing {handlerType}.OnBeforeReset during a client reset: {ex}"); + RealmLogger.Default.Log(LogLevel.Error, $"An error has occurred while executing {handlerType}.OnBeforeReset during a client reset: {ex}"); var exHandle = GCHandle.Alloc(ex); return GCHandle.ToIntPtr(exHandle); @@ -397,7 +397,7 @@ private static IntPtr NotifyAfterClientReset(IntPtr beforeFrozen, IntPtr after, catch (Exception ex) { var handlerType = syncConfig is null ? "ClientResetHandler" : syncConfig.ClientResetHandler.GetType().Name; - Logger.Default.Log(LogLevel.Error, $"An error has occurred while executing {handlerType}.OnAfterReset during a client reset: {ex}"); + RealmLogger.Default.Log(LogLevel.Error, $"An error has occurred while executing {handlerType}.OnAfterReset during a client reset: {ex}"); var exHandle = GCHandle.Alloc(ex); return GCHandle.ToIntPtr(exHandle); @@ -460,7 +460,7 @@ private static void HandleSessionPropertyChangedCallback(IntPtr managedSessionHa } catch (Exception ex) { - Logger.Default.Log(LogLevel.Error, $"An error has occurred while raising a property changed event: {ex}"); + RealmLogger.Default.Log(LogLevel.Error, $"An error has occurred while raising a property changed event: {ex}"); } } diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index 0205b86011..e3321ededd 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -851,7 +851,7 @@ private static void OnDisposeGCHandle(IntPtr handle) [MonoPInvokeCallback(typeof(NativeMethods.LogMessageCallback))] private static void LogMessage(StringValue message, LogLevel level, StringValue categoryName) { - Logger.LogDefault(level, message!, LogCategory.FromName(categoryName!)); + RealmLogger.LogDefault(level, LogCategory.FromName(categoryName!), message!); } [MonoPInvokeCallback(typeof(NativeMethods.MigrationCallback))] diff --git a/Realm/Realm/Handles/SyncUserHandle.cs b/Realm/Realm/Handles/SyncUserHandle.cs index 11dcbb7cfd..fc385365d1 100644 --- a/Realm/Realm/Handles/SyncUserHandle.cs +++ b/Realm/Realm/Handles/SyncUserHandle.cs @@ -463,7 +463,7 @@ private static void HandleUserChanged(IntPtr managedUserHandle) } catch (Exception ex) { - Logger.Default.Log(LogLevel.Error, $"An error has occurred while raising User.Changed event: {ex}"); + RealmLogger.Default.Log(LogLevel.Error, $"An error has occurred while raising User.Changed event: {ex}"); } } } diff --git a/Realm/Realm/Helpers/Argument.cs b/Realm/Realm/Helpers/Argument.cs index 958bbf8903..6afba4e8a3 100644 --- a/Realm/Realm/Helpers/Argument.cs +++ b/Realm/Realm/Helpers/Argument.cs @@ -95,7 +95,7 @@ public static T ValidateNotNull(T value, string paramName) public static void AssertDebug(string message) { - Logger.LogDefault(LogLevel.Error, $"{message} {OpenIssueText}"); + RealmLogger.LogDefault(LogLevel.Error, $"{message} {OpenIssueText}"); #if DEBUG throw new Exception(message); diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index d6e581b389..5a57fb83e5 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -25,25 +25,43 @@ namespace Realms.Logging { + /// + [Obsolete("Use RealmLogger instead. If using a custom logger, RealmLogger.LogImpl() additionally receives the log category.")] + public abstract class Logger : RealmLogger + { + /// + /// The internal implementation being called from . + /// + /// The criticality level for the message. + /// The message to log. + protected abstract void LogImpl(LogLevel level, string message); + + /// + protected override void LogImpl(LogLevel level, LogCategory category, string message) + { + LogImpl(level, message); + } + } + /// /// A logger that logs messages originating from Realm. The default logger can be replaced by setting . ///
/// A few built-in implementations are provided by , , and , /// but you can implement your own. ///
- public abstract class Logger + public abstract class RealmLogger { private readonly Lazy _gcHandle; private static readonly LogCategory _defaultLogCategory = LogCategory.Realm; - private static Logger? _defaultLogger; + private static RealmLogger? _defaultLogger; /// /// Gets a that outputs messages to the default console. For most project types, that will be /// using but certain platforms may use different implementations. /// - /// A instance that outputs to the platform's console. - public static Logger Console { get; internal set; } = new ConsoleLogger(); + /// A instance that outputs to the platform's console. + public static RealmLogger Console { get; internal set; } = new ConsoleLogger(); /// /// Gets a that saves the log messages to a file. @@ -54,15 +72,15 @@ public abstract class Logger /// Please note that this logger is not optimized for performance, and could lead to overall sync performance slowdown with more verbose log levels. /// /// - /// A instance that will save log messages to a file. + /// A instance that will save log messages to a file. /// - public static Logger File(string filePath, Encoding? encoding = null) => new FileLogger(filePath, encoding); + public static RealmLogger File(string filePath, Encoding? encoding = null) => new FileLogger(filePath, encoding); /// /// Gets a that ignores all messages. /// - /// A that doesn't output any messages. - public static Logger Null { get; } = new NullLogger(); + /// A that doesn't output any messages. + public static RealmLogger Null { get; } = new NullLogger(); /// /// Gets a that proxies Log calls to the supplied function. The message will @@ -70,28 +88,28 @@ public abstract class Logger /// /// Function to proxy log calls to. /// - /// A instance that will invoke for each message. + /// A instance that will invoke for each message. /// - public static Logger Function(Action logFunction) => new FunctionLogger((level, category, message) => logFunction(FormatLog(level, message, category))); + public static RealmLogger Function(Action logFunction) => new FunctionLogger((level, category, message) => logFunction(FormatLog(level, category, message))); /// /// Gets a that proxies Log calls to the supplied function. /// /// Function to proxy log calls to. /// - /// A instance that will invoke for each message. + /// A instance that will invoke for each message. /// [Obsolete("Use Function(Action logFunction).")] - public static Logger Function(Action logFunction) => new FunctionLogger((level, _, message) => logFunction(level, message)); + public static RealmLogger Function(Action logFunction) => new FunctionLogger((level, _, message) => logFunction(level, message)); /// /// Gets a that proxies Log calls to the supplied function. /// /// Function to proxy log calls to. /// - /// A instance that will invoke for each message. + /// A instance that will invoke for each message. /// - public static Logger Function(Action logFunction) => new FunctionLogger(logFunction); + public static RealmLogger Function(Action logFunction) => new FunctionLogger(logFunction); /// /// Gets or sets the verbosity of log messages for all log categories via . @@ -132,11 +150,11 @@ public static void SetLogLevel(LogLevel level, LogCategory? category = null) } /// - /// Gets or sets a custom implementation that will be used by + /// Gets or sets a custom implementation that will be used by /// Realm whenever information must be logged. /// /// The logger to be used for Realm-originating messages. - public static Logger Default + public static RealmLogger Default { get => _defaultLogger ?? Console; set => _defaultLogger = value; @@ -145,24 +163,35 @@ public static Logger Default internal GCHandle GCHandle => _gcHandle.Value; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - protected Logger() + protected RealmLogger() { _gcHandle = new Lazy(() => GCHandle.Alloc(this)); } - internal static void LogDefault(LogLevel level, string message, LogCategory? category = null) => Default?.Log(level, message, category); + internal static void LogDefault(LogLevel level, string message) => Default?.Log(level, message); + + internal static void LogDefault(LogLevel level, LogCategory category, string message) => Default?.Log(level, category, message); + + /// + /// Log a message at the supplied level and default category . + /// + /// The criticality level for the message. + /// The message to log. + public void Log(LogLevel level, string message) + { + Log(level, LogCategory.Realm.SDK, message); + } /// /// Log a message at the supplied level and category. /// /// The criticality level for the message. + /// The category for the message. /// The message to log. - /// The category for the message. Defaults to if not specified. - public void Log(LogLevel level, string message, LogCategory? category = null) + public void Log(LogLevel level, LogCategory category, string message) { - category ??= LogCategory.Realm.SDK; if (level < GetLogLevel(category)) { return; @@ -170,11 +199,11 @@ public void Log(LogLevel level, string message, LogCategory? category = null) try { - LogImpl(level, message, category); + LogImpl(level, category, message); } catch (Exception ex) { - Console.Log(LogLevel.Error, $"An exception occurred while trying to log the message: '{message}' at level: {level} in category: '{category}'. Error: {ex}"); + Console.Log(LogLevel.Error, $"An exception occurred while trying to log the message: '{message}' at level: '{level}' in category: '{category}'. Error: {ex}"); } } @@ -182,23 +211,21 @@ public void Log(LogLevel level, string message, LogCategory? category = null) /// The internal implementation being called from . /// /// The criticality level for the message. - /// The message to log. /// The category for the message. - protected abstract void LogImpl(LogLevel level, string message, LogCategory? category = null); + /// The message to log. + protected abstract void LogImpl(LogLevel level, LogCategory category, string message); - internal static string FormatLog(LogLevel level, string message, LogCategory category) => $"{DateTimeOffset.UtcNow:yyyy-MM-dd HH:mm:ss.fff} {category} {level}: {message}"; + internal static string FormatLog(LogLevel level, LogCategory category, string message) => $"{DateTimeOffset.UtcNow:yyyy-MM-dd HH:mm:ss.fff} {category} {level}: {message}"; - private class ConsoleLogger : Logger + private class ConsoleLogger : RealmLogger { - protected override void LogImpl(LogLevel level, string message, LogCategory? category = null) + protected override void LogImpl(LogLevel level, LogCategory category, string message) { - // TODO(lj): Currently using `!` since calls always go thru `Log()`; however, we - // could do `category ??= LogCategory.Realm.SDK;` in all logger classes. - System.Console.WriteLine(FormatLog(level, message, category!)); + System.Console.WriteLine(FormatLog(level, category, message)); } } - private class FileLogger : Logger + private class FileLogger : RealmLogger { private readonly object _locker = new(); private readonly string _filePath; @@ -210,16 +237,16 @@ public FileLogger(string filePath, Encoding? encoding = null) _encoding = encoding ?? Encoding.UTF8; } - protected override void LogImpl(LogLevel level, string message, LogCategory? category = null) + protected override void LogImpl(LogLevel level, LogCategory category, string message) { lock (_locker) { - System.IO.File.AppendAllText(_filePath, FormatLog(level, message, category!) + Environment.NewLine, _encoding); + System.IO.File.AppendAllText(_filePath, FormatLog(level, category, message) + Environment.NewLine, _encoding); } } } - private class FunctionLogger : Logger + private class FunctionLogger : RealmLogger { private readonly Action _logFunction; @@ -228,25 +255,25 @@ public FunctionLogger(Action logFunction) _logFunction = logFunction; } - protected override void LogImpl(LogLevel level, string message, LogCategory? category = null) => _logFunction(level, category!, message); + protected override void LogImpl(LogLevel level, LogCategory category, string message) => _logFunction(level, category, message); } - private class NullLogger : Logger + private class NullLogger : RealmLogger { - protected override void LogImpl(LogLevel level, string message, LogCategory? category = null) + protected override void LogImpl(LogLevel level, LogCategory category, string message) { } } - internal class InMemoryLogger : Logger + internal class InMemoryLogger : RealmLogger { private readonly StringBuilder _builder = new(); - protected override void LogImpl(LogLevel level, string message, LogCategory? category = null) + protected override void LogImpl(LogLevel level, LogCategory category, string message) { lock (_builder) { - _builder.AppendLine(FormatLog(level, message, category!)); + _builder.AppendLine(FormatLog(level, category, message)); } } @@ -267,7 +294,7 @@ public void Clear() } } - internal class AsyncFileLogger : Logger, IDisposable + internal class AsyncFileLogger : RealmLogger, IDisposable { private readonly ConcurrentQueue _queue = new(); private readonly string _filePath; @@ -294,11 +321,11 @@ public void Dispose() _flush.Dispose(); } - protected override void LogImpl(LogLevel level, string message, LogCategory? category = null) + protected override void LogImpl(LogLevel level, LogCategory category, string message) { if (!_isFlushing) { - _queue.Enqueue(FormatLog(level, message, category!)); + _queue.Enqueue(FormatLog(level, category, message)); _hasNewItems.Set(); } } diff --git a/Realm/Realm/Native/NativeCommon.cs b/Realm/Realm/Native/NativeCommon.cs index 45e718b9e1..f33b2ffaab 100644 --- a/Realm/Realm/Native/NativeCommon.cs +++ b/Realm/Realm/Native/NativeCommon.cs @@ -96,7 +96,7 @@ public static void CleanupNativeResources(string reason) { if (Interlocked.CompareExchange(ref _isInitialized, 0, 1) == 1) { - Logger.LogDefault(LogLevel.Info, $"Realm: Force closing all native instances: {reason}"); + RealmLogger.LogDefault(LogLevel.Info, $"Realm: Force closing all native instances: {reason}"); var sw = new Stopwatch(); sw.Start(); @@ -106,12 +106,12 @@ public static void CleanupNativeResources(string reason) SharedRealmHandle.ForceCloseNativeRealms(); sw.Stop(); - Logger.LogDefault(LogLevel.Info, $"Realm: Closed all native instances in {sw.ElapsedMilliseconds} ms."); + RealmLogger.LogDefault(LogLevel.Info, $"Realm: Closed all native instances in {sw.ElapsedMilliseconds} ms."); } } catch (Exception ex) { - Logger.LogDefault(LogLevel.Error, $"Realm: Failed to close all native instances. You may need to restart your app. Error: {ex}"); + RealmLogger.LogDefault(LogLevel.Error, $"Realm: Failed to close all native instances. You may need to restart your app. Error: {ex}"); } } diff --git a/Realm/Realm/Native/SyncSocketProvider.EventLoop.cs b/Realm/Realm/Native/SyncSocketProvider.EventLoop.cs index 369da7daf7..bf85babd7f 100644 --- a/Realm/Realm/Native/SyncSocketProvider.EventLoop.cs +++ b/Realm/Realm/Native/SyncSocketProvider.EventLoop.cs @@ -32,7 +32,7 @@ private class Timer internal Timer(TimeSpan delay, IntPtr nativeCallback, ChannelWriter workQueue) { - Logger.LogDefault(LogLevel.Trace, $"Creating timer with delay {delay} and target {nativeCallback}."); + RealmLogger.LogDefault(LogLevel.Trace, $"Creating timer with delay {delay} and target {nativeCallback}."); var cancellationToken = _cts.Token; Task.Delay(delay, cancellationToken).ContinueWith(async _ => { @@ -42,7 +42,7 @@ internal Timer(TimeSpan delay, IntPtr nativeCallback, ChannelWriter workQ internal void Cancel() { - Logger.LogDefault(LogLevel.Trace, $"Canceling timer."); + RealmLogger.LogDefault(LogLevel.Trace, $"Canceling timer."); _cts.Cancel(); _cts.Dispose(); } @@ -72,7 +72,7 @@ public void Execute() { if (cancellationToken.IsCancellationRequested) { - Logger.LogDefault(LogLevel.Trace, "Deleting EventLoopWork callback only because event loop was cancelled."); + RealmLogger.LogDefault(LogLevel.Trace, "Deleting EventLoopWork callback only because event loop was cancelled."); NativeMethods.delete_callback(nativeCallback); return; } @@ -83,7 +83,7 @@ public void Execute() private static void RunCallback(IntPtr nativeCallback, Status status) { - Logger.LogDefault(LogLevel.Trace, $"SyncSocketProvider running native callback {nativeCallback} with status {status.Code} \"{status.Reason}\"."); + RealmLogger.LogDefault(LogLevel.Trace, $"SyncSocketProvider running native callback {nativeCallback} with status {status.Code} \"{status.Reason}\"."); using var arena = new Arena(); NativeMethods.run_callback(nativeCallback, status.Code, StringValue.AllocateFrom(status.Reason, arena)); @@ -91,13 +91,13 @@ private static void RunCallback(IntPtr nativeCallback, Status status) private async Task PostWorkAsync(IntPtr nativeCallback) { - Logger.LogDefault(LogLevel.Trace, "Posting work to SyncSocketProvider event loop."); + RealmLogger.LogDefault(LogLevel.Trace, "Posting work to SyncSocketProvider event loop."); await _workQueue.Writer.WriteAsync(new EventLoopWork(nativeCallback, _cts.Token)); } private async partial Task WorkThread() { - Logger.LogDefault(LogLevel.Trace, "Starting SyncSocketProvider event loop."); + RealmLogger.LogDefault(LogLevel.Trace, "Starting SyncSocketProvider event loop."); try { while (await _workQueue.Reader.WaitToReadAsync()) @@ -110,15 +110,15 @@ private async partial Task WorkThread() } catch (Exception e) { - Logger.LogDefault(LogLevel.Error, $"Error occurred in SyncSocketProvider event loop {e.GetType().FullName}: {e.Message}"); + RealmLogger.LogDefault(LogLevel.Error, $"Error occurred in SyncSocketProvider event loop {e.GetType().FullName}: {e.Message}"); if (!string.IsNullOrEmpty(e.StackTrace)) { - Logger.LogDefault(LogLevel.Trace, e.StackTrace); + RealmLogger.LogDefault(LogLevel.Trace, e.StackTrace); } throw; } - Logger.LogDefault(LogLevel.Trace, "Exiting SyncSocketProvider event loop."); + RealmLogger.LogDefault(LogLevel.Trace, "Exiting SyncSocketProvider event loop."); } } diff --git a/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs b/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs index 43cc8c09aa..64b20c29e3 100644 --- a/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs +++ b/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs @@ -46,7 +46,7 @@ private class Socket : IDisposable internal Socket(ClientWebSocket webSocket, IntPtr observer, ChannelWriter workQueue, Uri uri) { - Logger.LogDefault(LogLevel.Trace, $"Creating a WebSocket to {uri.GetLeftPart(UriPartial.Path)}"); + RealmLogger.LogDefault(LogLevel.Trace, $"Creating a WebSocket to {uri.GetLeftPart(UriPartial.Path)}"); _webSocket = webSocket; _observer = observer; _workQueue = workQueue; @@ -57,7 +57,7 @@ internal Socket(ClientWebSocket webSocket, IntPtr observer, ChannelWriter private async Task ReadThread() { - Logger.LogDefault(LogLevel.Trace, "Entering WebSocket event loop."); + RealmLogger.LogDefault(LogLevel.Trace, "Entering WebSocket event loop."); try { @@ -68,7 +68,7 @@ private async Task ReadThread() { var builder = new StringBuilder(); FormatExceptionForLogging(e, builder); - Logger.LogDefault(LogLevel.Error, $"Error establishing WebSocket connection {builder}"); + RealmLogger.LogDefault(LogLevel.Error, $"Error establishing WebSocket connection {builder}"); await _workQueue.WriteAsync(new WebSocketClosedWork(false, (WebSocketCloseStatus)RLM_ERR_WEBSOCKET_CONNECTION_FAILED, e.Message, _observer, _cancellationToken)); return; @@ -93,11 +93,11 @@ private async Task ReadThread() break; case WebSocketMessageType.Close: - Logger.LogDefault(LogLevel.Trace, $"WebSocket closed with status {result.CloseStatus!.Value} and description \"{result.CloseStatusDescription}\""); + RealmLogger.LogDefault(LogLevel.Trace, $"WebSocket closed with status {result.CloseStatus!.Value} and description \"{result.CloseStatusDescription}\""); await _workQueue.WriteAsync(new WebSocketClosedWork(clean: true, result.CloseStatus!.Value, result.CloseStatusDescription!, _observer, _cancellationToken)); break; default: - Logger.LogDefault(LogLevel.Trace, $"Received unexpected text WebSocket message: {Encoding.UTF8.GetString(buffer, 0, result.Count)}"); + RealmLogger.LogDefault(LogLevel.Trace, $"Received unexpected text WebSocket message: {Encoding.UTF8.GetString(buffer, 0, result.Count)}"); break; } } @@ -105,7 +105,7 @@ private async Task ReadThread() { var builder = new StringBuilder(); FormatExceptionForLogging(e, builder); - Logger.LogDefault(LogLevel.Error, $"Error reading from WebSocket {builder}"); + RealmLogger.LogDefault(LogLevel.Error, $"Error reading from WebSocket {builder}"); await _workQueue.WriteAsync(new WebSocketClosedWork(false, (WebSocketCloseStatus)RLM_ERR_WEBSOCKET_READ_ERROR, e.Message, _observer, _cancellationToken)); return; @@ -131,7 +131,7 @@ public async void Write(BinaryValue data, IntPtr native_callback) { var builder = new StringBuilder(); FormatExceptionForLogging(e, builder); - Logger.LogDefault(LogLevel.Error, $"Error writing to WebSocket {builder}"); + RealmLogger.LogDefault(LogLevel.Error, $"Error writing to WebSocket {builder}"); // in case of errors notify the websocket observer and just dispose the callback await _workQueue.WriteAsync(new WebSocketClosedWork(false, (WebSocketCloseStatus)RLM_ERR_WEBSOCKET_WRITE_ERROR, e.Message, _observer, _cancellationToken)); @@ -165,7 +165,7 @@ public async void Dispose() _webSocket.Dispose(); _receiveBuffer.Dispose(); _cts.Dispose(); - Logger.LogDefault(LogLevel.Trace, "Disposing WebSocket."); + RealmLogger.LogDefault(LogLevel.Trace, "Disposing WebSocket."); try { @@ -187,7 +187,7 @@ private static void FormatExceptionForLogging(Exception ex, StringBuilder builde builder.AppendFormat("{0}: {1}", ex.GetType().FullName, ex.Message); builder.AppendLine(); // TODO(lj): May update to log for a specific category. - if (Logger.GetLogLevel() >= LogLevel.Trace && !string.IsNullOrEmpty(ex.StackTrace)) + if (RealmLogger.GetLogLevel() >= LogLevel.Trace && !string.IsNullOrEmpty(ex.StackTrace)) { builder.Append(indentation); var indentedTrace = ex.StackTrace.Replace(Environment.NewLine, Environment.NewLine + indentation); diff --git a/Realm/Realm/Native/SyncSocketProvider.cs b/Realm/Realm/Native/SyncSocketProvider.cs index 2cc5e67686..48cbb186d7 100644 --- a/Realm/Realm/Native/SyncSocketProvider.cs +++ b/Realm/Realm/Native/SyncSocketProvider.cs @@ -156,7 +156,7 @@ private interface IWork internal SyncSocketProvider(Action? onWebSocketConnection) { - Logger.LogDefault(LogLevel.Debug, "Creating SyncSocketProvider."); + RealmLogger.LogDefault(LogLevel.Debug, "Creating SyncSocketProvider."); _onWebSocketConnection = onWebSocketConnection; _workQueue = Channel.CreateUnbounded(new() { SingleReader = true }); _workThread = Task.Run(WorkThread); @@ -166,7 +166,7 @@ internal SyncSocketProvider(Action? onWebSocketConnectio public void Dispose() { - Logger.LogDefault(LogLevel.Debug, "Destroying SyncSocketProvider."); + RealmLogger.LogDefault(LogLevel.Debug, "Destroying SyncSocketProvider."); _workQueue.Writer.Complete(); _cts.Cancel(); _cts.Dispose(); diff --git a/Realm/Realm/Realm.cs b/Realm/Realm/Realm.cs index 9d741994d7..1a50e2eea3 100644 --- a/Realm/Realm/Realm.cs +++ b/Realm/Realm/Realm.cs @@ -414,7 +414,7 @@ internal void NotifyError(Exception ex) { if (Error is null) { - Logger.LogDefault(LogLevel.Error, "A realm-level exception has occurred. To handle and react to those, subscribe to the Realm.Error event."); + RealmLogger.LogDefault(LogLevel.Error, "A realm-level exception has occurred. To handle and react to those, subscribe to the Realm.Error event."); } Error?.Invoke(this, new ErrorEventArgs(ex)); diff --git a/Realm/Realm/Sync/ProgressNotifications/ProgressNotificationToken.cs b/Realm/Realm/Sync/ProgressNotifications/ProgressNotificationToken.cs index 57674fc30a..03a74eb692 100644 --- a/Realm/Realm/Sync/ProgressNotifications/ProgressNotificationToken.cs +++ b/Realm/Realm/Sync/ProgressNotifications/ProgressNotificationToken.cs @@ -58,7 +58,7 @@ public void Notify(double progressEstimate) } catch (Exception ex) { - Logger.Default.Log(LogLevel.Warn, $"An error occurred while reporting progress: {ex}"); + RealmLogger.Default.Log(LogLevel.Warn, $"An error occurred while reporting progress: {ex}"); } }); } diff --git a/Tests/Realm.Tests/Database/GuidRepresentationMigrationTests.cs b/Tests/Realm.Tests/Database/GuidRepresentationMigrationTests.cs index 0984eda9ad..187af873d7 100644 --- a/Tests/Realm.Tests/Database/GuidRepresentationMigrationTests.cs +++ b/Tests/Realm.Tests/Database/GuidRepresentationMigrationTests.cs @@ -55,8 +55,8 @@ public void Migration_FlipGuid_ShouldProduceCorrectRepresentation() [Test] public void Migration_FromLittleEndianGuidFile([Values(true, false)] bool useLegacyRepresentation) { - var logger = new Logger.InMemoryLogger(); - Logger.Default = logger; + var logger = new RealmLogger.InMemoryLogger(); + RealmLogger.Default = logger; #pragma warning disable CS0618 // Type or member is obsolete Realm.UseLegacyGuidRepresentation = useLegacyRepresentation; @@ -94,8 +94,8 @@ public void Migration_FromLittleEndianGuidFile([Values(true, false)] bool useLeg [Test] public void PopulatingANewFile([Values(true, false)] bool useLegacyRepresentation) { - var logger = new Logger.InMemoryLogger(); - Logger.Default = logger; + var logger = new RealmLogger.InMemoryLogger(); + RealmLogger.Default = logger; #pragma warning disable CS0618 // Type or member is obsolete Realm.UseLegacyGuidRepresentation = useLegacyRepresentation; @@ -171,8 +171,8 @@ public void FlexibleSync_Subscriptions_MatchesGuid([Values(true, false)] bool us [Test] public void UnmigratedRealm_WhenOpenedAsReadonly_LogsAMessageAndDoesntChangeFile() { - var logger = new Logger.InMemoryLogger(); - Logger.Default = logger; + var logger = new RealmLogger.InMemoryLogger(); + RealmLogger.Default = logger; TestHelpers.CopyBundledFileToDocuments("guids.realm", _configuration.DatabasePath); _configuration.IsReadOnly = true; @@ -213,8 +213,8 @@ public void MigratedRealm_WhenOpenedAsReadonly_DoesntDoAnything() // Open the Realm to migrate it } - var logger = new Logger.InMemoryLogger(); - Logger.Default = logger; + var logger = new RealmLogger.InMemoryLogger(); + RealmLogger.Default = logger; _configuration.IsReadOnly = true; @@ -243,8 +243,8 @@ public void Migration_FromLittleEndian_WhenContainingAmbiguousGuids_LogsWarning( { // This tests that a file that doesn't appear to have little-endian guids is not migrated // See comment in guid_representation_migration.cpp/flip_guid - var logger = new Logger.InMemoryLogger(); - Logger.Default = logger; + var logger = new RealmLogger.InMemoryLogger(); + RealmLogger.Default = logger; TestHelpers.CopyBundledFileToDocuments("bad-guids.realm", _configuration.DatabasePath); @@ -273,8 +273,8 @@ public void Migration_FromLittleEndian_WhenContainingBothGoodAndBadGuids_LogsWar // This tests that a file that contains both ambiguous (xxxxxxxx-xxxx-4x4x-xxxx-xxxxxxxx) and unambiguous guids // does get migrated. // See comment in guid_representation_migration.cpp/flip_guid - var logger = new Logger.InMemoryLogger(); - Logger.Default = logger; + var logger = new RealmLogger.InMemoryLogger(); + RealmLogger.Default = logger; TestHelpers.CopyBundledFileToDocuments("mixed-guids.realm", _configuration.DatabasePath); @@ -301,8 +301,8 @@ public void Migration_FromLittleEndian_WhenContainingBothGoodAndBadGuids_LogsWar [Test] public void SynchronizedRealm_DoesntMigrate([Values(true, false)] bool useLegacyRepresentation) { - var logger = new Logger.InMemoryLogger(); - Logger.Default = logger; + var logger = new RealmLogger.InMemoryLogger(); + RealmLogger.Default = logger; #pragma warning disable CS0618 // Type or member is obsolete Realm.UseLegacyGuidRepresentation = useLegacyRepresentation; diff --git a/Tests/Realm.Tests/Database/InstanceTests.cs b/Tests/Realm.Tests/Database/InstanceTests.cs index 5c982313a9..0383366379 100644 --- a/Tests/Realm.Tests/Database/InstanceTests.cs +++ b/Tests/Realm.Tests/Database/InstanceTests.cs @@ -1271,8 +1271,8 @@ public void RealmDispose_DisposesActiveTransaction() [Test] public void Logger_ChangeLevel_ReflectedImmediately() { - var logger = new Logger.InMemoryLogger(); - Logger.Default = logger; + var logger = new RealmLogger.InMemoryLogger(); + RealmLogger.Default = logger; using var realm = GetRealm(Guid.NewGuid().ToString()); @@ -1281,14 +1281,14 @@ public void Logger_ChangeLevel_ReflectedImmediately() // We're at info level, so we don't expect any statements. WriteAndVerifyLogs(); - Logger.SetLogLevel(LogLevel.Debug); + RealmLogger.SetLogLevel(LogLevel.Debug); // We're at Debug level now, so we should see the write message. var expectedWriteLog = new Regex("Debug: DB: .* Commit of size [^ ]* done in [^ ]* us"); WriteAndVerifyLogs(expectedWriteLog); // Revert back to Info level and make sure we don't log anything - Logger.SetLogLevel(LogLevel.Info); + RealmLogger.SetLogLevel(LogLevel.Info); WriteAndVerifyLogs(); void WriteAndVerifyLogs(Regex? expectedRegex = null) diff --git a/Tests/Realm.Tests/Database/LoggerTests.cs b/Tests/Realm.Tests/Database/LoggerTests.cs index 705e73bd32..da781ca433 100644 --- a/Tests/Realm.Tests/Database/LoggerTests.cs +++ b/Tests/Realm.Tests/Database/LoggerTests.cs @@ -28,20 +28,45 @@ namespace Realms.Tests.Database public class LoggerTests { private readonly LogCategory _originalLogCategory = LogCategory.Realm; - private readonly LogLevel _originalLogLevel = Logger.GetLogLevel(LogCategory.Realm); - private Logger _originalLogger = null!; + private readonly LogLevel _originalLogLevel = RealmLogger.GetLogLevel(LogCategory.Realm); + private RealmLogger _originalLogger = null!; + + private class UserDefinedLogger : RealmLogger + { + private readonly Action _logFunction; + + public UserDefinedLogger(Action logFunction) + { + _logFunction = logFunction; + } + + protected override void LogImpl(LogLevel level, LogCategory category, string message) => _logFunction(level, category, message); + } + + [Obsolete("Using obsolete logger.")] + private class ObsoleteUserDefinedLogger : Logger + { + private readonly Action _logFunction; + + public ObsoleteUserDefinedLogger(Action logFunction) + { + _logFunction = logFunction; + } + + protected override void LogImpl(LogLevel level, string message) => _logFunction(level, message); + } [SetUp] public void Setup() { - _originalLogger = Logger.Default; + _originalLogger = RealmLogger.Default; } [TearDown] public void TearDown() { - Logger.Default = _originalLogger; - Logger.SetLogLevel(_originalLogLevel, _originalLogCategory); + RealmLogger.Default = _originalLogger; + RealmLogger.SetLogLevel(_originalLogLevel, _originalLogCategory); } private void AssertLogMessageContains(string actual, LogLevel level, LogCategory category, string message) @@ -53,24 +78,49 @@ private void AssertLogMessageContains(string actual, LogLevel level, LogCategory } [Test] - public void Logger_CanSetDefaultLogger() + public void Logger_CanSetDefaultLoggerToBuiltInLogger() { var messages = new List(); - Logger.Default = Logger.Function(message => messages.Add(message)); + RealmLogger.Default = RealmLogger.Function(message => messages.Add(message)); - Logger.LogDefault(LogLevel.Warn, "This is very dangerous!", LogCategory.Realm.SDK); + RealmLogger.LogDefault(LogLevel.Warn, LogCategory.Realm.SDK, "This is very dangerous!"); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "This is very dangerous!"); } + [Test] + public void Logger_CanSetDefaultLoggerToUserDefinedLogger() + { + var messages = new List(); + RealmLogger.Default = new UserDefinedLogger((level, category, message) => messages.Add(RealmLogger.FormatLog(level, category, message))); + + RealmLogger.LogDefault(LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); + + Assert.That(messages.Count, Is.EqualTo(1)); + AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); + } + + [Test] + [Obsolete("Using obsolete logger class.")] + public void ObsoleteLogger_CanSetDefaultLoggerToUserDefinedLogger() + { + var messages = new List(); + Logger.Default = new ObsoleteUserDefinedLogger((level, message) => messages.Add(Logger.FormatLog(level, LogCategory.Realm.SDK, message))); + + Logger.LogDefault(LogLevel.Warn, "A log message"); + + Assert.That(messages.Count, Is.EqualTo(1)); + AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); + } + [Test] public void Logger_SkipsDebugMessagesByDefault() { var messages = new List(); - Logger.Default = Logger.Function(message => messages.Add(message)); + RealmLogger.Default = RealmLogger.Function(message => messages.Add(message)); - Logger.LogDefault(LogLevel.Debug, "This is a debug message!"); + RealmLogger.LogDefault(LogLevel.Debug, "This is a debug message!"); Assert.That(messages.Count, Is.EqualTo(0)); } @@ -81,8 +131,8 @@ public void Logger_SetsLogLevelAtGivenCategory() var categories = LogCategory.NameToCategory.Values; foreach (var category in categories) { - Logger.SetLogLevel(LogLevel.All, category); - Assert.That(Logger.GetLogLevel(category), Is.EqualTo(LogLevel.All)); + RealmLogger.SetLogLevel(LogLevel.All, category); + Assert.That(RealmLogger.GetLogLevel(category), Is.EqualTo(LogLevel.All)); } } @@ -98,13 +148,13 @@ public void Logger_SetsLogLevelAtSubcategories() }; foreach (var category in storageCategories) { - Assert.That(Logger.GetLogLevel(category), Is.Not.EqualTo(LogLevel.Error)); + Assert.That(RealmLogger.GetLogLevel(category), Is.Not.EqualTo(LogLevel.Error)); } - Logger.SetLogLevel(LogLevel.Error, LogCategory.Realm.Storage); + RealmLogger.SetLogLevel(LogLevel.Error, LogCategory.Realm.Storage); foreach (var category in storageCategories) { - Assert.That(Logger.GetLogLevel(category), Is.EqualTo(LogLevel.Error)); + Assert.That(RealmLogger.GetLogLevel(category), Is.EqualTo(LogLevel.Error)); } } @@ -113,11 +163,11 @@ public void Logger_SetsLogLevelAtSubcategories() public void Logger_WhenUsingLogLevelSetter_OverwritesCategory() { var category = LogCategory.Realm.Storage; - Logger.SetLogLevel(LogLevel.Error, category); - Assert.That(Logger.GetLogLevel(category), Is.EqualTo(LogLevel.Error)); + RealmLogger.SetLogLevel(LogLevel.Error, category); + Assert.That(RealmLogger.GetLogLevel(category), Is.EqualTo(LogLevel.Error)); - Logger.LogLevel = LogLevel.All; - Assert.That(Logger.GetLogLevel(category), Is.EqualTo(LogLevel.All)); + RealmLogger.LogLevel = LogLevel.All; + Assert.That(RealmLogger.GetLogLevel(category), Is.EqualTo(LogLevel.All)); } [TestCase(LogLevel.Error)] @@ -129,12 +179,12 @@ public void Logger_WhenLevelIsSet_LogsOnlyExpectedLevels(LogLevel level) foreach (var category in categories) { var messages = new List(); - Logger.Default = Logger.Function(message => messages.Add(message)); - Logger.SetLogLevel(level, category); + RealmLogger.Default = RealmLogger.Function(message => messages.Add(message)); + RealmLogger.SetLogLevel(level, category); - Logger.LogDefault(level - 1, "This is at level - 1", category); - Logger.LogDefault(level, "This is at the same level", category); - Logger.LogDefault(level + 1, "This is at level + 1", category); + RealmLogger.LogDefault(level - 1, category, "This is at level - 1"); + RealmLogger.LogDefault(level, category, "This is at the same level"); + RealmLogger.LogDefault(level + 1, category, "This is at level + 1"); Assert.That(messages.Count, Is.EqualTo(2)); AssertLogMessageContains(messages[0], level, category, "This is at the same level"); @@ -149,9 +199,9 @@ public void Logger_LogsAtGivenCategory() foreach (var category in categories) { var messages = new List(); - Logger.Default = Logger.Function((message) => messages.Add(message)); + RealmLogger.Default = RealmLogger.Function((message) => messages.Add(message)); - Logger.LogDefault(LogLevel.Warn, "A log message", category); + RealmLogger.LogDefault(LogLevel.Warn, category, "A log message"); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, category, "A log message"); @@ -162,9 +212,9 @@ public void Logger_LogsAtGivenCategory() public void Logger_LogsSdkCategoryByDefault() { var messages = new List(); - Logger.Default = Logger.Function((message) => messages.Add(message)); + RealmLogger.Default = RealmLogger.Function((message) => messages.Add(message)); - Logger.LogDefault(LogLevel.Warn, "A log message"); + RealmLogger.LogDefault(LogLevel.Warn, "A log message"); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); @@ -174,9 +224,9 @@ public void Logger_LogsSdkCategoryByDefault() public void Logger_CallsCustomFunction() { var messages = new List(); - Logger.Default = Logger.Function((level, category, message) => messages.Add(Logger.FormatLog(level, message, category))); + RealmLogger.Default = RealmLogger.Function((level, category, message) => messages.Add(RealmLogger.FormatLog(level, category, message))); - Logger.LogDefault(LogLevel.Warn, "A log message", LogCategory.Realm.SDK); + RealmLogger.LogDefault(LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); @@ -187,9 +237,9 @@ public void Logger_CallsCustomFunction() public void Logger_CallsObsoleteCustomFunction() { var messages = new List(); - Logger.Default = Logger.Function((level, message) => messages.Add(Logger.FormatLog(level, message, LogCategory.Realm.SDK))); + RealmLogger.Default = RealmLogger.Function((level, message) => messages.Add(RealmLogger.FormatLog(level, LogCategory.Realm.SDK, message))); - Logger.LogDefault(LogLevel.Warn, "A log message"); + RealmLogger.LogDefault(LogLevel.Warn, "A log message"); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); @@ -225,17 +275,17 @@ public void FileLogger() { var tempFilePath = Path.GetTempFileName(); - Logger.SetLogLevel(LogLevel.All); - Logger.Default = Logger.File(tempFilePath); + RealmLogger.SetLogLevel(LogLevel.All); + RealmLogger.Default = RealmLogger.File(tempFilePath); var warnMessage = "This is very dangerous!"; var debugMessage = "This is a debug message!"; var errorMessage = "This is an error!"; var timeString = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd"); - Logger.LogDefault(LogLevel.Warn, warnMessage); - Logger.LogDefault(LogLevel.Debug, debugMessage); - Logger.LogDefault(LogLevel.Error, errorMessage); + RealmLogger.LogDefault(LogLevel.Warn, warnMessage); + RealmLogger.LogDefault(LogLevel.Debug, debugMessage); + RealmLogger.LogDefault(LogLevel.Error, errorMessage); var loggedStrings = File.ReadAllLines(tempFilePath); diff --git a/Tests/Realm.Tests/Database/NotificationTests.cs b/Tests/Realm.Tests/Database/NotificationTests.cs index 96e99f754e..91d9796220 100644 --- a/Tests/Realm.Tests/Database/NotificationTests.cs +++ b/Tests/Realm.Tests/Database/NotificationTests.cs @@ -53,8 +53,8 @@ public void ShouldTriggerRealmChangedEvent() [Test] public void RealmError_WhenNoSubscribers_OutputsMessageInConsole() { - var logger = new Logger.InMemoryLogger(); - Logger.Default = logger; + var logger = new RealmLogger.InMemoryLogger(); + RealmLogger.Default = logger; _realm.NotifyError(new Exception()); Assert.That(logger.GetLog(), Does.Contain("exception").And.Contains("Realm.Error")); diff --git a/Tests/Realm.Tests/RealmTest.cs b/Tests/Realm.Tests/RealmTest.cs index 27550349e6..593eaec391 100644 --- a/Tests/Realm.Tests/RealmTest.cs +++ b/Tests/Realm.Tests/RealmTest.cs @@ -33,8 +33,8 @@ public abstract class RealmTest { private readonly ConcurrentQueue> _realms = new(); private readonly LogCategory _originalLogCategory = LogCategory.Realm; - private readonly LogLevel _originalLogLevel = Logger.GetLogLevel(LogCategory.Realm); - private Logger _originalLogger = null!; + private readonly LogLevel _originalLogLevel = RealmLogger.GetLogLevel(LogCategory.Realm); + private RealmLogger _originalLogger = null!; private bool _isSetup; @@ -58,7 +58,7 @@ public void SetUp() { if (!_isSetup) { - _originalLogger = Logger.Default; + _originalLogger = RealmLogger.Default; if (OverrideDefaultConfig) { @@ -86,8 +86,8 @@ public void TearDown() { CustomTearDown(); - Logger.Default = _originalLogger; - Logger.SetLogLevel(_originalLogLevel, _originalLogCategory); + RealmLogger.Default = _originalLogger; + RealmLogger.SetLogLevel(_originalLogLevel, _originalLogCategory); #pragma warning disable CS0618 // Type or member is obsolete Realm.UseLegacyGuidRepresentation = false; diff --git a/Tests/Realm.Tests/Sync/AppTests.cs b/Tests/Realm.Tests/Sync/AppTests.cs index 0763774bb5..5a538b4d8e 100644 --- a/Tests/Realm.Tests/Sync/AppTests.cs +++ b/Tests/Realm.Tests/Sync/AppTests.cs @@ -167,9 +167,9 @@ public void RealmConfiguration_WithCustomLogger_LogsSyncOperations(LogLevel logL { SyncTestHelpers.RunBaasTestAsync(async () => { - Logger.SetLogLevel(logLevel); - var logger = new Logger.InMemoryLogger(); - Logger.Default = logger; + RealmLogger.SetLogLevel(logLevel); + var logger = new RealmLogger.InMemoryLogger(); + RealmLogger.Default = logger; var config = await GetIntegrationConfigAsync(Guid.NewGuid().ToString()); using var realm = await GetRealmAsync(config); diff --git a/Tests/Realm.Tests/Sync/SyncTestHelpers.cs b/Tests/Realm.Tests/Sync/SyncTestHelpers.cs index 2cb11f7873..40a0546ca8 100644 --- a/Tests/Realm.Tests/Sync/SyncTestHelpers.cs +++ b/Tests/Realm.Tests/Sync/SyncTestHelpers.cs @@ -142,10 +142,10 @@ public static (string[] RemainingArgs, IDisposable? Logger) SetLoggerFromArgs(st var logLevel = (LogLevel)Enum.Parse(typeof(LogLevel), extracted.RealmLogLevel!); TestHelpers.Output.WriteLine($"Setting log level to {logLevel}"); - Logger.SetLogLevel(logLevel); + RealmLogger.SetLogLevel(logLevel); } - Logger.AsyncFileLogger? logger = null; + RealmLogger.AsyncFileLogger? logger = null; if (!string.IsNullOrEmpty(extracted.RealmLogFile)) { if (!Process.GetCurrentProcess().ProcessName.ToLower().Contains("testhost")) @@ -153,14 +153,14 @@ public static (string[] RemainingArgs, IDisposable? Logger) SetLoggerFromArgs(st TestHelpers.Output.WriteLine($"Setting sync logger to file: {extracted.RealmLogFile}"); // We're running in a test runner, so we need to use the sync logger - Logger.Default = Logger.File(extracted.RealmLogFile!); + RealmLogger.Default = RealmLogger.File(extracted.RealmLogFile!); } else { TestHelpers.Output.WriteLine($"Setting async logger to file: {extracted.RealmLogFile}"); // We're running standalone (likely on CI), so we use the async logger - Logger.Default = logger = new Logger.AsyncFileLogger(extracted.RealmLogFile!); + RealmLogger.Default = logger = new RealmLogger.AsyncFileLogger(extracted.RealmLogFile!); } } diff --git a/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs b/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs index 1c1b199da7..c2bd1259a6 100644 --- a/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs +++ b/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs @@ -214,8 +214,8 @@ public void GetInstanceAsync_WithOnProgressThrowing_ReportsErrorToLogs() await PopulateData(config); - var logger = new Logger.InMemoryLogger(); - Logger.Default = logger; + var logger = new RealmLogger.InMemoryLogger(); + RealmLogger.Default = logger; config = await GetIntegrationConfigAsync((string?)config.Partition); config.OnProgress = _ => throw new Exception("Exception in OnProgress"); @@ -726,9 +726,9 @@ public void RealmDispose_ClosesSessions() [Test] public void SyncTimeouts_ArePassedCorrectlyToCore() { - var logger = new Logger.InMemoryLogger(); - Logger.Default = logger; - Logger.SetLogLevel(LogLevel.Debug); + var logger = new RealmLogger.InMemoryLogger(); + RealmLogger.Default = logger; + RealmLogger.SetLogLevel(LogLevel.Debug); SyncTestHelpers.RunBaasTestAsync(async () => { @@ -803,7 +803,7 @@ public void SyncLogger_WhenLevelChanges_LogsAtNewLevel() } var regex = new Regex("Connection\\[\\d+] Session\\[\\d+]"); - var logger = Logger.Function((level, msg) => + var logger = RealmLogger.Function((level, msg) => { if (regex.IsMatch(msg)) { @@ -811,8 +811,8 @@ public void SyncLogger_WhenLevelChanges_LogsAtNewLevel() } }); - Logger.SetLogLevel(LogLevel.Info); - Logger.Default = logger; + RealmLogger.SetLogLevel(LogLevel.Info); + RealmLogger.Default = logger; SyncTestHelpers.RunBaasTestAsync(async () => { @@ -824,7 +824,7 @@ public void SyncLogger_WhenLevelChanges_LogsAtNewLevel() Assert.That(initialInfoLogs, Is.GreaterThan(0)); Assert.That(logs[LogLevel.Debug].Count, Is.EqualTo(0)); - Logger.SetLogLevel(LogLevel.Debug); + RealmLogger.SetLogLevel(LogLevel.Debug); realm.Write(() => { From 66cdff59b4a73b8a996812b050a30b41a5f67d52 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Thu, 11 Jul 2024 11:57:33 +0200 Subject: [PATCH 34/43] Check log level for 'Realm.SDK' in 'SyncSocketProvider'. --- Realm/Realm/Logging/LogCategory.cs | 1 - Realm/Realm/Native/SyncSocketProvider.WebSocket.cs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Realm/Realm/Logging/LogCategory.cs b/Realm/Realm/Logging/LogCategory.cs index 141c0ca811..eefd0f39f6 100644 --- a/Realm/Realm/Logging/LogCategory.cs +++ b/Realm/Realm/Logging/LogCategory.cs @@ -115,7 +115,6 @@ public class RealmLogCategory : LogCategory /// public LogCategory App { get; } - // TODO(lj): Prefer `SDK` or `Sdk` for c#? /// /// Gets the category for receiving log messages pertaining to the SDK. /// diff --git a/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs b/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs index 64b20c29e3..2af8ce6d76 100644 --- a/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs +++ b/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs @@ -28,7 +28,6 @@ namespace Realms.Native; -// TODO(lj): For the logs happening in this class, should we log for `LogCategory.Realm.Sync.Client`? internal partial class SyncSocketProvider { private class Socket : IDisposable @@ -186,8 +185,7 @@ private static void FormatExceptionForLogging(Exception ex, StringBuilder builde builder.AppendFormat("{0}: {1}", ex.GetType().FullName, ex.Message); builder.AppendLine(); - // TODO(lj): May update to log for a specific category. - if (RealmLogger.GetLogLevel() >= LogLevel.Trace && !string.IsNullOrEmpty(ex.StackTrace)) + if (RealmLogger.GetLogLevel(LogCategory.Realm.SDK) >= LogLevel.Trace && !string.IsNullOrEmpty(ex.StackTrace)) { builder.Append(indentation); var indentedTrace = ex.StackTrace.Replace(Environment.NewLine, Environment.NewLine + indentation); From b4efe0522607042027805b6d012c850e0d9614f3 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Thu, 11 Jul 2024 12:21:21 +0200 Subject: [PATCH 35/43] Move marshalling from 'MarshaledVector' onto 'TypeErasedMarshaledVector'. --- wrappers/src/marshalling.hpp | 13 ++++++------- wrappers/src/shared_realm_cs.cpp | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/wrappers/src/marshalling.hpp b/wrappers/src/marshalling.hpp index 96b1b7abc4..337b775b25 100644 --- a/wrappers/src/marshalling.hpp +++ b/wrappers/src/marshalling.hpp @@ -31,10 +31,15 @@ namespace realm::binding { /// A struct used when marshaling of `MarshaledVector` cannot be /// compiled, e.g. for MSVC when returning a `MarshaledVector` from /// CPP directly, as compared to when nested within another struct. -struct AnyMarshaledVector +struct TypeErasedMarshaledVector { const void* items; size_t count; + + template + static TypeErasedMarshaledVector for_marshalling(const std::vector& vector) { + return {vector.data(), vector.size()}; + } }; template @@ -95,12 +100,6 @@ struct MarshaledVector iterator begin() const noexcept { return {items}; } iterator end() const noexcept { return {items + count}; } - - /// Needed for MSVC when returning a `MarshaledVector` from CPP - /// directly, as compared to when nested within another struct. - AnyMarshaledVector for_any_type_marshalling() const { - return {items, count}; - } }; enum class realm_value_type : uint8_t { diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index fd65e31302..3b9aeaaf08 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -306,7 +306,7 @@ REALM_EXPORT void shared_realm_set_log_level(Logger::Level level, uint16_t* cate LogCategory::get_category(category_name).set_default_level_threshold(level); } -REALM_EXPORT AnyMarshaledVector shared_realm_get_log_category_names() { +REALM_EXPORT TypeErasedMarshaledVector shared_realm_get_log_category_names() { const auto names = LogCategory::get_category_names(); // Declare the vector as static in order to make it a globally allocated // and keep the vector alive beyond this call. @@ -320,7 +320,7 @@ REALM_EXPORT AnyMarshaledVector shared_realm_get_log_category_names() { } } - return MarshaledVector(result).for_any_type_marshalling(); + return TypeErasedMarshaledVector::for_marshalling(result); } REALM_EXPORT SharedRealm* shared_realm_open(Configuration configuration, NativeException::Marshallable& ex) From c66bdf16a6b9929801da4ef15b9bbdd043e96256 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Thu, 11 Jul 2024 12:32:05 +0200 Subject: [PATCH 36/43] Minor update to API docs. --- Realm/Realm.UnityUtils/UnityLogger.cs | 3 +++ Realm/Realm/Logging/LogCategory.cs | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Realm/Realm.UnityUtils/UnityLogger.cs b/Realm/Realm.UnityUtils/UnityLogger.cs index 7be96ce55b..4f2713179c 100644 --- a/Realm/Realm.UnityUtils/UnityLogger.cs +++ b/Realm/Realm.UnityUtils/UnityLogger.cs @@ -20,6 +20,9 @@ namespace UnityUtils { + /// + /// A that outputs messages via UnityEngine. + /// public class UnityLogger : RealmLogger { protected override void LogImpl(LogLevel level, LogCategory category, string message) diff --git a/Realm/Realm/Logging/LogCategory.cs b/Realm/Realm/Logging/LogCategory.cs index eefd0f39f6..75095f31ba 100644 --- a/Realm/Realm/Logging/LogCategory.cs +++ b/Realm/Realm/Logging/LogCategory.cs @@ -26,6 +26,8 @@ namespace Realms.Logging /// logger. The will always be set for a specific category. /// Setting the log level for one category will automatically set the same level /// for all of its subcategories. + ///

+ /// The category hierarchy is the following: /// /// Realm /// ├─► Storage @@ -41,7 +43,7 @@ namespace Realms.Logging /// │ │ └─► Reset /// │ └─► Server /// ├─► App - /// └─► Sdk + /// └─► SDK /// ///
/// From 53074145a5b82fd5117d73e603623e374ed44fe4 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Thu, 11 Jul 2024 12:48:15 +0200 Subject: [PATCH 37/43] Update 'cref's to resolve doc links. --- Realm/Realm/Logging/Logger.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index 5a57fb83e5..672cc4207a 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -30,7 +30,7 @@ namespace Realms.Logging public abstract class Logger : RealmLogger { /// - /// The internal implementation being called from . + /// The internal implementation being called from . /// /// The criticality level for the message. /// The message to log. @@ -208,7 +208,7 @@ public void Log(LogLevel level, LogCategory category, string message) } /// - /// The internal implementation being called from . + /// The internal implementation being called from . /// /// The criticality level for the message. /// The category for the message. From e7de88ac2090605d4e6fa64e01fee4b025370eb7 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Thu, 11 Jul 2024 13:09:01 +0200 Subject: [PATCH 38/43] Add a log API for Core-only messages to avoid checking level. --- Realm/Realm/Handles/SharedRealmHandle.cs | 2 +- Realm/Realm/Logging/Logger.cs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index e3321ededd..99eeec6a96 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -851,7 +851,7 @@ private static void OnDisposeGCHandle(IntPtr handle) [MonoPInvokeCallback(typeof(NativeMethods.LogMessageCallback))] private static void LogMessage(StringValue message, LogLevel level, StringValue categoryName) { - RealmLogger.LogDefault(level, LogCategory.FromName(categoryName!), message!); + RealmLogger.CoreLogDefault(level, LogCategory.FromName(categoryName!), message!); } [MonoPInvokeCallback(typeof(NativeMethods.MigrationCallback))] diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index 672cc4207a..6e90067444 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -174,6 +174,8 @@ protected RealmLogger() internal static void LogDefault(LogLevel level, LogCategory category, string message) => Default?.Log(level, category, message); + internal static void CoreLogDefault(LogLevel level, LogCategory category, string message) => Default?.CoreLog(level, category, message); + /// /// Log a message at the supplied level and default category . /// @@ -207,6 +209,22 @@ public void Log(LogLevel level, LogCategory category, string message) } } + /// + /// Log a message from Core that does not require checking the current + /// level as that check has already been performed by Core. + /// + private void CoreLog(LogLevel level, LogCategory category, string message) + { + try + { + LogImpl(level, category, message); + } + catch (Exception ex) + { + Console.Log(LogLevel.Error, $"An exception occurred while trying to log the message: '{message}' at level: '{level}' in category: '{category}'. Error: {ex}"); + } + } + /// /// The internal implementation being called from . /// From f2a938d2b7c8a9b37e8019c79916c0d71bf42b02 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Thu, 11 Jul 2024 13:21:27 +0200 Subject: [PATCH 39/43] Make arg order in wrapper consistent. --- Realm/Realm/Handles/SharedRealmHandle.cs | 5 ++--- wrappers/src/shared_realm_cs.cpp | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index 99eeec6a96..9b508df999 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -75,9 +75,8 @@ public struct CategoryNamesContainer [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void DisposeGCHandleCallback(IntPtr handle); - // TODO(lj): Update arg order to be the same across the SDK. [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void LogMessageCallback(StringValue message, LogLevel level, StringValue categoryName); + public delegate void LogMessageCallback(LogLevel level, StringValue categoryName, StringValue message); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void HandleTaskCompletionCallback(IntPtr tcs_ptr, [MarshalAs(UnmanagedType.U1)] bool invoke_async, NativeException ex); @@ -849,7 +848,7 @@ private static void OnDisposeGCHandle(IntPtr handle) } [MonoPInvokeCallback(typeof(NativeMethods.LogMessageCallback))] - private static void LogMessage(StringValue message, LogLevel level, StringValue categoryName) + private static void LogMessage(LogLevel level, StringValue categoryName, StringValue message) { RealmLogger.CoreLogDefault(level, LogCategory.FromName(categoryName!), message!); } diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index 3b9aeaaf08..e2a6e33c00 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -48,8 +48,7 @@ using namespace realm::util; using OpenRealmCallbackT = void(void* task_completion_source, ThreadSafeReference* ref, NativeException::Marshallable ex); using RealmChangedT = void(void* managed_state_handle); using ReleaseGCHandleT = void(void* managed_handle); -// TODO(lj): Update arg order to be the same across the SDK. -using LogMessageT = void(realm_string_t message, util::Logger::Level level, realm_string_t category_name); +using LogMessageT = void(util::Logger::Level level, realm_string_t category_name, realm_string_t message); using MigrationCallbackT = void*(realm::SharedRealm* old_realm, realm::SharedRealm* new_realm, Schema* migration_schema, MarshaledVector, uint64_t schema_version, void* managed_migration_handle); using HandleTaskCompletionCallbackT = void(void* tcs_ptr, bool invoke_async, NativeException::Marshallable ex); using SharedSyncSession = std::shared_ptr; @@ -100,7 +99,7 @@ namespace binding { protected: void do_log(const LogCategory& category, Level level, const std::string& message) override final { - s_log_message(to_capi(message), level, to_capi(category.get_name())); + s_log_message(level, to_capi(category.get_name()), to_capi(message)); } }; } From a3e3deb2922e4dba9a199c7af50a26a7461855ff Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:02:04 +0200 Subject: [PATCH 40/43] Call non-level-check log impl from 'Log()'. --- Realm/Realm/Logging/Logger.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index 6e90067444..2a33af5d62 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -199,14 +199,7 @@ public void Log(LogLevel level, LogCategory category, string message) return; } - try - { - LogImpl(level, category, message); - } - catch (Exception ex) - { - Console.Log(LogLevel.Error, $"An exception occurred while trying to log the message: '{message}' at level: '{level}' in category: '{category}'. Error: {ex}"); - } + CoreLog(level, category, message); } /// From db579eebc2cb66d7e5cc809c7b063b0a38d23349 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:11:21 +0200 Subject: [PATCH 41/43] Rename 'CoreLog' to 'LogAnyLevel' to suit current call pattern. --- Realm/Realm/Logging/Logger.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index 2a33af5d62..55f26155f5 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -174,7 +174,7 @@ protected RealmLogger() internal static void LogDefault(LogLevel level, LogCategory category, string message) => Default?.Log(level, category, message); - internal static void CoreLogDefault(LogLevel level, LogCategory category, string message) => Default?.CoreLog(level, category, message); + internal static void CoreLogDefault(LogLevel level, LogCategory category, string message) => Default?.LogAnyLevel(level, category, message); /// /// Log a message at the supplied level and default category . @@ -199,14 +199,14 @@ public void Log(LogLevel level, LogCategory category, string message) return; } - CoreLog(level, category, message); + LogAnyLevel(level, category, message); } /// - /// Log a message from Core that does not require checking the current - /// level as that check has already been performed by Core. + /// Log a message without calling into Core to check the current level. Logs from + /// Core should always call this API as they already check the level prior to notifying. /// - private void CoreLog(LogLevel level, LogCategory category, string message) + private void LogAnyLevel(LogLevel level, LogCategory category, string message) { try { From cf653505effb19d9d1ec5857f6673b7dba8999d3 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:30:29 +0200 Subject: [PATCH 42/43] Remove internal 'LogDefault()'. --- Realm/Realm/Handles/SharedRealmHandle.cs | 2 +- Realm/Realm/Helpers/Argument.cs | 2 +- Realm/Realm/Logging/Logger.cs | 8 +----- Realm/Realm/Native/NativeCommon.cs | 6 ++-- .../Native/SyncSocketProvider.EventLoop.cs | 18 ++++++------ .../Native/SyncSocketProvider.WebSocket.cs | 16 +++++------ Realm/Realm/Native/SyncSocketProvider.cs | 4 +-- Realm/Realm/Realm.cs | 2 +- Tests/Realm.Tests/Database/LoggerTests.cs | 28 +++++++++---------- 9 files changed, 40 insertions(+), 46 deletions(-) diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index 9b508df999..17962d92b2 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -850,7 +850,7 @@ private static void OnDisposeGCHandle(IntPtr handle) [MonoPInvokeCallback(typeof(NativeMethods.LogMessageCallback))] private static void LogMessage(LogLevel level, StringValue categoryName, StringValue message) { - RealmLogger.CoreLogDefault(level, LogCategory.FromName(categoryName!), message!); + RealmLogger.Default.LogAnyLevel(level, LogCategory.FromName(categoryName!), message!); } [MonoPInvokeCallback(typeof(NativeMethods.MigrationCallback))] diff --git a/Realm/Realm/Helpers/Argument.cs b/Realm/Realm/Helpers/Argument.cs index 6afba4e8a3..bb674114b7 100644 --- a/Realm/Realm/Helpers/Argument.cs +++ b/Realm/Realm/Helpers/Argument.cs @@ -95,7 +95,7 @@ public static T ValidateNotNull(T value, string paramName) public static void AssertDebug(string message) { - RealmLogger.LogDefault(LogLevel.Error, $"{message} {OpenIssueText}"); + RealmLogger.Default.Log(LogLevel.Error, $"{message} {OpenIssueText}"); #if DEBUG throw new Exception(message); diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index 55f26155f5..da28f9a331 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -170,12 +170,6 @@ protected RealmLogger() _gcHandle = new Lazy(() => GCHandle.Alloc(this)); } - internal static void LogDefault(LogLevel level, string message) => Default?.Log(level, message); - - internal static void LogDefault(LogLevel level, LogCategory category, string message) => Default?.Log(level, category, message); - - internal static void CoreLogDefault(LogLevel level, LogCategory category, string message) => Default?.LogAnyLevel(level, category, message); - /// /// Log a message at the supplied level and default category . /// @@ -206,7 +200,7 @@ public void Log(LogLevel level, LogCategory category, string message) /// Log a message without calling into Core to check the current level. Logs from /// Core should always call this API as they already check the level prior to notifying. /// - private void LogAnyLevel(LogLevel level, LogCategory category, string message) + internal void LogAnyLevel(LogLevel level, LogCategory category, string message) { try { diff --git a/Realm/Realm/Native/NativeCommon.cs b/Realm/Realm/Native/NativeCommon.cs index f33b2ffaab..9dc192fc9e 100644 --- a/Realm/Realm/Native/NativeCommon.cs +++ b/Realm/Realm/Native/NativeCommon.cs @@ -96,7 +96,7 @@ public static void CleanupNativeResources(string reason) { if (Interlocked.CompareExchange(ref _isInitialized, 0, 1) == 1) { - RealmLogger.LogDefault(LogLevel.Info, $"Realm: Force closing all native instances: {reason}"); + RealmLogger.Default.Log(LogLevel.Info, $"Realm: Force closing all native instances: {reason}"); var sw = new Stopwatch(); sw.Start(); @@ -106,12 +106,12 @@ public static void CleanupNativeResources(string reason) SharedRealmHandle.ForceCloseNativeRealms(); sw.Stop(); - RealmLogger.LogDefault(LogLevel.Info, $"Realm: Closed all native instances in {sw.ElapsedMilliseconds} ms."); + RealmLogger.Default.Log(LogLevel.Info, $"Realm: Closed all native instances in {sw.ElapsedMilliseconds} ms."); } } catch (Exception ex) { - RealmLogger.LogDefault(LogLevel.Error, $"Realm: Failed to close all native instances. You may need to restart your app. Error: {ex}"); + RealmLogger.Default.Log(LogLevel.Error, $"Realm: Failed to close all native instances. You may need to restart your app. Error: {ex}"); } } diff --git a/Realm/Realm/Native/SyncSocketProvider.EventLoop.cs b/Realm/Realm/Native/SyncSocketProvider.EventLoop.cs index bf85babd7f..d74a5f5161 100644 --- a/Realm/Realm/Native/SyncSocketProvider.EventLoop.cs +++ b/Realm/Realm/Native/SyncSocketProvider.EventLoop.cs @@ -32,7 +32,7 @@ private class Timer internal Timer(TimeSpan delay, IntPtr nativeCallback, ChannelWriter workQueue) { - RealmLogger.LogDefault(LogLevel.Trace, $"Creating timer with delay {delay} and target {nativeCallback}."); + RealmLogger.Default.Log(LogLevel.Trace, $"Creating timer with delay {delay} and target {nativeCallback}."); var cancellationToken = _cts.Token; Task.Delay(delay, cancellationToken).ContinueWith(async _ => { @@ -42,7 +42,7 @@ internal Timer(TimeSpan delay, IntPtr nativeCallback, ChannelWriter workQ internal void Cancel() { - RealmLogger.LogDefault(LogLevel.Trace, $"Canceling timer."); + RealmLogger.Default.Log(LogLevel.Trace, $"Canceling timer."); _cts.Cancel(); _cts.Dispose(); } @@ -72,7 +72,7 @@ public void Execute() { if (cancellationToken.IsCancellationRequested) { - RealmLogger.LogDefault(LogLevel.Trace, "Deleting EventLoopWork callback only because event loop was cancelled."); + RealmLogger.Default.Log(LogLevel.Trace, "Deleting EventLoopWork callback only because event loop was cancelled."); NativeMethods.delete_callback(nativeCallback); return; } @@ -83,7 +83,7 @@ public void Execute() private static void RunCallback(IntPtr nativeCallback, Status status) { - RealmLogger.LogDefault(LogLevel.Trace, $"SyncSocketProvider running native callback {nativeCallback} with status {status.Code} \"{status.Reason}\"."); + RealmLogger.Default.Log(LogLevel.Trace, $"SyncSocketProvider running native callback {nativeCallback} with status {status.Code} \"{status.Reason}\"."); using var arena = new Arena(); NativeMethods.run_callback(nativeCallback, status.Code, StringValue.AllocateFrom(status.Reason, arena)); @@ -91,13 +91,13 @@ private static void RunCallback(IntPtr nativeCallback, Status status) private async Task PostWorkAsync(IntPtr nativeCallback) { - RealmLogger.LogDefault(LogLevel.Trace, "Posting work to SyncSocketProvider event loop."); + RealmLogger.Default.Log(LogLevel.Trace, "Posting work to SyncSocketProvider event loop."); await _workQueue.Writer.WriteAsync(new EventLoopWork(nativeCallback, _cts.Token)); } private async partial Task WorkThread() { - RealmLogger.LogDefault(LogLevel.Trace, "Starting SyncSocketProvider event loop."); + RealmLogger.Default.Log(LogLevel.Trace, "Starting SyncSocketProvider event loop."); try { while (await _workQueue.Reader.WaitToReadAsync()) @@ -110,15 +110,15 @@ private async partial Task WorkThread() } catch (Exception e) { - RealmLogger.LogDefault(LogLevel.Error, $"Error occurred in SyncSocketProvider event loop {e.GetType().FullName}: {e.Message}"); + RealmLogger.Default.Log(LogLevel.Error, $"Error occurred in SyncSocketProvider event loop {e.GetType().FullName}: {e.Message}"); if (!string.IsNullOrEmpty(e.StackTrace)) { - RealmLogger.LogDefault(LogLevel.Trace, e.StackTrace); + RealmLogger.Default.Log(LogLevel.Trace, e.StackTrace); } throw; } - RealmLogger.LogDefault(LogLevel.Trace, "Exiting SyncSocketProvider event loop."); + RealmLogger.Default.Log(LogLevel.Trace, "Exiting SyncSocketProvider event loop."); } } diff --git a/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs b/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs index 2af8ce6d76..5aa91cc811 100644 --- a/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs +++ b/Realm/Realm/Native/SyncSocketProvider.WebSocket.cs @@ -45,7 +45,7 @@ private class Socket : IDisposable internal Socket(ClientWebSocket webSocket, IntPtr observer, ChannelWriter workQueue, Uri uri) { - RealmLogger.LogDefault(LogLevel.Trace, $"Creating a WebSocket to {uri.GetLeftPart(UriPartial.Path)}"); + RealmLogger.Default.Log(LogLevel.Trace, $"Creating a WebSocket to {uri.GetLeftPart(UriPartial.Path)}"); _webSocket = webSocket; _observer = observer; _workQueue = workQueue; @@ -56,7 +56,7 @@ internal Socket(ClientWebSocket webSocket, IntPtr observer, ChannelWriter private async Task ReadThread() { - RealmLogger.LogDefault(LogLevel.Trace, "Entering WebSocket event loop."); + RealmLogger.Default.Log(LogLevel.Trace, "Entering WebSocket event loop."); try { @@ -67,7 +67,7 @@ private async Task ReadThread() { var builder = new StringBuilder(); FormatExceptionForLogging(e, builder); - RealmLogger.LogDefault(LogLevel.Error, $"Error establishing WebSocket connection {builder}"); + RealmLogger.Default.Log(LogLevel.Error, $"Error establishing WebSocket connection {builder}"); await _workQueue.WriteAsync(new WebSocketClosedWork(false, (WebSocketCloseStatus)RLM_ERR_WEBSOCKET_CONNECTION_FAILED, e.Message, _observer, _cancellationToken)); return; @@ -92,11 +92,11 @@ private async Task ReadThread() break; case WebSocketMessageType.Close: - RealmLogger.LogDefault(LogLevel.Trace, $"WebSocket closed with status {result.CloseStatus!.Value} and description \"{result.CloseStatusDescription}\""); + RealmLogger.Default.Log(LogLevel.Trace, $"WebSocket closed with status {result.CloseStatus!.Value} and description \"{result.CloseStatusDescription}\""); await _workQueue.WriteAsync(new WebSocketClosedWork(clean: true, result.CloseStatus!.Value, result.CloseStatusDescription!, _observer, _cancellationToken)); break; default: - RealmLogger.LogDefault(LogLevel.Trace, $"Received unexpected text WebSocket message: {Encoding.UTF8.GetString(buffer, 0, result.Count)}"); + RealmLogger.Default.Log(LogLevel.Trace, $"Received unexpected text WebSocket message: {Encoding.UTF8.GetString(buffer, 0, result.Count)}"); break; } } @@ -104,7 +104,7 @@ private async Task ReadThread() { var builder = new StringBuilder(); FormatExceptionForLogging(e, builder); - RealmLogger.LogDefault(LogLevel.Error, $"Error reading from WebSocket {builder}"); + RealmLogger.Default.Log(LogLevel.Error, $"Error reading from WebSocket {builder}"); await _workQueue.WriteAsync(new WebSocketClosedWork(false, (WebSocketCloseStatus)RLM_ERR_WEBSOCKET_READ_ERROR, e.Message, _observer, _cancellationToken)); return; @@ -130,7 +130,7 @@ public async void Write(BinaryValue data, IntPtr native_callback) { var builder = new StringBuilder(); FormatExceptionForLogging(e, builder); - RealmLogger.LogDefault(LogLevel.Error, $"Error writing to WebSocket {builder}"); + RealmLogger.Default.Log(LogLevel.Error, $"Error writing to WebSocket {builder}"); // in case of errors notify the websocket observer and just dispose the callback await _workQueue.WriteAsync(new WebSocketClosedWork(false, (WebSocketCloseStatus)RLM_ERR_WEBSOCKET_WRITE_ERROR, e.Message, _observer, _cancellationToken)); @@ -164,7 +164,7 @@ public async void Dispose() _webSocket.Dispose(); _receiveBuffer.Dispose(); _cts.Dispose(); - RealmLogger.LogDefault(LogLevel.Trace, "Disposing WebSocket."); + RealmLogger.Default.Log(LogLevel.Trace, "Disposing WebSocket."); try { diff --git a/Realm/Realm/Native/SyncSocketProvider.cs b/Realm/Realm/Native/SyncSocketProvider.cs index 48cbb186d7..e281847a9f 100644 --- a/Realm/Realm/Native/SyncSocketProvider.cs +++ b/Realm/Realm/Native/SyncSocketProvider.cs @@ -156,7 +156,7 @@ private interface IWork internal SyncSocketProvider(Action? onWebSocketConnection) { - RealmLogger.LogDefault(LogLevel.Debug, "Creating SyncSocketProvider."); + RealmLogger.Default.Log(LogLevel.Debug, "Creating SyncSocketProvider."); _onWebSocketConnection = onWebSocketConnection; _workQueue = Channel.CreateUnbounded(new() { SingleReader = true }); _workThread = Task.Run(WorkThread); @@ -166,7 +166,7 @@ internal SyncSocketProvider(Action? onWebSocketConnectio public void Dispose() { - RealmLogger.LogDefault(LogLevel.Debug, "Destroying SyncSocketProvider."); + RealmLogger.Default.Log(LogLevel.Debug, "Destroying SyncSocketProvider."); _workQueue.Writer.Complete(); _cts.Cancel(); _cts.Dispose(); diff --git a/Realm/Realm/Realm.cs b/Realm/Realm/Realm.cs index 1a50e2eea3..1aa7bdb83a 100644 --- a/Realm/Realm/Realm.cs +++ b/Realm/Realm/Realm.cs @@ -414,7 +414,7 @@ internal void NotifyError(Exception ex) { if (Error is null) { - RealmLogger.LogDefault(LogLevel.Error, "A realm-level exception has occurred. To handle and react to those, subscribe to the Realm.Error event."); + RealmLogger.Default.Log(LogLevel.Error, "A realm-level exception has occurred. To handle and react to those, subscribe to the Realm.Error event."); } Error?.Invoke(this, new ErrorEventArgs(ex)); diff --git a/Tests/Realm.Tests/Database/LoggerTests.cs b/Tests/Realm.Tests/Database/LoggerTests.cs index da781ca433..522b0f3b72 100644 --- a/Tests/Realm.Tests/Database/LoggerTests.cs +++ b/Tests/Realm.Tests/Database/LoggerTests.cs @@ -83,7 +83,7 @@ public void Logger_CanSetDefaultLoggerToBuiltInLogger() var messages = new List(); RealmLogger.Default = RealmLogger.Function(message => messages.Add(message)); - RealmLogger.LogDefault(LogLevel.Warn, LogCategory.Realm.SDK, "This is very dangerous!"); + RealmLogger.Default.Log(LogLevel.Warn, LogCategory.Realm.SDK, "This is very dangerous!"); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "This is very dangerous!"); @@ -95,7 +95,7 @@ public void Logger_CanSetDefaultLoggerToUserDefinedLogger() var messages = new List(); RealmLogger.Default = new UserDefinedLogger((level, category, message) => messages.Add(RealmLogger.FormatLog(level, category, message))); - RealmLogger.LogDefault(LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); + RealmLogger.Default.Log(LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); @@ -108,7 +108,7 @@ public void ObsoleteLogger_CanSetDefaultLoggerToUserDefinedLogger() var messages = new List(); Logger.Default = new ObsoleteUserDefinedLogger((level, message) => messages.Add(Logger.FormatLog(level, LogCategory.Realm.SDK, message))); - Logger.LogDefault(LogLevel.Warn, "A log message"); + Logger.Default.Log(LogLevel.Warn, "A log message"); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); @@ -120,7 +120,7 @@ public void Logger_SkipsDebugMessagesByDefault() var messages = new List(); RealmLogger.Default = RealmLogger.Function(message => messages.Add(message)); - RealmLogger.LogDefault(LogLevel.Debug, "This is a debug message!"); + RealmLogger.Default.Log(LogLevel.Debug, "This is a debug message!"); Assert.That(messages.Count, Is.EqualTo(0)); } @@ -182,9 +182,9 @@ public void Logger_WhenLevelIsSet_LogsOnlyExpectedLevels(LogLevel level) RealmLogger.Default = RealmLogger.Function(message => messages.Add(message)); RealmLogger.SetLogLevel(level, category); - RealmLogger.LogDefault(level - 1, category, "This is at level - 1"); - RealmLogger.LogDefault(level, category, "This is at the same level"); - RealmLogger.LogDefault(level + 1, category, "This is at level + 1"); + RealmLogger.Default.Log(level - 1, category, "This is at level - 1"); + RealmLogger.Default.Log(level, category, "This is at the same level"); + RealmLogger.Default.Log(level + 1, category, "This is at level + 1"); Assert.That(messages.Count, Is.EqualTo(2)); AssertLogMessageContains(messages[0], level, category, "This is at the same level"); @@ -201,7 +201,7 @@ public void Logger_LogsAtGivenCategory() var messages = new List(); RealmLogger.Default = RealmLogger.Function((message) => messages.Add(message)); - RealmLogger.LogDefault(LogLevel.Warn, category, "A log message"); + RealmLogger.Default.Log(LogLevel.Warn, category, "A log message"); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, category, "A log message"); @@ -214,7 +214,7 @@ public void Logger_LogsSdkCategoryByDefault() var messages = new List(); RealmLogger.Default = RealmLogger.Function((message) => messages.Add(message)); - RealmLogger.LogDefault(LogLevel.Warn, "A log message"); + RealmLogger.Default.Log(LogLevel.Warn, "A log message"); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); @@ -226,7 +226,7 @@ public void Logger_CallsCustomFunction() var messages = new List(); RealmLogger.Default = RealmLogger.Function((level, category, message) => messages.Add(RealmLogger.FormatLog(level, category, message))); - RealmLogger.LogDefault(LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); + RealmLogger.Default.Log(LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); @@ -239,7 +239,7 @@ public void Logger_CallsObsoleteCustomFunction() var messages = new List(); RealmLogger.Default = RealmLogger.Function((level, message) => messages.Add(RealmLogger.FormatLog(level, LogCategory.Realm.SDK, message))); - RealmLogger.LogDefault(LogLevel.Warn, "A log message"); + RealmLogger.Default.Log(LogLevel.Warn, "A log message"); Assert.That(messages.Count, Is.EqualTo(1)); AssertLogMessageContains(messages[0], LogLevel.Warn, LogCategory.Realm.SDK, "A log message"); @@ -283,9 +283,9 @@ public void FileLogger() var errorMessage = "This is an error!"; var timeString = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd"); - RealmLogger.LogDefault(LogLevel.Warn, warnMessage); - RealmLogger.LogDefault(LogLevel.Debug, debugMessage); - RealmLogger.LogDefault(LogLevel.Error, errorMessage); + RealmLogger.Default.Log(LogLevel.Warn, warnMessage); + RealmLogger.Default.Log(LogLevel.Debug, debugMessage); + RealmLogger.Default.Log(LogLevel.Error, errorMessage); var loggedStrings = File.ReadAllLines(tempFilePath); From c0b98fba6324ae35aad61e8f8fb27b8f744c7a25 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:39:00 +0200 Subject: [PATCH 43/43] Update CHANGELOG. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0e706ae73..d43113c056 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ ```csharp RealmLogger.Default = RealmLogger.Function((level, category, message) => /* custom implementation */); ``` - * Added a `RealmLogger.Log()` overload taking a category. If unset, `LogCategory.Realm.SDK` will be used. + * Added a `RealmLogger.Log()` overload taking a category. The pre-existing `Log()` API will implicitly log at `LogCategory.Realm.SDK`. ```csharp RealmLogger.Default.Log(LogLevel.Warn, LogCategory.Realm, "A warning message"); ```