From aecedfef232655b8db34c529931c7574b0433c88 Mon Sep 17 00:00:00 2001 From: Michael Monsour Date: Mon, 6 Nov 2023 11:57:58 +1000 Subject: [PATCH] Removed dependency on LINQPad.Runtime - now reflecting necessary APIs instead upon initializing the sink --- .../LINQPadLoggerConfigurationExtensions.cs | 24 +++-- src/Serilog.Sinks.LINQPad/LINQPadSink.cs | 98 ++++++++++++++----- .../Serilog.Sinks.LINQPad.csproj | 2 - .../LINQPadSinkTests.cs | 1 - .../Serilog.Sinks.LINQPad.Tests.csproj | 2 + 5 files changed, 91 insertions(+), 36 deletions(-) diff --git a/src/Serilog.Sinks.LINQPad/LINQPadLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.LINQPad/LINQPadLoggerConfigurationExtensions.cs index 1298e8d..f794c80 100644 --- a/src/Serilog.Sinks.LINQPad/LINQPadLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.LINQPad/LINQPadLoggerConfigurationExtensions.cs @@ -20,8 +20,7 @@ using Serilog.Sinks.LINQPad.Output; using Serilog.Sinks.LINQPad.Themes; using System; - -using LINQPad; +using System.Reflection; namespace Serilog { @@ -45,7 +44,7 @@ public static class ConsoleLoggerConfigurationExtensions /// to be changed at runtime. /// The theme to apply to the styled output. If not specified, /// uses or if dark-mode is enabled . - /// Optional write to a specified and not direct to the result panel. + /// Optionally write to a specified and not directly to the result panel. /// Configuration object allowing method chaining. public static LoggerConfiguration LINQPad( this LoggerSinkConfiguration sinkConfiguration, @@ -54,7 +53,7 @@ public static LoggerConfiguration LINQPad( IFormatProvider formatProvider = null, LoggingLevelSwitch levelSwitch = null, ConsoleTheme theme = null, - DumpContainer dumpContainer = null + object dumpContainer = null ) { if (sinkConfiguration == null) { @@ -65,7 +64,7 @@ public static LoggerConfiguration LINQPad( throw new ArgumentNullException(nameof(outputTemplate)); } - var appliedTheme = theme ?? (Util.IsDarkThemeEnabled ? DefaultThemes.LINQPadDark : DefaultThemes.LINQPadLiterate); + var appliedTheme = theme ?? (_isDarkThemeEnabled.Value ? DefaultThemes.LINQPadDark : DefaultThemes.LINQPadLiterate); var formatter = new OutputTemplateRenderer(appliedTheme, outputTemplate, formatProvider); return sinkConfiguration.Sink(new LINQPadSink(appliedTheme, formatter, dumpContainer), restrictedToMinimumLevel, levelSwitch); @@ -81,14 +80,14 @@ public static LoggerConfiguration LINQPad( /// events passed through the sink. Ignored when is specified. /// A switch allowing the pass-through minimum level /// to be changed at runtime. - /// Optional write to a specified and not direct to the result panel. + /// Optionally write to a specified and not directly to the result panel. /// Configuration object allowing method chaining. public static LoggerConfiguration LINQPad( this LoggerSinkConfiguration sinkConfiguration, ITextFormatter formatter, LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, LoggingLevelSwitch levelSwitch = null, - DumpContainer dumpContainer = null) + object dumpContainer = null) { if (sinkConfiguration == null) { throw new ArgumentNullException(nameof(sinkConfiguration)); @@ -100,5 +99,16 @@ public static LoggerConfiguration LINQPad( return sinkConfiguration.Sink(new LINQPadSink(DefaultThemes.None, formatter, dumpContainer), restrictedToMinimumLevel, levelSwitch); } + + + private static Lazy _isDarkThemeEnabled = new(() => { +#if NETCOREAPP3_1_OR_GREATER + var utilType = Type.GetType("LINQPad.Util, LINQPad.Runtime"); +#elif NET48_OR_GREATER + var utilType = Type.GetType("LINQPad.Util, LINQPad"); +#endif + var isDarkThemeEnabledProperty = utilType!.GetProperty("IsDarkThemeEnabled", BindingFlags.Static | BindingFlags.Public); + return (bool)isDarkThemeEnabledProperty!.GetValue(null, null); + }); } } diff --git a/src/Serilog.Sinks.LINQPad/LINQPadSink.cs b/src/Serilog.Sinks.LINQPad/LINQPadSink.cs index 8eea637..df9a166 100644 --- a/src/Serilog.Sinks.LINQPad/LINQPadSink.cs +++ b/src/Serilog.Sinks.LINQPad/LINQPadSink.cs @@ -16,23 +16,21 @@ using Serilog.Events; using Serilog.Formatting; using Serilog.Sinks.LINQPad.Themes; - using System; -using LINQPad; using System.Collections.Generic; +using System.Linq; namespace Serilog.Sinks.LINQPad { - internal class LINQPadSink : ILogEventSink, IDisposable { - public LINQPadSink(ConsoleTheme theme, ITextFormatter formatter, DumpContainer dumpContainer = null) + public LINQPadSink(ConsoleTheme theme, ITextFormatter formatter, object dumpContainer = null) { - _theme = theme ?? throw new ArgumentNullException(nameof(theme)); _formatter = formatter; - _writer = new ThemedHtmlWriter(_theme); + _writer = new ThemedHtmlWriter(theme ?? throw new ArgumentNullException(nameof(theme))); _dumpContainer = dumpContainer; + ReflectLINQPadRuntime(); } @@ -40,19 +38,7 @@ public void Emit(LogEvent logEvent) { lock (SyncRoot) { _formatter.Format(logEvent, _writer); - - var rawHtml = Util.RawHtml($"{_writer}"); - - if (_dumpContainer != null) { -#if NETCOREAPP3_1_OR_GREATER - _dumpContainer.AppendContent(rawHtml); -#elif NET48_OR_GREATER - _content.Add(rawHtml); - _dumpContainer.Content = Util.VerticalRun(_content); -#endif - } else { - rawHtml.Dump(); - } + _dumpContent($"{_writer}"); _writer.Clear(); } } @@ -67,19 +53,79 @@ public void Dispose() } - private bool _disposed; + private void ReflectLINQPadRuntime() + { +#if NETCOREAPP3_1_OR_GREATER + var utilType = Type.GetType("LINQPad.Util, LINQPad.Runtime"); + var rawHtmlMethod = utilType!.GetMethod("RawHtml", new[] { typeof(string) }); + var rawHtmlFunc = (Func)rawHtmlMethod!.CreateDelegate(typeof(Func)); + + if (_dumpContainer != null) { + var dumpContainerType = Type.GetType("LINQPad.DumpContainer, LINQPad.Runtime"); + if (!_dumpContainer.GetType().IsAssignableFrom(dumpContainerType)) { + throw new ArgumentException($"The specified dumpContainer must be of type {dumpContainerType.FullName}", nameof(_dumpContainer)); + } + var appendContentMethod = dumpContainerType + !.GetMethod("AppendContent", 1, new[] { Type.MakeGenericMethodParameter(0), typeof(bool) }) + !.MakeGenericMethod(typeof(object)); + var appendContentFunc = (Func)appendContentMethod.CreateDelegate(typeof(Func), _dumpContainer); + _dumpContent = o => appendContentFunc(rawHtmlFunc(o), false); + + } else { + var extensionsType = Type.GetType("LINQPad.Extensions, LINQPad.Runtime"); + var dumpMethod = extensionsType + !.GetMethod("Dump", 1, new[] { Type.MakeGenericMethodParameter(0) }) + !.MakeGenericMethod(typeof(object)); + var dumpFunc = (Func)dumpMethod.CreateDelegate(typeof(Func)); + _dumpContent = o => dumpFunc(rawHtmlFunc(o)); + } +#elif NET48_OR_GREATER + var utilType = Type.GetType("LINQPad.Util, LINQPad"); + var rawHtmlMethod = utilType!.GetMethod("RawHtml", new[] { typeof(string) }); + var rawHtmlFunc = (Func)rawHtmlMethod!.CreateDelegate(typeof(Func)); + + if (_dumpContainer != null) { + var dumpContainerType = Type.GetType("LINQPad.DumpContainer, LINQPad"); + if (!_dumpContainer.GetType().IsAssignableFrom(dumpContainerType)) { + throw new ArgumentException($"The specified dumpContainer must be of type {dumpContainerType.FullName}", nameof(_dumpContainer)); + } + + var verticalRunMethod = utilType + !.GetMethod("VerticalRun", new[] { typeof(object[]) }); + var verticalRunFunc = (Func)verticalRunMethod!.CreateDelegate(typeof(Func)); + + var contentProperty = dumpContainerType!.GetProperty("Content"); + _dumpContent = o => { + _content.Add(o); + contentProperty!.SetValue(_dumpContainer, verticalRunFunc(new[] { rawHtmlFunc(o) }), null); + }; + + } else { + var extensionsType = Type.GetType("LINQPad.Extensions, LINQPad"); + var dumpMethod = extensionsType + !.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static) + .Where(x => x.Name == "Dump") + .Single(x => x.GetParameters().Length == 1) + .MakeGenericMethod(typeof(object)); + var dumpFunc = (Func)dumpMethod.CreateDelegate(typeof(Func)); + _dumpContent = o => dumpFunc(rawHtmlFunc(o)); + } +#endif + } + + + private bool _disposed; + private Action _dumpContent; - private readonly ConsoleTheme _theme; private readonly ITextFormatter _formatter; private readonly ThemedHtmlWriter _writer; - private readonly DumpContainer _dumpContainer; + private readonly object _dumpContainer; #if NET48_OR_GREATER - private readonly List _content = new List(); + private readonly List _content = new(); #endif - private static readonly object SyncRoot = new object(); + private static readonly object SyncRoot = new(); } - -} +} \ No newline at end of file diff --git a/src/Serilog.Sinks.LINQPad/Serilog.Sinks.LINQPad.csproj b/src/Serilog.Sinks.LINQPad/Serilog.Sinks.LINQPad.csproj index 42275a9..d37560a 100644 --- a/src/Serilog.Sinks.LINQPad/Serilog.Sinks.LINQPad.csproj +++ b/src/Serilog.Sinks.LINQPad/Serilog.Sinks.LINQPad.csproj @@ -24,8 +24,6 @@ - - diff --git a/tests/Serilog.Sinks.LINQPad.Tests/LINQPadSinkTests.cs b/tests/Serilog.Sinks.LINQPad.Tests/LINQPadSinkTests.cs index 9461fb6..44d0471 100644 --- a/tests/Serilog.Sinks.LINQPad.Tests/LINQPadSinkTests.cs +++ b/tests/Serilog.Sinks.LINQPad.Tests/LINQPadSinkTests.cs @@ -49,7 +49,6 @@ private static LogEvent CreateLogEvent(string text) return new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Debug, null, msg, Enumerable.Empty()); } - private const string DefaultConsoleOutputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"; private static readonly LINQPadTheme DefaultTestTheme = DefaultThemes.LINQPadLiterate; diff --git a/tests/Serilog.Sinks.LINQPad.Tests/Serilog.Sinks.LINQPad.Tests.csproj b/tests/Serilog.Sinks.LINQPad.Tests/Serilog.Sinks.LINQPad.Tests.csproj index ed40544..3174ac0 100644 --- a/tests/Serilog.Sinks.LINQPad.Tests/Serilog.Sinks.LINQPad.Tests.csproj +++ b/tests/Serilog.Sinks.LINQPad.Tests/Serilog.Sinks.LINQPad.Tests.csproj @@ -9,6 +9,8 @@ + +