Skip to content

Commit 20ae680

Browse files
committed
feat: better support numeric types in properties
1 parent bf21f36 commit 20ae680

File tree

5 files changed

+186
-21
lines changed

5 files changed

+186
-21
lines changed

src/Serilog.Sinks.BrowserConsole/Sinks/BrowserConsole/Output/MessageTemplateOutputTokenRenderer.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ public override void Render(LogEvent logEvent, TokenEmitter emitToken)
3030
break;
3131
case PropertyToken pt:
3232
if (logEvent.Properties.TryGetValue(pt.PropertyName, out var propertyValue))
33-
emitToken.Object(ObjectModelInterop.ToInteropValue(propertyValue));
33+
{
34+
new PropertyTokenRenderer(pt, propertyValue).Render(logEvent, emitToken);
35+
}
3436
break;
3537
default:
3638
throw new InvalidOperationException();

src/Serilog.Sinks.BrowserConsole/Sinks/BrowserConsole/Output/OutputTemplateTokenRenderer.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
using Serilog.Events;
1616
using System.Text;
17+
using static System.Net.Mime.MediaTypeNames;
1718

1819
namespace Serilog.Sinks.BrowserConsole.Output;
1920

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

25-
internal void Literal(string text)
26+
internal void Literal(string template)
2627
{
27-
_template.Append(text.Replace("%", "%%"));
28+
_template.Append(template.Replace("%", "%%"));
2829
}
2930

30-
internal void Text(string text) {
31+
internal void Text(object @string)
32+
{
3133
_template.Append("%s");
32-
_args.Add(text);
34+
_args.Add(@string);
3335
}
36+
internal void Text(string @string) => Text((object)@string);
3437

35-
internal void Object(object? value)
38+
internal void Object(object? @object)
3639
{
3740
_template.Append("%o");
38-
_args.Add(value);
41+
_args.Add(@object);
42+
}
43+
44+
internal void Integer(object @int)
45+
{
46+
_template.Append("%d");
47+
_args.Add(@int);
3948
}
49+
50+
internal void Float(object @float) {
51+
_template.Append("%f");
52+
_args.Add(@float);
53+
}
54+
4055
internal object?[] YieldArgs() => [_template.ToString(), .. _args];
4156

4257
internal void Style(string styleContent)

src/Serilog.Sinks.BrowserConsole/Sinks/BrowserConsole/Output/PropertiesTokenRenderer.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,47 @@ public override void Render(LogEvent logEvent, TokenEmitter emitToken)
3636

3737
foreach (var property in included)
3838
{
39-
emitToken.Object(ObjectModelInterop.ToInteropValue(property.Value, _token.Format));
39+
new PropertyTokenRenderer(_token, property.Value).Render(logEvent, emitToken);
40+
}
41+
}
42+
private void HandleProperty(LogEventProperty property, TokenEmitter emitToken)
43+
{
44+
if (property.Value is ScalarValue sv)
45+
{
46+
if(sv.Value is null)
47+
{
48+
emitToken.Object(ObjectModelInterop.ToInteropValue(property.Value, _token.Format));
49+
return;
50+
}
51+
switch (Type.GetTypeCode(sv.Value.GetType()))
52+
{
53+
// See https://stackoverflow.com/a/1750024
54+
case TypeCode.Byte:
55+
case TypeCode.SByte:
56+
case TypeCode.UInt16:
57+
case TypeCode.UInt32:
58+
case TypeCode.UInt64:
59+
case TypeCode.Int16:
60+
case TypeCode.Int32:
61+
case TypeCode.Int64:
62+
emitToken.Integer(sv.Value);
63+
break;
64+
case TypeCode.Decimal:
65+
case TypeCode.Double:
66+
case TypeCode.Single:
67+
emitToken.Float(sv.Value);
68+
break;
69+
case TypeCode.String:
70+
case TypeCode.Char:
71+
emitToken.Text(sv.Value);
72+
break;
73+
default:
74+
emitToken.Object(ObjectModelInterop.ToInteropValue(property.Value, _token.Format));
75+
break;
76+
}
4077
}
78+
else
79+
emitToken.Object(ObjectModelInterop.ToInteropValue(property.Value, _token.Format));
4180
}
4281

4382
static bool TemplateContainsPropertyName(MessageTemplate template, string propertyName)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2017 Serilog Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System;
16+
using System.Linq;
17+
using Serilog.Events;
18+
using Serilog.Parsing;
19+
20+
namespace Serilog.Sinks.BrowserConsole.Output
21+
{
22+
class PropertyTokenRenderer : OutputTemplateTokenRenderer
23+
{
24+
readonly PropertyToken _token;
25+
readonly LogEventPropertyValue _propertyValue;
26+
public PropertyTokenRenderer(PropertyToken token, LogEventPropertyValue propertyValue)
27+
{
28+
_token = token;
29+
_propertyValue = propertyValue;
30+
}
31+
32+
public override void Render(LogEvent logEvent, TokenEmitter emitToken)
33+
{
34+
if (_propertyValue is ScalarValue sv)
35+
{
36+
if (sv.Value is null)
37+
{
38+
emitToken.Object(ObjectModelInterop.ToInteropValue(sv));
39+
return;
40+
}
41+
switch (Type.GetTypeCode(sv.Value.GetType()))
42+
{
43+
// See https://stackoverflow.com/a/1750024
44+
case TypeCode.Byte:
45+
case TypeCode.SByte:
46+
case TypeCode.UInt16:
47+
case TypeCode.UInt32:
48+
case TypeCode.UInt64:
49+
case TypeCode.Int16:
50+
case TypeCode.Int32:
51+
case TypeCode.Int64:
52+
emitToken.Integer(sv.Value);
53+
break;
54+
case TypeCode.Decimal:
55+
case TypeCode.Double:
56+
case TypeCode.Single:
57+
emitToken.Float(sv.Value);
58+
break;
59+
case TypeCode.String:
60+
case TypeCode.Char:
61+
emitToken.Text(sv.Value);
62+
break;
63+
default:
64+
emitToken.Object(ObjectModelInterop.ToInteropValue(sv));
65+
break;
66+
}
67+
}
68+
else
69+
emitToken.Object(ObjectModelInterop.ToInteropValue(_propertyValue));
70+
}
71+
}
72+
}

test/Serilog.Sinks.BrowserConsole.Tests/FormatterTests.cs

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public void SupportsStylingWithProperties()
6464
};
6565
var formatter = new OutputTemplateRenderer($"<<{STYLE1}>>Hello<<_>> <<{STYLE2}>>{{{OutputProperties.PropertiesPropertyName}}}<<_>>", default);
6666
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, MessageTemplate.Empty, PROPERTIES));
67-
Assert.Equal([$"%cHello%c %c%o%c", STYLE1, "", STYLE2, ((ScalarValue)PROPERTIES[0].Value).Value, ""], args);
67+
Assert.Equal([$"%cHello%c %c%d%c", STYLE1, "", STYLE2, ((ScalarValue)PROPERTIES[0].Value).Value, ""], args);
6868
}
6969

7070
[Fact]
@@ -77,24 +77,61 @@ public void SupportsStylingWithSimpleMessage()
7777
}
7878

7979
[Fact]
80-
public void SupportsStylingWithMessageContainingInterpolation()
80+
public void SupportsStylingWithinSimpleMessageContainingStyle()
8181
{
82-
var PLACE = "my tests";
83-
var MESSAGE = "and welcome to {Place}";
82+
var MESSAGE = $"and <<{STYLE3}>>welcome";
8483
var formatter = new OutputTemplateRenderer($"<<{STYLE1}>>Hello<<_>> <<{STYLE2}>>{{{OutputProperties.MessagePropertyName}}}<<_>>", default);
85-
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, new MessageTemplateParser().Parse(MESSAGE), new[]{
86-
new LogEventProperty("Place", new ScalarValue(PLACE)),
87-
}));
88-
Assert.Equal([$"%cHello%c %c{MESSAGE.Replace("{Place}", "%o")}%c", STYLE1, "", STYLE2, PLACE, ""], args);
84+
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, new MessageTemplateParser().Parse(MESSAGE), Array.Empty<LogEventProperty>()));
85+
Assert.Equal([$"%cHello%c %c{MESSAGE.Replace($"<<{STYLE3}>>", "%c")}%c", STYLE1, "", STYLE2, STYLE3, ""], args);
86+
}
87+
88+
[Theory]
89+
[InlineData("short", "%d", (short)42)]
90+
[InlineData("int", "%d", (int)42)]
91+
[InlineData("long", "%d", (long)42)]
92+
[InlineData("ushort", "%d", (ushort)42)]
93+
[InlineData("uint", "%d", (uint)42)]
94+
[InlineData("ulong", "%d", (ulong)42)]
95+
[InlineData("byte", "%d", (byte)42)]
96+
[InlineData("sbyte", "%d", (sbyte)42)]
97+
// [InlineData("decimal", "%f", (decimal)42m)] // Unsupported
98+
[InlineData("double", "%f", (double)42m)]
99+
[InlineData("float", "%f", (float)42m)]
100+
[InlineData("string", "%s", (string)"foo")]
101+
[InlineData("char", "%s", (char)'f')]
102+
public void SupportsStylingWithMessageContainingScalarStandardValues(string propertyName, string template, object value)
103+
{
104+
var MESSAGE = $"where the prop is {{{propertyName}}}";
105+
var PROPERTIES = new[] {
106+
new LogEventProperty(propertyName, new ScalarValue(value)),
107+
};
108+
var formatter = new OutputTemplateRenderer($"<<{STYLE1}>>Test {{{OutputProperties.MessagePropertyName}}} End<<_>>", default);
109+
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, new MessageTemplateParser().Parse(MESSAGE), PROPERTIES));
110+
Assert.Equal([$"%cTest where the prop is {template} End%c", STYLE1, ((ScalarValue)PROPERTIES[0].Value).Value, ""], args);
89111
}
90112

91113
[Fact]
92-
public void SupportsStylingWithinSimpleMessage()
114+
public void SupportsStylingWithMessageContainingScalarDecimal()
93115
{
94-
var MESSAGE = $"and <<{STYLE3}>>welcome";
95-
var formatter = new OutputTemplateRenderer($"<<{STYLE1}>>Hello<<_>> <<{STYLE2}>>{{{OutputProperties.MessagePropertyName}}}<<_>>", default);
96-
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, new MessageTemplateParser().Parse(MESSAGE), Array.Empty<LogEventProperty>()));
97-
Assert.Equal([$"%cHello%c %c{MESSAGE.Replace($"<<{STYLE3}>>", "%c")}%c", STYLE1, "", STYLE2, STYLE3, ""], args);
116+
var MESSAGE = $"where the prop is {{decimal}}";
117+
var PROPERTIES = new[] {
118+
new LogEventProperty("decimal", new ScalarValue((decimal)42m)),
119+
};
120+
var formatter = new OutputTemplateRenderer($"<<{STYLE1}>>Test {{{OutputProperties.MessagePropertyName}}} End<<_>>", default);
121+
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, new MessageTemplateParser().Parse(MESSAGE), PROPERTIES));
122+
Assert.Equal([$"%cTest where the prop is %f End%c", STYLE1, ((ScalarValue)PROPERTIES[0].Value).Value, ""], args);
123+
}
124+
125+
[Fact]
126+
public void SupportsStylingWithMessageContainingScalarObject()
127+
{
128+
var MESSAGE = $"where the prop is {{object}}";
129+
var PROPERTIES = new[] {
130+
new LogEventProperty("object", new ScalarValue(new { Hello = "world"})),
131+
};
132+
var formatter = new OutputTemplateRenderer($"<<{STYLE1}>>Test {{{OutputProperties.MessagePropertyName}}} End<<_>>", default);
133+
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, new MessageTemplateParser().Parse(MESSAGE), PROPERTIES));
134+
Assert.Equal([$"%cTest where the prop is %c%o%c End%c", STYLE1, "", ((ScalarValue)PROPERTIES[0].Value).Value, STYLE1, ""], args);
98135
}
99136

100137
[Fact]

0 commit comments

Comments
 (0)