Skip to content

Commit

Permalink
Merge pull request #137 from nblumhardt/wide-level-casing
Browse files Browse the repository at this point in the history
Support upper and lowercase level names for all widths
  • Loading branch information
nblumhardt authored Feb 3, 2023
2 parents 49df3e6 + 834cd89 commit a7427f5
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 79 deletions.
1 change: 1 addition & 0 deletions src/Serilog.Sinks.Console/Serilog.Sinks.Console.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<TreatSpecificWarningsAsErrors />
<RootNamespace>Serilog</RootNamespace>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' != 'net45' ">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2017 Serilog Contributors
// Copyright 2023 Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -12,89 +12,90 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using Serilog.Events;
using Serilog.Sinks.SystemConsole.Rendering;

namespace Serilog.Sinks.SystemConsole.Output
{
/// <summary>
/// Implements the {Level} element.
/// can now have a fixed width applied to it, as well as casing rules.
/// Width is set through formats like "u3" (uppercase three chars),
/// "w1" (one lowercase char), or "t4" (title case four chars).
/// </summary>
static class LevelOutputFormat
{
static readonly string[][] TitleCaseLevelMap =
{
new[] { "V", "Vb", "Vrb", "Verb" },
new[] { "D", "De", "Dbg", "Dbug" },
new[] { "I", "In", "Inf", "Info" },
new[] { "W", "Wn", "Wrn", "Warn" },
new[] { "E", "Er", "Err", "Eror" },
new[] { "F", "Fa", "Ftl", "Fatl" },
};
namespace Serilog.Sinks.SystemConsole.Output;

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

static readonly string[][] UppercaseLevelMap =
{
new[] { "V", "VB", "VRB", "VERB" },
new[] { "D", "DE", "DBG", "DBUG" },
new[] { "I", "IN", "INF", "INFO" },
new[] { "W", "WN", "WRN", "WARN" },
new[] { "E", "ER", "ERR", "EROR" },
new[] { "F", "FA", "FTL", "FATL" },
};
static readonly string[][] LowerCaseLevelMap = {
new []{ "v", "vb", "vrb", "verb", "verbo", "verbos", "verbose" },
new []{ "d", "de", "dbg", "dbug", "debug" },
new []{ "i", "in", "inf", "info", "infor", "inform", "informa", "informat", "informati", "informatio", "information" },
new []{ "w", "wn", "wrn", "warn", "warni", "warnin", "warning" },
new []{ "e", "er", "err", "eror", "error" },
new []{ "f", "fa", "ftl", "fatl", "fatal" }
};

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

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

if (width < 1)
return string.Empty;
if (format == null || format.Length != 2 && format.Length != 3)
return Casing.Format(GetLevelMoniker(TitleCaseLevelMap, index), format);

if (width > 4)
{
var stringValue = value.ToString();
if (stringValue.Length > width)
stringValue = stringValue.Substring(0, width);
return Casing.Format(stringValue);
}
// Using int.Parse() here requires allocating a string to exclude the first character prefix.
// Junk like "wxy" will be accepted but produce benign results.
var width = format[1] - '0';
if (format.Length == 3)
{
width *= 10;
width += format[2] - '0';
}

var index = (int)value;
if (index >= 0 && index <= (int)LogEventLevel.Fatal)
{
switch (format[0])
{
case 'w':
return LowercaseLevelMap[index][width - 1];
case 'u':
return UppercaseLevelMap[index][width - 1];
case 't':
return TitleCaseLevelMap[index][width - 1];
}
}
if (width < 1)
return string.Empty;

return Casing.Format(value.ToString(), format);
switch (format[0])
{
case 'w':
return GetLevelMoniker(LowerCaseLevelMap, index, width);
case 'u':
return GetLevelMoniker(UpperCaseLevelMap, index, width);
case 't':
return GetLevelMoniker(TitleCaseLevelMap, index, width);
default:
return Casing.Format(GetLevelMoniker(TitleCaseLevelMap, index), format);
}
}
}

static string GetLevelMoniker(string[][] caseLevelMap, int index, int width)
{
var caseLevel = caseLevelMap[index];
return caseLevel[Math.Min(width, caseLevel.Length) - 1];
}

static string GetLevelMoniker(string[][] caseLevelMap, int index)
{
var caseLevel = caseLevelMap[index];
return caseLevel[caseLevel.Length - 1];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,23 @@ public void FixedLengthLevelIsSupported(
int width,
string expected)
{
var formatter = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:t{width}}}", CultureInfo.InvariantCulture);
var evt = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello"));
var sw = new StringWriter();
formatter.Format(evt, sw);
Assert.Equal(expected, sw.ToString());
var formatter1 = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:t{width}}}", CultureInfo.InvariantCulture);
var evt1 = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello"));
var sw1 = new StringWriter();
formatter1.Format(evt1, sw1);
Assert.Equal(expected, sw1.ToString());

var formatter2 = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:u{width}}}", CultureInfo.InvariantCulture);
var evt2 = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello"));
var sw2 = new StringWriter();
formatter2.Format(evt2, sw2);
Assert.Equal(expected.ToUpper(), sw2.ToString());

var formatter3 = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:w{width}}}", CultureInfo.InvariantCulture);
var evt3 = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello"));
var sw3 = new StringWriter();
formatter3.Format(evt3, sw3);
Assert.Equal(expected.ToLower(), sw3.ToString());
}

[Fact]
Expand All @@ -131,6 +143,16 @@ public void FixedLengthLevelSupportsLowerCasing()
formatter.Format(evt, sw);
Assert.Equal("inf", sw.ToString());
}

[Fact]
public void FixedLengthLevelSupportsCasingForWideNames()
{
var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Level:w6}", CultureInfo.InvariantCulture);
var evt = DelegatingSink.GetLogEvent(l => l.Information("Hello"));
var sw = new StringWriter();
formatter.Format(evt, sw);
Assert.Equal("inform", sw.ToString());
}

[Fact]
public void DefaultLevelLengthIsFullText()
Expand Down

0 comments on commit a7427f5

Please sign in to comment.