From ccd6d54b3882fd451e11469ed0f366b7fce77ce3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 11 Jun 2020 16:34:36 +0200 Subject: [PATCH] Add a simple non-serilog debug log sink. --- samples/ControlCatalog.NetCore/Program.cs | 7 +- src/Avalonia.Base/Logging/DebugLogSink.cs | 178 +++++++++++++++++++++ src/Avalonia.Controls/LoggingExtensions.cs | 26 +++ 3 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 src/Avalonia.Base/Logging/DebugLogSink.cs create mode 100644 src/Avalonia.Controls/LoggingExtensions.cs diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 2a5294cacc..5df8c1be64 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -1,13 +1,11 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Threading; using Avalonia; -using Avalonia.ReactiveUI; using Avalonia.Dialogs; -using Avalonia.OpenGL; +using Avalonia.ReactiveUI; namespace ControlCatalog.NetCore { @@ -69,7 +67,8 @@ namespace ControlCatalog.NetCore }) .UseSkia() .UseReactiveUI() - .UseManagedSystemDialogs(); + .UseManagedSystemDialogs() + .LogToDebug(); static void SilenceConsole() { diff --git a/src/Avalonia.Base/Logging/DebugLogSink.cs b/src/Avalonia.Base/Logging/DebugLogSink.cs new file mode 100644 index 0000000000..a26ecde305 --- /dev/null +++ b/src/Avalonia.Base/Logging/DebugLogSink.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using Avalonia.Utilities; + +namespace Avalonia.Logging +{ + public class DebugLogSink : ILogSink + { + private readonly LogEventLevel _level; + private readonly IList _areas; + + public DebugLogSink( + LogEventLevel minimumLevel, + IList areas = null) + { + _level = minimumLevel; + _areas = areas?.Count > 0 ? areas : null; + } + + public bool IsEnabled(LogEventLevel level) => level >= _level; + + public void Log(LogEventLevel level, string area, object source, string messageTemplate) + { + if (IsEnabled(level, area)) + { + Debug.WriteLine(Format(area, messageTemplate, source)); + } + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0) + { + if (IsEnabled(level, area)) + { + Debug.WriteLine(Format(area, messageTemplate, source, propertyValue0)); + } + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + if (IsEnabled(level, area)) + { + Debug.WriteLine(Format(area, messageTemplate, source, propertyValue0, propertyValue1)); + } + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + if (IsEnabled(level, area)) + { + Debug.WriteLine(Format(area, messageTemplate, source, propertyValue0, propertyValue1, propertyValue2)); + } + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, params object[] propertyValues) + { + if (IsEnabled(level, area)) + { + Debug.WriteLine(Format(area, messageTemplate, source, propertyValues)); + } + } + + private bool IsEnabled(LogEventLevel level, string area) => IsEnabled(level) && (_areas?.Contains(area) ?? true); + + private static string Format( + string area, + string template, + object source, + T0 v0 = default, + T1 v1 = default, + T2 v2 = default) + { + var result = new StringBuilder(template.Length); + var r = new CharacterReader(template.AsSpan()); + var i = 0; + + result.Append('['); + result.Append(area); + result.Append("] "); + + while (!r.End) + { + var c = r.Take(); + + if (c != '{') + { + result.Append(c); + } + else + { + if (r.Peek != '{') + { + result.Append('\''); + result.Append(i++ switch + { + 0 => v0, + 1 => v1, + 2 => v2, + _ => null + }); + result.Append('\''); + r.TakeUntil('}'); + r.Take(); + } + else + { + result.Append('{'); + r.Take(); + } + } + } + + if (source is object) + { + result.Append(" ("); + result.Append(source.GetType().Name); + result.Append(" #"); + result.Append(source.GetHashCode()); + result.Append(')'); + } + + return result.ToString(); + } + + private static string Format( + string area, + string template, + object source, + object[] v) + { + var result = new StringBuilder(template.Length); + var r = new CharacterReader(template.AsSpan()); + var i = 0; + + result.Append('['); + result.Append(area); + result.Append(']'); + + while (!r.End) + { + var c = r.Take(); + + if (c != '{') + { + result.Append(c); + } + else + { + if (r.Peek != '{') + { + result.Append('\''); + result.Append(i < v.Length ? v[i++] : null); + result.Append('\''); + r.TakeUntil('}'); + r.Take(); + } + else + { + result.Append('{'); + r.Take(); + } + } + } + + if (source is object) + { + result.Append('('); + result.Append(source.GetType().Name); + result.Append(" #"); + result.Append(source.GetHashCode()); + result.Append(')'); + } + + return result.ToString(); + } + } +} diff --git a/src/Avalonia.Controls/LoggingExtensions.cs b/src/Avalonia.Controls/LoggingExtensions.cs new file mode 100644 index 0000000000..44e570bdfa --- /dev/null +++ b/src/Avalonia.Controls/LoggingExtensions.cs @@ -0,0 +1,26 @@ +using Avalonia.Controls; +using Avalonia.Logging; + +namespace Avalonia +{ + public static class LoggingExtensions + { + /// + /// Logs Avalonia events to the sink. + /// + /// The application class type. + /// The app builder instance. + /// The minimum level to log. + /// The areas to log. Valid values are listed in . + /// The app builder instance. + public static T LogToDebug( + this T builder, + LogEventLevel level = LogEventLevel.Warning, + params string[] areas) + where T : AppBuilderBase, new() + { + Logger.Sink = new DebugLogSink(level, areas); + return builder; + } + } +}