-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MicrosoftConsoleJsonLayout - Simulates Microsoft AddJsonConsole Forma…
…tter (#555)
- Loading branch information
Showing
4 changed files
with
265 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
154 changes: 154 additions & 0 deletions
154
src/NLog.Extensions.Logging/Layouts/MicrosoftConsoleJsonLayout.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using NLog.Config; | ||
using NLog.Layouts; | ||
|
||
namespace NLog.Extensions.Logging | ||
{ | ||
/// <summary> | ||
/// Renders output that simulates Microsoft Json Console Formatter from AddJsonConsol | ||
/// </summary> | ||
[Layout("MicrosoftConsoleJsonLayout")] | ||
[ThreadAgnostic] | ||
public class MicrosoftConsoleJsonLayout : JsonLayout | ||
{ | ||
private static readonly string[] EventIdMapper = Enumerable.Range(0, 50).Select(id => id.ToString(System.Globalization.CultureInfo.InvariantCulture)).ToArray(); | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="MicrosoftConsoleJsonLayout" /> class. | ||
/// </summary> | ||
public MicrosoftConsoleJsonLayout() | ||
{ | ||
Attributes.Add(new JsonAttribute("Timestamp", "${date:format=O:universalTime=true}")); | ||
Attributes.Add(new JsonAttribute("EventId", Layout.FromMethod(evt => LookupEventId(evt), LayoutRenderOptions.ThreadAgnostic)) { Encode = false }); | ||
Attributes.Add(new JsonAttribute("LogLevel", Layout.FromMethod(evt => ConvertLogLevel(evt.Level), LayoutRenderOptions.ThreadAgnostic))); | ||
Attributes.Add(new JsonAttribute("Category", "${logger}")); | ||
Attributes.Add(new JsonAttribute("Message", "${message}")); | ||
Attributes.Add(new JsonAttribute("Exception", "${replace-newlines:${exception:format=tostring,data}}")); | ||
var stateJsonLayout = new JsonLayout() { IncludeEventProperties = true }; | ||
stateJsonLayout.ExcludeProperties.Add("EventId"); | ||
stateJsonLayout.ExcludeProperties.Add("EventId_Id"); | ||
stateJsonLayout.Attributes.Add(new JsonAttribute("{OriginalFormat}", "${message:raw=true}")); | ||
Attributes.Add(new JsonAttribute("State", stateJsonLayout) { Encode = false }); | ||
} | ||
|
||
/// <summary> | ||
/// Gets the array of attributes for the "state"-section | ||
/// </summary> | ||
[ArrayParameter(typeof(JsonAttribute), "state")] | ||
public IList<JsonAttribute> StateAttributes | ||
{ | ||
get | ||
{ | ||
var index = LookupNamedAttributeIndex("State"); | ||
return index >= 0 ? (Attributes[index]?.Layout as JsonLayout)?.Attributes : null; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets or sets whether to include "scopes"-section | ||
/// </summary> | ||
public bool IncludeScopes | ||
{ | ||
get => LookupNamedAttributeIndex("Scopes") >= 0; | ||
set | ||
{ | ||
var index = LookupNamedAttributeIndex("Scopes"); | ||
if (index >= 0) | ||
{ | ||
if (!value) | ||
Attributes.RemoveAt(index); | ||
} | ||
else if (value) | ||
{ | ||
Attributes.Add(new JsonAttribute("Scopes", "${scopenested:format=@}") { Encode = false }); | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets or sets whether to include "Timestamp"-section | ||
/// </summary> | ||
public string TimestampFormat | ||
{ | ||
get | ||
{ | ||
var index = LookupNamedAttributeIndex("Timestamp"); | ||
return index >= 0 ? ((Attributes[index].Layout as SimpleLayout)?.LayoutRenderers?.FirstOrDefault() as NLog.LayoutRenderers.DateLayoutRenderer)?.Format : null; | ||
} | ||
set | ||
{ | ||
var index = LookupNamedAttributeIndex("Timestamp"); | ||
if (index >= 0) | ||
{ | ||
Attributes.RemoveAt(index); | ||
} | ||
|
||
if (!string.IsNullOrEmpty(value)) | ||
{ | ||
Attributes.Insert(0, new JsonAttribute("Timestamp", $"${{date:format={value}:universalTime=true}}")); | ||
} | ||
} | ||
} | ||
|
||
private int LookupNamedAttributeIndex(string attributeName) | ||
{ | ||
for (int i = 0; i < Attributes.Count; ++i) | ||
{ | ||
if (attributeName.Equals(Attributes[i].Name, StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
return i; | ||
} | ||
} | ||
return -1; | ||
} | ||
|
||
private static string LookupEventId(LogEventInfo logEvent) | ||
{ | ||
if (logEvent.HasProperties) | ||
{ | ||
if (logEvent.Properties.TryGetValue("EventId", out var eventObject)) | ||
{ | ||
if (eventObject is int eventId) | ||
return ConvertEventId(eventId); | ||
else if (eventObject is Microsoft.Extensions.Logging.EventId eventIdStruct) | ||
return ConvertEventId(eventIdStruct.Id); | ||
} | ||
|
||
if (logEvent.Properties.TryGetValue("EventId_Id", out var eventid) && eventid is int) | ||
{ | ||
return ConvertEventId((int)eventid); | ||
} | ||
} | ||
|
||
return "0"; | ||
} | ||
|
||
private static string ConvertEventId(int eventId) | ||
{ | ||
if (eventId == 0) | ||
return "0"; | ||
else if (eventId > 0 || eventId < EventIdMapper.Length) | ||
return EventIdMapper[eventId]; | ||
else | ||
return eventId.ToString(System.Globalization.CultureInfo.InvariantCulture); | ||
} | ||
|
||
private static string ConvertLogLevel(LogLevel logLevel) | ||
{ | ||
if (logLevel == LogLevel.Trace) | ||
return nameof(Microsoft.Extensions.Logging.LogLevel.Trace); | ||
else if (logLevel == LogLevel.Debug) | ||
return nameof(Microsoft.Extensions.Logging.LogLevel.Debug); | ||
else if (logLevel == LogLevel.Info) | ||
return nameof(Microsoft.Extensions.Logging.LogLevel.Information); | ||
else if (logLevel == LogLevel.Warn) | ||
return nameof(Microsoft.Extensions.Logging.LogLevel.Warning); | ||
else if (logLevel == LogLevel.Error) | ||
return nameof(Microsoft.Extensions.Logging.LogLevel.Error); | ||
else | ||
return nameof(Microsoft.Extensions.Logging.LogLevel.Critical); | ||
} | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
test/NLog.Extensions.Logging.Tests/MicrosoftConsoleJsonLayoutTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using Microsoft.Extensions.Logging; | ||
using Xunit; | ||
|
||
namespace NLog.Extensions.Logging.Tests | ||
{ | ||
public class MicrosoftConsoleJsonLayoutTests | ||
{ | ||
[Fact] | ||
public void MicrosoftConsoleJsonLayout_NullEvent() | ||
{ | ||
var layout = new MicrosoftConsoleJsonLayout() { TimestampFormat = null }; | ||
var result = layout.Render(LogEventInfo.CreateNullEvent()); | ||
Assert.Contains("{ \"EventId\": 0, \"LogLevel\": \"Critical\", \"State\": { } }", result); | ||
} | ||
|
||
[Fact] | ||
public void MicrosoftConsoleJsonLayout_ExceptionEvent() | ||
{ | ||
var layout = new MicrosoftConsoleJsonLayout(); | ||
var exception = new ArgumentException("Test"); | ||
var eventId = 42; | ||
var logEvent1 = new LogEventInfo(LogLevel.Error, "MyLogger", null, "Alert {EventId}", new object[] { eventId }, exception); | ||
var result1 = layout.Render(logEvent1); | ||
Assert.Equal($"{{ \"Timestamp\": \"{logEvent1.TimeStamp.ToUniversalTime().ToString("O")}\", \"EventId\": {eventId}, \"LogLevel\": \"Error\", \"Category\": \"MyLogger\", \"Message\": \"Alert {eventId}\", \"Exception\": \"{exception.ToString()}\", \"State\": {{ \"{{OriginalFormat}}\": \"Alert {{EventId}}\" }} }}", result1); | ||
var logEvent2 = new LogEventInfo(LogLevel.Error, "MyLogger", null, "Alert {EventId_Id}", new object[] { eventId }, exception); | ||
var result2 = layout.Render(logEvent2); | ||
Assert.Equal($"{{ \"Timestamp\": \"{logEvent2.TimeStamp.ToUniversalTime().ToString("O")}\", \"EventId\": {eventId}, \"LogLevel\": \"Error\", \"Category\": \"MyLogger\", \"Message\": \"Alert {eventId}\", \"Exception\": \"{exception.ToString()}\", \"State\": {{ \"{{OriginalFormat}}\": \"Alert {{EventId_Id}}\" }} }}", result2); | ||
} | ||
|
||
[Fact] | ||
public void MicrosoftConsoleJsonLayout_IncludeScopesEvent() | ||
{ | ||
var logFactory = new LogFactory().Setup().LoadConfiguration(builder => | ||
{ | ||
var layout = new MicrosoftConsoleJsonLayout() { IncludeScopes = true }; | ||
builder.ForLogger().WriteTo(new NLog.Targets.MemoryTarget("test") { Layout = layout }); | ||
}).LogFactory; | ||
var logger = logFactory.GetCurrentClassLogger(); | ||
|
||
var exception = new ArgumentException("Test"); | ||
var eventId = 42; | ||
using var requestScope = logger.PushScopeNested("Request Started"); | ||
using var activityScope = logger.PushScopeNested("Activity Started"); | ||
var logEvent = new LogEventInfo(LogLevel.Error, null, null, "Alert {EventId}", new object[] { eventId }, exception); | ||
logger.Log(logEvent); | ||
var result = logFactory.Configuration.FindTargetByName<NLog.Targets.MemoryTarget>("test")?.Logs?.FirstOrDefault(); | ||
Assert.Equal($"{{ \"Timestamp\": \"{logEvent.TimeStamp.ToUniversalTime().ToString("O")}\", \"EventId\": {eventId}, \"LogLevel\": \"Error\", \"Category\": \"{typeof(MicrosoftConsoleJsonLayoutTests).FullName}\", \"Message\": \"Alert {eventId}\", \"Exception\": \"{exception.ToString()}\", \"State\": {{ \"{{OriginalFormat}}\": \"Alert {{EventId}}\" }}, \"Scopes\": [ \"Request Started\", \"Activity Started\" ] }}", result); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters