using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using log4net.Appender;
using log4net.Core;
using log4net.Layout;
namespace Logging
{
public sealed class AsyncRollingFileAppender : AppenderSkeleton, IFlushable
{
private BlockingCollection<string> _queue;
private Thread _worker;
private CancellationTokenSource _cts;
private FileStream _stream;
private StreamWriter _writer;
private long _currentSize;
private string _fullPath = null;
private string _directory = null;
public string FileName { get; set; }
public int MaxSizeRollBackups { get; set; } = 5;
public long MaximumFileSizeBytes { get; set; } = 20 * 1024 * 1024;
public TimeSpan IdleFlushInterval { get; set; } = TimeSpan.FromSeconds(5);
public int BufferSize { get; set; } = 10000;
public override void ActivateOptions()
{
base.ActivateOptions();
if (Layout == null)
Layout = new PatternLayout("%date %-5level %message%newline");
if (_stream != null)
{
OnClose();
}
_queue = new BlockingCollection<string>(BufferSize);
_cts = new CancellationTokenSource();
_fullPath = ResolveFilePath(FileName);
_directory = Path.GetDirectoryName(_fullPath);
Directory.CreateDirectory(_directory);
OpenFile();
_worker = new Thread(WorkerLoop)
{
IsBackground = true,
Name = "AsyncRollingFileAppender"
};
_worker.Start();
}
protected override void Append(LoggingEvent loggingEvent)
{
loggingEvent.Fix = FixFlags.All;
var rendered = RenderLoggingEvent(loggingEvent);
_queue.TryAdd(rendered);
}
private void WorkerLoop()
{
try
{
while (!_cts.IsCancellationRequested)
{
if (_queue.TryTake(out var line, IdleFlushInterval))
{
WriteInternal(line);
}
else
{
FlushInternal();
}
}
}
catch
{
// logging mag nooit exceptions naar buiten gooien
}
}
private void WriteInternal(string text)
{
var bytes = Encoding.UTF8.GetByteCount(text);
if (_currentSize + bytes > MaximumFileSizeBytes)
RollFile();
_writer.Write(text);
_currentSize += bytes;
}
private void FlushInternal()
{
_writer?.Flush();
_stream?.Flush(true);
}
private static string ResolveFilePath(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
throw new ArgumentException("File name must be set");
// 1. Environment variables (%TEMP%, %APPDATA%, etc.)
var expanded = Environment.ExpandEnvironmentVariables(fileName);
// 2. Absolute path maken (relatief → base directory)
var fullPath = Path.GetFullPath(expanded);
// 3. Directory bepalen
var directory = Path.GetDirectoryName(fullPath);
// 4. Alleen directory aanmaken als die bestaat
if (!string.IsNullOrEmpty(directory))
{
Directory.CreateDirectory(directory);
}
return fullPath;
}
private void OpenFile()
{
var fullPath = ResolveFilePath(FileName);
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
_stream = new FileStream(
fullPath,
FileMode.Append,
FileAccess.Write,
FileShare.Read,
4096,
FileOptions.SequentialScan);
_writer = new StreamWriter(_stream, Encoding.UTF8)
{
AutoFlush = false
};
_currentSize = _stream.Length;
}
private void RollFile()
{
FlushInternal();
_writer.Dispose();
_stream.Dispose();
var baseFileName = Path.GetFileName(_fullPath);
for (int i = MaxSizeRollBackups - 1; i >= 1; i--)
{
var src = Path.Combine(_directory, $"{baseFileName}.{i}");
var dst = Path.Combine(_directory, $"{baseFileName}.{i + 1}");
if (File.Exists(src))
{
if (File.Exists(dst)) File.Delete(dst);
File.Move(src, dst);
}
}
if (MaxSizeRollBackups > 0)
{
var first = Path.Combine(_directory, $"{baseFileName}.1");
if (File.Exists(first)) File.Delete(first);
File.Move(_fullPath, first);
}
OpenFile();
}
protected override void OnClose()
{
Flush(5000);
_cts.Cancel();
_queue.CompleteAdding();
_worker.Join(5000);
_writer?.Dispose();
_stream?.Dispose();
base.OnClose();
}
bool IFlushable.Flush(int millisecondsTimeout)
{
var sw = Stopwatch.StartNew();
while (_queue.Count > 0 && sw.ElapsedMilliseconds < millisecondsTimeout)
{
Thread.Sleep(10);
}
FlushInternal();
return _queue.Count == 0;
}
}
}
1005700cookie-checkC# log4net AsyncRollingFileAppender