From d7099f376ab23a108352e0f02d6006bc744d4e02 Mon Sep 17 00:00:00 2001 From: Rolf Kristensen Date: Sun, 30 Jul 2023 12:36:07 +0200 Subject: [PATCH 1/4] LogEvent without message template parameters should output formatted message --- nlog-targets-seq.sln | 9 +- src/NLog.Targets.Seq/CompactJsonLayout.cs | 26 ++-- .../FormattedMessageLayout.cs | 53 ++++++++ src/NLog.Targets.Seq/RenderingsLayout.cs | 55 ++++---- src/NLog.Targets.Seq/SeqTarget.cs | 128 ++++++++++++------ test/NLog.Targets.Seq.Tests/SeqTargetTests.cs | 21 ++- 6 files changed, 204 insertions(+), 88 deletions(-) create mode 100644 src/NLog.Targets.Seq/FormattedMessageLayout.cs diff --git a/nlog-targets-seq.sln b/nlog-targets-seq.sln index ffa4e3c..7a704c1 100644 --- a/nlog-targets-seq.sln +++ b/nlog-targets-seq.sln @@ -1,19 +1,18 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29209.62 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33627.172 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AC295EEA-D319-4146-97E0-B978DF6F2557}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{66B005E9-A083-41E8-BD89-4D6E753CD8BF}" ProjectSection(SolutionItems) = preProject + .gitattributes = .gitattributes + .gitignore = .gitignore appveyor.yml = appveyor.yml Build.ps1 = Build.ps1 LICENSE = LICENSE README.md = README.md - .gitattributes = .gitattributes - .gitignore = .gitignore - global.json = global.json EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2ED926D3-7AC8-4BFD-A16B-74D942602968}" diff --git a/src/NLog.Targets.Seq/CompactJsonLayout.cs b/src/NLog.Targets.Seq/CompactJsonLayout.cs index ebfc229..96e1445 100644 --- a/src/NLog.Targets.Seq/CompactJsonLayout.cs +++ b/src/NLog.Targets.Seq/CompactJsonLayout.cs @@ -13,36 +13,30 @@ // limitations under the License. using System; +using NLog.Config; using NLog.Layouts; namespace NLog.Targets.Seq { + [ThreadAgnostic] class CompactJsonLayout : JsonLayout { readonly JsonAttribute - _timestampAttribute = new JsonAttribute("@t", new SimpleLayout("${date:o}")), + _timestampAttribute = new JsonAttribute("@t", new SimpleLayout("${date:format=o}")), _levelAttribute = new JsonAttribute("@l", new SimpleLayout("${level}")), _exceptionAttribute = new JsonAttribute("@x", new SimpleLayout("${exception:format=toString}")), - _messageAttribute = new JsonAttribute("@m", new SimpleLayout("${message}")), - _messageTemplateAttribute = new JsonAttribute("@mt", new SimpleLayout("${message:raw=true}")); + _messageAttribute = new JsonAttribute("@m", new FormattedMessageLayout()), + _messageTemplateAttribute = new JsonAttribute("@mt", new SimpleLayout("${onhasproperties:${message:raw=true}}")); - public CompactJsonLayout(bool usesTemplate) + public CompactJsonLayout() { Attributes.Add(_timestampAttribute); Attributes.Add(_levelAttribute); Attributes.Add(_exceptionAttribute); - - if (usesTemplate) - { - Attributes.Add(_messageTemplateAttribute); - - var renderingsAttribute = new JsonAttribute("@r", new RenderingsLayout(new Lazy(ResolveService)), encode: false); - Attributes.Add(renderingsAttribute); - } - else - { - Attributes.Add(_messageAttribute); - } + Attributes.Add(_messageTemplateAttribute); + var renderingsAttribute = new JsonAttribute("@r", new RenderingsLayout(new Lazy(ResolveService)), encode: false); + Attributes.Add(renderingsAttribute); + Attributes.Add(_messageAttribute); IncludeEventProperties = true; IncludeScopeProperties = true; diff --git a/src/NLog.Targets.Seq/FormattedMessageLayout.cs b/src/NLog.Targets.Seq/FormattedMessageLayout.cs new file mode 100644 index 0000000..7dc623f --- /dev/null +++ b/src/NLog.Targets.Seq/FormattedMessageLayout.cs @@ -0,0 +1,53 @@ +// Seq Target for NLog - Copyright 2014-2017 Datalust and contributors +// +// 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.Text; +using NLog.Config; +using NLog.Layouts; + +namespace NLog.Targets.Seq +{ + [ThreadAgnostic] + class FormattedMessageLayout : Layout + { + protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target) + { + target.Append(GetFormattedMessage(logEvent)); + } + + protected override string GetFormattedMessage(LogEventInfo logEvent) + { + if (HasMessageTemplateSyntax(logEvent)) + { + return string.Empty; // Message Template Syntax, no need to include formatted message + } + + return logEvent.FormattedMessage; + } + + bool HasMessageTemplateSyntax(LogEventInfo logEvent) + { + if (!logEvent.HasProperties) + return false; + + if (logEvent.Message?.IndexOf("{0", System.StringComparison.Ordinal) >= 0) + { + var mtp = logEvent.MessageTemplateParameters; + return !mtp.IsPositional; + } + + return true; + } + } +} diff --git a/src/NLog.Targets.Seq/RenderingsLayout.cs b/src/NLog.Targets.Seq/RenderingsLayout.cs index d13777d..ed471d3 100644 --- a/src/NLog.Targets.Seq/RenderingsLayout.cs +++ b/src/NLog.Targets.Seq/RenderingsLayout.cs @@ -13,13 +13,14 @@ // limitations under the License. using System; -using System.IO; using System.Text; +using NLog.Config; using NLog.Layouts; using NLog.MessageTemplates; namespace NLog.Targets.Seq { + [ThreadAgnostic] class RenderingsLayout : Layout { readonly Lazy _jsonConverter; @@ -31,15 +32,13 @@ public RenderingsLayout(Lazy jsonConverter) protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target) { - var result = RenderLogEvent(logEvent, target); - if (result == null) - target.Append("null"); + RenderLogEvent(logEvent, target); } protected override string GetFormattedMessage(LogEventInfo logEvent) { var result = RenderLogEvent(logEvent); - return result?.ToString() ?? "null"; + return result?.ToString() ?? ""; } StringBuilder RenderLogEvent(LogEventInfo logEvent, StringBuilder preallocated = null) @@ -49,38 +48,31 @@ StringBuilder RenderLogEvent(LogEventInfo logEvent, StringBuilder preallocated = try { + if (!logEvent.HasProperties) + return null; + var nextDelimiter = ""; var mtp = logEvent.MessageTemplateParameters; + if (mtp.IsPositional || mtp.Count == 0) + return null; foreach (var parameter in mtp) { - if (parameter.Format == null) continue; - + if (string.IsNullOrEmpty(parameter.Format)) continue; + + if (!logEvent.Properties.TryGetValue(parameter.Name, out var value)) continue; + if (output == null) { output = preallocated ?? new StringBuilder(); - output.Append("["); + output.Append('['); } - var space = new StringWriter(); - - if (logEvent.Properties != null && - logEvent.Properties.TryGetValue(parameter.Name, out var value)) - { - if (parameter.CaptureType == CaptureType.Normal) - { - var formatString = string.Concat("{0:", parameter.Format, "}"); - space.Write(formatString, value); - } - else - { - space.Write(value); - } - } + string formattedValue = FormatToString(parameter, value); output.Append(nextDelimiter); nextDelimiter = ","; - _jsonConverter.Value.SerializeObject(space.ToString(), output); + _jsonConverter.Value.SerializeObject(formattedValue, output); } return output; @@ -94,7 +86,20 @@ StringBuilder RenderLogEvent(LogEventInfo logEvent, StringBuilder preallocated = } finally { - output?.Append("]"); + output?.Append(']'); + } + } + + private static string FormatToString(MessageTemplateParameter parameter, object value) + { + if (parameter.CaptureType == CaptureType.Normal) + { + var formatString = string.Concat("{0:", parameter.Format, "}"); + return string.Format(formatString, value); + } + else + { + return Convert.ToString(value); } } } diff --git a/src/NLog.Targets.Seq/SeqTarget.cs b/src/NLog.Targets.Seq/SeqTarget.cs index f670faa..972a9b7 100644 --- a/src/NLog.Targets.Seq/SeqTarget.cs +++ b/src/NLog.Targets.Seq/SeqTarget.cs @@ -35,6 +35,8 @@ public sealed class SeqTarget : Target Layout _serverUrl; Layout _apiKey; Layout _proxyAddress; + readonly JsonLayout _compactLayout = new CompactJsonLayout(); + readonly char[] _reusableEncodingBuffer = new char[32 * 1024]; // Avoid large-object-heap Uri _webRequestUri; string _headerApiKey; @@ -46,12 +48,12 @@ public sealed class SeqTarget : Target /// /// The layout used to format `LogEvent`s as compact JSON. /// - public JsonLayout TemplatedClefLayout { get; } = new CompactJsonLayout(true); + public JsonLayout TemplatedClefLayout => _compactLayout; /// /// The layout used to format `LogEvent`s as compact JSON. /// - public JsonLayout TextClefLayout { get; } = new CompactJsonLayout(false); + public JsonLayout TextClefLayout => _compactLayout; /// /// Maximum size allowed for JSON payload sent to Seq-Server. Discards log events that are larger than limit. @@ -93,8 +95,17 @@ public sealed class SeqTarget : Target /// public int MaxRecursionLimit { - get => TemplatedClefLayout.MaxRecursionLimit; - set { TemplatedClefLayout.MaxRecursionLimit = value; TextClefLayout.MaxRecursionLimit = value; } + get => _compactLayout.MaxRecursionLimit; + set => _compactLayout.MaxRecursionLimit = value; + } + + /// + /// Gets or sets whether to include the contents of the dictionary. + /// + public bool IncludeScopeProperties + { + get => _compactLayout.IncludeScopeProperties; + set => _compactLayout.IncludeScopeProperties = value; } /// @@ -116,8 +127,7 @@ protected override void InitializeTarget() foreach (var prop in Properties) { var attr = new JsonAttribute(prop.Name, prop.Value, prop.AsString); - TextClefLayout.Attributes.Add(attr); - TemplatedClefLayout.Attributes.Add(attr); + AddJsonAttribute(_compactLayout, attr); } if (!string.IsNullOrEmpty(ServerUrl)) @@ -199,12 +209,7 @@ async Task PostBatch(IList logEvents) if (_webRequestUri == null) return; - var request = new HttpRequestMessage(HttpMethod.Post, _webRequestUri); - if (!string.IsNullOrWhiteSpace(_headerApiKey)) - request.Headers.Add(SeqApi.ApiKeyHeaderName, _headerApiKey); - List extraBatch = null; - var totalPayload = 0; var payload = new StringBuilder(); for (var i = 0; i < logEvents.Count; ++i) @@ -214,29 +219,52 @@ async Task PostBatch(IList logEvents) if (evt.Level < _minimumLevel) continue; - var json = RenderCompactJsonLine(evt); - - if (JsonPayloadMaxLength > 0) + int orgLength = payload.Length; + var jsonLength = RenderCompactJsonLine(evt, payload); + if (jsonLength > JsonPayloadMaxLength) { - if (json.Length > JsonPayloadMaxLength) - { - InternalLogger.Warn("Seq(Name={0}): Event JSON representation exceeds the char limit: {1} > {2}", Name, json.Length, JsonPayloadMaxLength); - continue; - } - if (totalPayload + json.Length > JsonPayloadMaxLength) - { - extraBatch = new List(logEvents.Count - i); - for (; i < logEvents.Count; ++i) - extraBatch.Add(logEvents[i]); - break; - } + InternalLogger.Warn("Seq(Name={0}): Event JSON representation exceeds the char limit: {1} > {2}", Name, jsonLength, JsonPayloadMaxLength); + payload.Length = orgLength; + logEvents[i].Continuation(new ArgumentException("Seq JSON Payload max length exceeded")); } + else if (payload.Length > JsonPayloadMaxLength) + { + extraBatch = new List(logEvents.Count - i); + for (; i < logEvents.Count; ++i) + extraBatch.Add(logEvents[i]); + payload.Length = orgLength; + break; + } + else if (jsonLength > 0) + { + payload.AppendLine(); + } + } - totalPayload += json.Length; - payload.AppendLine(json); + if (payload.Length > 0) + { + await SendPayload(payload).ConfigureAwait(false); } - request.Content = new StringContent(payload.ToString(), Utf8, SeqApi.CompactLogEventFormatMediaType); + var completedCount = logEvents.Count - (extraBatch?.Count ?? 0); + for (var i = 0; i < completedCount; ++i) + logEvents[i].Continuation(null); + + if (extraBatch != null) + { + await PostBatch(extraBatch).ConfigureAwait(false); + } + } + + private async Task SendPayload(StringBuilder payload) + { + var request = new HttpRequestMessage(HttpMethod.Post, _webRequestUri); + if (!string.IsNullOrWhiteSpace(_headerApiKey)) + request.Headers.Add(SeqApi.ApiKeyHeaderName, _headerApiKey); + + var httpContent = new ByteArrayContent(EncodePayload(Utf8, payload)); + httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(SeqApi.CompactLogEventFormatMediaType) { CharSet = Utf8.WebName }; + request.Content = httpContent; // Even if no events are above `_minimumLevel`, we'll send a batch to make sure we observe minimum // level changes sent by the server. @@ -260,22 +288,31 @@ async Task PostBatch(IList logEvents) } } } + } - var completedCount = logEvents.Count - (extraBatch?.Count ?? 0); - for (var i = 0; i < completedCount; ++i) - logEvents[i].Continuation(null); - - if (extraBatch != null) + private byte[] EncodePayload(Encoding encoder, StringBuilder payload) + { + lock (_reusableEncodingBuffer) { - await PostBatch(extraBatch).ConfigureAwait(false); + int totalLength = payload.Length; + if (totalLength < _reusableEncodingBuffer.Length) + { + payload.CopyTo(0, _reusableEncodingBuffer, 0, payload.Length); + return encoder.GetBytes(_reusableEncodingBuffer, 0, totalLength); + } + else + { + return encoder.GetBytes(payload.ToString()); + } } } - internal string RenderCompactJsonLine(LogEventInfo evt) + internal int RenderCompactJsonLine(LogEventInfo evt, StringBuilder payload) { - var hasProperties = evt.HasProperties && evt.Properties.Count > 0; - var json = RenderLogEvent(hasProperties ? TemplatedClefLayout : TextClefLayout, evt); - return json; + var orgLength = payload.Length; + _compactLayout.Render(evt, payload); + var jsonLength = payload.Length - orgLength; + return jsonLength; } internal void TestInitialize() @@ -283,6 +320,19 @@ internal void TestInitialize() InitializeTarget(); } + private static void AddJsonAttribute(JsonLayout jsonLayout, JsonAttribute jsonAttribute) + { + for (int i = jsonLayout.Attributes.Count - 1; i >= 0; --i) + { + if (jsonLayout.Attributes[i].Name == jsonAttribute.Name) + { + jsonLayout.Attributes.RemoveAt(i); + } + } + + jsonLayout.Attributes.Add(jsonAttribute); + } + /// protected override void Dispose(bool disposing) { diff --git a/test/NLog.Targets.Seq.Tests/SeqTargetTests.cs b/test/NLog.Targets.Seq.Tests/SeqTargetTests.cs index 3b0d707..aa1a66a 100644 --- a/test/NLog.Targets.Seq.Tests/SeqTargetTests.cs +++ b/test/NLog.Targets.Seq.Tests/SeqTargetTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using Xunit; namespace NLog.Targets.Seq.Tests @@ -23,8 +24,9 @@ static void ToCompactJson(LogEventInfo evt, TextWriter output, IEnumerable act, IEnumerable properties = null, int? maxRecursionLimit = null) @@ -59,7 +61,19 @@ public void ANonInfoLevelEventIsValid() [Fact] public void AMinimalEventIsValidJson() { - AssertValidJson(log => log.Info("One {Property}", 42)); + var evt = AssertValidJson(log => log.Info("One {Property}", 42)); + Assert.Equal(42, evt["Property"].Value()); + Assert.Equal("One {Property}", evt["@mt"].Value()); + } + + [Fact] + public void APositionalEventIsValidJson() + { + var logEvent = LogEventInfo.Create(LogLevel.Info, null, null, 42); + logEvent.Properties["Property"] = 42; + var evt = AssertValidJson(log => log.Log(logEvent)); + Assert.Equal(42, evt["Property"].Value()); + Assert.Equal("42", evt["@m"].Value()); } [Fact] @@ -69,6 +83,7 @@ public void SimpleEventPropertiesAreRenderedIntoJson() logEvent.Properties["Answer"] = 42; var evt = AssertValidJson(log => log.Info(logEvent)); Assert.Equal(42, evt["Answer"].Value()); + Assert.Equal("Hello ", evt["@mt"].Value()); } [Fact] From b937257006e82fee90f3eb69412483178d6051e9 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Fri, 18 Aug 2023 08:53:26 +1000 Subject: [PATCH 2/4] Bump minor version; template handling has changed slightly --- src/NLog.Targets.Seq/NLog.Targets.Seq.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NLog.Targets.Seq/NLog.Targets.Seq.csproj b/src/NLog.Targets.Seq/NLog.Targets.Seq.csproj index 8aad972..cc55b27 100644 --- a/src/NLog.Targets.Seq/NLog.Targets.Seq.csproj +++ b/src/NLog.Targets.Seq/NLog.Targets.Seq.csproj @@ -3,7 +3,7 @@ An NLog target that writes structured log events to Seq Datalust;Contributors - 3.0.0 + 3.1.0 net45;net462;netstandard2.0;net5.0;net6.0 true true From 64624f2f5c429847bfe7b2a8955bf4f5237be645 Mon Sep 17 00:00:00 2001 From: Rolf Kristensen Date: Sun, 31 Dec 2023 15:44:52 +0100 Subject: [PATCH 3/4] Introduced ExcludeProperties from inner JsonLayout --- src/NLog.Targets.Seq/SeqTarget.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/NLog.Targets.Seq/SeqTarget.cs b/src/NLog.Targets.Seq/SeqTarget.cs index 972a9b7..32a6c02 100644 --- a/src/NLog.Targets.Seq/SeqTarget.cs +++ b/src/NLog.Targets.Seq/SeqTarget.cs @@ -74,8 +74,8 @@ public sealed class SeqTarget : Target /// /// The address of the proxy to use, including port separated by a colon. If not provided, default operating system proxy will be used. /// - public string ProxyAddress { get => (_proxyAddress as SimpleLayout)?.Text; set => _proxyAddress = value ?? string.Empty; } + /// /// Use default credentials /// @@ -108,6 +108,15 @@ public bool IncludeScopeProperties set => _compactLayout.IncludeScopeProperties = value; } + /// + /// List of property names to exclude from LogEventInfo-Properties or ScopeContext-Properties. + /// + public ISet ExcludeProperties + { + get => _compactLayout.ExcludeProperties; + set => _compactLayout.ExcludeProperties = value; + } + /// /// Construct a . /// @@ -223,9 +232,9 @@ async Task PostBatch(IList logEvents) var jsonLength = RenderCompactJsonLine(evt, payload); if (jsonLength > JsonPayloadMaxLength) { - InternalLogger.Warn("Seq(Name={0}): Event JSON representation exceeds the char limit: {1} > {2}", Name, jsonLength, JsonPayloadMaxLength); + InternalLogger.Warn("Seq(Name={0}): LogEvent JSON-length exceeds JsonPayloadMaxLength: {1} > {2}", Name, jsonLength, JsonPayloadMaxLength); payload.Length = orgLength; - logEvents[i].Continuation(new ArgumentException("Seq JSON Payload max length exceeded")); + logEvents[i].Continuation(new ArgumentException("Seq LogEvent JSON-length exceeds JsonPayloadMaxLength")); } else if (payload.Length > JsonPayloadMaxLength) { From 26b8572b174f9f9206df0e53c4a54a232c13e847 Mon Sep 17 00:00:00 2001 From: Rolf Kristensen Date: Tue, 9 Jan 2024 20:08:24 +0100 Subject: [PATCH 4/4] Updated dependency to NLog v5.2.5 and removed NET5 --- global.json | 3 ++- nlog-targets-seq.sln | 1 + src/NLog.Targets.Seq/NLog.Targets.Seq.csproj | 6 +++--- test/NLog.Targets.Seq.Tests/NLog.Targets.Seq.Tests.csproj | 2 +- test/NLog.Targets.Seq.Tests/SeqTargetTests.cs | 5 +++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/global.json b/global.json index dfa39aa..08fc58c 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,6 @@ { "sdk": { - "version": "6.0.408" + "version": "6.0.100", + "rollForward": "latestMinor" } } \ No newline at end of file diff --git a/nlog-targets-seq.sln b/nlog-targets-seq.sln index 7a704c1..839d08f 100644 --- a/nlog-targets-seq.sln +++ b/nlog-targets-seq.sln @@ -11,6 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{66B005 .gitignore = .gitignore appveyor.yml = appveyor.yml Build.ps1 = Build.ps1 + global.json = global.json LICENSE = LICENSE README.md = README.md EndProjectSection diff --git a/src/NLog.Targets.Seq/NLog.Targets.Seq.csproj b/src/NLog.Targets.Seq/NLog.Targets.Seq.csproj index cc55b27..71aa3b4 100644 --- a/src/NLog.Targets.Seq/NLog.Targets.Seq.csproj +++ b/src/NLog.Targets.Seq/NLog.Targets.Seq.csproj @@ -1,10 +1,10 @@ - + An NLog target that writes structured log events to Seq Datalust;Contributors 3.1.0 - net45;net462;netstandard2.0;net5.0;net6.0 + net45;net462;netstandard2.0;net6.0 true true ../../asset/nlog-targets-seq.snk @@ -31,7 +31,7 @@ - + diff --git a/test/NLog.Targets.Seq.Tests/NLog.Targets.Seq.Tests.csproj b/test/NLog.Targets.Seq.Tests/NLog.Targets.Seq.Tests.csproj index 2fc9a89..dfbfbb9 100644 --- a/test/NLog.Targets.Seq.Tests/NLog.Targets.Seq.Tests.csproj +++ b/test/NLog.Targets.Seq.Tests/NLog.Targets.Seq.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/test/NLog.Targets.Seq.Tests/SeqTargetTests.cs b/test/NLog.Targets.Seq.Tests/SeqTargetTests.cs index aa1a66a..e2dc773 100644 --- a/test/NLog.Targets.Seq.Tests/SeqTargetTests.cs +++ b/test/NLog.Targets.Seq.Tests/SeqTargetTests.cs @@ -32,9 +32,10 @@ static void ToCompactJson(LogEventInfo evt, TextWriter output, IEnumerable act, IEnumerable properties = null, int? maxRecursionLimit = null) { var logger = LogManager.GetCurrentClassLogger(); + var config = new LoggingConfiguration(); var target = new CollectingTarget(); - - SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Trace); + config.AddRuleForAllLevels(target); + LogManager.Configuration = config; act(logger);