Updated (added more header options)

This commit is contained in:
2026-01-30 20:40:41 +01:00
parent f18456e263
commit 04a807df4f
9 changed files with 512 additions and 390 deletions

View File

@@ -25,7 +25,7 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="EonaCat.Logger" Version="1.5.5" />
<PackageReference Include="EonaCat.Logger" Version="1.5.7" />
<PackageReference Include="System.Net.Http.Json" Version="10.0.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -13,8 +13,8 @@
<Copyright>EonaCat (Jeroen Saey)</Copyright>
<PackageTags>EonaCat;Logger;EonaCatLogger;Log;Writer;Jeroen;Saey</PackageTags>
<PackageIconUrl />
<Version>1.5.7</Version>
<FileVersion>1.5.7</FileVersion>
<Version>1.5.8</Version>
<FileVersion>1.5.8</FileVersion>
<PackageReadmeFile>README.md</PackageReadmeFile>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
@@ -25,7 +25,7 @@
</PropertyGroup>
<PropertyGroup>
<EVRevisionFormat>1.5.7+{chash:10}.{c:ymd}</EVRevisionFormat>
<EVRevisionFormat>1.5.8+{chash:10}.{c:ymd}</EVRevisionFormat>
<EVDefault>true</EVDefault>
<EVInfo>true</EVInfo>
<EVTagMatch>v[0-9]*</EVTagMatch>

View File

@@ -53,7 +53,9 @@ namespace EonaCat.Logger.EonaCatCoreLogger
Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel) || formatter == null)
{
return;
}
if (IncludeCorrelationId)
{
@@ -71,16 +73,24 @@ namespace EonaCat.Logger.EonaCatCoreLogger
};
foreach (var kvp in _context.GetAll())
{
logParts.Add($"`{kvp.Key}`: {kvp.Value}");
}
if (exception != null)
{
logParts.Add($"Exception: {exception}");
}
// Limit queue size to prevent memory growth
if (_messageQueue.Count < 1000)
{
_messageQueue.Enqueue(string.Join("\n", logParts));
}
else
{
OnException?.Invoke(this, new Exception("DiscordLogger queue overflow"));
}
}
private async Task FlushLoopAsync(CancellationToken token)
@@ -107,7 +117,9 @@ namespace EonaCat.Logger.EonaCatCoreLogger
private async Task FlushBufferAsync(CancellationToken token)
{
if (!await _flushLock.WaitAsync(0, token).ConfigureAwait(false))
{
return;
}
try
{

View File

@@ -54,8 +54,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger.Internal
? exception.FormatExceptionToMessage() + Environment.NewLine
: formatter(state, exception);
message = LogHelper.FormatMessageWithHeader(_loggerSettings, logLevel.FromLogLevel(), message, timestamp.DateTime, category)
+ Environment.NewLine;
message = LogHelper.FormatMessageWithHeader(_loggerSettings, logLevel.FromLogLevel(), message, timestamp.DateTime, category) + Environment.NewLine;
var currentMessage = new EonaCatLogMessage
{

View File

@@ -1,365 +1,394 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.SymbolStore;
using System.Threading;
using System.Threading.Tasks;
using EonaCat.Logger.Managers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace EonaCat.Logger.EonaCatCoreLogger.Internal;
// 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.
public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
{
private readonly int _batchSize;
private readonly List<LogMessage> _currentBatch = new();
private CancellationTokenSource _cancellationTokenSource;
private LoggerSettings _loggerSettings;
private ConcurrentQueue<LogMessage> _messageQueue;
private Task _outputTask;
private object _writeLock = new object();
private bool _isDisposing;
protected string Category;
protected BatchingLoggerProvider(IOptions<BatchingLoggerOptions> options)
{
var loggerOptions = options.Value;
if (loggerOptions.FlushPeriod <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(loggerOptions.FlushPeriod),
$"{nameof(loggerOptions.FlushPeriod)} must be longer than zero.");
}
if (options.Value is FileLoggerOptions fileLoggerOptions)
{
UseLocalTime = fileLoggerOptions.UseLocalTime;
UseMask = fileLoggerOptions.UseMask;
}
_batchSize = loggerOptions.BatchSize;
StartAsync().ConfigureAwait(false);
}
protected DateTimeOffset CurrentDateTimeOffset => UseLocalTime ? DateTimeOffset.Now : DateTimeOffset.UtcNow;
protected DateTime CurrentDateTme => UseLocalTime ? DateTime.Now : DateTime.UtcNow;
protected bool UseLocalTime { get; set; }
protected LoggerSettings LoggerSettings
{
get
{
if (_loggerSettings != null)
{
return _loggerSettings;
}
_loggerSettings = new LoggerSettings();
_loggerSettings.UseLocalTime = UseLocalTime;
_loggerSettings.UseMask = UseMask;
return _loggerSettings;
}
set => _loggerSettings = value;
using EonaCat.Logger.Managers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace EonaCat.Logger.EonaCatCoreLogger.Internal;
// 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.
public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
{
private int _batchSize;
private readonly Channel<LogMessage> _channel =
Channel.CreateUnbounded<LogMessage>(new UnboundedChannelOptions
{
SingleReader = true,
SingleWriter = false
});
private CancellationTokenSource _cancellationTokenSource;
private Task _outputTask;
private bool _isDisposed;
protected BatchingLoggerProvider(IOptions<BatchingLoggerOptions> options)
{
var loggerOptions = options.Value ?? throw new ArgumentNullException(nameof(options));
if (loggerOptions.FlushPeriod <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(loggerOptions.FlushPeriod),
$"{nameof(loggerOptions.FlushPeriod)} must be longer than zero.");
}
if (options.Value is FileLoggerOptions fileLoggerOptions)
{
UseLocalTime = fileLoggerOptions.UseLocalTime;
UseMask = fileLoggerOptions.UseMask;
}
_batchSize = loggerOptions.BatchSize > 0 ? loggerOptions.BatchSize : 100;
StartAsync();
}
private SemaphoreSlim _writeSemaphore = new SemaphoreSlim(1, 1);
public bool IsStarted { get; set; }
public bool UseMask { get; set; }
public async void Dispose()
{
while (!_messageQueue.IsEmpty)
{
// Do nothing, just wait for the queue to be empty
await Task.Delay(1000);
}
_isDisposing = true;
await StopAsync().ConfigureAwait(false);
GC.SuppressFinalize(this);
}
public ILogger CreateLogger(string categoryName)
{
Category = categoryName;
return new BatchingLogger(this, categoryName, LoggerSettings);
}
protected abstract Task WriteMessagesAsync(IEnumerable<LogMessage> messages, CancellationToken token);
private async Task ProcessLogQueueAsync(object state)
protected DateTimeOffset CurrentDateTimeOffset => UseLocalTime ? DateTimeOffset.Now : DateTimeOffset.UtcNow;
protected DateTime CurrentDateTme => UseLocalTime ? DateTime.Now : DateTime.UtcNow;
protected bool UseLocalTime { get; set; }
protected LoggerSettings LoggerSettings
{
while (!_cancellationTokenSource.IsCancellationRequested)
get
{
var limit = _batchSize <= 0 ? int.MaxValue : _batchSize;
while (limit > 0 && _messageQueue.TryDequeue(out var message))
if (_loggerSettings != null)
{
_currentBatch.Add(message);
limit--;
return _loggerSettings;
}
if (_currentBatch.Count > 0)
{
try
{
if (_isDisposing)
{
return;
}
_loggerSettings = new LoggerSettings();
_loggerSettings.UseLocalTime = UseLocalTime;
_loggerSettings.UseMask = UseMask;
return _loggerSettings;
}
await _writeSemaphore.WaitAsync();
await WriteMessagesAsync(_currentBatch, _cancellationTokenSource.Token).ConfigureAwait(false);
_currentBatch.Clear();
}
catch
set => _loggerSettings = value;
}
public bool IsStarted { get; set; }
public bool UseMask { get; set; }
private LoggerSettings _loggerSettings;
public ILogger CreateLogger(string categoryName)
{
return new BatchingLogger(this, categoryName, LoggerSettings);
}
protected abstract Task WriteMessagesAsync(IEnumerable<LogMessage> messages, CancellationToken token);
private async Task ProcessLogQueueAsync()
{
var batchSize = _batchSize > 0 ? _batchSize : 100;
var batch = new List<LogMessage>(batchSize);
try
{
var token = _cancellationTokenSource.Token;
while (await _channel.Reader.WaitToReadAsync(token).ConfigureAwait(false))
{
batch.Clear();
while (batch.Count < batchSize && _channel.Reader.TryRead(out var message))
{
// ignored
batch.Add(message);
}
finally
if (batch.Count > 0)
{
_writeSemaphore.Release();
await WriteMessagesAsync(batch, token).ConfigureAwait(false);
}
}
await Task.Delay(500);
}
catch (OperationCanceledException)
{
// normal shutdown
}
catch (Exception)
{
throw;
}
}
protected async Task WriteStartMessage()
{
var message = LogHelper.GetStartupMessage();
var token = _cancellationTokenSource?.Token ?? CancellationToken.None;
await WriteMessagesAsync(new List<LogMessage> { CreateLoggerMessage(message, CurrentDateTimeOffset) }, token).ConfigureAwait(false);
}
private LogMessage CreateLoggerMessage(string message, DateTimeOffset currentDateTimeOffset)
{
var result = new LogMessage() { Message = message, Timestamp = currentDateTimeOffset };
if (LoggerSettings != null && LoggerSettings.UseMask)
{
// Masking sensitive information
result.Message = MaskSensitiveInformation(result.Message);
}
return result;
}
/// <summary>
/// Masks sensitive information within the provided message string.
/// This method is virtual and can be overridden to customize masking behavior.
/// </summary>
/// <param name="message">The log message potentially containing sensitive information.</param>
/// <returns>The masked log message.</returns>
protected virtual string MaskSensitiveInformation(string message)
{
if (string.IsNullOrEmpty(message))
{
return message;
}
if (LoggerSettings != null && LoggerSettings.UseDefaultMasking)
{
// Mask IP addresses
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b(?:\d{1,3}\.){3}\d{1,3}\b(?!\d)",
LoggerSettings.Mask);
// Mask MAC addresses
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b(?:[0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}\b",
LoggerSettings.Mask);
// Mask emails
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b",
LoggerSettings.Mask,
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// Mask passwords
message = System.Text.RegularExpressions.Regex.Replace(message,
@"(?i)(password\s*[:= ]\s*|pwd\s*[:= ]\s*)[^\s]+",
$"password={LoggerSettings.Mask}");
// Mask credit card numbers
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b(?:\d{4}[ -]?){3}\d{4}\b",
LoggerSettings.Mask);
// Mask social security numbers (SSN) and BSN (Dutch Citizen Service Number)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b\d{3}-\d{2}-\d{4}\b|\b\d{9}\b",
LoggerSettings.Mask); // SSN (USA)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b\d{9}\b",
LoggerSettings.Mask); // BSN (Dutch)
// Mask passwords (Dutch)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"(?i)(wachtwoord\s*[:= ]\s*|ww\s*=\s*)[^\s]+",
$"wachtwoord={LoggerSettings.Mask}");
// Mask API keys/tokens
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Za-z0-9-_]{20,}\b",
LoggerSettings.Mask);
// Mask phone numbers (generic and Dutch specific)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b(\+?\d{1,4}[-.\s]?)?(\(?\d{3}\)?[-.\s]?)?\d{3}[-.\s]?\d{4}\b",
LoggerSettings.Mask); // Generic
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b(\+31|0031|0|06)[-\s]?\d{8}\b",
LoggerSettings.Mask); // Dutch
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b(\+32|0032|0|06)[-\s]?\d{8}\b",
LoggerSettings.Mask); // Belgium
// Mask dates of birth (DOB) or other date formats
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b\d{2}[/-]\d{2}[/-]\d{4}\b",
LoggerSettings.Mask);
// Mask Dutch postal codes
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b\d{4}\s?[A-Z]{2}\b",
LoggerSettings.Mask);
// Mask IBAN/Bank account numbers (generic and Dutch specific)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Z]{2}\d{2}[A-Z0-9]{1,30}\b",
LoggerSettings.Mask); // Generic for EU and other country IBANs
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\bNL\d{2}[A-Z]{4}\d{10}\b",
LoggerSettings.Mask); // Dutch IBAN
// Mask JWT Tokens
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Za-z0-9-_]{16,}\.[A-Za-z0-9-_]{16,}\.[A-Za-z0-9-_]{16,}\b",
LoggerSettings.Mask);
// Mask URLs with sensitive query strings
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\bhttps?:\/\/[^\s?]+(\?[^\s]+?[\&=](password|key|token|wachtwoord|sleutel))[^&\s]*",
LoggerSettings.Mask,
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// Mask license keys
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}\b",
LoggerSettings.Mask);
// Mask public and private keys (e.g., PEM format)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"-----BEGIN [A-Z ]+KEY-----[\s\S]+?-----END [A-Z ]+KEY-----",
LoggerSettings.Mask);
// Mask Dutch KVK number (8 or 12 digits)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b\d{8}|\d{12}\b",
LoggerSettings.Mask);
// Mask Dutch BTW-nummer (VAT number)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\bNL\d{9}B\d{2}\b",
LoggerSettings.Mask);
// Mask Dutch driving license number (10-12 characters)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Z0-9]{10,12}\b",
LoggerSettings.Mask);
// Mask Dutch health insurance number (Zorgnummer)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b\d{9}\b",
LoggerSettings.Mask);
// Mask other Dutch Bank Account numbers (9-10 digits)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b\d{9,10}\b",
LoggerSettings.Mask);
// Mask Dutch Passport Numbers (9 alphanumeric characters)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Z0-9]{9}\b",
LoggerSettings.Mask);
// Mask Dutch Identification Document Numbers (varying formats)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Z]{2}\d{6,7}\b",
LoggerSettings.Mask);
}
// Mask custom keywords specified in LoggerSettings
if (LoggerSettings?.MaskedKeywords != null)
{
foreach (var keyword in LoggerSettings.MaskedKeywords)
{
if (string.IsNullOrWhiteSpace(keyword))
{
continue;
}
message = message.Replace(keyword, LoggerSettings.Mask);
}
}
return message;
}
internal string AddMessage(DateTimeOffset timestamp, string message)
{
var result = CreateLoggerMessage(message, timestamp);
if (!_channel.Writer.TryWrite(result))
{
_ = _channel.Writer.WriteAsync(result);
}
return result.Message;
}
private void StartAsync()
{
if (_cancellationTokenSource != null)
{
return;
}
_cancellationTokenSource = new CancellationTokenSource();
_outputTask = Task.Run(ProcessLogQueueAsync);
IsStarted = true;
}
private async Task StopAsync()
{
if (_cancellationTokenSource == null)
{
return;
}
try
{
_cancellationTokenSource.Cancel();
_channel.Writer.Complete();
if (_outputTask != null)
{
await _outputTask.ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
// expected on shutdown
}
catch (AggregateException exception) when (exception.InnerExceptions.Count == 1 && exception.InnerExceptions[0] is TaskCanceledException)
{
// Do nothing
}
finally
{
try
{
_cancellationTokenSource.Dispose();
}
catch
{
// Do nothing
}
_cancellationTokenSource = null;
_outputTask = null;
IsStarted = false;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
if (disposing)
{
try
{
StopAsync().GetAwaiter().GetResult();
}
catch
{
// Do nothing
}
}
}
protected async Task WriteStartMessage()
{
var message = LogHelper.GetStartupMessage();
await WriteMessagesAsync(new List<LogMessage> { CreateLoggerMessage(message, CurrentDateTimeOffset) }, _cancellationTokenSource.Token).ConfigureAwait(false);
}
private LogMessage CreateLoggerMessage(string message, DateTimeOffset currentDateTimeOffset)
{
var result = new LogMessage() { Message = message, Timestamp = currentDateTimeOffset };
if (LoggerSettings != null && LoggerSettings.UseMask)
{
// Masking sensitive information
result.Message = MaskSensitiveInformation(result.Message);
}
return result;
}
/// <summary>
/// Masks sensitive information within the provided message string.
/// This method is virtual and can be overridden to customize masking behavior.
/// </summary>
/// <param name="message">The log message potentially containing sensitive information.</param>
/// <returns>The masked log message.</returns>
protected virtual string MaskSensitiveInformation(string message)
{
if (string.IsNullOrEmpty(message))
{
return message;
}
if (LoggerSettings != null && LoggerSettings.UseDefaultMasking)
{
// Mask IP addresses
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b(?:\d{1,3}\.){3}\d{1,3}\b(?!\d)",
LoggerSettings.Mask);
// Mask MAC addresses
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b(?:[0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}\b",
LoggerSettings.Mask);
// Mask emails
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b",
LoggerSettings.Mask,
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// Mask passwords
message = System.Text.RegularExpressions.Regex.Replace(message,
@"(?i)(password\s*[:= ]\s*|pwd\s*[:= ]\s*)[^\s]+",
$"password={LoggerSettings.Mask}");
// Mask credit card numbers
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b(?:\d{4}[ -]?){3}\d{4}\b",
LoggerSettings.Mask);
// Mask social security numbers (SSN) and BSN (Dutch Citizen Service Number)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b\d{3}-\d{2}-\d{4}\b|\b\d{9}\b",
LoggerSettings.Mask); // SSN (USA)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b\d{9}\b",
LoggerSettings.Mask); // BSN (Dutch)
// Mask passwords (Dutch)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"(?i)(wachtwoord\s*[:= ]\s*|ww\s*=\s*)[^\s]+",
$"wachtwoord={LoggerSettings.Mask}");
// Mask API keys/tokens
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Za-z0-9-_]{20,}\b",
LoggerSettings.Mask);
// Mask phone numbers (generic and Dutch specific)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b(\+?\d{1,4}[-.\s]?)?(\(?\d{3}\)?[-.\s]?)?\d{3}[-.\s]?\d{4}\b",
LoggerSettings.Mask); // Generic
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b(\+31|0031|0|06)[-\s]?\d{8}\b",
LoggerSettings.Mask); // Dutch
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b(\+32|0032|0|06)[-\s]?\d{8}\b",
LoggerSettings.Mask); // Belgium
// Mask dates of birth (DOB) or other date formats
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b\d{2}[/-]\d{2}[/-]\d{4}\b",
LoggerSettings.Mask);
// Mask Dutch postal codes
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b\d{4}\s?[A-Z]{2}\b",
LoggerSettings.Mask);
// Mask IBAN/Bank account numbers (generic and Dutch specific)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Z]{2}\d{2}[A-Z0-9]{1,30}\b",
LoggerSettings.Mask); // Generic for EU and other country IBANs
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\bNL\d{2}[A-Z]{4}\d{10}\b",
LoggerSettings.Mask); // Dutch IBAN
// Mask JWT Tokens
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Za-z0-9-_]{16,}\.[A-Za-z0-9-_]{16,}\.[A-Za-z0-9-_]{16,}\b",
LoggerSettings.Mask);
// Mask URLs with sensitive query strings
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\bhttps?:\/\/[^\s?]+(\?[^\s]+?[\&=](password|key|token|wachtwoord|sleutel))[^&\s]*",
LoggerSettings.Mask,
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// Mask license keys
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}\b",
LoggerSettings.Mask);
// Mask public and private keys (e.g., PEM format)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"-----BEGIN [A-Z ]+KEY-----[\s\S]+?-----END [A-Z ]+KEY-----",
LoggerSettings.Mask);
// Mask Dutch KVK number (8 or 12 digits)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b\d{8}|\d{12}\b",
LoggerSettings.Mask);
// Mask Dutch BTW-nummer (VAT number)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\bNL\d{9}B\d{2}\b",
LoggerSettings.Mask);
// Mask Dutch driving license number (10-12 characters)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Z0-9]{10,12}\b",
LoggerSettings.Mask);
// Mask Dutch health insurance number (Zorgnummer)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b\d{9}\b",
LoggerSettings.Mask);
// Mask other Dutch Bank Account numbers (9-10 digits)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b\d{9,10}\b",
LoggerSettings.Mask);
// Mask Dutch Passport Numbers (9 alphanumeric characters)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Z0-9]{9}\b",
LoggerSettings.Mask);
// Mask Dutch Identification Document Numbers (varying formats)
message = System.Text.RegularExpressions.Regex.Replace(message,
@"\b[A-Z]{2}\d{6,7}\b",
LoggerSettings.Mask);
}
// Mask custom keywords specified in LoggerSettings
if (LoggerSettings?.MaskedKeywords != null)
{
foreach (var keyword in LoggerSettings.MaskedKeywords)
{
if (string.IsNullOrWhiteSpace(keyword))
{
continue;
}
message = message.Replace(keyword, LoggerSettings.Mask);
}
}
return message;
}
internal string AddMessage(DateTimeOffset timestamp, string message)
{
var result = CreateLoggerMessage(message, timestamp);
_messageQueue.Enqueue(result);
return result.Message;
}
private Task StartAsync()
{
IsStarted = true;
_messageQueue = new ConcurrentQueue<LogMessage>();
_cancellationTokenSource = new CancellationTokenSource();
_outputTask = Task.Factory.StartNew(
ProcessLogQueueAsync,
null,
TaskCreationOptions.LongRunning);
return Task.CompletedTask;
}
private async Task StopAsync()
{
_cancellationTokenSource.Cancel();
try
{
while (_outputTask.Status != TaskStatus.RanToCompletion && _outputTask.Status != TaskStatus.Canceled)
{
await _outputTask.ConfigureAwait(false);
await Task.Delay(100);
}
}
catch (TaskCanceledException)
{
}
catch (AggregateException exception) when (exception.InnerExceptions.Count == 1 &&
exception.InnerExceptions[0] is TaskCanceledException)
{
}
}
~BatchingLoggerProvider()
{
Dispose();
}
}

View File

@@ -32,7 +32,7 @@ public static class ExceptionExtensions
var sb = new StringBuilderChill();
sb.AppendLine();
sb.AppendLine($"--- Exception details provided by {DllInfo.ApplicationName} ---");
sb.AppendLine($"--- Exception details provided by {DllInfo.ApplicationName} on {Environment.MachineName} ---");
if (!string.IsNullOrEmpty(module))
{
sb.AppendLine(" Module : " + module);

View File

@@ -1,14 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using EonaCat.Json;
using EonaCat.Json;
using EonaCat.Logger.Extensions;
using EonaCat.Logger.Servers.GrayLog;
using EonaCat.Logger.Servers.Splunk.Models;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
// 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.
@@ -23,6 +25,34 @@ public class ErrorMessage
public static class LogHelper
{
public sealed class HeaderContext
{
public DateTime Timestamp { get; set; }
public string TimestampFormat { get; set; }
public string HostName { get; set; }
public string Category { get; set; }
public ELogType LogType { get; set; }
public LoggerSettings Settings { 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> OnLogLevelDisabled;
@@ -73,34 +103,71 @@ public static class LogHelper
return syslogMessage;
}
internal static string FormatMessageWithHeader(LoggerSettings settings, ELogType logType, string currentMessage,
DateTime dateTime, string category = null)
private static string ResolveHeader(string format, HeaderContext ctx)
{
if (string.IsNullOrWhiteSpace(currentMessage))
if (string.IsNullOrWhiteSpace(format))
{
return currentMessage;
return string.Empty;
}
category ??= "General";
var sb = new StringBuilder(settings?.HeaderFormat);
var timestamp = dateTime.ToString(settings?.TimestampFormat ?? "yyyy-MM-dd HH:mm:ss");
var timeLabel = settings?.UseLocalTime ?? false ? "[LOCAL]" : "[UTC]";
sb.Replace("{ts}", $"{timestamp} {timeLabel}")
.Replace("{host}", $"[Host:{HostName}]")
.Replace("{category}", $"[Category:{category}]")
.Replace("{thread}", $"[ThreadId:{Environment.CurrentManagedThreadId}]")
.Replace("{sev}", $"[{logType}]");
if (sb.Length > 0)
return Regex.Replace(format, @"\{([^}]+)\}", match =>
{
sb.Append(" ");
}
sb.Append(currentMessage);
return sb.ToString();
var token = match.Groups[1].Value;
var parts = token.Split(new[] { ':' }, 2);
var key = parts[0];
if (!TokenResolvers.TryGetValue(key, out var resolver))
{
return match.Value;
}
if (key.Equals("ts", StringComparison.OrdinalIgnoreCase) && parts.Length == 2)
{
ctx.TimestampFormat = parts[1];
}
return resolver(ctx);
});
}
internal static string FormatMessageWithHeader(
LoggerSettings settings,
ELogType logType,
string message,
DateTime dateTime,
string category = null)
{
if (string.IsNullOrWhiteSpace(message))
{
return message;
}
var ctx = new HeaderContext
{
Timestamp = dateTime,
TimestampFormat = settings?.TimestampFormat ?? "yyyy-MM-dd HH:mm:ss",
HostName = HostName,
Category = category ?? "General",
LogType = logType,
Settings = settings,
EnvironmentName = settings?.EnvironmentName
};
string header = settings?.CustomHeaderFormatter != null
? settings.CustomHeaderFormatter(ctx)
: ResolveHeader(settings?.HeaderFormat, ctx);
if (!string.IsNullOrEmpty(header))
{
header += " ";
}
return header + message;
}
internal static void SendToConsole(LoggerSettings settings, ELogType logType, string message)
{
if (settings == null || string.IsNullOrWhiteSpace(message))

View File

@@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using EonaCat.Json.Linq;
using EonaCat.Json.Linq;
using EonaCat.Logger.EonaCatCoreLogger;
using EonaCat.Logger.EonaCatCoreLogger.Models;
using EonaCat.Logger.Servers.GrayLog;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using static EonaCat.Logger.Managers.LogHelper;
namespace EonaCat.Logger.Managers;
// This file is part of the EonaCat project(s) which is released under the Apache License.
@@ -60,6 +61,10 @@ public class LoggerSettings
}
}
public Func<HeaderContext, string> CustomHeaderFormatter { get; set; }
public string EnvironmentName { get; set; }
/// <summary>
/// Timestamp format.
/// </summary>

View File

@@ -37,7 +37,17 @@ public class Logger
UseLocalTime = UseLocalTime,
},
};
LoggerSettings.CustomHeaderFormatter = ctx =>
{
if (ctx.LogType == ELogType.Error)
{
return $"{ctx.Timestamp:HH:mm:ss} [{ctx.LogType}]";
}
return $"{ctx.Timestamp:HH:mm:ss} [{ctx.LogType}]";
};
_logManager = new LogManager(LoggerSettings);
_logManager.Settings.TypesToLog.Clear();
_logManager.Settings.LogInfo();