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.6.6</Version>
|
<Version>1.6.7</Version>
|
||||||
<FileVersion>1.6.6</FileVersion>
|
<FileVersion>1.6.7</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.6.6+{chash:10}.{c:ymd}</EVRevisionFormat>
|
<EVRevisionFormat>1.6.7+{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>
|
||||||
|
|||||||
@@ -22,54 +22,32 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
|
|||||||
private readonly int _maxRolloverFiles;
|
private readonly int _maxRolloverFiles;
|
||||||
|
|
||||||
private readonly LoggerScopedContext _context = new LoggerScopedContext();
|
private readonly LoggerScopedContext _context = new LoggerScopedContext();
|
||||||
private readonly Dictionary<string, FileState> _files = new Dictionary<string, FileState>();
|
private readonly ConcurrentDictionary<string, FileState> _files = new ConcurrentDictionary<string, FileState>();
|
||||||
private static readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
|
|
||||||
|
|
||||||
// High-performance buffer pool
|
private const int BufferSize = 256 * 1024;
|
||||||
private static readonly ConcurrentBag<byte[]> _bufferPool = new ConcurrentBag<byte[]>();
|
private static readonly Encoding Utf8 = new UTF8Encoding(false);
|
||||||
private const int BufferSize = 256 * 1024; // 256KB buffers
|
|
||||||
private const int MaxPoolSize = 100;
|
|
||||||
|
|
||||||
// Pre-allocated StringBuilder pool for low memory
|
|
||||||
private static readonly ConcurrentBag<StringBuilder> _stringBuilderPool = new ConcurrentBag<StringBuilder>();
|
|
||||||
private const int MaxStringBuilderPoolSize = 50;
|
|
||||||
|
|
||||||
public bool IncludeCorrelationId { get; }
|
public bool IncludeCorrelationId { get; }
|
||||||
public bool EnableCategoryRouting { get; }
|
public bool EnableCategoryRouting { get; }
|
||||||
|
|
||||||
|
public string LogFile => _files.TryGetValue(string.Empty, out var state) ? state.FilePath : null;
|
||||||
|
|
||||||
public event EventHandler<ErrorMessage> OnError;
|
public event EventHandler<ErrorMessage> OnError;
|
||||||
public event EventHandler<string> OnRollOver;
|
public event EventHandler<string> OnRollOver;
|
||||||
|
|
||||||
public string LogFile
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
FileState state;
|
|
||||||
return _files.TryGetValue(string.Empty, out state) ? state.FilePath : string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class FileState
|
private sealed class FileState
|
||||||
{
|
{
|
||||||
public string FilePath;
|
public string FilePath;
|
||||||
public long Size;
|
public long Size;
|
||||||
public DateTime Date;
|
public DateTime Date;
|
||||||
public readonly SemaphoreSlim WriteLock = new SemaphoreSlim(1, 1);
|
public byte[] Buffer = new byte[BufferSize];
|
||||||
|
|
||||||
// Buffered writing for high throughput
|
|
||||||
public byte[] Buffer;
|
|
||||||
public int BufferPosition;
|
public int BufferPosition;
|
||||||
public FileStream Stream;
|
public FileStream Stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileLoggerProvider(IOptions<FileLoggerOptions> options)
|
public FileLoggerProvider(IOptions<FileLoggerOptions> options) : base(options)
|
||||||
: base(options)
|
|
||||||
{
|
{
|
||||||
var o = options.Value;
|
var o = options.Value ?? throw new ArgumentNullException(nameof(options));
|
||||||
if (o == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("options");
|
|
||||||
}
|
|
||||||
|
|
||||||
_path = o.LogDirectory;
|
_path = o.LogDirectory;
|
||||||
_fileNamePrefix = o.FileNamePrefix;
|
_fileNamePrefix = o.FileNamePrefix;
|
||||||
@@ -81,75 +59,68 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
|
|||||||
|
|
||||||
Directory.CreateDirectory(_path);
|
Directory.CreateDirectory(_path);
|
||||||
|
|
||||||
// Initialize
|
var defaultState = CreateFileState(DateTime.UtcNow.Date, o.Category);
|
||||||
var defaultState = CreateFileState(DateTime.UtcNow.Date, options.Value.Category);
|
|
||||||
_files[string.Empty] = defaultState;
|
_files[string.Empty] = defaultState;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override async Task WriteMessagesAsync(IReadOnlyList<LogMessage> messages, CancellationToken token)
|
internal override Task WriteMessagesAsync(IReadOnlyList<LogMessage> messages, CancellationToken token)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// Group messages by category for batch processing
|
|
||||||
if (EnableCategoryRouting)
|
if (EnableCategoryRouting)
|
||||||
{
|
{
|
||||||
var grouped = messages.GroupBy(m => SanitizeCategory(m.Category));
|
var grouped = messages.GroupBy(m => SanitizeCategory(m.Category));
|
||||||
|
|
||||||
foreach (var group in grouped)
|
foreach (var group in grouped)
|
||||||
{
|
{
|
||||||
var categoryKey = group.Key;
|
var categoryKey = group.Key;
|
||||||
|
|
||||||
FileState state;
|
var state = _files.GetOrAdd(categoryKey,
|
||||||
if (!_files.TryGetValue(categoryKey, out state))
|
_ => CreateFileState(DateTime.UtcNow.Date, categoryKey));
|
||||||
{
|
|
||||||
var date = DateTime.UtcNow.Date;
|
|
||||||
state = CreateFileState(date, categoryKey);
|
|
||||||
_files[categoryKey] = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
await WriteBatchAsync(state, group, categoryKey, token);
|
WriteBatch(state, group, categoryKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var categoryKey = string.Empty;
|
var state = _files.GetOrAdd(string.Empty,
|
||||||
|
_ => CreateFileState(DateTime.UtcNow.Date, string.Empty));
|
||||||
|
|
||||||
FileState state;
|
WriteBatch(state, messages, string.Empty);
|
||||||
if (!_files.TryGetValue(categoryKey, out state))
|
|
||||||
{
|
|
||||||
var date = DateTime.UtcNow.Date;
|
|
||||||
state = CreateFileState(date, categoryKey);
|
|
||||||
_files[categoryKey] = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
await WriteBatchAsync(state, messages, categoryKey, token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteOldLogFiles();
|
DeleteOldLogFiles();
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
OnError?.Invoke(this, new ErrorMessage { Exception = ex, Message = ex.Message });
|
||||||
|
}
|
||||||
|
|
||||||
private async Task WriteBatchAsync(FileState state, IEnumerable<LogMessage> messages, string categoryKey, CancellationToken token)
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteBatch(FileState state, IEnumerable<LogMessage> messages, string categoryKey)
|
||||||
{
|
{
|
||||||
await state.WriteLock.WaitAsync(token);
|
if (!File.Exists(state.FilePath))
|
||||||
try
|
|
||||||
{
|
{
|
||||||
|
RecreateFile(state, categoryKey);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var msg in messages)
|
foreach (var msg in messages)
|
||||||
{
|
{
|
||||||
if (token.IsCancellationRequested)
|
|
||||||
break;
|
|
||||||
|
|
||||||
var date = msg.Timestamp.UtcDateTime.Date;
|
var date = msg.Timestamp.UtcDateTime.Date;
|
||||||
|
|
||||||
// Rotate file by date
|
|
||||||
if (state.Date != date)
|
if (state.Date != date)
|
||||||
{
|
{
|
||||||
await FlushBufferAsync(state);
|
FlushBuffer(state);
|
||||||
RotateByDate(state, date, categoryKey);
|
RotateByDate(state, date, categoryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteMessageToBuffer(state, msg);
|
WriteMessageToBuffer(state, msg);
|
||||||
|
|
||||||
// Flush buffer if nearly full or file size limit reached
|
|
||||||
if (state.BufferPosition >= BufferSize - 1024 || state.Size >= _maxFileSize)
|
if (state.BufferPosition >= BufferSize - 1024 || state.Size >= _maxFileSize)
|
||||||
{
|
{
|
||||||
await FlushBufferAsync(state);
|
FlushBuffer(state);
|
||||||
|
|
||||||
if (state.Size >= _maxFileSize)
|
if (state.Size >= _maxFileSize)
|
||||||
{
|
{
|
||||||
@@ -158,130 +129,69 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final flush for this batch
|
FlushBuffer(state);
|
||||||
await FlushBufferAsync(state);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (OnError != null)
|
|
||||||
{
|
|
||||||
OnError.Invoke(this, new ErrorMessage { Exception = ex, Message = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
state.WriteLock.Release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private FileState CreateFileState(DateTime date, string category)
|
private FileState CreateFileState(DateTime date, string category)
|
||||||
{
|
{
|
||||||
var path = GetFullName(date, category);
|
var path = GetFullName(date, category);
|
||||||
|
|
||||||
var buffer = GetBuffer();
|
return new FileState
|
||||||
var stream = new FileStream(
|
|
||||||
path,
|
|
||||||
FileMode.Append,
|
|
||||||
FileAccess.Write,
|
|
||||||
FileShare.ReadWrite,
|
|
||||||
BufferSize,
|
|
||||||
FileOptions.Asynchronous | FileOptions.SequentialScan);
|
|
||||||
|
|
||||||
var state = new FileState
|
|
||||||
{
|
{
|
||||||
Stream = stream,
|
|
||||||
FilePath = path,
|
FilePath = path,
|
||||||
Size = GetFileSize(path),
|
|
||||||
Date = date,
|
Date = date,
|
||||||
Buffer = buffer,
|
Size = GetFileSize(path),
|
||||||
BufferPosition = 0
|
Stream = OpenFileWithRetry(path)
|
||||||
};
|
};
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] GetBuffer()
|
private static FileStream OpenFileWithRetry(string path)
|
||||||
{
|
{
|
||||||
byte[] buffer;
|
const int retries = 3;
|
||||||
if (!_bufferPool.TryTake(out buffer))
|
|
||||||
{
|
|
||||||
buffer = new byte[BufferSize];
|
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ReturnBuffer(byte[] buffer)
|
for (int i = 0; i < retries; i++)
|
||||||
{
|
|
||||||
if (_bufferPool.Count < MaxPoolSize)
|
|
||||||
{
|
|
||||||
Array.Clear(buffer, 0, buffer.Length);
|
|
||||||
_bufferPool.Add(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static StringBuilder GetStringBuilder()
|
|
||||||
{
|
|
||||||
StringBuilder sb;
|
|
||||||
if (!_stringBuilderPool.TryTake(out sb))
|
|
||||||
{
|
|
||||||
sb = new StringBuilder(512);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.Clear();
|
|
||||||
}
|
|
||||||
return sb;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ReturnStringBuilder(StringBuilder sb)
|
|
||||||
{
|
|
||||||
if (_stringBuilderPool.Count < MaxStringBuilderPoolSize && sb.Capacity <= 2048)
|
|
||||||
{
|
|
||||||
sb.Clear();
|
|
||||||
_stringBuilderPool.Add(sb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long GetFileSize(string path)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return File.Exists(path) ? new FileInfo(path).Length : 0;
|
return new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, 4096, FileOptions.SequentialScan);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return 0;
|
Thread.Sleep(5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new IOException("Unable to open log file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecreateFile(FileState state, string category)
|
||||||
|
{
|
||||||
|
FlushBuffer(state);
|
||||||
|
state.Stream?.Dispose();
|
||||||
|
|
||||||
|
state.FilePath = GetFullName(DateTime.UtcNow.Date, category);
|
||||||
|
state.Size = 0;
|
||||||
|
state.BufferPosition = 0;
|
||||||
|
|
||||||
|
state.Stream = OpenFileWithRetry(state.FilePath);
|
||||||
|
}
|
||||||
|
|
||||||
private void RotateByDate(FileState state, DateTime newDate, string category)
|
private void RotateByDate(FileState state, DateTime newDate, string category)
|
||||||
{
|
{
|
||||||
if (state.Stream != null)
|
state.Stream?.Dispose();
|
||||||
{
|
|
||||||
state.Stream.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Date = newDate;
|
state.Date = newDate;
|
||||||
state.FilePath = GetFullName(newDate, category);
|
state.FilePath = GetFullName(newDate, category);
|
||||||
state.Size = GetFileSize(state.FilePath);
|
state.Size = GetFileSize(state.FilePath);
|
||||||
state.BufferPosition = 0;
|
state.BufferPosition = 0;
|
||||||
|
|
||||||
state.Stream = new FileStream(
|
state.Stream = OpenFileWithRetry(state.FilePath);
|
||||||
state.FilePath,
|
|
||||||
FileMode.Append,
|
|
||||||
FileAccess.Write,
|
|
||||||
FileShare.ReadWrite,
|
|
||||||
BufferSize,
|
|
||||||
FileOptions.Asynchronous | FileOptions.SequentialScan);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RollOver(FileState state, string category)
|
private void RollOver(FileState state, string category)
|
||||||
{
|
{
|
||||||
if (state.Stream != null)
|
FlushBuffer(state);
|
||||||
{
|
state.Stream?.Dispose();
|
||||||
state.Stream.Dispose();
|
|
||||||
state.Stream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dir = Path.GetDirectoryName(state.FilePath);
|
var dir = Path.GetDirectoryName(state.FilePath);
|
||||||
var name = Path.GetFileNameWithoutExtension(state.FilePath);
|
var name = Path.GetFileNameWithoutExtension(state.FilePath);
|
||||||
@@ -289,8 +199,8 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
|
|||||||
|
|
||||||
for (int i = _maxRolloverFiles - 1; i >= 1; i--)
|
for (int i = _maxRolloverFiles - 1; i >= 1; i--)
|
||||||
{
|
{
|
||||||
var src = Path.Combine(dir, string.Format("{0}.{1}{2}", name, i, ext));
|
var src = Path.Combine(dir, $"{name}.{i}{ext}");
|
||||||
var dst = Path.Combine(dir, string.Format("{0}.{1}{2}", name, i + 1, ext));
|
var dst = Path.Combine(dir, $"{name}.{i + 1}{ext}");
|
||||||
|
|
||||||
if (File.Exists(dst))
|
if (File.Exists(dst))
|
||||||
{
|
{
|
||||||
@@ -303,43 +213,50 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var first = Path.Combine(dir, string.Format("{0}.1{1}", name, ext));
|
var first = Path.Combine(dir, $"{name}.1{ext}");
|
||||||
if (File.Exists(state.FilePath))
|
if (File.Exists(state.FilePath))
|
||||||
{
|
{
|
||||||
File.Move(state.FilePath, first);
|
File.Move(state.FilePath, first);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OnRollOver != null)
|
OnRollOver?.Invoke(this, state.FilePath);
|
||||||
{
|
|
||||||
OnRollOver.Invoke(this, state.FilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Size = 0;
|
state.Size = 0;
|
||||||
state.BufferPosition = 0;
|
state.BufferPosition = 0;
|
||||||
|
state.Stream = OpenFileWithRetry(state.FilePath);
|
||||||
state.Stream = new FileStream(
|
|
||||||
state.FilePath,
|
|
||||||
FileMode.Append,
|
|
||||||
FileAccess.Write,
|
|
||||||
FileShare.ReadWrite,
|
|
||||||
BufferSize,
|
|
||||||
FileOptions.Asynchronous | FileOptions.SequentialScan);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteMessageToBuffer(FileState state, LogMessage msg)
|
private void WriteMessageToBuffer(FileState state, LogMessage msg)
|
||||||
{
|
{
|
||||||
var sb = GetStringBuilder();
|
var text = BuildMessage(msg);
|
||||||
|
var byteCount = Utf8.GetByteCount(text);
|
||||||
|
|
||||||
try
|
if (state.BufferPosition + byteCount > BufferSize)
|
||||||
{
|
{
|
||||||
sb.Append(msg.Message);
|
FlushBuffer(state);
|
||||||
|
}
|
||||||
|
|
||||||
if (IncludeCorrelationId)
|
var written = Utf8.GetBytes(text, 0, text.Length, state.Buffer, state.BufferPosition);
|
||||||
|
state.BufferPosition += written;
|
||||||
|
state.Size += written;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildMessage(LogMessage msg)
|
||||||
{
|
{
|
||||||
|
if (!IncludeCorrelationId)
|
||||||
|
{
|
||||||
|
return msg.Message + Environment.NewLine;
|
||||||
|
}
|
||||||
|
|
||||||
var ctx = _context.GetAll();
|
var ctx = _context.GetAll();
|
||||||
if (ctx.Count > 0)
|
if (ctx.Count == 0)
|
||||||
{
|
{
|
||||||
sb.Append(" [");
|
return msg.Message + Environment.NewLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb = new StringBuilder(256);
|
||||||
|
sb.Append(msg.Message).Append(" [");
|
||||||
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
foreach (var kv in ctx)
|
foreach (var kv in ctx)
|
||||||
{
|
{
|
||||||
@@ -351,42 +268,25 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
|
|||||||
sb.Append(kv.Key).Append('=').Append(kv.Value);
|
sb.Append(kv.Key).Append('=').Append(kv.Value);
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
sb.Append(']');
|
|
||||||
}
|
sb.Append(']').AppendLine();
|
||||||
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.AppendLine();
|
private static void FlushBuffer(FileState state)
|
||||||
|
|
||||||
var text = sb.ToString();
|
|
||||||
var byteCount = Encoding.UTF8.GetByteCount(text);
|
|
||||||
|
|
||||||
// If message won't fit in buffer, flush first
|
|
||||||
if (state.BufferPosition + byteCount > BufferSize)
|
|
||||||
{
|
|
||||||
FlushBufferAsync(state).Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write to buffer
|
|
||||||
var written = Encoding.UTF8.GetBytes(text, 0, text.Length, state.Buffer, state.BufferPosition);
|
|
||||||
state.BufferPosition += written;
|
|
||||||
state.Size += written;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ReturnStringBuilder(sb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task FlushBufferAsync(FileState state)
|
|
||||||
{
|
{
|
||||||
if (state.BufferPosition == 0 || state.Stream == null)
|
if (state.BufferPosition == 0 || state.Stream == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await state.Stream.WriteAsync(state.Buffer, 0, state.BufferPosition);
|
state.Stream.Write(state.Buffer, 0, state.BufferPosition);
|
||||||
await state.Stream.FlushAsync();
|
|
||||||
state.BufferPosition = 0;
|
state.BufferPosition = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static long GetFileSize(string path)
|
||||||
|
=> File.Exists(path) ? new FileInfo(path).Length : 0;
|
||||||
|
|
||||||
private void DeleteOldLogFiles()
|
private void DeleteOldLogFiles()
|
||||||
{
|
{
|
||||||
if (_maxRetainedFiles <= 0)
|
if (_maxRetainedFiles <= 0)
|
||||||
@@ -395,18 +295,8 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
var files = new DirectoryInfo(_path)
|
var files = new DirectoryInfo(_path)
|
||||||
.GetFiles(string.Format("{0}*", _fileNamePrefix))
|
.GetFiles($"{_fileNamePrefix}*")
|
||||||
.OrderByDescending(f =>
|
.OrderByDescending(f => f.LastWriteTimeUtc)
|
||||||
{
|
|
||||||
var name = Path.GetFileNameWithoutExtension(f.Name);
|
|
||||||
var parts = name.Split('_');
|
|
||||||
var datePart = parts.LastOrDefault();
|
|
||||||
DateTime dt;
|
|
||||||
return DateTime.TryParseExact(datePart, "yyyyMMdd", null,
|
|
||||||
System.Globalization.DateTimeStyles.None, out dt)
|
|
||||||
? dt
|
|
||||||
: DateTime.MinValue;
|
|
||||||
})
|
|
||||||
.Skip(_maxRetainedFiles);
|
.Skip(_maxRetainedFiles);
|
||||||
|
|
||||||
foreach (var f in files)
|
foreach (var f in files)
|
||||||
@@ -422,16 +312,12 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
|
|||||||
|
|
||||||
if (!EnableCategoryRouting || string.IsNullOrWhiteSpace(category))
|
if (!EnableCategoryRouting || string.IsNullOrWhiteSpace(category))
|
||||||
{
|
{
|
||||||
return string.IsNullOrWhiteSpace(_fileNamePrefix)
|
return Path.Combine(_path, $"{_fileNamePrefix}_{machine}_{datePart}.log");
|
||||||
? Path.Combine(_path, string.Format("{0}_{1}.log", machine, datePart))
|
|
||||||
: Path.Combine(_path, string.Format("{0}_{1}_{2}.log", _fileNamePrefix, machine, datePart));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var safeCategory = SanitizeCategory(category);
|
var safeCategory = SanitizeCategory(category);
|
||||||
|
|
||||||
return string.IsNullOrWhiteSpace(_fileNamePrefix)
|
return Path.Combine(_path, $"{_fileNamePrefix}_{machine}_{safeCategory}_{datePart}.log");
|
||||||
? Path.Combine(_path, string.Format("{0}_{1}_{2}.log", machine, safeCategory, datePart))
|
|
||||||
: Path.Combine(_path, string.Format("{0}_{1}_{2}_{3}.log", _fileNamePrefix, machine, safeCategory, datePart));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string SanitizeCategory(string category)
|
private static string SanitizeCategory(string category)
|
||||||
@@ -446,29 +332,19 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
|
|||||||
|
|
||||||
protected override void OnShutdownFlush()
|
protected override void OnShutdownFlush()
|
||||||
{
|
{
|
||||||
foreach (var kvp in _files)
|
foreach (var state in _files.Values)
|
||||||
{
|
|
||||||
var state = kvp.Value;
|
|
||||||
if (state != null)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
FlushBufferAsync(state).Wait();
|
FlushBuffer(state);
|
||||||
if (state.Stream != null)
|
state.Stream?.Dispose();
|
||||||
{
|
|
||||||
state.Stream.Dispose();
|
|
||||||
}
|
|
||||||
if (state.Buffer != null)
|
|
||||||
{
|
|
||||||
ReturnBuffer(state.Buffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// Ignore errors during shutdown
|
// Do nothing during shutdown flush
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_files.Clear();
|
_files.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var fs = new FileStream(_filePath, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, true))
|
using (var fs = new FileStream(_filePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, 4096, true))
|
||||||
using (var sw = new StreamWriter(fs, Encoding.UTF8))
|
using (var sw = new StreamWriter(fs, Encoding.UTF8))
|
||||||
{
|
{
|
||||||
await sw.WriteAsync(logString).ConfigureAwait(false);
|
await sw.WriteAsync(logString).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -58,19 +58,19 @@ public class Logger
|
|||||||
{
|
{
|
||||||
var logFileName = logName + ".log";
|
var logFileName = logName + ".log";
|
||||||
|
|
||||||
await using var fS = new FileStream(Path.Combine(ConvertToAbsolutePath(LogFolder), logFileName), FileMode.Open,
|
await using var fileStream = new FileStream(Path.Combine(ConvertToAbsolutePath(LogFolder), logFileName), FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, 64 * 1024, true);
|
||||||
FileAccess.Read, FileShare.ReadWrite, 64 * 1024, true);
|
|
||||||
var response = context.Response;
|
var response = context.Response;
|
||||||
|
|
||||||
response.ContentType = "text/plain";
|
response.ContentType = "text/plain";
|
||||||
response.Headers.ContentDisposition = "attachment;filename=" + logFileName;
|
response.Headers.ContentDisposition = "attachment;filename=" + logFileName;
|
||||||
|
|
||||||
if (limit > fS.Length || limit < 1)
|
if (limit > fileStream.Length || limit < 1)
|
||||||
{
|
{
|
||||||
limit = fS.Length;
|
limit = fileStream.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
var oFS = new OffsetStream(fS, 0, limit);
|
var oFS = new OffsetStream(fileStream, 0, limit);
|
||||||
var request = context.Request;
|
var request = context.Request;
|
||||||
Stream stream;
|
Stream stream;
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ public class Logger
|
|||||||
{
|
{
|
||||||
await oFS.CopyToAsync(stream).ConfigureAwait(false);
|
await oFS.CopyToAsync(stream).ConfigureAwait(false);
|
||||||
|
|
||||||
if (fS.Length > limit)
|
if (fileStream.Length > limit)
|
||||||
{
|
{
|
||||||
await stream.WriteAsync("\r\n####___TRUNCATED___####"u8.ToArray()).ConfigureAwait(false);
|
await stream.WriteAsync("\r\n####___TRUNCATED___####"u8.ToArray()).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user