Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Unify the logging API #2276

Merged
merged 4 commits into from
Mar 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@ indent_style = space
tab_width = 4

# New line preferences
end_of_line = lf
LaPeste marked this conversation as resolved.
Show resolved Hide resolved
insert_final_newline = false
insert_final_newline = true

#### .NET Coding Conventions ####

# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = true
file_header_template = unset
file_header_template = ////////////////////////////////////////////////////////////////////////////\n//\n// Copyright 2021 Realm Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the "License")\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an "AS IS" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n////////////////////////////////////////////////////////////////////////////

# this. and Me. preferences
dotnet_style_qualification_for_event = false:silent
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
```
* Added support for value substitution in string based queries. This enables expressions following [this syntax](https://github.com/realm/realm-js/blob/master/docs/tutorials/query-language.md): `realm.All<T>().Filter("field1 = $0 && field2 = $1", 123, "some-string-value")`. (Issue [#1822](https://github.com/realm/realm-dotnet/issues/1822))
* Reduced the size of the native binaries by ~5%. (PR [#2239](https://github.com/realm/realm-dotnet/pull/2239))
* Added a new class - `Logger`, which allows you to override the default logger implementation (previously writing to `stdout` or `stderr`) with a custom one by setting
`Logger.Default`. This replaces `AppConfiguration.CustomLogger` and `AppConfiguration.LogLevel` which will be removed in a future release. The built-in implementations are:
* `Console` - uses the `System.Console` for most projects and `UnityEngine.Debug` for Unity projects.
* `Null` - ignores all messages.
* `Function` - proxies calls to a supplied function.

Custom loggers can derive from the `Logger` class and provide their own implementation for the `Log` method or use `Function` and provide an `Action<string>`. (PR [#2276](https://github.com/realm/realm-dotnet/pull/2276))

### Compatibility
* Realm Studio: 10.0.0 or later.
Expand Down
14 changes: 7 additions & 7 deletions Realm/Realm/Handles/AppHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Realms.Logging;
using Realms.Native;
using Realms.Sync.Exceptions;
using Realms.Sync.Native;
Expand All @@ -37,7 +37,7 @@ private static class NativeMethods
#pragma warning disable IDE1006 // Naming Styles

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void LogMessageCallback(IntPtr managed_handler, byte* message_buf, IntPtr message_len, LogLevel logLevel);
public unsafe delegate void LogMessageCallback(IntPtr managed_handler, PrimitiveValue messageValue, LogLevel logLevel);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void UserCallback(IntPtr tcs_ptr, IntPtr user_ptr, AppError error);
Expand Down Expand Up @@ -293,18 +293,18 @@ protected override void Unbind()
}

[MonoPInvokeCallback(typeof(NativeMethods.LogMessageCallback))]
private static unsafe void HandleLogMessage(IntPtr managedHandler, byte* messageBuffer, IntPtr messageLength, LogLevel level)
private static unsafe void HandleLogMessage(IntPtr managedHandler, PrimitiveValue messageValue, LogLevel level)
{
try
{
var message = Encoding.UTF8.GetString(messageBuffer, (int)messageLength);
var logCallback = (Action<string, LogLevel>)GCHandle.FromIntPtr(managedHandler).Target;
logCallback.Invoke(message, level);
var message = messageValue.AsString();
var logger = (Logger)GCHandle.FromIntPtr(managedHandler).Target;
logger.Log(level, message);
}
catch (Exception ex)
{
var errorMessage = $"An error occurred while trying to log a message: {ex}";
Console.Error.WriteLine(errorMessage);
Logger.Console.Log(LogLevel.Error, errorMessage);
}
}

Expand Down
23 changes: 20 additions & 3 deletions Realm/Realm/Handles/SharedRealmHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using System.Threading.Tasks;
using MongoDB.Bson;
using Realms.Exceptions;
using Realms.Logging;
using Realms.Native;
using Realms.Schema;
using Realms.Sync.Exceptions;
Expand All @@ -49,6 +50,9 @@ private static class NativeMethods
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void OnBindingContextDestructedCallback(IntPtr handle);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LogMessageCallback(PrimitiveValue message, LogLevel level);

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_open", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr open(Configuration configuration,
[MarshalAs(UnmanagedType.LPArray), In] SchemaObject[] objects, int objects_length,
Expand Down Expand Up @@ -135,7 +139,12 @@ private static class NativeMethods
public static extern void get_schema(SharedRealmHandle sharedRealm, IntPtr callback, out NativeException ex);

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_install_callbacks", CallingConvention = CallingConvention.Cdecl)]
public static extern void install_callbacks(NotifyRealmCallback notifyRealmCallback, GetNativeSchemaCallback nativeSchemaCallback, OpenRealmCallback openCallback, OnBindingContextDestructedCallback contextDestructedCallback);
public static extern void install_callbacks(
NotifyRealmCallback notifyRealmCallback,
GetNativeSchemaCallback nativeSchemaCallback,
OpenRealmCallback openCallback,
OnBindingContextDestructedCallback contextDestructedCallback,
LogMessageCallback logMessageCallback);

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_has_changed", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.U1)]
Expand Down Expand Up @@ -165,13 +174,15 @@ static unsafe SharedRealmHandle()
NativeMethods.GetNativeSchemaCallback getNativeSchema = GetNativeSchema;
NativeMethods.OpenRealmCallback openRealm = HandleOpenRealmCallback;
NativeMethods.OnBindingContextDestructedCallback onBindingContextDestructed = OnBindingContextDestructed;
NativeMethods.LogMessageCallback logMessage = LogMessage;

GCHandle.Alloc(notifyRealm);
GCHandle.Alloc(getNativeSchema);
GCHandle.Alloc(openRealm);
GCHandle.Alloc(onBindingContextDestructed);
GCHandle.Alloc(logMessage);

NativeMethods.install_callbacks(notifyRealm, getNativeSchema, openRealm, onBindingContextDestructed);
NativeMethods.install_callbacks(notifyRealm, getNativeSchema, openRealm, onBindingContextDestructed, logMessage);
}

[Preserve]
Expand Down Expand Up @@ -438,7 +449,7 @@ private static unsafe void HandleOpenRealmCallback(IntPtr taskCompletionSource,
}
}

[MonoPInvokeCallbackAttribute(typeof(NativeMethods.OnBindingContextDestructedCallback))]
[MonoPInvokeCallback(typeof(NativeMethods.OnBindingContextDestructedCallback))]
LaPeste marked this conversation as resolved.
Show resolved Hide resolved
public static void OnBindingContextDestructed(IntPtr handle)
{
if (handle != IntPtr.Zero)
Expand All @@ -448,6 +459,12 @@ public static void OnBindingContextDestructed(IntPtr handle)
}
}

[MonoPInvokeCallback(typeof(NativeMethods.LogMessageCallback))]
private static void LogMessage(PrimitiveValue message, LogLevel level)
{
Logger.LogDefault(level, message.AsString());
}

public class SchemaMarshaler
{
public readonly SchemaObject[] Objects;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
//
////////////////////////////////////////////////////////////////////////////

namespace Realms.Sync
namespace Realms.Logging
{
/// <summary>
/// Specifies the criticality level above which messages will be logged
Expand Down
181 changes: 181 additions & 0 deletions Realm/Realm/Logging/Logger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2021 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.Runtime.InteropServices;
using System.Text;
using Realms.Sync;

namespace Realms.Logging
{
/// <summary>
/// A logger that logs messages originating from Realm. The default logger can be replaced by setting <see cref="Default"/>.
/// </summary>
/// <remarks>
/// A few default implementations are provided by <see cref="Console"/>, <see cref="Null"/>, and <see cref="Function(Action{string})"/>, but you
/// can implement your own.
/// </remarks>
public abstract class Logger
{
private readonly Lazy<GCHandle> _gcHandle;

/// <summary>
/// Gets a <see cref="ConsoleLogger"/> that outputs messages to the default console. For most project types, that will be
/// using <see cref="Console.WriteLine()"/> but certain platforms may use different implementations.
/// </summary>
/// <value>A <see cref="Logger"/> instance that outputs to the platform's console.</value>
public static Logger Console { get; internal set; } = new ConsoleLogger();

/// <summary>
/// Gets a <see cref="NullLogger"/> that ignores all messages.
/// </summary>
/// <value>A <see cref="Logger"/> that doesn't output any messages.</value>
public static Logger Null { get; } = new NullLogger();

/// <summary>
/// Gets a <see cref="FunctionLogger"/> that proxies Log calls to the supplied function.
/// </summary>
/// <param name="logFunction">Function to proxy log calls to.</param>
/// <returns>
/// A <see cref="Logger"/> instance that will invoke <paramref name="logFunction"/> for each message.
/// </returns>
public static Logger Function(Action<LogLevel, string> logFunction) => new FunctionLogger(logFunction);

/// <summary>
/// Gets a <see cref="FunctionLogger"/> that proxies Log calls to the supplied function. The message will
/// already be formatted with the default message formatting that includes a timestamp.
/// </summary>
/// <param name="logFunction">Function to proxy log calls to.</param>
/// <returns>
/// A <see cref="Logger"/> instance that will invoke <paramref name="logFunction"/> for each message.
/// </returns>
public static Logger Function(Action<string> logFunction) => new FunctionLogger((level, message) => logFunction(FormatLog(level, message)));

/// <summary>
/// Gets or sets the verbosity of log messages.
/// </summary>
/// <remarks>
/// This replaces the deprecated <see cref="AppConfiguration.LogLevel"/>.
/// </remarks>
/// <value>The log level for Realm-originating messages.</value>
public static LogLevel LogLevel { get; set; } = LogLevel.Info;

/// <summary>
/// Gets or sets a custom <see cref="Logger"/> implementation that will be used by
/// Realm whenever information must be logged.
/// </summary>
/// <remarks>
/// This is the logger that will be used to log diagnostic messages from Sync. It
/// replaces the deprecated <see cref="AppConfiguration.CustomLogger"/>.
/// </remarks>
/// <value>The logger to be used for Realm-originating messages.</value>
public static Logger Default { get; set; } = Console;

internal GCHandle GCHandle => _gcHandle.Value;

// This is only needed for backward compatibility - the App logger sets its own level separately
// Once that is removed, we should use Logger.LogLevel across the board.
[Obsolete("Remove when we remove the AppConfiguration.CustomLogger")]
internal LogLevel _logLevel = LogLevel;

/// <summary>
/// Initializes a new instance of the <see cref="Logger"/> class.
/// </summary>
protected Logger()
{
_gcHandle = new Lazy<GCHandle>(() => GCHandle.Alloc(this));
}

internal static void LogDefault(LogLevel level, string message) => Default?.Log(level, message);

/// <summary>
/// Log a message at the supplied level.
/// </summary>
/// <param name="level">The criticality level for the message.</param>
/// <param name="message">The message to log.</param>
public void Log(LogLevel level, string message)
{
if (level < _logLevel)
{
return;
}

try
{
LogImpl(level, message);
}
catch (Exception ex)
{
Console.Log(LogLevel.Error, $"An exception occurred while trying to log the message: '{message}' at level: {level}. Error: {ex}");
}
}

/// <summary>
/// The internal implementation being called from <see cref="Log"/>.
/// </summary>
/// <param name="level">The criticality level for the message.</param>
/// <param name="message">The message to log.</param>
protected abstract void LogImpl(LogLevel level, string message);

internal static string FormatLog(LogLevel level, string message) => $"{DateTimeOffset.UtcNow:yyyy-MM-dd HH:mm:ss.fff} {level}: {message}";

private class ConsoleLogger : Logger
{
protected override void LogImpl(LogLevel level, string message)
{
System.Console.WriteLine(FormatLog(level, message));
}
}

private class FunctionLogger : Logger
{
private readonly Action<LogLevel, string> _logFunction;

public FunctionLogger(Action<LogLevel, string> logFunction)
{
_logFunction = logFunction;
}

protected override void LogImpl(LogLevel level, string message) => _logFunction(level, message);
}

private class NullLogger : Logger
{
protected override void LogImpl(LogLevel level, string message)
{
}
}

internal class InMemoryLogger : Logger
{
private readonly StringBuilder _builder = new StringBuilder();

protected override void LogImpl(LogLevel level, string message)
{
lock (_builder)
{
_builder.AppendLine(FormatLog(level, message));
}
}

public string GetLog() => _builder.ToString();

public void Clear() => _builder.Clear();
}
}
}
3 changes: 2 additions & 1 deletion Realm/Realm/Native/AppConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

using System;
using System.Runtime.InteropServices;
using Realms.Logging;

namespace Realms.Sync.Native
{
Expand Down Expand Up @@ -107,6 +108,6 @@ internal string LocalAppVersion

internal LogLevel log_level;

internal IntPtr managed_log_callback;
internal IntPtr managed_logger;
}
}
Loading