diff --git a/EonaCat.Logger/EonaCat.Logger.csproj b/EonaCat.Logger/EonaCat.Logger.csproj
index e8d9dad..2ff60db 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.6.1
- 1.6.1
+ 1.6.2
+ 1.6.2
README.md
True
LICENSE
@@ -25,7 +25,7 @@
- 1.6.1+{chash:10}.{c:ymd}
+ 1.6.2+{chash:10}.{c:ymd}
true
true
v[0-9]*
diff --git a/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs
index ece1b20..617e440 100644
--- a/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs
+++ b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs
@@ -13,9 +13,6 @@ using Microsoft.Extensions.Options;
namespace EonaCat.Logger.EonaCatCoreLogger;
-// 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.
-
[ProviderAlias("EonaCatFileLogger")]
public sealed class FileLoggerProvider : BatchingLoggerProvider
{
@@ -25,19 +22,20 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
private readonly int _maxRetainedFiles;
private readonly int _maxRolloverFiles;
- private readonly ConcurrentDictionary _buffer = new();
- private readonly SemaphoreSlim _writeLock = new(1, 1);
- private readonly SemaphoreSlim _rolloverLock = new(1, 1);
- private readonly LoggerScopedContext _context = new();
-
private string _logFile;
private long _currentFileSize;
- private int _isWriting;
+
+ private readonly ConcurrentDictionary _buffers = new();
+ private readonly ConcurrentDictionary _fileSizes = new();
+ private readonly SemaphoreSlim _writeLock = new(1, 1);
+ private readonly SemaphoreSlim _rolloverLock = new(1, 1);
+
+ private readonly LoggerScopedContext _context = new();
+
+ public string LogFile => _logFile ?? string.Empty;
public event EventHandler OnError;
- public string LogFile => _logFile;
-
public FileLoggerProvider(IOptions options) : base(options)
{
var o = options.Value;
@@ -47,68 +45,56 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
_maxRetainedFiles = o.RetainedFileCountLimit;
_maxRolloverFiles = o.MaxRolloverFiles;
IncludeCorrelationId = o.IncludeCorrelationId;
+
+ Directory.CreateDirectory(_path);
+ InitializeCurrentFile();
}
public bool IncludeCorrelationId { get; }
- protected override async Task WriteMessagesAsync(
+ internal override async Task WriteMessagesAsync(
IReadOnlyList messages,
CancellationToken token)
{
- if (Interlocked.Exchange(ref _isWriting, 1) == 1)
+ if (messages.Count == 0)
{
return;
}
- try
+ Directory.CreateDirectory(_path);
+
+ // Group messages by date
+ foreach (var group in messages.GroupBy(m => (m.Timestamp.Year, m.Timestamp.Month, m.Timestamp.Day)))
{
- Directory.CreateDirectory(_path);
+ var file = GetFullName(group.Key);
+ InitializeFile(file);
- foreach (var group in messages.GroupBy(GetGrouping))
+ var sb = _buffers.GetOrAdd(file, _ => new StringBuilder(4096));
+
+ lock (sb)
{
- var file = GetFullName(group.Key);
- InitializeFile(file);
-
- var stringBuilder = _buffer.GetOrAdd(file, _ => new StringBuilder(4096));
- lock (stringBuilder)
+ foreach (var message in group)
{
- foreach (var message in group)
- {
- AppendMessage(stringBuilder, message);
- }
+ AppendMessage(sb, message);
}
-
- await FlushAsync(file, stringBuilder, token).ConfigureAwait(false);
- DeleteOldLogFiles();
}
- }
- catch (Exception ex)
- {
- OnError?.Invoke(this, new ErrorMessage
- {
- Exception = ex,
- Message = "Failed to write log file"
- });
- }
- finally
- {
- Interlocked.Exchange(ref _isWriting, 0);
+
+ await FlushAsync(file, sb, token).ConfigureAwait(false);
+ DeleteOldLogFiles();
}
}
private void AppendMessage(StringBuilder sb, LogMessage msg)
{
- // Ensure correlation id exists (once per scope)
+ // Ensure correlation id exists (once per async context)
if (IncludeCorrelationId)
{
var cid = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString();
_context.Set("CorrelationId", cid);
}
- // 1. Append the already-formatted message FIRST
sb.Append(msg.Message);
- // 2. Append context AFTER the message
var ctx = _context.GetAll();
if (ctx.Count > 0)
{
@@ -121,19 +107,26 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
sb.Append(' ');
}
- sb.Append(kv.Key)
- .Append('=')
- .Append(kv.Value);
-
+ sb.Append(kv.Key).Append('=').Append(kv.Value);
first = false;
}
sb.Append(']');
}
- // 3. End the line
sb.AppendLine();
}
+ public void InitializeCurrentFile()
+ {
+ if (!string.IsNullOrEmpty(_logFile))
+ {
+ return;
+ }
+
+ _logFile = GetFullName((DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day));
+ _currentFileSize = File.Exists(_logFile) ? new FileInfo(_logFile).Length : 0;
+ }
+
private async Task FlushAsync(string file, StringBuilder sb, CancellationToken token)
{
if (sb.Length == 0)
@@ -152,14 +145,16 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
64 * 1024,
useAsync: true);
- using var writer = new StreamWriter(fs);
+ using var writer = new StreamWriter(fs, Encoding.UTF8);
var text = sb.ToString();
await writer.WriteAsync(text).ConfigureAwait(false);
- _currentFileSize += Encoding.UTF8.GetByteCount(text);
+ _fileSizes.AddOrUpdate(file, Encoding.UTF8.GetByteCount(text), (_, old) => old + Encoding.UTF8.GetByteCount(text));
+ _currentFileSize = _fileSizes[file];
+
sb.Clear();
- if (_currentFileSize >= _maxFileSize)
+ if (_fileSizes[file] >= _maxFileSize)
{
await RollOverAsync(file).ConfigureAwait(false);
}
@@ -195,8 +190,13 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
}
}
- File.Move(file, Path.Combine(dir, $"{name}.1{ext}"));
- _currentFileSize = 0;
+ var firstRoll = Path.Combine(dir, $"{name}.1{ext}");
+ if (File.Exists(file))
+ {
+ File.Move(file, firstRoll);
+ }
+
+ _fileSizes[file] = 0;
}
finally
{
@@ -206,15 +206,8 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
private void InitializeFile(string file)
{
- if (_logFile == file)
- {
- return;
- }
-
- _logFile = file;
- _currentFileSize = File.Exists(file)
- ? new FileInfo(file).Length
- : 0;
+ _fileSizes.TryAdd(file, File.Exists(file) ? new FileInfo(file).Length : 0);
+ _buffers.TryAdd(file, new StringBuilder(4096));
}
private (int Year, int Month, int Day) GetGrouping(LogMessage m) =>
@@ -225,7 +218,7 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
? Path.Combine(_path, $"{g.Year:0000}{g.Month:00}{g.Day:00}.log")
: Path.Combine(_path, $"{_fileNamePrefix}_{g.Year:0000}{g.Month:00}{g.Day:00}.log");
- protected void DeleteOldLogFiles()
+ private void DeleteOldLogFiles()
{
if (_maxRetainedFiles <= 0)
{
@@ -234,13 +227,24 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
var files = new DirectoryInfo(_path)
.GetFiles($"{_fileNamePrefix}*")
- .Where(f => !f.FullName.Equals(_logFile, StringComparison.OrdinalIgnoreCase))
- .OrderByDescending(f => f.CreationTimeUtc)
+ .OrderByDescending(f =>
+ {
+ // Parse date from filename instead of CreationTimeUtc
+ var name = Path.GetFileNameWithoutExtension(f.Name);
+ var parts = name.Split('_');
+ var datePart = parts.Length > 1 ? parts[1] : parts[0];
+ if (DateTime.TryParseExact(datePart, "yyyyMMdd", null, System.Globalization.DateTimeStyles.None, out var dt))
+ {
+ return dt;
+ }
+
+ return DateTime.MinValue;
+ })
.Skip(_maxRetainedFiles);
foreach (var f in files)
{
- f.Delete();
+ try { f.Delete(); } catch { }
}
}
}
diff --git a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs
index f946a71..6ef69a7 100644
--- a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs
+++ b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs
@@ -1,8 +1,11 @@
-using System;
-using EonaCat.Logger.EonaCatCoreLogger.Models;
+using EonaCat.Logger.EonaCatCoreLogger.Models;
using EonaCat.Logger.Extensions;
using EonaCat.Logger.Managers;
using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
namespace EonaCat.Logger.EonaCatCoreLogger.Internal
{
@@ -26,7 +29,6 @@ namespace EonaCat.Logger.EonaCatCoreLogger.Internal
{
get
{
- // Avoid DateTimeOffset.Now if UseLocalTime is false
return _settings.UseLocalTime ? DateTimeOffset.Now : DateTimeOffset.UtcNow;
}
}
@@ -63,36 +65,52 @@ namespace EonaCat.Logger.EonaCatCoreLogger.Internal
}
var timestamp = Now;
- LogInternal(timestamp, logLevel, rawMessage, exception);
+ LogInternalAsync(timestamp, logLevel, rawMessage, exception).ConfigureAwait(false);
}
- private void LogInternal(
- DateTimeOffset timestamp,
- LogLevel logLevel,
- string message,
- Exception exception)
+ private async Task LogInternalAsync(
+ DateTimeOffset timestamp,
+ LogLevel logLevel,
+ string message,
+ Exception exception)
{
- string formatted = LogHelper.FormatMessageWithHeader(
- _settings,
- logLevel.FromLogLevel(),
- message,
- timestamp.DateTime,
- _category);
-
- var writtenMessage = _provider.AddMessage(timestamp, formatted, _category);
- var onLogEvent = _settings.OnLogEvent;
-
- if (onLogEvent != null)
+ try
{
- onLogEvent(new EonaCatLogMessage
+ string formatted = LogHelper.FormatMessageWithHeader(
+ _settings,
+ logLevel.FromLogLevel(),
+ message,
+ timestamp.DateTime,
+ _category);
+
+ var writtenMessage = _provider.AddMessage(timestamp, formatted, _category);
+ var onLogEvent = _settings.OnLogEvent;
+
+ if (onLogEvent != null)
{
- DateTime = timestamp.DateTime,
- Message = writtenMessage,
- LogType = logLevel.FromLogLevel(),
- Category = _category,
- Exception = exception,
- Origin = string.IsNullOrWhiteSpace(_settings.LogOrigin) ? "BatchingLogger" : _settings.LogOrigin
- });
+ onLogEvent(new EonaCatLogMessage
+ {
+ DateTime = timestamp.DateTime,
+ Message = writtenMessage,
+ LogType = logLevel.FromLogLevel(),
+ Category = _category,
+ Exception = exception,
+ Origin = string.IsNullOrWhiteSpace(_settings.LogOrigin) ? "BatchingLogger" : _settings.LogOrigin
+ });
+ }
+
+ await _provider.WriteMessagesAsync(new List
+ {
+ new LogMessage
+ {
+ Timestamp = timestamp,
+ Message = formatted
+ }
+ }, CancellationToken.None).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Logging error: {ex.Message}");
}
}
diff --git a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs
index ed8f48c..39c4f0b 100644
--- a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs
+++ b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs
@@ -22,17 +22,17 @@ public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
protected BatchingLoggerProvider(IOptions options)
{
- var o = options.Value ?? throw new ArgumentNullException(nameof(options));
+ var currentOptions = options.Value ?? throw new ArgumentNullException(nameof(options));
- if (o.FlushPeriod <= TimeSpan.Zero)
+ if (currentOptions.FlushPeriod <= TimeSpan.Zero)
{
- throw new ArgumentOutOfRangeException(nameof(o.FlushPeriod));
+ throw new ArgumentOutOfRangeException(nameof(currentOptions.FlushPeriod));
}
- _batchSize = o.BatchSize > 0 ? o.BatchSize : 100;
+ _batchSize = currentOptions.BatchSize > 0 ? currentOptions.BatchSize : 100;
_queue = new BlockingCollection(new ConcurrentQueue());
- if (o is FileLoggerOptions file)
+ if (currentOptions is FileLoggerOptions file)
{
UseLocalTime = file.UseLocalTime;
UseMask = file.UseMask;
@@ -72,7 +72,7 @@ public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
public ILogger CreateLogger(string categoryName)
=> new BatchingLogger(this, categoryName, LoggerSettings);
- protected abstract Task WriteMessagesAsync(
+ internal abstract Task WriteMessagesAsync(
IReadOnlyList messages,
CancellationToken token);
@@ -99,7 +99,7 @@ public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
};
}
- private async void ProcessLoop()
+ private async Task ProcessLoop()
{
var batch = new List(_batchSize);
@@ -109,32 +109,45 @@ public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
{
batch.Add(item);
- if (batch.Count < _batchSize)
+ if (batch.Count >= _batchSize)
{
- continue;
+ await FlushBatchAsync(batch);
}
-
- await WriteMessagesAsync(batch, _cts.Token).ConfigureAwait(false);
- batch.Clear();
}
if (batch.Count > 0)
{
- await WriteMessagesAsync(batch, _cts.Token).ConfigureAwait(false);
+ await FlushBatchAsync(batch);
}
}
catch (OperationCanceledException)
{
- // normal shutdown
+ if (batch.Count > 0)
+ {
+ await FlushBatchAsync(batch);
+ }
}
catch (Exception ex)
{
- // last-resort logging
Console.Error.WriteLine(ex);
}
}
-
+ private async Task FlushBatchAsync(List batch)
+ {
+ try
+ {
+ await WriteMessagesAsync(batch, _cts.Token).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred while processing log batches.", ex);
+ }
+ finally
+ {
+ batch.Clear();
+ }
+ }
public void Dispose()
{
diff --git a/EonaCat.Logger/EonaCatCoreLogger/TelegramLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/TelegramLogger.cs
index 05e5ec8..ddd754b 100644
--- a/EonaCat.Logger/EonaCatCoreLogger/TelegramLogger.cs
+++ b/EonaCat.Logger/EonaCatCoreLogger/TelegramLogger.cs
@@ -48,7 +48,10 @@ namespace EonaCat.Logger.EonaCatCoreLogger
public void Log(LogLevel logLevel, EventId eventId, TState state,
Exception exception, Func formatter)
{
- if (!IsEnabled(logLevel) || formatter == null) return;
+ if (!IsEnabled(logLevel) || formatter == null)
+ {
+ return;
+ }
try
{
diff --git a/EonaCat.Logger/Managers/LogManager.cs b/EonaCat.Logger/Managers/LogManager.cs
index 9ef12f7..69568bc 100644
--- a/EonaCat.Logger/Managers/LogManager.cs
+++ b/EonaCat.Logger/Managers/LogManager.cs
@@ -55,9 +55,23 @@ namespace EonaCat.Logger.Managers
public ILogger Logger { get; private set; }
public bool IsRunning { get; private set; }
- public string CurrentLogFile => LoggerProvider is FileLoggerProvider fileLoggerProvider
- ? fileLoggerProvider.LogFile
- : string.Empty;
+ public string CurrentLogFile
+ {
+ get
+ {
+ if (LoggerProvider is FileLoggerProvider fileLoggerProvider)
+ {
+ // Ensure log file is initialized
+ if (string.IsNullOrEmpty(fileLoggerProvider.LogFile))
+ {
+ fileLoggerProvider.InitializeCurrentFile();
+ }
+ return fileLoggerProvider.LogFile;
+ }
+
+ return string.Empty;
+ }
+ }
private DateTime CurrentDateTime => Settings.UseLocalTime ? DateTime.Now : DateTime.UtcNow;
@@ -104,11 +118,17 @@ namespace EonaCat.Logger.Managers
return;
}
+ if (!IsRunning)
+ {
+ await StartNewLogAsync().ConfigureAwait(false);
+ }
+
await InternalWriteAsync(CurrentDateTime, message, logType, writeToConsole,
customSplunkSourceType, grayLogFacility, grayLogSource, grayLogVersion, disableSplunkSSL)
.ConfigureAwait(false);
}
+
public async Task StartNewLogAsync()
{
if (_isDisposing || _tokenSource.IsCancellationRequested)
@@ -121,10 +141,17 @@ namespace EonaCat.Logger.Managers
await StopLoggingAsync().ConfigureAwait(false);
}
- IsRunning = true;
CreateLogger();
+
+ // Ensure log file exists
Directory.CreateDirectory(Settings.FileLoggerOptions.LogDirectory);
+ if (LoggerProvider is FileLoggerProvider fileProvider)
+ {
+ fileProvider.InitializeCurrentFile();
+ }
+
_logDate = CurrentDateTime;
+ IsRunning = true;
}
@@ -291,8 +318,8 @@ namespace EonaCat.Logger.Managers
_tokenSource?.Dispose();
_tokenSource = null;
- LoggerProvider?.Dispose();
- LoggerFactory?.Dispose();
+ try { LoggerProvider?.Dispose(); } catch { }
+ try { LoggerFactory?.Dispose(); } catch { }
}
}
diff --git a/Testers/EonaCat.Logger.Test.Web/Program.cs b/Testers/EonaCat.Logger.Test.Web/Program.cs
index c9c4036..9d2170f 100644
--- a/Testers/EonaCat.Logger.Test.Web/Program.cs
+++ b/Testers/EonaCat.Logger.Test.Web/Program.cs
@@ -33,7 +33,7 @@
PatternDetectionInterval = TimeSpan.FromMinutes(3)
};
- MemoryGuard.Start(_config);
+ //MemoryGuard.Start(_config);
var builder = WebApplication.CreateBuilder(args);
int onLogCounter = 0;
@@ -94,6 +94,7 @@
// Create the adapter
var adapter = new LogCentralEonaCatAdapter(logger.LoggerSettings, logClient);
+ await LogManager.Instance.WriteAsync("LogCentral adapter initialized", ELogType.INFO).ConfigureAwait(false);
// Now all EonaCat.Logger logs will be sent to LogCentral automatically
await logger.LogAsync("This is a test log message sent to LogCentral!", ELogType.INFO).ConfigureAwait(false);