Skip to content

Commit a7427f5

Browse files
authored
Merge pull request #137 from nblumhardt/wide-level-casing
Support upper and lowercase level names for all widths
2 parents 49df3e6 + 834cd89 commit a7427f5

File tree

3 files changed

+103
-79
lines changed

3 files changed

+103
-79
lines changed

src/Serilog.Sinks.Console/Serilog.Sinks.Console.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
2121
<TreatSpecificWarningsAsErrors />
2222
<RootNamespace>Serilog</RootNamespace>
23+
<LangVersion>latest</LangVersion>
2324
</PropertyGroup>
2425

2526
<PropertyGroup Condition=" '$(TargetFramework)' != 'net45' ">
Lines changed: 75 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2017 Serilog Contributors
1+
// Copyright 2023 Serilog Contributors
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -12,89 +12,90 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
using System;
1516
using Serilog.Events;
1617
using Serilog.Sinks.SystemConsole.Rendering;
1718

18-
namespace Serilog.Sinks.SystemConsole.Output
19-
{
20-
/// <summary>
21-
/// Implements the {Level} element.
22-
/// can now have a fixed width applied to it, as well as casing rules.
23-
/// Width is set through formats like "u3" (uppercase three chars),
24-
/// "w1" (one lowercase char), or "t4" (title case four chars).
25-
/// </summary>
26-
static class LevelOutputFormat
27-
{
28-
static readonly string[][] TitleCaseLevelMap =
29-
{
30-
new[] { "V", "Vb", "Vrb", "Verb" },
31-
new[] { "D", "De", "Dbg", "Dbug" },
32-
new[] { "I", "In", "Inf", "Info" },
33-
new[] { "W", "Wn", "Wrn", "Warn" },
34-
new[] { "E", "Er", "Err", "Eror" },
35-
new[] { "F", "Fa", "Ftl", "Fatl" },
36-
};
19+
namespace Serilog.Sinks.SystemConsole.Output;
3720

38-
static readonly string[][] LowercaseLevelMap =
39-
{
40-
new[] { "v", "vb", "vrb", "verb" },
41-
new[] { "d", "de", "dbg", "dbug" },
42-
new[] { "i", "in", "inf", "info" },
43-
new[] { "w", "wn", "wrn", "warn" },
44-
new[] { "e", "er", "err", "eror" },
45-
new[] { "f", "fa", "ftl", "fatl" },
46-
};
21+
/// <summary>
22+
/// Implements the {Level} element.
23+
/// can now have a fixed width applied to it, as well as casing rules.
24+
/// Width is set through formats like "u3" (uppercase three chars),
25+
/// "w1" (one lowercase char), or "t4" (title case four chars).
26+
/// </summary>
27+
static class LevelOutputFormat
28+
{
29+
static readonly string[][] TitleCaseLevelMap = {
30+
new []{ "V", "Vb", "Vrb", "Verb", "Verbo", "Verbos", "Verbose" },
31+
new []{ "D", "De", "Dbg", "Dbug", "Debug" },
32+
new []{ "I", "In", "Inf", "Info", "Infor", "Inform", "Informa", "Informat", "Informati", "Informatio", "Information" },
33+
new []{ "W", "Wn", "Wrn", "Warn", "Warni", "Warnin", "Warning" },
34+
new []{ "E", "Er", "Err", "Eror", "Error" },
35+
new []{ "F", "Fa", "Ftl", "Fatl", "Fatal" }
36+
};
4737

48-
static readonly string[][] UppercaseLevelMap =
49-
{
50-
new[] { "V", "VB", "VRB", "VERB" },
51-
new[] { "D", "DE", "DBG", "DBUG" },
52-
new[] { "I", "IN", "INF", "INFO" },
53-
new[] { "W", "WN", "WRN", "WARN" },
54-
new[] { "E", "ER", "ERR", "EROR" },
55-
new[] { "F", "FA", "FTL", "FATL" },
56-
};
38+
static readonly string[][] LowerCaseLevelMap = {
39+
new []{ "v", "vb", "vrb", "verb", "verbo", "verbos", "verbose" },
40+
new []{ "d", "de", "dbg", "dbug", "debug" },
41+
new []{ "i", "in", "inf", "info", "infor", "inform", "informa", "informat", "informati", "informatio", "information" },
42+
new []{ "w", "wn", "wrn", "warn", "warni", "warnin", "warning" },
43+
new []{ "e", "er", "err", "eror", "error" },
44+
new []{ "f", "fa", "ftl", "fatl", "fatal" }
45+
};
5746

58-
public static string GetLevelMoniker(LogEventLevel value, string? format = null)
59-
{
60-
if (format is null || format.Length != 2 && format.Length != 3)
61-
return Casing.Format(value.ToString(), format);
47+
static readonly string[][] UpperCaseLevelMap = {
48+
new []{ "V", "VB", "VRB", "VERB", "VERBO", "VERBOS", "VERBOSE" },
49+
new []{ "D", "DE", "DBG", "DBUG", "DEBUG" },
50+
new []{ "I", "IN", "INF", "INFO", "INFOR", "INFORM", "INFORMA", "INFORMAT", "INFORMATI", "INFORMATIO", "INFORMATION" },
51+
new []{ "W", "WN", "WRN", "WARN", "WARNI", "WARNIN", "WARNING" },
52+
new []{ "E", "ER", "ERR", "EROR", "ERROR" },
53+
new []{ "F", "FA", "FTL", "FATL", "FATAL" }
54+
};
6255

63-
// Using int.Parse() here requires allocating a string to exclude the first character prefix.
64-
// Junk like "wxy" will be accepted but produce benign results.
65-
var width = format[1] - '0';
66-
if (format.Length == 3)
67-
{
68-
width *= 10;
69-
width += format[2] - '0';
70-
}
56+
public static string GetLevelMoniker(LogEventLevel value, string? format = null)
57+
{
58+
var index = (int)value;
59+
if (index is < 0 or > (int)LogEventLevel.Fatal)
60+
return Casing.Format(value.ToString(), format);
7161

72-
if (width < 1)
73-
return string.Empty;
62+
if (format == null || format.Length != 2 && format.Length != 3)
63+
return Casing.Format(GetLevelMoniker(TitleCaseLevelMap, index), format);
7464

75-
if (width > 4)
76-
{
77-
var stringValue = value.ToString();
78-
if (stringValue.Length > width)
79-
stringValue = stringValue.Substring(0, width);
80-
return Casing.Format(stringValue);
81-
}
65+
// Using int.Parse() here requires allocating a string to exclude the first character prefix.
66+
// Junk like "wxy" will be accepted but produce benign results.
67+
var width = format[1] - '0';
68+
if (format.Length == 3)
69+
{
70+
width *= 10;
71+
width += format[2] - '0';
72+
}
8273

83-
var index = (int)value;
84-
if (index >= 0 && index <= (int)LogEventLevel.Fatal)
85-
{
86-
switch (format[0])
87-
{
88-
case 'w':
89-
return LowercaseLevelMap[index][width - 1];
90-
case 'u':
91-
return UppercaseLevelMap[index][width - 1];
92-
case 't':
93-
return TitleCaseLevelMap[index][width - 1];
94-
}
95-
}
74+
if (width < 1)
75+
return string.Empty;
9676

97-
return Casing.Format(value.ToString(), format);
77+
switch (format[0])
78+
{
79+
case 'w':
80+
return GetLevelMoniker(LowerCaseLevelMap, index, width);
81+
case 'u':
82+
return GetLevelMoniker(UpperCaseLevelMap, index, width);
83+
case 't':
84+
return GetLevelMoniker(TitleCaseLevelMap, index, width);
85+
default:
86+
return Casing.Format(GetLevelMoniker(TitleCaseLevelMap, index), format);
9887
}
9988
}
100-
}
89+
90+
static string GetLevelMoniker(string[][] caseLevelMap, int index, int width)
91+
{
92+
var caseLevel = caseLevelMap[index];
93+
return caseLevel[Math.Min(width, caseLevel.Length) - 1];
94+
}
95+
96+
static string GetLevelMoniker(string[][] caseLevelMap, int index)
97+
{
98+
var caseLevel = caseLevelMap[index];
99+
return caseLevel[caseLevel.Length - 1];
100+
}
101+
}

test/Serilog.Sinks.Console.Tests/Output/OutputTemplateRendererTests.cs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,23 @@ public void FixedLengthLevelIsSupported(
105105
int width,
106106
string expected)
107107
{
108-
var formatter = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:t{width}}}", CultureInfo.InvariantCulture);
109-
var evt = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello"));
110-
var sw = new StringWriter();
111-
formatter.Format(evt, sw);
112-
Assert.Equal(expected, sw.ToString());
108+
var formatter1 = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:t{width}}}", CultureInfo.InvariantCulture);
109+
var evt1 = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello"));
110+
var sw1 = new StringWriter();
111+
formatter1.Format(evt1, sw1);
112+
Assert.Equal(expected, sw1.ToString());
113+
114+
var formatter2 = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:u{width}}}", CultureInfo.InvariantCulture);
115+
var evt2 = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello"));
116+
var sw2 = new StringWriter();
117+
formatter2.Format(evt2, sw2);
118+
Assert.Equal(expected.ToUpper(), sw2.ToString());
119+
120+
var formatter3 = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:w{width}}}", CultureInfo.InvariantCulture);
121+
var evt3 = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello"));
122+
var sw3 = new StringWriter();
123+
formatter3.Format(evt3, sw3);
124+
Assert.Equal(expected.ToLower(), sw3.ToString());
113125
}
114126

115127
[Fact]
@@ -131,6 +143,16 @@ public void FixedLengthLevelSupportsLowerCasing()
131143
formatter.Format(evt, sw);
132144
Assert.Equal("inf", sw.ToString());
133145
}
146+
147+
[Fact]
148+
public void FixedLengthLevelSupportsCasingForWideNames()
149+
{
150+
var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Level:w6}", CultureInfo.InvariantCulture);
151+
var evt = DelegatingSink.GetLogEvent(l => l.Information("Hello"));
152+
var sw = new StringWriter();
153+
formatter.Format(evt, sw);
154+
Assert.Equal("inform", sw.ToString());
155+
}
134156

135157
[Fact]
136158
public void DefaultLevelLengthIsFullText()

0 commit comments

Comments
 (0)