Updated
This commit is contained in:
@@ -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.7.6</Version>
|
<Version>1.7.7</Version>
|
||||||
<FileVersion>1.7.6</FileVersion>
|
<FileVersion>1.7.7</FileVersion>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Microsoft.Extensions.Options;
|
|||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -27,12 +28,16 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
private readonly Channel<LogMessage> _channel;
|
private readonly Channel<LogMessage> _channel;
|
||||||
private readonly Thread _writerThread;
|
private readonly Thread _writerThread;
|
||||||
|
|
||||||
private readonly string _filePath;
|
private static readonly TimeSpan FlushInterval = TimeSpan.FromMilliseconds(500);
|
||||||
|
private long _lastFlushTicks = Stopwatch.GetTimestamp();
|
||||||
|
|
||||||
|
private string _filePath;
|
||||||
private readonly int _maxFileSize;
|
private readonly int _maxFileSize;
|
||||||
private readonly bool _encryptionEnabled;
|
private readonly bool _encryptionEnabled;
|
||||||
|
|
||||||
private readonly Aes _aes;
|
private readonly Aes _aes;
|
||||||
private readonly ICryptoTransform _encryptor;
|
private readonly ICryptoTransform _encryptor;
|
||||||
|
public event Action<Exception>? OnError;
|
||||||
|
|
||||||
public bool IncludeCorrelationId { get; }
|
public bool IncludeCorrelationId { get; }
|
||||||
public bool EnableCategoryRouting { get; }
|
public bool EnableCategoryRouting { get; }
|
||||||
@@ -45,6 +50,8 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static StringBuilder? _cachedStringBuilder;
|
private static StringBuilder? _cachedStringBuilder;
|
||||||
|
|
||||||
|
public bool IsHealthy => _running && _stream != null;
|
||||||
|
|
||||||
public string LogFile => _filePath;
|
public string LogFile => _filePath;
|
||||||
private volatile bool _running = true;
|
private volatile bool _running = true;
|
||||||
private readonly int _maxRolloverFiles;
|
private readonly int _maxRolloverFiles;
|
||||||
@@ -56,10 +63,23 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
{
|
{
|
||||||
var o = options.Value;
|
var o = options.Value;
|
||||||
|
|
||||||
_filePath = Path.Combine(o.LogDirectory,
|
string primaryDirectory = o.LogDirectory;
|
||||||
$"{o.FileNamePrefix}_{Environment.MachineName}_{DateTime.UtcNow:yyyyMMdd}.log");
|
string fileName = $"{o.FileNamePrefix}_{Environment.MachineName}_{DateTime.UtcNow:yyyyMMdd}.log";
|
||||||
|
|
||||||
Directory.CreateDirectory(o.LogDirectory);
|
_filePath = Path.Combine(primaryDirectory, fileName);
|
||||||
|
|
||||||
|
if (!TryInitializePath(primaryDirectory, fileName))
|
||||||
|
{
|
||||||
|
// Fallback to temp path with required format: EonaCat_date.log
|
||||||
|
string tempDirectory = Path.GetTempPath();
|
||||||
|
string fallbackFileName = $"EonaCat_{DateTime.UtcNow:yyyyMMdd}.log";
|
||||||
|
|
||||||
|
if (!TryInitializePath(tempDirectory, fallbackFileName))
|
||||||
|
{
|
||||||
|
_running = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_maxFileSize = o.FileSizeLimit;
|
_maxFileSize = o.FileSizeLimit;
|
||||||
_maxRolloverFiles = o.MaxRolloverFiles;
|
_maxRolloverFiles = o.MaxRolloverFiles;
|
||||||
@@ -77,8 +97,18 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
IncludeCorrelationId = o.IncludeCorrelationId;
|
IncludeCorrelationId = o.IncludeCorrelationId;
|
||||||
EnableCategoryRouting = o.EnableCategoryRouting;
|
EnableCategoryRouting = o.EnableCategoryRouting;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
_stream = new FileStream(_filePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, 1, FileOptions.WriteThrough | FileOptions.SequentialScan);
|
_stream = new FileStream(_filePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, 1, FileOptions.WriteThrough | FileOptions.SequentialScan);
|
||||||
_size = _stream.Length;
|
_size = _stream.Length;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
RaiseError(ex);
|
||||||
|
_running = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_buffer = ArrayPool<byte>.Shared.Rent(BufferSize);
|
_buffer = ArrayPool<byte>.Shared.Rent(BufferSize);
|
||||||
|
|
||||||
_channel = Channel.CreateBounded<LogMessage>(
|
_channel = Channel.CreateBounded<LogMessage>(
|
||||||
@@ -98,6 +128,34 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
_writerThread.Start();
|
_writerThread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool TryInitializePath(string directory, string fileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
|
string fullPath = Path.Combine(directory, fileName);
|
||||||
|
|
||||||
|
if (!EnsureWritable(fullPath))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_filePath = fullPath;
|
||||||
|
|
||||||
|
_stream = new FileStream(_filePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, 1, FileOptions.WriteThrough | FileOptions.SequentialScan);
|
||||||
|
_size = _stream.Length;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
RaiseError(ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
internal override Task WriteMessagesAsync(IReadOnlyList<LogMessage> messages, CancellationToken token)
|
internal override Task WriteMessagesAsync(IReadOnlyList<LogMessage> messages, CancellationToken token)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < messages.Count; i++)
|
for (int i = 0; i < messages.Count; i++)
|
||||||
@@ -116,6 +174,11 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
|
|
||||||
private void WriterLoop()
|
private void WriterLoop()
|
||||||
{
|
{
|
||||||
|
if (_stream == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var reader = _channel.Reader;
|
var reader = _channel.Reader;
|
||||||
var spin = new SpinWait();
|
var spin = new SpinWait();
|
||||||
|
|
||||||
@@ -126,7 +189,7 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
WriteMessage(message);
|
WriteMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
FlushIfNeeded();
|
FlushIfNeededTimed();
|
||||||
|
|
||||||
spin.SpinOnce();
|
spin.SpinOnce();
|
||||||
}
|
}
|
||||||
@@ -134,6 +197,33 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
FlushFinal();
|
FlushFinal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void FlushIfNeededTimed()
|
||||||
|
{
|
||||||
|
if (_position == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nowTicks = DateTime.UtcNow.Ticks;
|
||||||
|
|
||||||
|
// Size-based flush (existing behavior)
|
||||||
|
if (_position >= FlushThreshold)
|
||||||
|
{
|
||||||
|
FlushInternal();
|
||||||
|
_lastFlushTicks = nowTicks;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time-based flush (new behavior)
|
||||||
|
if (nowTicks - _lastFlushTicks >= FlushInterval.Ticks)
|
||||||
|
{
|
||||||
|
FlushInternal();
|
||||||
|
_lastFlushTicks = nowTicks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void WriteMessage(LogMessage msg)
|
private void WriteMessage(LogMessage msg)
|
||||||
{
|
{
|
||||||
@@ -268,8 +358,18 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void WriteDirect(byte[] data, int length)
|
private void WriteDirect(byte[] data, int length)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_stream.Write(data, 0, length);
|
_stream.Write(data, 0, length);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
RaiseError(ex);
|
||||||
|
_running = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_size += length;
|
_size += length;
|
||||||
|
|
||||||
if (_maxFileSize > 0 && _size >= _maxFileSize)
|
if (_maxFileSize > 0 && _size >= _maxFileSize)
|
||||||
@@ -294,7 +394,16 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
_stream.Write(_buffer, 0, _position);
|
_stream.Write(_buffer, 0, _position);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
RaiseError(ex);
|
||||||
|
_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
_position = 0;
|
_position = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,6 +416,8 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
private readonly object _rollLock = new();
|
private readonly object _rollLock = new();
|
||||||
|
|
||||||
private void RollFile()
|
private void RollFile()
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
lock (_rollLock)
|
lock (_rollLock)
|
||||||
{
|
{
|
||||||
@@ -327,6 +438,12 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
_size = 0;
|
_size = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
RaiseError(ex);
|
||||||
|
_running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
private void RotateCompressedFiles()
|
private void RotateCompressedFiles()
|
||||||
{
|
{
|
||||||
int maxFiles = _maxRolloverFiles;
|
int maxFiles = _maxRolloverFiles;
|
||||||
@@ -362,6 +479,37 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool EnsureWritable(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string? directory = Path.GetDirectoryName(path);
|
||||||
|
if (string.IsNullOrWhiteSpace(directory))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
|
// Test write access
|
||||||
|
using (var fs = new FileStream(
|
||||||
|
path,
|
||||||
|
FileMode.Append,
|
||||||
|
FileAccess.Write,
|
||||||
|
FileShare.ReadWrite | FileShare.Delete))
|
||||||
|
{
|
||||||
|
// Just opening is enough to validate permission
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
RaiseError(ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void CompressToIndex(string sourceFile, int index)
|
private void CompressToIndex(string sourceFile, int index)
|
||||||
{
|
{
|
||||||
@@ -395,6 +543,18 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RaiseError(Exception ex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OnError?.Invoke(ex);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Never allow logging failure to crash app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public new void Dispose()
|
public new void Dispose()
|
||||||
{
|
{
|
||||||
OnShutdownFlushAsync().GetAwaiter().GetResult();
|
OnShutdownFlushAsync().GetAwaiter().GetResult();
|
||||||
|
|||||||
Reference in New Issue
Block a user