dotnet add package ConsoleMonster.Extensions.Logging
The provider depends only on Microsoft.Extensions.Logging.Abstractions and ConsoleMonster, and works with trimming and native AOT on .NET 8 and later.
Log entries the ConsoleMonster way
Every level, a scope, a structured message and an exception, through the Microsoft.Extensions.Logging provider.
09:56:46 VERB Cache Probing 4 cache shards 09:56:46 DBG Cache Shard eu-1 answered in 12 ms 09:56:46 Orders (order 1042) Payment accepted: 129.90 EUR 09:56:46 ▴ WARN Orders (order 1042) Stock is low for A-17: 3 left 09:56:46 ✗ ERR Orders Refund for order 1038 failed ✗ TimeoutException The payment gateway did not answer within 30 s. at Program.Logging() ConsoleMonster.Examples.Extensions/Program.cs:44 09:56:46 ✗ ERR Orders critical: Queue orders-eu is not draining 09:56:46 Orders Done; 57 orders processed
private static void Logging() {
using ILoggerFactory factory = LoggerFactory.Create(logging => logging.SetMinimumLevel(LogLevel.Trace).AddConsoleMonster());
ILogger orders = factory.CreateLogger("Shop.Services.Orders");
ILogger cache = factory.CreateLogger("Shop.Infrastructure.Cache");
cache.LogTrace("Probing {Count} cache shards", 4);
cache.LogDebug("Shard {Shard} answered in {Ms} ms", "eu-1", 12);
using (orders.BeginScope("order {Id}", 1042)) {
orders.LogInformation("Payment accepted: {Amount} EUR", 129.90m);
orders.LogWarning("Stock is low for {Sku}: {Left} left", "A-17", 3);
}
try {
throw new TimeoutException("The payment gateway did not answer within 30 s.");
} catch (TimeoutException ex) {
orders.LogError(ex, "Refund for order {Id} failed", 1038);
}
orders.LogCritical("Queue {Queue} is not draining", "orders-eu");
orders.LogInformation("Done; {Processed} orders processed", 57);
}Full program
using System;
using System.Threading;
using System.Threading.Tasks;
using ConsoleMonster.Extensions.Logging;
using ConsoleMonster.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace ConsoleMonster.Examples.Extensions {
/// <summary>Examples for the logging provider and the hosting dashboard: <c>logging</c> or <c>hosting [seconds]</c>.</summary>
public static class Program {
/// <summary>Runs an example.</summary>
/// <param name="args"><c>logging</c> or <c>hosting [seconds]</c>.</param>
/// <returns>The exit code.</returns>
public static async Task<int> Main(string[] args) {
string name = args.Length > 0 ? args[0].ToLowerInvariant() : "logging";
switch (name) {
case "logging":
Logging();
return 0;
case "hosting":
int seconds = args.Length > 1 && int.TryParse(args[1], out int value) ? value : 12;
await HostingAsync(seconds).ConfigureAwait(false);
return 0;
default:
Console.Error.WriteLine("Use: logging | hosting [seconds]");
return 1;
}
}
// Every level, a scope, a structured message and an exception.
private static void Logging() {
using ILoggerFactory factory = LoggerFactory.Create(logging => logging.SetMinimumLevel(LogLevel.Trace).AddConsoleMonster());
ILogger orders = factory.CreateLogger("Shop.Services.Orders");
ILogger cache = factory.CreateLogger("Shop.Infrastructure.Cache");
cache.LogTrace("Probing {Count} cache shards", 4);
cache.LogDebug("Shard {Shard} answered in {Ms} ms", "eu-1", 12);
using (orders.BeginScope("order {Id}", 1042)) {
orders.LogInformation("Payment accepted: {Amount} EUR", 129.90m);
orders.LogWarning("Stock is low for {Sku}: {Left} left", "A-17", 3);
}
try {
throw new TimeoutException("The payment gateway did not answer within 30 s.");
} catch (TimeoutException ex) {
orders.LogError(ex, "Refund for order {Id} failed", 1038);
}
orders.LogCritical("Queue {Queue} is not draining", "orders-eu");
orders.LogInformation("Done; {Processed} orders processed", 57);
}
// A generic host with three workers and the console dashboard; logs go into its log region.
private static async Task HostingAsync(int seconds) {
HostApplicationBuilder builder = Host.CreateApplicationBuilder(new HostApplicationBuilderSettings { ApplicationName = "Orders", EnvironmentName = "Staging" });
builder.Logging.ClearProviders();
builder.Logging.AddConsoleMonster();
builder.Services.Configure<HostOptions>(options => options.BackgroundServiceExceptionBehavior = BackgroundServiceExceptionBehavior.Ignore);
builder.Services.AddHostedService<QueueWorker>();
builder.Services.AddHostedService<CacheWarmer>();
builder.Services.AddHostedService<ReportBuilder>();
builder.Services.AddConsoleDashboard(dashboard => dashboard.LogHeight = 6);
using IHost host = builder.Build();
using CancellationTokenSource stop = new CancellationTokenSource(TimeSpan.FromSeconds(seconds));
await host.RunAsync(stop.Token).ConfigureAwait(false);
}
private sealed class QueueWorker : BackgroundService {
private readonly ILogger<QueueWorker> _log;
private readonly IConsoleDashboard _dashboard;
public QueueWorker(ILogger<QueueWorker> log, IConsoleDashboard dashboard) {
_log = log;
_dashboard = dashboard;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
int order = 1040;
while (!stoppingToken.IsCancellationRequested) {
order++;
_dashboard.SetDetail("Queue", (50 - order % 50) + " waiting");
_log.LogInformation("Picked up order {Order}", order);
if (order % 4 == 0) {
_log.LogWarning("Payment slow for order {Order}", order);
}
try {
await Task.Delay(900, stoppingToken).ConfigureAwait(false);
} catch (OperationCanceledException) {
break;
}
}
}
}
private sealed class CacheWarmer : BackgroundService {
private readonly ILogger<CacheWarmer> _log;
public CacheWarmer(ILogger<CacheWarmer> log) {
_log = log;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
await Task.Delay(2500, stoppingToken).ConfigureAwait(false);
_log.LogInformation("Cache warmed: {Entries} entries", 12840);
}
}
private sealed class ReportBuilder : BackgroundService {
private readonly ILogger<ReportBuilder> _log;
public ReportBuilder(ILogger<ReportBuilder> log) {
_log = log;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
await Task.Delay(4000, stoppingToken).ConfigureAwait(false);
_log.LogError("The report template folder is missing");
throw new InvalidOperationException("The report template folder is missing.");
}
}
}
}Use it in your app
dotnet add package ConsoleMonster.Extensions.LoggingReference: Logging, Package README
What it writes
- The level tag and colour that
Write-ConsoleLogand the dashboard log region use:✗ ERRfor Error and Critical,▴ WARNfor Warning,DBGandVERBmuted for Debug and Trace, and no tag for Information. - A timestamp (
TimestampFormat, defaultHH:mm:ss; null leaves it out), then the category (the last part by default,FullorNone) and the scopes in brackets. - An exception's type and message under the entry, with up to
MaxExceptionLineslines of details. - While a ConsoleMonster dashboard or
AddConsoleDashboardshows, entries go into its log region, so they never print over the view. - With output redirected, plain lines without escape codes.
Options
| Option | Default | Meaning |
|---|---|---|
MinimumLevel | Trace | The lowest level written; the logging builder's filters apply first. |
TimestampFormat | HH:mm:ss | A .NET date format, or null for no timestamp. |
UseUtcTimestamp | false | UTC instead of local time. |
Category | Short | Short, Full or None. |
IncludeScopes | true | Shows the scopes after the category. |
IncludeEventId | false | Shows the event id. |
MaxExceptionLines | 12 | Lines of exception details; 0 for the type and message only. |
LogRegion | $log | The live log region to write into while one runs; null always prints. |
Without dependency injection, use loggerFactory.AddConsoleMonster(). The provider is added once: a second AddConsoleMonster keeps the first call's options. It is thread-safe.