diff --git a/EonaCat.Logger.LogClient/EonaCat.Logger.LogClient.csproj b/EonaCat.Logger.LogClient/EonaCat.Logger.LogClient.csproj
index 3a539d1..5c2640f 100644
--- a/EonaCat.Logger.LogClient/EonaCat.Logger.LogClient.csproj
+++ b/EonaCat.Logger.LogClient/EonaCat.Logger.LogClient.csproj
@@ -25,7 +25,7 @@
-
+
diff --git a/EonaCat.Logger/EonaCat.Logger.csproj b/EonaCat.Logger/EonaCat.Logger.csproj
index e8f6e9d..c905100 100644
--- a/EonaCat.Logger/EonaCat.Logger.csproj
+++ b/EonaCat.Logger/EonaCat.Logger.csproj
@@ -13,8 +13,8 @@
EonaCat (Jeroen Saey)
EonaCat;Logger;EonaCatLogger;Log;Writer;Jeroen;Saey
- 1.5.7
- 1.5.7
+ 1.5.8
+ 1.5.8
README.md
True
LICENSE
@@ -25,7 +25,7 @@
- 1.5.7+{chash:10}.{c:ymd}
+ 1.5.8+{chash:10}.{c:ymd}
true
true
v[0-9]*
diff --git a/EonaCat.Logger/EonaCatCoreLogger/DiscordLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/DiscordLogger.cs
index 9a5f29d..8cac15c 100644
--- a/EonaCat.Logger/EonaCatCoreLogger/DiscordLogger.cs
+++ b/EonaCat.Logger/EonaCatCoreLogger/DiscordLogger.cs
@@ -53,7 +53,9 @@ namespace EonaCat.Logger.EonaCatCoreLogger
Exception exception, Func 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
{
diff --git a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs
index 4d4bfff..5bd70a9 100644
--- a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs
+++ b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs
@@ -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
{
diff --git a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs
index 79fd59d..e370c90 100644
--- a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs
+++ b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs
@@ -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 _currentBatch = new();
- private CancellationTokenSource _cancellationTokenSource;
- private LoggerSettings _loggerSettings;
-
- private ConcurrentQueue _messageQueue;
- private Task _outputTask;
- private object _writeLock = new object();
- private bool _isDisposing;
- protected string Category;
-
- protected BatchingLoggerProvider(IOptions 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 _channel =
+ Channel.CreateUnbounded(new UnboundedChannelOptions
+ {
+ SingleReader = true,
+ SingleWriter = false
+ });
+
+ private CancellationTokenSource _cancellationTokenSource;
+ private Task _outputTask;
+ private bool _isDisposed;
+
+ protected BatchingLoggerProvider(IOptions 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 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 messages, CancellationToken token);
+
+ private async Task ProcessLogQueueAsync()
+ {
+ var batchSize = _batchSize > 0 ? _batchSize : 100;
+ var batch = new List(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 { 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;
+ }
+
+ ///
+ /// Masks sensitive information within the provided message string.
+ /// This method is virtual and can be overridden to customize masking behavior.
+ ///
+ /// The log message potentially containing sensitive information.
+ /// The masked log message.
+ 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 { 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;
- }
-
- ///
- /// Masks sensitive information within the provided message string.
- /// This method is virtual and can be overridden to customize masking behavior.
- ///
- /// The log message potentially containing sensitive information.
- /// The masked log message.
- 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();
- _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();
- }
}
\ No newline at end of file
diff --git a/EonaCat.Logger/Extensions/ExceptionExtensions.cs b/EonaCat.Logger/Extensions/ExceptionExtensions.cs
index 5d67c3f..5364285 100644
--- a/EonaCat.Logger/Extensions/ExceptionExtensions.cs
+++ b/EonaCat.Logger/Extensions/ExceptionExtensions.cs
@@ -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);
diff --git a/EonaCat.Logger/Managers/LogHelper.cs b/EonaCat.Logger/Managers/LogHelper.cs
index f775502..98e2103 100644
--- a/EonaCat.Logger/Managers/LogHelper.cs
+++ b/EonaCat.Logger/Managers/LogHelper.cs
@@ -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> 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 OnException;
internal static event EventHandler 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))
diff --git a/EonaCat.Logger/Managers/LoggerSettings.cs b/EonaCat.Logger/Managers/LoggerSettings.cs
index a27532d..ab8bfee 100644
--- a/EonaCat.Logger/Managers/LoggerSettings.cs
+++ b/EonaCat.Logger/Managers/LoggerSettings.cs
@@ -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 CustomHeaderFormatter { get; set; }
+
+ public string EnvironmentName { get; set; }
+
///
/// Timestamp format.
///
diff --git a/Testers/EonaCat.Logger.Test.Web/Logger.cs b/Testers/EonaCat.Logger.Test.Web/Logger.cs
index b68be30..e95da27 100644
--- a/Testers/EonaCat.Logger.Test.Web/Logger.cs
+++ b/Testers/EonaCat.Logger.Test.Web/Logger.cs
@@ -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();