-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Theme for coloring all line #35
Comments
Hi @Lukino2000 - no, this isn't possible in the current version of the sink. Just to gauge interest - how important is the line coloring for you/your app? It's possible we could extend the theming system to support it, but there aren't all that many possible themes that can be created using this mechanism so we've held off on attempting this so far. |
While I personally don't have a need for coloring lines, I'd like to be able to change the output color for an individual log event, such as coloring certain currency logs different colors (for example -- to make it easier to distinguish between GBP and USD), without having to create a theme. |
I'd love to see this, i.e. the whole line using the color of the level used, it would make the output much easier to read. I would never use the ability to customize the color of a single line, but colorizing parameters would be great, e.g. highlighting the exception class, a correlation ID, a server IP etc. |
This is a nice to have feature for a console application currently under development: I'm using Serilog to write execution status info to the output console with this simple template: |
This is exactly my use case too. For short-running applications this makes sense - esp. when you want to use a single framework to logging into file and having a nice formatted console output for the user. |
I haven’t been back for a while, but I did a spike on making the template token renderer public. I was attempting to see how a few different proposed features might work here: dev...adamchester:spike-themed-exception Edit: this has an example of how to colour lines by log level |
Righto, thanks for all the input! If anyone's keen to discuss design options, with a view to submitting a PR, I can help figure out how we can get this in 👍 |
I'd like to take a look at that @nblumhardt Because of the specific by level and extensibility there must be some hierarchy, so that when something isn't specified, it has to have some fallback value. (e.g. And by extensibility I mean, that it would also support variables from enrichers like I am still thinking what algorithm and data structure to use for it internally so that it is most efficient. It has to have possibly O(1) speed access with as little constant coeficient as possible. |
May I... up this? 👀 |
One more up vote from me! We are using serilog for a dotnet global tool so we can write to multiple sinks and it'd be useful to colorize the entire log line, in addition the background color of a log line when writing to the console. |
For those watching, we were able to implement a naive version that supports structure and full line colorization that meets our requirements using the following code. To support customized colorization at just the level or just the log line, you'd likely need your own implementation(s) of ITextFormatter which colorize the variety of parts. I'm certain that someone could generalize this to make it more broadly applicable using a them instead of hardcoding colorization requirements. Hope this helps the next person who needs this!
And the extension method to enable it:
|
I wrote an extension to accomplish this. I don't know if this is better or worse than the code above, but anyone is free to use it. It will accept colors. As written it only supports logging at information level. Output looks like this:
Complete code at this gist: |
This is somewhat tangentially related but I used Serilog.Expressions along with ANSI coloring to achieve full line coloring results. Here's how you can recreate what I did: I defined the following class to help build ANSI escape sequences to apply special effects such as coloring, bold, italics, underline, etc. in a more readable way. It also serves as a helpful tutorial for how ANSI works and how it's used as a standard across several different terminals. namespace AnsiHelpersCollection
{
public class AnsiHelpers
{
// ANSI is a standard used by text input programs such as command line or terminal emulators to control text formatting, color, and other output options.
// Here's a simple example of how to use ANSI escape codes to change the color of text in the terminal.
// See for more info on ANSI:
// - https://gist.github.com/ConnerWill/d4b6c776b509add763e17f9f113fd25b
// - https://youtu.be/yQ9Ns6Z4Q-s
//
// It doesn't matter what escape character you use
// Any of these can be used to prefix a semicolon-separated list of modifiers with a trailing 'm' to indicate the end of the sequence.
// That combination of effects will be applied until the next reset code is encountered.
public static string unicode_escape = "\u001b[";
public static string hex_escape = "\x1b[";
public static string ansi_reset_all_code = "0";
// Note that it does not matter which escape character you use. But for all Ansi effects, the prefix must be a valid escape character (unicode or hex escape works).
public static string reset_all = $"{unicode_escape}{ansi_reset_all_code}m";
// Modifiers
public static string bold = "1";
public static string bold_reset = "22";
public static string dim = "2";
public static string dim_reset = "22";
public static string italic = "3";
public static string italic_reset = "23";
public static string underline = "4";
public static string underline_reset = "24";
public static string blink = "5";
public static string blink_reset = "25";
public static string reverse = "7";
public static string reverse_reset = "27";
public static string hidden = "8";
public static string hidden_reset = "28";
public static string strikethrough = "9";
public static string strikethrough_reset = "29";
// Below is list of foreground and background codes:
public static string fg_black = "30";
public static string bg_black = "40";
public static string fg_red = "31";
public static string bg_red = "41";
public static string fg_green = "32";
public static string bg_green = "42";
public static string fg_yellow = "33";
public static string bg_yellow = "43";
public static string fg_blue = "34";
public static string bg_blue = "44";
public static string fg_magenta = "35";
public static string bg_magenta = "45";
public static string fg_cyan = "36";
public static string bg_cyan = "46";
public static string fg_white = "37";
public static string bg_white = "47";
// And a list of foreground and background codes if a terminal supports axiterm colors:
public static string fg_bright_black = "90";
public static string bg_bright_black = "100";
public static string fg_bright_red = "91";
public static string bg_bright_red = "101";
public static string fg_bright_green = "92";
public static string bg_bright_green = "102";
public static string fg_bright_yellow = "93";
public static string bg_bright_yellow = "103";
public static string fg_bright_blue = "94";
public static string bg_bright_blue = "104";
public static string fg_bright_magenta = "95";
public static string bg_bright_magenta = "105";
public static string fg_bright_cyan = "96";
public static string bg_bright_cyan = "106";
public static string fg_bright_white = "97";
public static string bg_bright_white = "107";
/// <summary>
/// The effects of the basic colors/modifiers and axiterm colors can be stacked using this combine function
/// </summary>
/// <param name="modifiers"></param>
/// <returns></returns>
public static string Combine(params string[] modifiers)
{
return $"{hex_escape}{string.Join(";", modifiers)}m";
}
/// <summary>
/// If your terminal supports 256 colors, you can use the following function to set the color of text.
/// See for color codes: https://user-images.githubusercontent.com/995050/47952855-ecb12480-df75-11e8-89d4-ac26c50e80b9.png
/// </summary>
/// <param name="color"></param>
/// <param name="getBackgroundCode"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static string ColorId256ToAnsi(int color, bool getBackgroundCode = false)
{
if (color < 0 || color > 255)
{
throw new ArgumentOutOfRangeException(nameof(color), "Color must be between 0 and 255.");
}
return $"{unicode_escape}{(getBackgroundCode ? "48" : "38")};5;{color}m";
}
/// <summary>
/// If your terminal supports true color, you can use the following function to set the color of text using RGB values.
/// </summary>
/// <param name="r"></param>
/// <param name="g"></param>
/// <param name="b"></param>
/// <param name="getBackgroundCode"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static string RgbToAnsi(int r, int g, int b, bool getBackgroundCode = false)
{
if (r < 0 || r > 255)
{
throw new ArgumentOutOfRangeException(nameof(r), "Red must be between 0 and 255.");
}
if (g < 0 || g > 255)
{
throw new ArgumentOutOfRangeException(nameof(g), "Green must be between 0 and 255.");
}
if (b < 0 || b > 255)
{
throw new ArgumentOutOfRangeException(nameof(b), "Blue must be between 0 and 255.");
}
return $"{unicode_escape}{(getBackgroundCode ? "48" : "38")};2;{r};{g};{b}m";
}
public static void TestTerminalAnsiSupport()
{
Console.WriteLine(
$"Here's some basic ANSI coloring: {Combine([bg_red, fg_white, bold])}Hello, World!{reset_all}{Combine([bold])} more bold text {Combine([bold_reset])}{reset_all} and more normal text ");
Console.WriteLine(
$"If your terminal supports axiterm bright colors: \n\t{Combine([bg_red, fg_white])}this should be white on normal red text and{reset_all}\n\t{Combine([bg_bright_red, fg_white])} this should be white on bright red text (slightly brighter background color) {reset_all} and more normal text ");
// Note that the 256 and rgb colors can be placed fully qualified, one after another, to apply both effects simultaneously.
Console.WriteLine(
$"If your terminal supports 256 color, {ColorId256ToAnsi(154, getBackgroundCode: false)}{ColorId256ToAnsi(90, getBackgroundCode: true)}this should be some green on purple text {reset_all}");
Console.WriteLine(
$"If your terminal supports Truecolor (RGB values), {RgbToAnsi(131, 1, 1, getBackgroundCode: false)}{RgbToAnsi(255, 129, 3, getBackgroundCode: true)}this should be some maroon on orange text {reset_all}");
}
}
} By the way, here's a python version of the same thing that might be a little easier to read if you want to understand the logic # ANSI is a standard used by text input programs such as command line or terminal emulators to control text formatting, color, and other output options.
# Here's a simple example of how to use ANSI escape codes to change the color of text in the terminal.
# See for more info on ANSI:
# - https://gist.github.com/ConnerWill/d4b6c776b509add763e17f9f113fd25b
# - https://youtu.be/yQ9Ns6Z4Q-s
# It doesn't matter what escape character you use
# Any of these can be used to prefix a semicolon-separated list of modifiers with a trailing 'm' to indicate the end of the sequence.
# That combination of effects will be applied until the next reset code is encountered.
unicode_escape = "\u001b["
octal_escape = "\033["
hex_escape = "\x1b["
ansi_fg_reset_all = f"0"
# Modifiers
bold, bold_reset = "1", "22"
dim, dim_reset = "2", "22"
italic, italic_reset = "3", "23"
underline, underline_reset = "4", "24"
blink, blink_reset = "5", "25"
reverse, reverse_reset = "7", "27"
hidden, hidden_reset = "8", "28"
strikethrough, strikethrough_reset = "9", "29"
# Below is list of foreground and background codes:
fg_black, bg_black = f"30", f"40"
fg_red, bg_red = f"31", f"41"
fg_green, bg_green = f"32", f"42"
fg_yellow, bg_yellow = f"33", f"43"
fg_blue, bg_blue = f"34", f"44"
fg_magenta, bg_magenta = f"35", f"45"
fg_cyan, bg_cyan = f"36", f"46"
fg_white, bg_white = f"37", f"47"
# And a list of foreground and background codes if a terminal supports axiterm colors:
fg_bright_black, bg_bright_black = f"90", f"100"
fg_bright_red, bg_bright_red = f"91", f"101"
fg_bright_green, bg_bright_green = f"92", f"102"
fg_bright_yellow, bg_bright_yellow = f"93", f"103"
fg_bright_blue, bg_bright_blue = f"94", f"104"
fg_bright_magenta, bg_bright_magenta = f"95", f"105"
fg_bright_cyan, bg_bright_cyan = f"96", f"106"
fg_bright_white, bg_bright_white = f"97", f"107"
# The effects of the basic colors/modifiers and axiterm colors can be stacked using this combine function
def combine(modifiers):
return f"{hex_escape}{';'.join(modifiers)}m"
# If your terminal supports 256 colors, you can use the following function to set the color of text.
# See for color codes: https://user-images.githubusercontent.com/995050/47952855-ecb12480-df75-11e8-89d4-ac26c50e80b9.png
def set_256_color(color, is_bg=False):
assert 0 <= color <= 255, f"Color must be between 0 and 255, not {color}"
return f"{unicode_escape}{'48' if is_bg else '38'};5;{color}m"
# If your terminal supports true color, you can use the following function to set the color of text.
def set_rgb_color(r, g, b, is_bg=False):
assert 0 <= r <= 255, f"Red must be between 0 and 255, not {r}"
assert 0 <= g <= 255, f"Green must be between 0 and 255, not {g}"
assert 0 <= b <= 255, f"Blue must be between 0 and 255, not {b}"
return f"{unicode_escape}{'48' if is_bg else '38'};2;{r};{g};{b}m"
# Reset all effects
reset_all = combine([ansi_fg_reset_all])
print(f"{combine([bg_red, fg_white, bold])}Hello, World!{reset_all}{combine([bg_bright_red, fg_white])} more bold text {combine([bold_reset])}{reset_all} and more normal text ")
# Note that the 256 and rgb colors can be placed fully qualified, one after another, to apply both effects simultaneously.
print(f"{set_256_color(154, is_bg=False)}{set_256_color(90, is_bg=True)}Here is some green on purple text{reset_all}")
print(f"{set_rgb_color(131, 1, 1, is_bg=False)}{set_rgb_color(255, 129, 3, is_bg=True)}Here is some maroon on orange text{reset_all}") Here's my main .csproj <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.Enrichers.CallerInfo" Version="1.0.5" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageReference Include="Serilog.Expressions" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AnsiHelpersProj\AnsiHelpersProj.csproj" />
</ItemGroup>
</Project> Here's my main program using AnsiHelpersCollection; // Defined above
using Serilog;
using Serilog.Enrichers.CallerInfo;
using Serilog.Events;
using Serilog.Templates;
using Serilog.Templates.Themes;
namespace SerilogWithCaller;
class Program
{
public static void Main(string[] args)
{
// Use for ColorId256 color codes: https://user-images.githubusercontent.com/995050/47952855-ecb12480-df75-11e8-89d4-ac26c50e80b9.png
var themeDict = (IReadOnlyDictionary<TemplateThemeStyle, string>)new Dictionary<TemplateThemeStyle, string>()
{
[TemplateThemeStyle.Text] = AnsiHelpers.Combine([AnsiHelpers.bg_black, AnsiHelpers.fg_bright_green]),
[TemplateThemeStyle.SecondaryText] =
AnsiHelpers.ColorId256ToAnsi(246, getBackgroundCode: false), // light gray foreground text
[TemplateThemeStyle.TertiaryText] =
AnsiHelpers.ColorId256ToAnsi(242, getBackgroundCode: false), // dark gray foreground text
[TemplateThemeStyle.Invalid] = AnsiHelpers.Combine([AnsiHelpers.fg_yellow, AnsiHelpers.bold]),
[TemplateThemeStyle.Null] =
AnsiHelpers.ColorId256ToAnsi(38, getBackgroundCode: false), // light aquamarine foreground text
[TemplateThemeStyle.Name] = AnsiHelpers.ColorId256ToAnsi(81, getBackgroundCode: false), // cyan foreground text
[TemplateThemeStyle.String] =
AnsiHelpers.ColorId256ToAnsi(216, getBackgroundCode: false), // beige foreground text
[TemplateThemeStyle.Number] =
AnsiHelpers.ColorId256ToAnsi(151, getBackgroundCode: false), // pale greenish blue foreground text
[TemplateThemeStyle.Boolean] =
AnsiHelpers.ColorId256ToAnsi(38, getBackgroundCode: false), // light aquamarine foreground text
[TemplateThemeStyle.Scalar] =
AnsiHelpers.ColorId256ToAnsi(79, getBackgroundCode: false), // aquamarine foreground text
[TemplateThemeStyle.LevelVerbose] = AnsiHelpers.ColorId256ToAnsi(75, getBackgroundCode: false), // light blue
[TemplateThemeStyle.LevelDebug] = AnsiHelpers.Combine([AnsiHelpers.fg_white]),
[TemplateThemeStyle.LevelInformation] = AnsiHelpers.Combine([AnsiHelpers.bg_black, AnsiHelpers.fg_green]),
[TemplateThemeStyle.LevelWarning] = AnsiHelpers.Combine([AnsiHelpers.fg_yellow]),
[TemplateThemeStyle.LevelError] =
$"{AnsiHelpers.Combine([AnsiHelpers.fg_bright_white])}{AnsiHelpers.Combine([AnsiHelpers.bg_bright_red])}",
[TemplateThemeStyle.LevelFatal] =
$"{AnsiHelpers.ColorId256ToAnsi(154, getBackgroundCode: false)}{AnsiHelpers.Combine([AnsiHelpers.bg_magenta])}", // lime green text on magenta background
};
var timeAndThreadPrefix = "{@t:yyyy-MM-dd HH:mm:ss.fff zzz} [{@p['ThreadId']}] ";
// We split the namespace to get only the class name to prevent long names from overflowing the console
var messageSuffix =
" [{Substring(@p['Namespace'], LastIndexOf(@p['Namespace'], '.') + 1)} - {@p['Method']}] - {@m} {@x}";
// Learn more about Serilog expression template language:
// - https://github.com/serilog/serilog-expressions?tab=readme-ov-file#formatting-with-expressiontemplate
// - https://github.com/serilog/serilog-expressions?tab=readme-ov-file#language-reference
var consoleLogExpressionTemplate = new ExpressionTemplate(
$"{{#if @l = 'Verbose'}}" +
$"{AnsiHelpers.reset_all}{themeDict[TemplateThemeStyle.LevelVerbose]}" + timeAndThreadPrefix + "VERBOSE" +
messageSuffix + $"{AnsiHelpers.reset_all}\n" +
$"{{#else if @l = 'Debug'}}" +
$"{AnsiHelpers.reset_all}{themeDict[TemplateThemeStyle.LevelDebug]}" + timeAndThreadPrefix + "DEBUG" +
messageSuffix + $"{AnsiHelpers.reset_all}\n" +
$"{{#else if @l = 'Information'}}" +
$"{AnsiHelpers.reset_all}{themeDict[TemplateThemeStyle.LevelInformation]}" + timeAndThreadPrefix + "INFO" +
messageSuffix + $"{AnsiHelpers.reset_all}\n" +
$"{{#else if @l = 'Warning'}}" +
$"{AnsiHelpers.reset_all}{themeDict[TemplateThemeStyle.LevelWarning]}" + timeAndThreadPrefix + "WARN" +
messageSuffix + $"{AnsiHelpers.reset_all}\n" +
$"{{#else if @l = 'Error'}}" +
$"{AnsiHelpers.reset_all}{themeDict[TemplateThemeStyle.LevelError]}" + timeAndThreadPrefix + "ERROR" +
messageSuffix + $"{AnsiHelpers.reset_all}\n" +
$"{{#else if @l = 'Fatal'}}" +
$"{AnsiHelpers.reset_all}{themeDict[TemplateThemeStyle.LevelFatal]}" + timeAndThreadPrefix + "FATAL" +
messageSuffix + $"{AnsiHelpers.reset_all}\n" +
$"{{#else}}" +
$"{AnsiHelpers.reset_all}{themeDict[TemplateThemeStyle.Text]}" + timeAndThreadPrefix + "OTHER" + messageSuffix +
$"{AnsiHelpers.reset_all}\n" +
$"{{#end}}"
);
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.WithCallerInfo(includeFileInfo: true, new List<string> {
"SerilogWithCaller"
})
.Enrich.WithThreadId()
.WriteTo.Console(consoleLogExpressionTemplate, restrictedToMinimumLevel:LogEventLevel.Verbose)
.CreateLogger();
var eng = new Engine();
eng.DoSomething();
Console.WriteLine();
}
} Here's Engine.cs using Serilog;
namespace SerilogWithCaller;
public class Engine
{
public void DoSomething()
{
var val = new { longitude = "some value", latitude = "some other value" };
Log.Verbose("Some Verbosity... {val}", val);
Log.Debug("Some debug");
Log.Information("Doing something...");
Log.Warning("Some warning...");
Log.Error(new Exception("Some exception text"), "Some error...");
Log.Fatal("Some fatal...");
}
} |
is there anyway to obtain an output like this using theme?
The text was updated successfully, but these errors were encountered: