Skip to content

Commit bf21f36

Browse files
committed
feat: add tokenStyles dictionary parameter
1 parent d956367 commit bf21f36

File tree

5 files changed

+96
-22
lines changed

5 files changed

+96
-22
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ Example:
3535
```
3636
<<color: red;>>{Level}<<_>>: {Message}
3737
```
38+
39+
You can also define styles for tokens via the `tokenStyles` dictionary.

src/Serilog.Sinks.BrowserConsole/LoggerConfigurationBrowserConsoleExtensions.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,20 @@ namespace Serilog;
2727
public static class LoggerConfigurationBrowserConsoleExtensions
2828
{
2929
const string SerilogToken =
30-
"%cserilog{_}color:white;background:#8c7574;border-radius:3px;padding:1px 2px;font-weight:600;";
31-
32-
const string DefaultConsoleOutputTemplate = SerilogToken + "{Message}{NewLine}{Exception}";
33-
30+
"<<color:white;background:#8c7574;border-radius:3px;padding:1px 2px;font-weight:600;>>serilog<<_>>";
31+
32+
const string DefaultConsoleOutputTemplate = SerilogToken + "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}";
33+
3434
/// <summary>
3535
/// Writes log events to the browser console.
3636
/// </summary>
3737
/// <param name="sinkConfiguration">Logger sink configuration.</param>
3838
/// <param name="restrictedToMinimumLevel">The minimum level for
3939
/// events passed through the sink. Ignored when <paramref name="levelSwitch"/> is specified.</param>
4040
/// <param name="outputTemplate">A message template describing the format used to write to the sink.
41-
/// The default is <code>"[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"</code>.</param>
41+
/// The default is <code>"(serilog) [{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"</code>.</param>
4242
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
43+
/// <param name="tokenStyles">A dictionary of styles to apply to tokens. See <a href="https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output">MDN about console styling</a></param>
4344
/// <param name="levelSwitch">A switch allowing the pass-through minimum level
4445
/// to be changed at runtime.</param>
4546
/// <param name="jsRuntime">An instance of <see cref="IJSRuntime"/> to interact with the browser.</param>
@@ -49,11 +50,12 @@ public static LoggerConfiguration BrowserConsole(
4950
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
5051
string outputTemplate = DefaultConsoleOutputTemplate,
5152
IFormatProvider? formatProvider = null,
53+
IReadOnlyDictionary<string, string>? tokenStyles = null,
5254
LoggingLevelSwitch? levelSwitch = null,
5355
IJSRuntime? jsRuntime = null)
5456
{
5557
ArgumentNullException.ThrowIfNull(sinkConfiguration);
56-
var formatter = new OutputTemplateRenderer(outputTemplate, formatProvider);
58+
var formatter = new OutputTemplateRenderer(outputTemplate, formatProvider, tokenStyles);
5759
return sinkConfiguration.Sink(new BrowserConsoleSink(jsRuntime, formatter), restrictedToMinimumLevel, levelSwitch);
5860
}
5961
}

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

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,56 @@
11
using Serilog.Events;
22
using Serilog.Formatting.Display;
33
using Serilog.Parsing;
4+
using System;
45

56
namespace Serilog.Sinks.BrowserConsole.Output;
67

78
class OutputTemplateRenderer
89
{
910
readonly OutputTemplateTokenRenderer[] _renderers;
11+
private readonly IFormatProvider? _formatProvider;
12+
private readonly IReadOnlyDictionary<string, string>? _tokenStyles;
13+
private readonly MessageTemplate _template;
1014

11-
public OutputTemplateRenderer(string outputTemplate, IFormatProvider? formatProvider)
15+
public OutputTemplateRenderer(string outputTemplate, IFormatProvider? formatProvider, IReadOnlyDictionary<string, string>? tokenStyles = default)
1216
{
1317
ArgumentNullException.ThrowIfNull(outputTemplate);
14-
var template = new MessageTemplateParser().Parse(outputTemplate);
15-
16-
_renderers = template.Tokens
17-
.Select(token => token switch
18+
_formatProvider = formatProvider;
19+
_tokenStyles = tokenStyles;
20+
_template = new MessageTemplateParser().Parse(outputTemplate);
21+
22+
_renderers = _template.Tokens
23+
.SelectMany(token => token switch
1824
{
19-
TextToken tt => new TextTokenRenderer(tt.Text),
20-
PropertyToken pt => pt.PropertyName switch
21-
{
22-
OutputProperties.LevelPropertyName => new LevelTokenRenderer(pt) as OutputTemplateTokenRenderer,
23-
OutputProperties.NewLinePropertyName => new NewLineTokenRenderer(pt.Alignment),
24-
OutputProperties.ExceptionPropertyName => new ExceptionTokenRenderer(),
25-
OutputProperties.MessagePropertyName => new MessageTemplateOutputTokenRenderer(),
26-
OutputProperties.TimestampPropertyName => new TimestampTokenRenderer(pt, formatProvider),
27-
OutputProperties.PropertiesPropertyName => new PropertiesTokenRenderer(pt, template),
28-
_ => new EventPropertyTokenRenderer(pt, formatProvider)
29-
},
25+
TextToken tt => [new TextTokenRenderer(tt.Text)],
26+
PropertyToken pt => WrapStyle(pt),
3027
_ => throw new InvalidOperationException()
3128
})
3229
.ToArray();
3330
}
3431

32+
private OutputTemplateTokenRenderer[] WrapStyle(PropertyToken token)
33+
{
34+
OutputTemplateTokenRenderer renderer = token.PropertyName switch
35+
{
36+
OutputProperties.LevelPropertyName => new LevelTokenRenderer(token),
37+
OutputProperties.NewLinePropertyName => new NewLineTokenRenderer(token.Alignment),
38+
OutputProperties.ExceptionPropertyName => new ExceptionTokenRenderer(),
39+
OutputProperties.MessagePropertyName => new MessageTemplateOutputTokenRenderer(),
40+
OutputProperties.TimestampPropertyName => new TimestampTokenRenderer(token, _formatProvider),
41+
OutputProperties.PropertiesPropertyName => new PropertiesTokenRenderer(token, _template),
42+
_ => new EventPropertyTokenRenderer(token, _formatProvider)
43+
};
44+
if (_tokenStyles?.TryGetValue(token.PropertyName, out var style) ?? false)
45+
{
46+
return [new StyleTokenRenderer(style), renderer, StyleTokenRenderer.Reset];
47+
}
48+
else
49+
{
50+
return [renderer];
51+
}
52+
}
53+
3554
public object?[] Format(LogEvent logEvent)
3655
{
3756
ArgumentNullException.ThrowIfNull(logEvent);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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 Serilog.Events;
16+
17+
namespace Serilog.Sinks.BrowserConsole.Output;
18+
19+
class StyleTokenRenderer : OutputTemplateTokenRenderer
20+
{
21+
public static readonly StyleTokenRenderer Reset = new("");
22+
23+
private readonly string _style;
24+
25+
public StyleTokenRenderer(string style)
26+
{
27+
_style = style;
28+
}
29+
30+
public override void Render(LogEvent logEvent, TokenEmitter emitToken)
31+
{
32+
emitToken.Style(_style);
33+
}
34+
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Serilog.Parsing;
44
using Serilog.Sinks.BrowserConsole.Output;
55
using System;
6+
using System.Collections.Generic;
67
using Xunit;
78

89
namespace Serilog.Sinks.BrowserConsole.Tests
@@ -104,5 +105,21 @@ public void EscapePercentFromMessages()
104105
var args = formatter.Format(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, new MessageTemplateParser().Parse(MESSAGE), Array.Empty<LogEventProperty>()));
105106
Assert.Equal([$"%cA first%c %% sign %c{MESSAGE.Replace($"%", "%%")}%c", STYLE1, "", STYLE2, ""], args);
106107
}
108+
109+
[Fact]
110+
public void SupportsTokenStyling()
111+
{
112+
var MESSAGE = $"Test";
113+
var LEVEL = LogEventLevel.Verbose;
114+
var NOW = DateTimeOffset.Now;
115+
var formatter = new OutputTemplateRenderer($"{{{OutputProperties.LevelPropertyName}}}@{{{OutputProperties.TimestampPropertyName}:HH:mm}}: {{{OutputProperties.MessagePropertyName}}}", default, new Dictionary<string, string>
116+
{
117+
{OutputProperties.LevelPropertyName, STYLE1 },
118+
{OutputProperties.TimestampPropertyName, STYLE2 },
119+
{OutputProperties.MessagePropertyName, STYLE3 }
120+
});
121+
var args = formatter.Format(new LogEvent(NOW, LEVEL, null, new MessageTemplateParser().Parse(MESSAGE), Array.Empty<LogEventProperty>()));
122+
Assert.Equal([$"%c%s%c@%c%s%c: %c{MESSAGE}%c", STYLE1, LEVEL.ToString(), "", STYLE2, NOW.ToString("HH:mm"), "", STYLE3, ""], args);
123+
}
107124
}
108125
}

0 commit comments

Comments
 (0)