Added custom token support
This commit is contained in:
@@ -25,7 +25,7 @@
|
|||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="EonaCat.Logger" Version="1.5.7" />
|
<PackageReference Include="EonaCat.Logger" Version="1.5.8" />
|
||||||
<PackageReference Include="System.Net.Http.Json" Version="10.0.1" />
|
<PackageReference Include="System.Net.Http.Json" Version="10.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
<Copyright>EonaCat (Jeroen Saey)</Copyright>
|
<Copyright>EonaCat (Jeroen Saey)</Copyright>
|
||||||
<PackageTags>EonaCat;Logger;EonaCatLogger;Log;Writer;Jeroen;Saey</PackageTags>
|
<PackageTags>EonaCat;Logger;EonaCatLogger;Log;Writer;Jeroen;Saey</PackageTags>
|
||||||
<PackageIconUrl />
|
<PackageIconUrl />
|
||||||
<Version>1.5.8</Version>
|
<Version>1.5.9</Version>
|
||||||
<FileVersion>1.5.8</FileVersion>
|
<FileVersion>1.5.9</FileVersion>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<EVRevisionFormat>1.5.8+{chash:10}.{c:ymd}</EVRevisionFormat>
|
<EVRevisionFormat>1.5.9+{chash:10}.{c:ymd}</EVRevisionFormat>
|
||||||
<EVDefault>true</EVDefault>
|
<EVDefault>true</EVDefault>
|
||||||
<EVInfo>true</EVInfo>
|
<EVInfo>true</EVInfo>
|
||||||
<EVTagMatch>v[0-9]*</EVTagMatch>
|
<EVTagMatch>v[0-9]*</EVTagMatch>
|
||||||
|
|||||||
@@ -8,15 +8,132 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using static EonaCat.Logger.Managers.LogHelper;
|
||||||
|
|
||||||
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
||||||
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
||||||
|
|
||||||
namespace EonaCat.Logger.Managers;
|
namespace EonaCat.Logger.Managers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a collection of predefined and customizable header token resolvers for formatting log or message headers.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>HeaderTokens exposes a set of standard tokens that can be used to insert runtime, environment, and
|
||||||
|
/// contextual information into header strings, such as timestamps, process details, thread information, and more.
|
||||||
|
/// Custom tokens can be added or overridden to support application-specific formatting needs. All token keys are
|
||||||
|
/// case-insensitive. This class is thread-safe for read operations, but adding or overriding tokens is not guaranteed
|
||||||
|
/// to be thread-safe and should be synchronized if used concurrently.</remarks>
|
||||||
|
public class HeaderTokens
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, Func<HeaderContext, string>> _tokenResolvers =
|
||||||
|
new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["date"] = ctx => $"[Date: {ctx.Timestamp:yyyy-MM-dd}]",
|
||||||
|
|
||||||
|
["time"] = ctx => $"[Time: {ctx.Timestamp:HH:mm:ss.fff}]",
|
||||||
|
|
||||||
|
["ticks"] = ctx => $"[Ticks: {ctx.Timestamp.Ticks}]",
|
||||||
|
|
||||||
|
["ts"] = ctx => $"[{ctx.Timestamp.ToString(ctx.TimestampFormat)}]",
|
||||||
|
|
||||||
|
["tz"] = ctx => $"[{(ctx.Timestamp.Kind == DateTimeKind.Utc ? "UTC" : "LOCAL")}]",
|
||||||
|
|
||||||
|
["unix"] = ctx => $"[Unix: {new DateTimeOffset(ctx.Timestamp).ToUnixTimeSeconds()}]",
|
||||||
|
|
||||||
|
["procstart"] = _ =>
|
||||||
|
{
|
||||||
|
var p = Process.GetCurrentProcess();
|
||||||
|
return $"[ProcStart: {p.StartTime:O}]";
|
||||||
|
},
|
||||||
|
|
||||||
|
["uptime"] = _ =>
|
||||||
|
{
|
||||||
|
var p = Process.GetCurrentProcess();
|
||||||
|
return $"[Uptime: {(DateTime.Now - p.StartTime).TotalSeconds:F0}s]";
|
||||||
|
},
|
||||||
|
|
||||||
|
["framework"] = _ =>
|
||||||
|
$"[Runtime: {RuntimeInformation.FrameworkDescription}]",
|
||||||
|
|
||||||
|
["os"] = _ =>
|
||||||
|
$"[OS: {RuntimeInformation.OSDescription}]",
|
||||||
|
|
||||||
|
["arch"] = _ =>
|
||||||
|
$"[Arch: {RuntimeInformation.ProcessArchitecture}]",
|
||||||
|
|
||||||
|
["mem"] = _ => $"[Memory: {GC.GetTotalMemory(false) / 1024 / 1024}MB]",
|
||||||
|
|
||||||
|
["gc"] = _ => $"[GC: {GC.CollectionCount(0)}/{GC.CollectionCount(1)}/{GC.CollectionCount(2)}]",
|
||||||
|
|
||||||
|
["cwd"] = _ => $"[CWD: {Environment.CurrentDirectory}]",
|
||||||
|
|
||||||
|
["app"] = _ => $"[App: {AppDomain.CurrentDomain.FriendlyName}]",
|
||||||
|
|
||||||
|
["appbase"] = _ => $"[AppBase: {AppDomain.CurrentDomain.BaseDirectory}]",
|
||||||
|
|
||||||
|
["domain"] = _ => $"[Domain: {AppDomain.CurrentDomain.Id}]",
|
||||||
|
|
||||||
|
["threadname"] = _ => $"[ThreadName: {Thread.CurrentThread.Name ?? "n/a"}]",
|
||||||
|
|
||||||
|
["task"] = _ => $"[TaskId: {Task.CurrentId?.ToString() ?? "n/a"}]",
|
||||||
|
|
||||||
|
["host"] = ctx => $"[Host: {ctx.HostName}]",
|
||||||
|
|
||||||
|
["machine"] = _ => $"[Machine: {Environment.MachineName}]",
|
||||||
|
|
||||||
|
["category"] = ctx => $"[Category: {ctx.Category}]",
|
||||||
|
|
||||||
|
["thread"] = _ => $"[Thread: {Environment.CurrentManagedThreadId}]",
|
||||||
|
|
||||||
|
["process"] = _ =>
|
||||||
|
{
|
||||||
|
var p = Process.GetCurrentProcess();
|
||||||
|
return $"[Process: {p.ProcessName}]";
|
||||||
|
},
|
||||||
|
|
||||||
|
["pid"] = _ => $"[PID: {Process.GetCurrentProcess().Id}]",
|
||||||
|
|
||||||
|
["sev"] = ctx => $"[Severity: {ctx.LogType}]",
|
||||||
|
|
||||||
|
["user"] = _ => $"[User: {Environment.UserName}]",
|
||||||
|
|
||||||
|
["env"] = ctx => $"[Env: {ctx.EnvironmentName}]",
|
||||||
|
|
||||||
|
["newline"] = _ => Environment.NewLine
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a read-only dictionary of token resolver functions used to generate header values based on a token name and
|
||||||
|
/// header context.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Each entry maps a token name to a function that produces the corresponding header value for a
|
||||||
|
/// given <see cref="HeaderContext"/>. The dictionary is static and cannot be modified at runtime.</remarks>
|
||||||
|
public IReadOnlyDictionary<string, Func<HeaderContext, string>> TokenResolvers => _tokenResolvers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds or overrides a custom token.
|
||||||
|
/// </summary>
|
||||||
|
public void AddCustomToken(string key, Func<HeaderContext, string> resolver)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(key))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Token key cannot be null or whitespace.", nameof(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolver == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(resolver));
|
||||||
|
}
|
||||||
|
|
||||||
|
_tokenResolvers[key] = resolver;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class ErrorMessage
|
public class ErrorMessage
|
||||||
{
|
{
|
||||||
public Exception Exception { get; set; }
|
public Exception Exception { get; set; }
|
||||||
@@ -36,23 +153,6 @@ public static class LogHelper
|
|||||||
public string EnvironmentName { get; set; }
|
public string EnvironmentName { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Dictionary<string, Func<HeaderContext, string>> TokenResolvers =
|
|
||||||
new(StringComparer.OrdinalIgnoreCase)
|
|
||||||
{
|
|
||||||
["ts"] = ctx => ctx.Timestamp.ToString(ctx.TimestampFormat),
|
|
||||||
["host"] = ctx => ctx.HostName,
|
|
||||||
["machine"] = ctx => Environment.MachineName,
|
|
||||||
["category"] = ctx => ctx.Category,
|
|
||||||
["thread"] = ctx => Environment.CurrentManagedThreadId.ToString(),
|
|
||||||
["process"] = ctx => Process.GetCurrentProcess().ProcessName,
|
|
||||||
["pid"] = ctx => Process.GetCurrentProcess().Id.ToString(),
|
|
||||||
["sev"] = ctx => ctx.LogType.ToString(),
|
|
||||||
["user"] = ctx => Environment.UserName,
|
|
||||||
["env"] = ctx => ctx.EnvironmentName,
|
|
||||||
["newline"] = _ => Environment.NewLine
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
internal static event EventHandler<ErrorMessage> OnException;
|
internal static event EventHandler<ErrorMessage> OnException;
|
||||||
|
|
||||||
internal static event EventHandler<ErrorMessage> OnLogLevelDisabled;
|
internal static event EventHandler<ErrorMessage> OnLogLevelDisabled;
|
||||||
@@ -112,11 +212,12 @@ public static class LogHelper
|
|||||||
|
|
||||||
return Regex.Replace(format, @"\{([^}]+)\}", match =>
|
return Regex.Replace(format, @"\{([^}]+)\}", match =>
|
||||||
{
|
{
|
||||||
var token = match.Groups[1].Value;
|
var tokenText = match.Groups[1].Value;
|
||||||
var parts = token.Split(new[] { ':' }, 2);
|
|
||||||
|
var parts = tokenText.Split(new[] { ':' }, 2);
|
||||||
var key = parts[0];
|
var key = parts[0];
|
||||||
|
|
||||||
if (!TokenResolvers.TryGetValue(key, out var resolver))
|
if (!ctx.Settings.HeaderTokens.TokenResolvers.TryGetValue(key, out var resolver))
|
||||||
{
|
{
|
||||||
return match.Value;
|
return match.Value;
|
||||||
}
|
}
|
||||||
@@ -125,13 +226,13 @@ public static class LogHelper
|
|||||||
{
|
{
|
||||||
ctx.TimestampFormat = parts[1];
|
ctx.TimestampFormat = parts[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolver(ctx);
|
return resolver(ctx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
internal static string FormatMessageWithHeader(LoggerSettings settings, ELogType logType, string message, DateTime dateTime, string category = null)
|
internal static string FormatMessageWithHeader(LoggerSettings settings, ELogType logType, string message, DateTime dateTime, string category = null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(message))
|
if (string.IsNullOrWhiteSpace(message))
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public class LoggerSettings
|
|||||||
|
|
||||||
private FileLoggerOptions _fileLoggerOptions;
|
private FileLoggerOptions _fileLoggerOptions;
|
||||||
|
|
||||||
private string _headerFormat = "{ts} {host} {category} {thread} {sev}";
|
private string _headerFormat = "{ts} {tz} {host} {category} {thread} {sev}";
|
||||||
private string _timestampFormat = "yyyy-MM-dd HH:mm:ss";
|
private string _timestampFormat = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -32,8 +32,18 @@ public class LoggerSettings
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseLocalTime { get; set; }
|
public bool UseLocalTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the unique identifier for the application instance.
|
||||||
|
/// </summary>
|
||||||
public string Id { get; set; } = DllInfo.ApplicationName;
|
public string Id { get; set; } = DllInfo.ApplicationName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the collection of custom header tokens to be included in outgoing requests.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Use this property to specify additional headers that should be sent with each request.
|
||||||
|
/// Modifying the collection affects all subsequent requests made by this instance.</remarks>
|
||||||
|
public HeaderTokens HeaderTokens { get; set; } = new HeaderTokens();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Header format. Provide a string that specifies how the preamble of each message should be structured. You can use
|
/// Header format. Provide a string that specifies how the preamble of each message should be structured. You can use
|
||||||
/// variables including:
|
/// variables including:
|
||||||
@@ -42,7 +52,7 @@ public class LoggerSettings
|
|||||||
/// {category}: Category
|
/// {category}: Category
|
||||||
/// {thread}: Thread ID
|
/// {thread}: Thread ID
|
||||||
/// {sev}: Severity
|
/// {sev}: Severity
|
||||||
/// Default: {ts} {host} {category} {thread} {sev}
|
/// Default: {ts} {tz} {host} {category} {thread} {sev}
|
||||||
/// A space will be inserted between the header and the message.
|
/// A space will be inserted between the header and the message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string HeaderFormat
|
public string HeaderFormat
|
||||||
|
|||||||
33
README.md
33
README.md
@@ -296,6 +296,39 @@ Dutch Passport Numbers (9 alphanumeric characters
|
|||||||
Dutch Identification Document Numbers (varying formats)
|
Dutch Identification Document Numbers (varying formats)
|
||||||
Custom keywords specified in LoggerSettings
|
Custom keywords specified in LoggerSettings
|
||||||
|
|
||||||
|
Header tokens:
|
||||||
|
|
||||||
|
{date} {time} {ts} {tz} {unix} {ticks}{newline}
|
||||||
|
{sev} {category} {env}{newline}
|
||||||
|
{host} {machine}{newline}
|
||||||
|
{process} {pid} {procstart} {uptime}{newline}
|
||||||
|
{thread} {threadname} {task}{newline}
|
||||||
|
{user}{newline}
|
||||||
|
{app} {appbase} {domain}{newline}
|
||||||
|
{framework} {os} {arch}{newline}
|
||||||
|
{mem} {gc}{newline}
|
||||||
|
{cwd}
|
||||||
|
|
||||||
|
Example of custom header:
|
||||||
|
```csharp
|
||||||
|
[Date: 2026-01-30] [Time: 14:22:11.384] [2026-01-30 14:22:11.384] [UTC] [Unix: 1738246931] [Ticks: 638422525313840000]
|
||||||
|
[Severity: Info] [Category: Startup] [Env: Production]
|
||||||
|
[Host: api-01] [Machine: API-SERVER-01]
|
||||||
|
[Process: MyService] [PID: 4216] [ProcStart: 2026-01-30T14:20:03.5123456Z] [Uptime: 128s]
|
||||||
|
[Thread: 9] [ThreadName: worker-1] [TaskId: 42]
|
||||||
|
[User: svc-api]
|
||||||
|
[App: MyService.exe] [AppBase: C:\apps\myservice\] [Domain: 1]
|
||||||
|
[Runtime: .NET 8.0.1] [OS: Linux 6.6.9] [Arch: X64]
|
||||||
|
[Memory: 128MB] [GC: 12/4/1]
|
||||||
|
[CWD: /app]
|
||||||
|
```
|
||||||
|
|
||||||
|
Add Custom tokens:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
Logger.LoggerSettings.HeaderTokens.AddCustomToken("spaceShuttleId", context => $"[Shuttle: {context.SpaceShuttleId}]");
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
**Code for enabling GrayLog in the above *advanced* logger class:**
|
**Code for enabling GrayLog in the above *advanced* logger class:**
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class Logger
|
|||||||
|
|
||||||
LoggerSettings.CustomHeaderFormatter = ctx =>
|
LoggerSettings.CustomHeaderFormatter = ctx =>
|
||||||
{
|
{
|
||||||
if (ctx.LogType == ELogType.Error)
|
if (ctx.LogType == ELogType.ERROR)
|
||||||
{
|
{
|
||||||
return $"{ctx.Timestamp:HH:mm:ss} [{ctx.LogType}]";
|
return $"{ctx.Timestamp:HH:mm:ss} [{ctx.LogType}]";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user