Skip to content

Instantly share code, notes, and snippets.

@GeorgeTsiokos
Last active April 10, 2023 11:33
Show Gist options
  • Save GeorgeTsiokos/b2443924c7d722859e6e0c3425e9af72 to your computer and use it in GitHub Desktop.
Save GeorgeTsiokos/b2443924c7d722859e6e0c3425e9af72 to your computer and use it in GitHub Desktop.
QuickFix/n daily log files
internal sealed class DailyFileLog : ILog
{
readonly DailyFileLogWriter _eventLog;
readonly DailyFileLogWriter _messageLog;
public DailyFileLog(FileLogWriterOptions eventLog, FileLogWriterOptions messageLog)
{
_eventLog = new DailyFileLogWriter(eventLog);
_messageLog = new DailyFileLogWriter(messageLog);
}
public void Dispose()
{
_eventLog.Dispose();
_messageLog.Dispose();
}
public void Clear()
{
_eventLog.Clear();
_messageLog.Clear();
}
public void OnIncoming(string msg) => _messageLog.Add(msg);
public void OnOutgoing(string msg) => _messageLog.Add(msg);
public void OnEvent(string s) => _eventLog.Add(s);
}
public sealed class DailyFileLogFactory : ILogFactory
{
const int DefaultBufferSize = 16384;
const int DefaultFlushInterval = 1000;
readonly int _eventBufferSize;
readonly int _eventFlushInterval;
readonly int _messagesBufferSize;
readonly int _messagesFlushInterval;
readonly SessionSettings _settings;
readonly TimeZoneInfo _timeZoneInfo;
public DailyFileLogFactory(SessionSettings settings, int messagesBufferSize = DefaultBufferSize, int messagesFlushInterval = DefaultFlushInterval, int eventBufferSize = DefaultBufferSize, int eventFlushInterval = DefaultFlushInterval, TimeZoneInfo timeZoneInfo = null)
{
_settings = settings;
_messagesBufferSize = messagesBufferSize;
_messagesFlushInterval = messagesFlushInterval;
_eventBufferSize = eventBufferSize;
_eventFlushInterval = eventFlushInterval;
_timeZoneInfo = timeZoneInfo ?? TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
}
public ILog Create(SessionID sessionId)
{
var prefix = FileLog.Prefix(sessionId);
var fileLogPath = GetFileLogPath(sessionId);
var encoding = new UTF8Encoding(false, false);
var eventLog = new FileLogWriterOptions(fileLogPath, prefix, "event.current.log", _eventBufferSize, _eventFlushInterval, encoding, _timeZoneInfo);
var messageLog = new FileLogWriterOptions(fileLogPath, prefix, "messages.current.log", _messagesBufferSize, _messagesFlushInterval, encoding, _timeZoneInfo);
return new DailyFileLog(eventLog, messageLog);
}
string GetFileLogPath(SessionID sessionId)
{
var fileLogPath = _settings.Get(sessionId).GetString("FileLogPath");
if (!Directory.Exists(fileLogPath))
Directory.CreateDirectory(fileLogPath);
return fileLogPath;
}
}
internal sealed class DailyFileLogWriter : IDisposable
{
readonly BlockingCollection<(bool Clear, DateTime DateTime, string Msg)> _blockingCollection = new BlockingCollection<(bool Clear, DateTime DateTime, string Msg)>();
readonly FileLogWriterOptions _options;
readonly ManualResetEventSlim _writerCompleteFlag = new ManualResetEventSlim();
int _clearCounter;
public DailyFileLogWriter(FileLogWriterOptions options)
{
_options = options;
Task.Factory.StartNew(Writer, TaskCreationOptions.LongRunning);
}
public void Dispose()
{
if (_blockingCollection.IsAddingCompleted)
{
_writerCompleteFlag.Wait();
return;
}
_blockingCollection.CompleteAdding();
_writerCompleteFlag.Wait();
_blockingCollection.Dispose();
}
public void Add(string value) => _blockingCollection.Add((false, DateTime.UtcNow, value));
void Writer()
{
var dateTime = DateTime.MinValue;
var streamWriter = StreamWriter.Null;
void Reset(DateTime logEntryDateTime)
{
using (streamWriter)
streamWriter.Flush();
dateTime = logEntryDateTime;
}
try
{
try
{
while (!_blockingCollection.IsCompleted)
{
var isValue = _blockingCollection.TryTake(out var value, _options.FlushInterval);
if (!isValue)
{
streamWriter.Flush();
continue;
}
var logEntryDateTime = Convert(value.DateTime);
if (value.Clear)
{
Reset(logEntryDateTime);
streamWriter = CreateStreamWriter(dateTime, Interlocked.Increment(ref _clearCounter) + _options.Suffix);
continue;
}
if (dateTime.Date != logEntryDateTime.Date)
{
Reset(logEntryDateTime);
streamWriter = CreateStreamWriter(dateTime, _options.Suffix);
}
streamWriter.WriteLine(Format(logEntryDateTime, value.Msg));
}
}
catch (OperationCanceledException) { }
finally
{
using (streamWriter)
streamWriter.Flush();
}
}
catch
{
// Add will throw
_blockingCollection.CompleteAdding();
throw;
}
finally
{
_writerCompleteFlag.Set();
}
}
static string Format(DateTime dateTime, string msg) => $"{dateTime:HH:mm:ss.fff} : {msg}";
DateTime Convert(DateTime value) => TimeZoneInfo.ConvertTimeFromUtc(value, _options.ZoneInfo);
StreamWriter CreateStreamWriter(DateTime dateTime, string suffix) => StreamWriterFactory.Create(Path.Combine(_options.FileLogPath, $"{dateTime:yyyyMMdd}.{_options.Prefix}.{suffix}"), _options.Encoding, _options.BufferSize, _options.AutoFlush);
public void Clear() => _blockingCollection.Add((true, DateTime.UtcNow, string.Empty));
}
internal struct FileLogWriterOptions
{
public string FileLogPath { get; }
public string Prefix { get; }
public string Suffix { get; }
public TimeZoneInfo ZoneInfo { get; }
public int FlushInterval { get; }
public int BufferSize { get; }
public bool AutoFlush => FlushInterval < 10;
public Encoding Encoding { get; }
public FileLogWriterOptions(string fileLogPath, string prefix, string suffix, int bufferSize, int flushInterval, Encoding encoding, TimeZoneInfo timeZoneInfo)
{
FileLogPath = fileLogPath;
Prefix = prefix;
Suffix = suffix;
ZoneInfo = timeZoneInfo;
BufferSize = bufferSize;
FlushInterval = flushInterval;
Encoding = encoding;
}
}
// default
builder.RegisterType<FileLogFactory>().As<ILogFactory>().ExternallyOwned();
// daily
builder.RegisterType<DailyFileLogFactory>().As<ILogFactory>().ExternallyOwned();
internal static class StreamWriterFactory
{
static FileStream CreateFileStream(string fileName, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, int bufferSize, FileOptions fileOptions) => new FileStream(fileName, fileMode, fileAccess, fileShare, bufferSize, fileOptions);
public static StreamWriter Create(string fileName, Encoding encoding, int bufferSize, bool autoFlush)
{
var fileStream = CreateFileStream(fileName, FileMode.Append, FileAccess.Write, FileShare.Read, bufferSize, FileOptions.WriteThrough);
return new StreamWriter(fileStream, encoding, bufferSize, false)
{
AutoFlush = autoFlush
};
}
}
@GeorgeTsiokos
Copy link
Author

Method SessionCount Concurrency MessageCount Mean Error StdDev
QuickFix 1 1 999 36.94 ms 0.7098 ms 1.298 ms
Daily 1 1 999 20.39 ms 0.4906 ms 1.408 ms
QuickFix 1 5 999 95.30 ms 1.7894 ms 1.757 ms
Daily 1 5 999 71.11 ms 6.3368 ms 18.585 ms
BenchmarkDotNet=v0.10.9, OS=Windows 8.1 (6.3.9600)
Processor=Intel Xeon CPU E5-2620 0 2.00GHzIntel Xeon CPU E5-2620 0 2.00GHzIntel Xeon CPU E5-2620 0 2.00GHzIntel Xeon CPU E5-2620 0 2.00GHz, ProcessorCount=4
Frequency=14318180 Hz, Resolution=69.8413 ns, Timer=HPET
  [Host]     : .NET Framework 4.7 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.2053.0
  DefaultJob : .NET Framework 4.7 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.2053.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment