diff --git a/TheDialgaTeam.Serilog/Configuration/LogLevelOptions.cs b/TheDialgaTeam.Serilog/Configuration/LogLevelOptions.cs index 39e40de..2d73c93 100644 --- a/TheDialgaTeam.Serilog/Configuration/LogLevelOptions.cs +++ b/TheDialgaTeam.Serilog/Configuration/LogLevelOptions.cs @@ -24,7 +24,7 @@ namespace TheDialgaTeam.Serilog.Configuration; -internal sealed class LogLevelOptions +public sealed class LogLevelOptions { public LogLevel Default { get; set; } = LogLevel.Information; diff --git a/TheDialgaTeam.Serilog/Configuration/SerilogLoggerSettings.cs b/TheDialgaTeam.Serilog/Configuration/SerilogLoggerSettings.cs index 2a4b415..bab5054 100644 --- a/TheDialgaTeam.Serilog/Configuration/SerilogLoggerSettings.cs +++ b/TheDialgaTeam.Serilog/Configuration/SerilogLoggerSettings.cs @@ -24,26 +24,47 @@ using Serilog; using Serilog.Configuration; using Serilog.Core; -using Serilog.Events; using Serilog.Extensions.Logging; namespace TheDialgaTeam.Serilog.Configuration; -internal sealed class SerilogLoggerSettings(IOptionsMonitor logLevelOptionsMonitor) : ILoggerSettings +internal sealed class SerilogLoggerSettings(IOptionsMonitor logLevelOptionsMonitor) : ILoggerSettings, IDisposable { + private readonly LoggingLevelSwitch _defaultLoggingLevelSwitch = new(); + private readonly Dictionary _overridesLoggingLevelSwitch = new(); + + private IDisposable? _disposable; + public void Configure(LoggerConfiguration loggerConfiguration) { - loggerConfiguration.Filter.ByIncludingOnly(logEvent => + UpdateLogLevel(logLevelOptionsMonitor.CurrentValue); + loggerConfiguration.MinimumLevel.ControlledBy(_defaultLoggingLevelSwitch); + + foreach (var (sourceContext, logLevel) in logLevelOptionsMonitor.CurrentValue.Overrides) { - string? sourceContext = null; + var temp = new LoggingLevelSwitch(LevelConvert.ToSerilogLevel(logLevel)); + _overridesLoggingLevelSwitch.Add(sourceContext, temp); + loggerConfiguration.MinimumLevel.Override(sourceContext, temp); + } + + _disposable = logLevelOptionsMonitor.OnChange(UpdateLogLevel); + } + + private void UpdateLogLevel(LogLevelOptions options) + { + _defaultLoggingLevelSwitch.MinimumLevel = LevelConvert.ToSerilogLevel(options.Default); - if (logEvent.Properties.TryGetValue(Constants.SourceContextPropertyName, out var logEventPropertyValue) && - logEventPropertyValue is ScalarValue { Value: string sourceContextValue }) + foreach (var (sourceContext, logLevel) in options.Overrides) + { + if (_overridesLoggingLevelSwitch.TryGetValue(sourceContext, out var levelSwitch)) { - sourceContext = sourceContextValue; + levelSwitch.MinimumLevel = LevelConvert.ToSerilogLevel(logLevel); } + } + } - return LevelConvert.ToExtensionsLevel(logEvent.Level) >= logLevelOptionsMonitor.CurrentValue.GetMinimumLogLevel(sourceContext); - }); + public void Dispose() + { + _disposable?.Dispose(); } } \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Extensions/HostBuilderExtensions.cs b/TheDialgaTeam.Serilog/Extensions/HostBuilderExtensions.cs index 69aaaae..1d919b1 100644 --- a/TheDialgaTeam.Serilog/Extensions/HostBuilderExtensions.cs +++ b/TheDialgaTeam.Serilog/Extensions/HostBuilderExtensions.cs @@ -25,9 +25,6 @@ using Microsoft.Extensions.Hosting; using Serilog; using TheDialgaTeam.Serilog.Configuration; -using TheDialgaTeam.Serilog.Formatting; -using TheDialgaTeam.Serilog.Formatting.Options; -using TheDialgaTeam.Serilog.Sinks.Action; namespace TheDialgaTeam.Serilog.Extensions; @@ -38,9 +35,19 @@ public static IHostBuilder ConfigureSerilog(this IHostBuilder hostBuilder, Actio return hostBuilder.ConfigureServices(static collection => { collection.AddOptions().BindConfiguration("TheDialgaTeam.Serilog:LogLevel"); - collection.AddOptions().BindConfiguration("TheDialgaTeam.Serilog:LogLevelMessageTemplate"); - collection.TryAddSingleton(); - collection.TryAddSingleton(); + collection.TryAddSingleton(); + }).UseSerilog((context, provider, configuration) => + { + configuration.ReadFrom.Settings(provider.GetRequiredService()); + configureLogger(context, provider, configuration); + }); + } + + public static IHostBuilder ConfigureSerilog(this IHostBuilder hostBuilder, string logLevelConfigSection, Action configureLogger) + { + return hostBuilder.ConfigureServices(collection => + { + collection.AddOptions().BindConfiguration(logLevelConfigSection); collection.TryAddSingleton(); }).UseSerilog((context, provider, configuration) => { diff --git a/TheDialgaTeam.Serilog/Formatting/AnsiMessageTemplateTextFormatter.cs b/TheDialgaTeam.Serilog/Formatting/AnsiMessageTemplateTextFormatter.cs index 7d51c90..dae2387 100644 --- a/TheDialgaTeam.Serilog/Formatting/AnsiMessageTemplateTextFormatter.cs +++ b/TheDialgaTeam.Serilog/Formatting/AnsiMessageTemplateTextFormatter.cs @@ -29,7 +29,7 @@ namespace TheDialgaTeam.Serilog.Formatting; public sealed class AnsiMessageTemplateTextFormatter( - AnsiMessageTemplateTextFormatterOptions options, + LogLevelMessageTemplateOptions options, IFormatProvider? formatProvider = null) : ITextFormatter { private readonly AnsiTemplateTextParser _ansiTemplateTextParser = new(); @@ -42,10 +42,8 @@ public void Format(LogEvent logEvent, TextWriter output) { sourceContext = sourceContextValue; } - - var logLevelMessageTemplateOptions = options.LogLevelMessageTemplateOptions; - - var messageTemplate = logLevelMessageTemplateOptions.GetMessageTemplate(sourceContext, LevelConvert.ToExtensionsLevel(logEvent.Level)); + + var messageTemplate = options.GetMessageTemplate(sourceContext, LevelConvert.ToExtensionsLevel(logEvent.Level)); foreach (var messageTemplateToken in _ansiTemplateTextParser.GetMessageTemplateTokens(messageTemplate)) { diff --git a/TheDialgaTeam.Serilog/Formatting/AnsiMessageTemplateTextFormatterOptions.cs b/TheDialgaTeam.Serilog/Formatting/AnsiMessageTemplateTextFormatterOptions.cs deleted file mode 100644 index 31e78bf..0000000 --- a/TheDialgaTeam.Serilog/Formatting/AnsiMessageTemplateTextFormatterOptions.cs +++ /dev/null @@ -1,55 +0,0 @@ -// MIT License -// -// Copyright (c) 2023 Yong Jian Ming -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -using Microsoft.Extensions.Options; -using TheDialgaTeam.Serilog.Formatting.Options; - -namespace TheDialgaTeam.Serilog.Formatting; - -public sealed class AnsiMessageTemplateTextFormatterOptions : IDisposable -{ - public LogLevelMessageTemplateOptions LogLevelMessageTemplateOptions { get; set; } = new(); - - private readonly IDisposable?[] _disposables = Array.Empty(); - - public AnsiMessageTemplateTextFormatterOptions() - { - } - - public AnsiMessageTemplateTextFormatterOptions(IOptionsMonitor logLevelMessageTemplateOptions) - { - LogLevelMessageTemplateOptions = logLevelMessageTemplateOptions.CurrentValue; - - _disposables = - [ - logLevelMessageTemplateOptions.OnChange(options => LogLevelMessageTemplateOptions = options) - ]; - } - - public void Dispose() - { - foreach (var disposable in _disposables) - { - disposable?.Dispose(); - } - } -} \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Formatting/LogLevelMessageTemplateOptions.cs b/TheDialgaTeam.Serilog/Formatting/LogLevelMessageTemplateOptions.cs new file mode 100644 index 0000000..1cf238d --- /dev/null +++ b/TheDialgaTeam.Serilog/Formatting/LogLevelMessageTemplateOptions.cs @@ -0,0 +1,196 @@ +// MIT License +// +// Copyright (c) 2023 Yong Jian Ming +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using Microsoft.Extensions.Logging; + +namespace TheDialgaTeam.Serilog.Formatting; + +public sealed class LogLevelMessageTemplateOptions +{ + public LogLevelMessageTemplate Default { get; set; } = new(); + + public Dictionary Overrides { get; set; } = new(); + + public string GetMessageTemplate(string? sourceContext, LogLevel logLevel) + { + var messageTemplate = GetLogLevelMessageTemplate(sourceContext); + + return logLevel switch + { + LogLevel.Trace => messageTemplate.Trace ?? messageTemplate.Default, + LogLevel.Debug => messageTemplate.Debug ?? messageTemplate.Default, + LogLevel.Information => messageTemplate.Information ?? messageTemplate.Default, + LogLevel.Warning => messageTemplate.Warning ?? messageTemplate.Default, + LogLevel.Error => messageTemplate.Error ?? messageTemplate.Default, + LogLevel.Critical => messageTemplate.Critical ?? messageTemplate.Default, + var _ => string.Empty + }; + } + + private LogLevelMessageTemplate GetLogLevelMessageTemplate(string? sourceContext) + { + if (sourceContext is null) + { + return Default; + } + + foreach (var (key, value) in Overrides.OrderByDescending(pair => pair.Key)) + { + if (sourceContext.StartsWith(key) && (sourceContext.Length == key.Length || sourceContext[key.Length] == '.')) + { + return value; + } + } + + return Default; + } +} + +public sealed class LogLevelMessageTemplateOptionsBuilder +{ + private readonly LogLevelMessageTemplateOptions _options = new(); + + public LogLevelMessageTemplateOptionsBuilder SetDefault(LogLevelMessageTemplate messageTemplate) + { + _options.Default = messageTemplate; + return this; + } + + public LogLevelMessageTemplateOptionsBuilder SetDefault(Action messageTemplate) + { + var logLevelMessageTemplateBuilder = new LogLevelMessageTemplateBuilder(); + messageTemplate(logLevelMessageTemplateBuilder); + _options.Default = logLevelMessageTemplateBuilder.Build(); + return this; + } + + public LogLevelMessageTemplateOptionsBuilder SetOverrides(Dictionary overrides) + { + _options.Overrides = overrides; + return this; + } + + public LogLevelMessageTemplateOptionsBuilder SetOverrides(string context, LogLevelMessageTemplate messageTemplate) + { + if (!_options.Overrides.TryAdd(context, messageTemplate)) + { + _options.Overrides[context] = messageTemplate; + } + + return this; + } + + public LogLevelMessageTemplateOptionsBuilder SetOverrides(string context, Action messageTemplate) + { + var logLevelMessageTemplateBuilder = new LogLevelMessageTemplateBuilder(); + messageTemplate(logLevelMessageTemplateBuilder); + + var logLevelMessageTemplate = logLevelMessageTemplateBuilder.Build(); + + if (!_options.Overrides.TryAdd(context, logLevelMessageTemplate)) + { + _options.Overrides[context] = logLevelMessageTemplate; + } + + return this; + } + + public LogLevelMessageTemplateOptionsBuilder ClearOverrides() + { + _options.Overrides.Clear(); + return this; + } + + public LogLevelMessageTemplateOptions Build() + { + return _options; + } +} + +public sealed class LogLevelMessageTemplate +{ + public string Default { get; set; } = "{Timestamp:yyyy-MM-dd HH:mm:ss} {Message:l}{NewLine}{Exception}"; + + public string? Trace { get; set; } + + public string? Debug { get; set; } + + public string? Information { get; set; } + + public string? Warning { get; set; } + + public string? Error { get; set; } + + public string? Critical { get; set; } +} + +public sealed class LogLevelMessageTemplateBuilder +{ + private readonly LogLevelMessageTemplate _template = new(); + + public LogLevelMessageTemplateBuilder SetDefault(string template) + { + _template.Default = template; + return this; + } + + public LogLevelMessageTemplateBuilder SetTrace(string? template) + { + _template.Trace = template; + return this; + } + + public LogLevelMessageTemplateBuilder SetDebug(string? template) + { + _template.Trace = template; + return this; + } + + public LogLevelMessageTemplateBuilder SetInformation(string? template) + { + _template.Trace = template; + return this; + } + + public LogLevelMessageTemplateBuilder SetWarning(string? template) + { + _template.Warning = template; + return this; + } + + public LogLevelMessageTemplateBuilder SetError(string? template) + { + _template.Error = template; + return this; + } + + public LogLevelMessageTemplateBuilder SetCritical(string? template) + { + _template.Critical = template; + return this; + } + + public LogLevelMessageTemplate Build() + { + return _template; + } +} \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Formatting/Options/LogLevelMessageTemplateOptions.cs b/TheDialgaTeam.Serilog/Formatting/Options/LogLevelMessageTemplateOptions.cs deleted file mode 100644 index c4d06c3..0000000 --- a/TheDialgaTeam.Serilog/Formatting/Options/LogLevelMessageTemplateOptions.cs +++ /dev/null @@ -1,83 +0,0 @@ -// MIT License -// -// Copyright (c) 2023 Yong Jian Ming -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -using Microsoft.Extensions.Logging; - -namespace TheDialgaTeam.Serilog.Formatting.Options; - -public sealed class LogLevelMessageTemplateOptions -{ - public LogLevelMessageTemplate Default { get; set; } = new(); - - public Dictionary Overrides { get; set; } = new(); - - public string GetMessageTemplate(string? sourceContext, LogLevel logLevel) - { - var messageTemplate = GetLogLevelMessageTemplate(sourceContext); - - return logLevel switch - { - LogLevel.Trace => messageTemplate.Trace ?? messageTemplate.Default, - LogLevel.Debug => messageTemplate.Debug ?? messageTemplate.Default, - LogLevel.Information => messageTemplate.Information ?? messageTemplate.Default, - LogLevel.Warning => messageTemplate.Warning ?? messageTemplate.Default, - LogLevel.Error => messageTemplate.Error ?? messageTemplate.Default, - LogLevel.Critical => messageTemplate.Critical ?? messageTemplate.Default, - var _ => string.Empty - }; - } - - private LogLevelMessageTemplate GetLogLevelMessageTemplate(string? sourceContext) - { - if (sourceContext is null) - { - return Default; - } - - foreach (var (key, value) in Overrides.OrderByDescending(pair => pair.Key)) - { - if (sourceContext.StartsWith(key) && (sourceContext.Length == key.Length || sourceContext[key.Length] == '.')) - { - return value; - } - } - - return Default; - } -} - -public sealed class LogLevelMessageTemplate -{ - public string Default { get; set; } = "{Timestamp:yyyy-MM-dd HH:mm:ss} {Message:l}{NewLine}{Exception}"; - - public string? Trace { get; set; } - - public string? Debug { get; set; } - - public string? Information { get; set; } - - public string? Warning { get; set; } - - public string? Error { get; set; } - - public string? Critical { get; set; } -} \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Native/WindowsConsoleNative.cs b/TheDialgaTeam.Serilog/Native/WindowsConsoleNative.cs index 3225551..32497d8 100644 --- a/TheDialgaTeam.Serilog/Native/WindowsConsoleNative.cs +++ b/TheDialgaTeam.Serilog/Native/WindowsConsoleNative.cs @@ -21,9 +21,11 @@ // SOFTWARE. using System.Runtime.InteropServices; +using System.Runtime.Versioning; namespace TheDialgaTeam.Serilog.Native; +[SupportedOSPlatform("windows")] internal static partial class WindowsConsoleNative { public const int StandardOutputHandleId = -11; @@ -31,14 +33,14 @@ internal static partial class WindowsConsoleNative public const int EnableVirtualTerminalProcessingMode = 4; public const int InvalidHandleValue = -1; - [LibraryImport("kernel32.dll")] + [LibraryImport("kernel32.dll", SetLastError = true)] public static partial IntPtr GetStdHandle(int handleId); - [LibraryImport("kernel32.dll")] + [LibraryImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static partial bool GetConsoleMode(IntPtr handle, out int mode); - [LibraryImport("kernel32.dll")] + [LibraryImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static partial bool SetConsoleMode(IntPtr handle, int mode); } \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Parsing/AnsiExceptionToken.cs b/TheDialgaTeam.Serilog/Parsing/AnsiExceptionToken.cs index d0bf75a..1f2f1d0 100644 --- a/TheDialgaTeam.Serilog/Parsing/AnsiExceptionToken.cs +++ b/TheDialgaTeam.Serilog/Parsing/AnsiExceptionToken.cs @@ -32,11 +32,9 @@ public override void Render(LogEvent logEvent, TextWriter output, IFormatProvide { if (logEvent.Exception is null) return; - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new LiteralScalarValue(logEvent.Exception) } - }, output, formatProvider); - + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new LiteralScalarValue(logEvent.Exception)); + Render(LogEventPropertyValues, output, formatProvider); output.WriteLine(); } } \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Parsing/AnsiLevelToken.cs b/TheDialgaTeam.Serilog/Parsing/AnsiLevelToken.cs index 037abb2..a1bc037 100644 --- a/TheDialgaTeam.Serilog/Parsing/AnsiLevelToken.cs +++ b/TheDialgaTeam.Serilog/Parsing/AnsiLevelToken.cs @@ -64,10 +64,9 @@ public override void Render(LogEvent logEvent, TextWriter output, IFormatProvide if (formatStringSpan.Length < 2) { - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new LiteralScalarValue(Enum.GetName(logEvent.Level)) } - }, output, formatProvider); + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new LiteralScalarValue(Enum.GetName(logEvent.Level))); + Render(LogEventPropertyValues, output, formatProvider); return; } @@ -83,17 +82,15 @@ public override void Render(LogEvent logEvent, TextWriter output, IFormatProvide { var result = (Enum.GetName(logEvent.Level) ?? string.Empty).ToLowerInvariant(); - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new LiteralScalarValue(result.Length >= formatLength ? result : result[..formatLength]) } - }, output, formatProvider); + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new LiteralScalarValue(result.Length >= formatLength ? result : result[..formatLength])); + Render(LogEventPropertyValues, output, formatProvider); } else { - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new LiteralScalarValue(LowercaseLevelMap[(int) logEvent.Level][formatLength - 1]) } - }, output, formatProvider); + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new LiteralScalarValue(LowercaseLevelMap[(int) logEvent.Level][formatLength - 1])); + Render(LogEventPropertyValues, output, formatProvider); } break; @@ -105,17 +102,15 @@ public override void Render(LogEvent logEvent, TextWriter output, IFormatProvide { var result = (Enum.GetName(logEvent.Level) ?? string.Empty).ToUpperInvariant(); - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new LiteralScalarValue(result.Length >= formatLength ? result : result[..formatLength]) } - }, output, formatProvider); + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new LiteralScalarValue(result.Length >= formatLength ? result : result[..formatLength])); + Render(LogEventPropertyValues, output, formatProvider); } else { - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new LiteralScalarValue(UppercaseLevelMap[(int) logEvent.Level][formatLength - 1]) } - }, output, formatProvider); + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new LiteralScalarValue(UppercaseLevelMap[(int) logEvent.Level][formatLength - 1])); + Render(LogEventPropertyValues, output, formatProvider); } break; @@ -127,17 +122,15 @@ public override void Render(LogEvent logEvent, TextWriter output, IFormatProvide { var result = Enum.GetName(logEvent.Level) ?? string.Empty; - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new LiteralScalarValue(result.Length >= formatLength ? result : result[..formatLength]) } - }, output, formatProvider); + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new LiteralScalarValue(result.Length >= formatLength ? result : result[..formatLength])); + Render(LogEventPropertyValues, output, formatProvider); } else { - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new LiteralScalarValue(TitleCaseLevelMap[(int) logEvent.Level][formatLength - 1]) } - }, output, formatProvider); + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new LiteralScalarValue(TitleCaseLevelMap[(int) logEvent.Level][formatLength - 1])); + Render(LogEventPropertyValues, output, formatProvider); } break; @@ -145,20 +138,18 @@ public override void Render(LogEvent logEvent, TextWriter output, IFormatProvide default: { - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new LiteralScalarValue(Enum.GetName(logEvent.Level)) } - }, output, formatProvider); + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new LiteralScalarValue(Enum.GetName(logEvent.Level))); + Render(LogEventPropertyValues, output, formatProvider); break; } } } else { - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new LiteralScalarValue(Enum.GetName(logEvent.Level)) } - }, output, formatProvider); + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new LiteralScalarValue(Enum.GetName(logEvent.Level))); + Render(LogEventPropertyValues, output, formatProvider); } } } \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Parsing/AnsiMessageTemplateToken.cs b/TheDialgaTeam.Serilog/Parsing/AnsiMessageTemplateToken.cs index cfd2530..68f57da 100644 --- a/TheDialgaTeam.Serilog/Parsing/AnsiMessageTemplateToken.cs +++ b/TheDialgaTeam.Serilog/Parsing/AnsiMessageTemplateToken.cs @@ -57,6 +57,8 @@ public abstract class AnsiMessageTemplateToken(TMessageTe protected readonly TMessageTemplateToken MessageTemplateToken = messageTemplateToken; + protected readonly Dictionary LogEventPropertyValues = new(); + public override void Render(IReadOnlyDictionary properties, TextWriter output, IFormatProvider? formatProvider = null) { var stringWriter = ReusableStringWriter.GetOrCreate(formatProvider); diff --git a/TheDialgaTeam.Serilog/Parsing/AnsiNewLineToken.cs b/TheDialgaTeam.Serilog/Parsing/AnsiNewLineToken.cs index ed2461d..c735b27 100644 --- a/TheDialgaTeam.Serilog/Parsing/AnsiNewLineToken.cs +++ b/TheDialgaTeam.Serilog/Parsing/AnsiNewLineToken.cs @@ -30,9 +30,8 @@ internal sealed class AnsiNewLineToken(PropertyToken propertyToken) : AnsiMessag { public override void Render(LogEvent logEvent, TextWriter output, IFormatProvider? formatProvider = null) { - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new LiteralScalarValue(Environment.NewLine) } - }, output, formatProvider); + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new LiteralScalarValue(Environment.NewLine)); + Render(LogEventPropertyValues, output, formatProvider); } } \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Parsing/AnsiPropertiesToken.cs b/TheDialgaTeam.Serilog/Parsing/AnsiPropertiesToken.cs index c3ddad6..47865c8 100644 --- a/TheDialgaTeam.Serilog/Parsing/AnsiPropertiesToken.cs +++ b/TheDialgaTeam.Serilog/Parsing/AnsiPropertiesToken.cs @@ -49,9 +49,8 @@ public override void Render(LogEvent logEvent, TextWriter output, IFormatProvide .Where(pair => !TemplateContainsPropertyName(logEvent.MessageTemplate, pair.Key) && !TemplateContainsPropertyName(messageTemplate, pair.Key)) .Select(pair => new LogEventProperty(pair.Key, pair.Value)); - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new StructureValue(propertiesToInclude) } - }, output, formatProvider); + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new StructureValue(propertiesToInclude)); + Render(LogEventPropertyValues, output, formatProvider); } } \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Parsing/AnsiPropertyToken.cs b/TheDialgaTeam.Serilog/Parsing/AnsiPropertyToken.cs index 304c01a..a48552c 100644 --- a/TheDialgaTeam.Serilog/Parsing/AnsiPropertyToken.cs +++ b/TheDialgaTeam.Serilog/Parsing/AnsiPropertyToken.cs @@ -20,8 +20,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System.Text.Json; using Serilog.Events; +using Serilog.Formatting.Json; using Serilog.Parsing; using TheDialgaTeam.Serilog.Rendering; @@ -39,7 +39,8 @@ public static void Render(PropertyToken propertyToken, LogEvent logEvent, TextWr } else if (isJson && propertyToken.Format is null) { - RenderJson(output, propertyValue); + var jsonValueFormatter = new JsonValueFormatter(); + jsonValueFormatter.Format(propertyValue, output); } else { @@ -52,94 +53,4 @@ public static void Render(PropertyToken propertyToken, LogEvent logEvent, TextWr AnsiEscapeCodeTextRenderer.Render(output, stringWriter.ToString()); } } - - private static void RenderJson(TextWriter output, LogEventPropertyValue propertyValue) - { - switch (propertyValue) - { - case ScalarValue scalarValue: - { - output.Write(JsonSerializer.Serialize(scalarValue)); - break; - } - - case SequenceValue sequenceValue: - { - output.Write("["); - - char? delim = null; - - foreach (var element in sequenceValue.Elements) - { - if (delim is not null) - { - output.Write(","); - } - - delim = ','; - RenderJson(output, element); - } - - output.Write("]"); - break; - } - - case StructureValue structureValue: - { - output.Write("{"); - - char? delim = null; - - foreach (var property in structureValue.Properties) - { - if (delim is not null) - { - output.Write(","); - } - - delim = ','; - - output.Write($"\"{property.Name}\""); - output.Write(":"); - - RenderJson(output, property.Value); - } - - output.Write("}"); - break; - } - - case DictionaryValue dictionaryValue: - { - output.Write("{"); - - char? delim = null; - - foreach (var element in dictionaryValue.Elements) - { - if (delim is not null) - { - output.Write(","); - } - - delim = ','; - - if (element.Key.Value is string key) - { - output.Write($"\"{key}\""); - } - else - { - output.Write($"\"{element.Key.Value?.ToString() ?? "null"}\""); - } - - output.Write(":"); - RenderJson(output, element.Value); - } - - output.Write("}"); - break; - } - } - } } \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Parsing/AnsiSpanIdToken.cs b/TheDialgaTeam.Serilog/Parsing/AnsiSpanIdToken.cs index d9d9556..a94472b 100644 --- a/TheDialgaTeam.Serilog/Parsing/AnsiSpanIdToken.cs +++ b/TheDialgaTeam.Serilog/Parsing/AnsiSpanIdToken.cs @@ -30,9 +30,8 @@ internal sealed class AnsiSpanIdToken(PropertyToken propertyToken) : AnsiMessage { public override void Render(LogEvent logEvent, TextWriter output, IFormatProvider? formatProvider = null) { - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new LiteralScalarValue(logEvent.SpanId) } - }, output, formatProvider); + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new LiteralScalarValue(logEvent.SpanId)); + Render(LogEventPropertyValues, output, formatProvider); } } \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Parsing/AnsiTemplateTextParser.cs b/TheDialgaTeam.Serilog/Parsing/AnsiTemplateTextParser.cs index ffde72d..ac6245a 100644 --- a/TheDialgaTeam.Serilog/Parsing/AnsiTemplateTextParser.cs +++ b/TheDialgaTeam.Serilog/Parsing/AnsiTemplateTextParser.cs @@ -20,7 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System.Collections.Concurrent; using Serilog.Formatting.Display; using Serilog.Parsing; @@ -29,11 +28,17 @@ namespace TheDialgaTeam.Serilog.Parsing; internal sealed class AnsiTemplateTextParser { private readonly MessageTemplateParser _messageTemplateParser = new(); - private readonly ConcurrentDictionary _textFormatters = new(); + private readonly Dictionary _textFormatters = new(); - public AnsiMessageTemplateToken[] GetMessageTemplateTokens(string messageTemplateFormat) + public IEnumerable GetMessageTemplateTokens(string messageTemplateFormat) { - return _textFormatters.GetOrAdd(messageTemplateFormat, static (key, args) => args.GenerateMessageTemplateTokens(key).ToArray(), this); + if (!_textFormatters.TryGetValue(messageTemplateFormat, out var result)) + { + result = GenerateMessageTemplateTokens(messageTemplateFormat).ToArray(); + _textFormatters.Add(messageTemplateFormat, result); + } + + return result; } private IEnumerable GenerateMessageTemplateTokens(string messageTemplateFormat) diff --git a/TheDialgaTeam.Serilog/Parsing/AnsiTimestampToken.cs b/TheDialgaTeam.Serilog/Parsing/AnsiTimestampToken.cs index e3c16dc..0003558 100644 --- a/TheDialgaTeam.Serilog/Parsing/AnsiTimestampToken.cs +++ b/TheDialgaTeam.Serilog/Parsing/AnsiTimestampToken.cs @@ -30,9 +30,8 @@ internal sealed class AnsiTimestampToken(PropertyToken propertyToken) : AnsiMess { public override void Render(LogEvent logEvent, TextWriter output, IFormatProvider? formatProvider = null) { - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new LiteralScalarValue(logEvent.Timestamp) } - }, output, formatProvider); + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new LiteralScalarValue(logEvent.Timestamp)); + Render(LogEventPropertyValues, output, formatProvider); } } \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Parsing/AnsiTraceIdToken.cs b/TheDialgaTeam.Serilog/Parsing/AnsiTraceIdToken.cs index 33cb46e..b073bb5 100644 --- a/TheDialgaTeam.Serilog/Parsing/AnsiTraceIdToken.cs +++ b/TheDialgaTeam.Serilog/Parsing/AnsiTraceIdToken.cs @@ -30,9 +30,8 @@ internal sealed class AnsiTraceIdToken(PropertyToken propertyToken) : AnsiMessag { public override void Render(LogEvent logEvent, TextWriter output, IFormatProvider? formatProvider = null) { - Render(new Dictionary - { - { MessageTemplateToken.PropertyName, new LiteralScalarValue(logEvent.TraceId) } - }, output, formatProvider); + LogEventPropertyValues.Clear(); + LogEventPropertyValues.Add(MessageTemplateToken.PropertyName, new LiteralScalarValue(logEvent.TraceId)); + Render(LogEventPropertyValues, output, formatProvider); } } \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Rendering/AnsiEscapeCodeTextRenderer.cs b/TheDialgaTeam.Serilog/Rendering/AnsiEscapeCodeTextRenderer.cs index c2cd2ed..2e07ed1 100644 --- a/TheDialgaTeam.Serilog/Rendering/AnsiEscapeCodeTextRenderer.cs +++ b/TheDialgaTeam.Serilog/Rendering/AnsiEscapeCodeTextRenderer.cs @@ -20,6 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using System.Runtime.InteropServices; using System.Text.RegularExpressions; using TheDialgaTeam.Serilog.Formatting; using TheDialgaTeam.Serilog.Native; @@ -45,18 +46,32 @@ public static void Render(TextWriter output, string text) { if (!IsAnsiEscapeCodeSupported.TryGetValue(output, out var isSupported)) { - if (output == Console.Out) + if (output == Console.Out && !Console.IsOutputRedirected) { - if (WindowsConsoleNative.GetConsoleMode(WindowsConsoleNative.StandardOutputHandleId, out var mode)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - isSupported = (mode & WindowsConsoleNative.EnableVirtualTerminalProcessingMode) == WindowsConsoleNative.EnableVirtualTerminalProcessingMode; + if (WindowsConsoleNative.GetConsoleMode(WindowsConsoleNative.StandardOutputHandleId, out var mode)) + { + isSupported = (mode & WindowsConsoleNative.EnableVirtualTerminalProcessingMode) == WindowsConsoleNative.EnableVirtualTerminalProcessingMode; + } + } + else + { + isSupported = true; } } - else if (output == Console.Error) + else if (output == Console.Error && !Console.IsErrorRedirected) { - if (WindowsConsoleNative.GetConsoleMode(WindowsConsoleNative.StandardErrorHandleId, out var mode)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + if (WindowsConsoleNative.GetConsoleMode(WindowsConsoleNative.StandardErrorHandleId, out var mode)) + { + isSupported = (mode & WindowsConsoleNative.EnableVirtualTerminalProcessingMode) == WindowsConsoleNative.EnableVirtualTerminalProcessingMode; + } + } + else { - isSupported = (mode & WindowsConsoleNative.EnableVirtualTerminalProcessingMode) == WindowsConsoleNative.EnableVirtualTerminalProcessingMode; + isSupported = true; } } else @@ -92,7 +107,7 @@ public static void Render(TextWriter output, string text) output.Write(currentText.Slice(currentIndex, ansiToken.Index)); } - if (output == Console.Out || output == Console.Error) + if ((output == Console.Out && !Console.IsOutputRedirected) || (output == Console.Error && !Console.IsErrorRedirected)) { switch (ansiToken.Value) { diff --git a/TheDialgaTeam.Serilog/Sinks/Action/ActionSink.cs b/TheDialgaTeam.Serilog/Sinks/Action/ActionSink.cs index 3eeb792..6efe514 100644 --- a/TheDialgaTeam.Serilog/Sinks/Action/ActionSink.cs +++ b/TheDialgaTeam.Serilog/Sinks/Action/ActionSink.cs @@ -30,23 +30,28 @@ public sealed class ActionSink(ITextFormatter textFormatter, ActionSinkOptions a { [ThreadStatic] private static StringWriter? t_stringWriter; + + private readonly object _syncLock = new(); public void Emit(LogEvent logEvent) { - t_stringWriter ??= new StringWriter(); - - textFormatter.Format(logEvent, t_stringWriter); + lock (_syncLock) + { + t_stringWriter ??= new StringWriter(); + + textFormatter.Format(logEvent, t_stringWriter); - var stringBuilder = t_stringWriter.GetStringBuilder(); - if (stringBuilder.Length == 0) return; + var stringBuilder = t_stringWriter.GetStringBuilder(); + if (stringBuilder.Length == 0) return; - actionSinkOptions.RegisteredActionLogger?.Invoke(stringBuilder.ToString()); + actionSinkOptions.RegisteredActionLogger?.Invoke(stringBuilder.ToString()); - stringBuilder.Clear(); + stringBuilder.Clear(); - if (stringBuilder.Capacity > 1024) - { - stringBuilder.Capacity = 1024; + if (stringBuilder.Capacity > 1024) + { + stringBuilder.Capacity = 1024; + } } } } \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Sinks/Action/ActionSinkExtensions.cs b/TheDialgaTeam.Serilog/Sinks/Action/ActionSinkExtensions.cs index 9c67deb..04f9f89 100644 --- a/TheDialgaTeam.Serilog/Sinks/Action/ActionSinkExtensions.cs +++ b/TheDialgaTeam.Serilog/Sinks/Action/ActionSinkExtensions.cs @@ -20,11 +20,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using Microsoft.Extensions.DependencyInjection; using Serilog; using Serilog.Configuration; using Serilog.Formatting; -using TheDialgaTeam.Serilog.Formatting; namespace TheDialgaTeam.Serilog.Sinks.Action; @@ -34,9 +32,4 @@ public static LoggerConfiguration ActionSink(this LoggerSinkConfiguration sinkCo { return sinkConfiguration.Async(configuration => configuration.Sink(new ActionSink(textFormatter, actionSinkOptions))); } - - public static LoggerConfiguration ActionSink(this LoggerSinkConfiguration sinkConfiguration, IServiceProvider serviceProvider) - { - return sinkConfiguration.Async(configuration => configuration.Sink(new ActionSink(ActivatorUtilities.CreateInstance(serviceProvider), serviceProvider.GetRequiredService()))); - } } \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Sinks/AnsiConsole/AnsiConsoleSink.cs b/TheDialgaTeam.Serilog/Sinks/AnsiConsole/AnsiConsoleSink.cs index 9ac7ed7..3b24e16 100644 --- a/TheDialgaTeam.Serilog/Sinks/AnsiConsole/AnsiConsoleSink.cs +++ b/TheDialgaTeam.Serilog/Sinks/AnsiConsole/AnsiConsoleSink.cs @@ -22,15 +22,33 @@ using Serilog.Core; using Serilog.Events; -using Serilog.Formatting; +using TheDialgaTeam.Serilog.Formatting; namespace TheDialgaTeam.Serilog.Sinks.AnsiConsole; -public sealed class AnsiConsoleSink(ITextFormatter textFormatter) : ILogEventSink +public sealed class AnsiConsoleSink : ILogEventSink { + private readonly AnsiMessageTemplateTextFormatter _textFormatter; + private readonly object _syncLock = new(); + + public AnsiConsoleSink(AnsiMessageTemplateTextFormatter textFormatter) + { + _textFormatter = textFormatter; + } + + public AnsiConsoleSink(LogLevelMessageTemplateOptions options, IFormatProvider? formatProvider = null) + { + _textFormatter = new AnsiMessageTemplateTextFormatter(options, formatProvider); + } + public void Emit(LogEvent logEvent) { var output = logEvent.Level < LogEventLevel.Error ? Console.Out : Console.Error; - textFormatter.Format(logEvent, output); + + lock (_syncLock) + { + _textFormatter.Format(logEvent, output); + output.Flush(); + } } } \ No newline at end of file diff --git a/TheDialgaTeam.Serilog/Sinks/AnsiConsole/AnsiConsoleSinkExtensions.cs b/TheDialgaTeam.Serilog/Sinks/AnsiConsole/AnsiConsoleSinkExtensions.cs index 4fe061d..48d8e23 100644 --- a/TheDialgaTeam.Serilog/Sinks/AnsiConsole/AnsiConsoleSinkExtensions.cs +++ b/TheDialgaTeam.Serilog/Sinks/AnsiConsole/AnsiConsoleSinkExtensions.cs @@ -21,10 +21,8 @@ // SOFTWARE. using System.Runtime.InteropServices; -using Microsoft.Extensions.DependencyInjection; using Serilog; using Serilog.Configuration; -using Serilog.Formatting; using TheDialgaTeam.Serilog.Formatting; using TheDialgaTeam.Serilog.Native; @@ -32,16 +30,26 @@ namespace TheDialgaTeam.Serilog.Sinks.AnsiConsole; public static class AnsiConsoleSinkExtensions { - public static LoggerConfiguration AnsiConsoleSink(this LoggerSinkConfiguration sinkConfiguration, ITextFormatter textFormatter) + public static LoggerConfiguration AnsiConsoleSink(this LoggerSinkConfiguration sinkConfiguration, IFormatProvider? formatProvider = null) { SetAnsiConsole(); - return sinkConfiguration.Async(configuration => configuration.Sink(new AnsiConsoleSink(textFormatter))); + return sinkConfiguration.Async(configuration => configuration.Sink(new AnsiConsoleSink(new LogLevelMessageTemplateOptions(), formatProvider))); } - - public static LoggerConfiguration AnsiConsoleSink(this LoggerSinkConfiguration sinkConfiguration, IServiceProvider serviceProvider) + + public static LoggerConfiguration AnsiConsoleSink(this LoggerSinkConfiguration sinkConfiguration, LogLevelMessageTemplateOptions options, IFormatProvider? formatProvider = null) { SetAnsiConsole(); - return sinkConfiguration.Async(configuration => configuration.Sink(new AnsiConsoleSink(ActivatorUtilities.CreateInstance(serviceProvider)))); + return sinkConfiguration.Async(configuration => configuration.Sink(new AnsiConsoleSink(options, formatProvider))); + } + + public static LoggerConfiguration AnsiConsoleSink(this LoggerSinkConfiguration sinkConfiguration, Action options, IFormatProvider? formatProvider = null) + { + SetAnsiConsole(); + + var optionsBuilder = new LogLevelMessageTemplateOptionsBuilder(); + options(optionsBuilder); + + return sinkConfiguration.Async(configuration => configuration.Sink(new AnsiConsoleSink(optionsBuilder.Build(), formatProvider))); } private static void SetAnsiConsole() diff --git a/TheDialgaTeam.Serilog/TheDialgaTeam.Serilog.csproj b/TheDialgaTeam.Serilog/TheDialgaTeam.Serilog.csproj index 1e3d99d..35eb8fa 100644 --- a/TheDialgaTeam.Serilog/TheDialgaTeam.Serilog.csproj +++ b/TheDialgaTeam.Serilog/TheDialgaTeam.Serilog.csproj @@ -14,7 +14,7 @@ true TheDialgaTeam.Serilog - 1.2.0 + 1.3.0 Yong Jian Ming TheDialgaTeam TheDialgaTeam.Serilog