Skip to content

Commit

Permalink
feat: better support numeric types in properties
Browse files Browse the repository at this point in the history
  • Loading branch information
GerkinDev committed Oct 14, 2024
1 parent bf21f36 commit 20ae680
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ public override void Render(LogEvent logEvent, TokenEmitter emitToken)
break;
case PropertyToken pt:
if (logEvent.Properties.TryGetValue(pt.PropertyName, out var propertyValue))
emitToken.Object(ObjectModelInterop.ToInteropValue(propertyValue));
{
new PropertyTokenRenderer(pt, propertyValue).Render(logEvent, emitToken);
}
break;
default:
throw new InvalidOperationException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

using Serilog.Events;
using System.Text;
using static System.Net.Mime.MediaTypeNames;

namespace Serilog.Sinks.BrowserConsole.Output;

Expand All @@ -22,21 +23,35 @@ class TokenEmitter
private StringBuilder _template = new();
private List<object?> _args = [];

internal void Literal(string text)
internal void Literal(string template)
{
_template.Append(text.Replace("%", "%%"));
_template.Append(template.Replace("%", "%%"));
}

internal void Text(string text) {
internal void Text(object @string)
{
_template.Append("%s");
_args.Add(text);
_args.Add(@string);
}
internal void Text(string @string) => Text((object)@string);

internal void Object(object? value)
internal void Object(object? @object)
{
_template.Append("%o");
_args.Add(value);
_args.Add(@object);
}

internal void Integer(object @int)
{
_template.Append("%d");
_args.Add(@int);
}

internal void Float(object @float) {
_template.Append("%f");
_args.Add(@float);
}

internal object?[] YieldArgs() => [_template.ToString(), .. _args];

internal void Style(string styleContent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,47 @@ public override void Render(LogEvent logEvent, TokenEmitter emitToken)

foreach (var property in included)
{
emitToken.Object(ObjectModelInterop.ToInteropValue(property.Value, _token.Format));
new PropertyTokenRenderer(_token, property.Value).Render(logEvent, emitToken);
}
}
private void HandleProperty(LogEventProperty property, TokenEmitter emitToken)
{
if (property.Value is ScalarValue sv)
{
if(sv.Value is null)
{
emitToken.Object(ObjectModelInterop.ToInteropValue(property.Value, _token.Format));
return;
}
switch (Type.GetTypeCode(sv.Value.GetType()))
{
// See https://stackoverflow.com/a/1750024
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
emitToken.Integer(sv.Value);
break;
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single:
emitToken.Float(sv.Value);
break;
case TypeCode.String:
case TypeCode.Char:
emitToken.Text(sv.Value);
break;
default:
emitToken.Object(ObjectModelInterop.ToInteropValue(property.Value, _token.Format));
break;
}
}
else
emitToken.Object(ObjectModelInterop.ToInteropValue(property.Value, _token.Format));
}

static bool TemplateContainsPropertyName(MessageTemplate template, string propertyName)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2017 Serilog 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;
using System.Linq;
using Serilog.Events;
using Serilog.Parsing;

namespace Serilog.Sinks.BrowserConsole.Output
{
class PropertyTokenRenderer : OutputTemplateTokenRenderer
{
readonly PropertyToken _token;
readonly LogEventPropertyValue _propertyValue;
public PropertyTokenRenderer(PropertyToken token, LogEventPropertyValue propertyValue)
{
_token = token;
_propertyValue = propertyValue;
}

public override void Render(LogEvent logEvent, TokenEmitter emitToken)
{
if (_propertyValue is ScalarValue sv)
{
if (sv.Value is null)
{
emitToken.Object(ObjectModelInterop.ToInteropValue(sv));
return;
}
switch (Type.GetTypeCode(sv.Value.GetType()))
{
// See https://stackoverflow.com/a/1750024
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
emitToken.Integer(sv.Value);
break;
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single:
emitToken.Float(sv.Value);
break;
case TypeCode.String:
case TypeCode.Char:
emitToken.Text(sv.Value);
break;
default:
emitToken.Object(ObjectModelInterop.ToInteropValue(sv));
break;
}
}
else
emitToken.Object(ObjectModelInterop.ToInteropValue(_propertyValue));
}
}
}
63 changes: 50 additions & 13 deletions test/Serilog.Sinks.BrowserConsole.Tests/FormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public void SupportsStylingWithProperties()
};
var formatter = new OutputTemplateRenderer($"<<{STYLE1}>>Hello<<_>> <<{STYLE2}>>{{{OutputProperties.PropertiesPropertyName}}}<<_>>", default);
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, MessageTemplate.Empty, PROPERTIES));
Assert.Equal([$"%cHello%c %c%o%c", STYLE1, "", STYLE2, ((ScalarValue)PROPERTIES[0].Value).Value, ""], args);
Assert.Equal([$"%cHello%c %c%d%c", STYLE1, "", STYLE2, ((ScalarValue)PROPERTIES[0].Value).Value, ""], args);
}

[Fact]
Expand All @@ -77,24 +77,61 @@ public void SupportsStylingWithSimpleMessage()
}

[Fact]
public void SupportsStylingWithMessageContainingInterpolation()
public void SupportsStylingWithinSimpleMessageContainingStyle()
{
var PLACE = "my tests";
var MESSAGE = "and welcome to {Place}";
var MESSAGE = $"and <<{STYLE3}>>welcome";
var formatter = new OutputTemplateRenderer($"<<{STYLE1}>>Hello<<_>> <<{STYLE2}>>{{{OutputProperties.MessagePropertyName}}}<<_>>", default);
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, new MessageTemplateParser().Parse(MESSAGE), new[]{
new LogEventProperty("Place", new ScalarValue(PLACE)),
}));
Assert.Equal([$"%cHello%c %c{MESSAGE.Replace("{Place}", "%o")}%c", STYLE1, "", STYLE2, PLACE, ""], args);
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, new MessageTemplateParser().Parse(MESSAGE), Array.Empty<LogEventProperty>()));
Assert.Equal([$"%cHello%c %c{MESSAGE.Replace($"<<{STYLE3}>>", "%c")}%c", STYLE1, "", STYLE2, STYLE3, ""], args);
}

[Theory]
[InlineData("short", "%d", (short)42)]
[InlineData("int", "%d", (int)42)]
[InlineData("long", "%d", (long)42)]
[InlineData("ushort", "%d", (ushort)42)]
[InlineData("uint", "%d", (uint)42)]
[InlineData("ulong", "%d", (ulong)42)]
[InlineData("byte", "%d", (byte)42)]
[InlineData("sbyte", "%d", (sbyte)42)]
// [InlineData("decimal", "%f", (decimal)42m)] // Unsupported
[InlineData("double", "%f", (double)42m)]
[InlineData("float", "%f", (float)42m)]
[InlineData("string", "%s", (string)"foo")]
[InlineData("char", "%s", (char)'f')]
public void SupportsStylingWithMessageContainingScalarStandardValues(string propertyName, string template, object value)
{
var MESSAGE = $"where the prop is {{{propertyName}}}";
var PROPERTIES = new[] {
new LogEventProperty(propertyName, new ScalarValue(value)),
};
var formatter = new OutputTemplateRenderer($"<<{STYLE1}>>Test {{{OutputProperties.MessagePropertyName}}} End<<_>>", default);
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, new MessageTemplateParser().Parse(MESSAGE), PROPERTIES));
Assert.Equal([$"%cTest where the prop is {template} End%c", STYLE1, ((ScalarValue)PROPERTIES[0].Value).Value, ""], args);
}

[Fact]
public void SupportsStylingWithinSimpleMessage()
public void SupportsStylingWithMessageContainingScalarDecimal()
{
var MESSAGE = $"and <<{STYLE3}>>welcome";
var formatter = new OutputTemplateRenderer($"<<{STYLE1}>>Hello<<_>> <<{STYLE2}>>{{{OutputProperties.MessagePropertyName}}}<<_>>", default);
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, new MessageTemplateParser().Parse(MESSAGE), Array.Empty<LogEventProperty>()));
Assert.Equal([$"%cHello%c %c{MESSAGE.Replace($"<<{STYLE3}>>", "%c")}%c", STYLE1, "", STYLE2, STYLE3, ""], args);
var MESSAGE = $"where the prop is {{decimal}}";
var PROPERTIES = new[] {
new LogEventProperty("decimal", new ScalarValue((decimal)42m)),
};
var formatter = new OutputTemplateRenderer($"<<{STYLE1}>>Test {{{OutputProperties.MessagePropertyName}}} End<<_>>", default);
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, new MessageTemplateParser().Parse(MESSAGE), PROPERTIES));
Assert.Equal([$"%cTest where the prop is %f End%c", STYLE1, ((ScalarValue)PROPERTIES[0].Value).Value, ""], args);
}

[Fact]
public void SupportsStylingWithMessageContainingScalarObject()
{
var MESSAGE = $"where the prop is {{object}}";
var PROPERTIES = new[] {
new LogEventProperty("object", new ScalarValue(new { Hello = "world"})),
};
var formatter = new OutputTemplateRenderer($"<<{STYLE1}>>Test {{{OutputProperties.MessagePropertyName}}} End<<_>>", default);
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, new MessageTemplateParser().Parse(MESSAGE), PROPERTIES));
Assert.Equal([$"%cTest where the prop is %c%o%c End%c", STYLE1, "", ((ScalarValue)PROPERTIES[0].Value).Value, STYLE1, ""], args);
}

[Fact]
Expand Down

0 comments on commit 20ae680

Please sign in to comment.