From d3d3c9e3e9897a20ebc9b02248b1907a0d47101b Mon Sep 17 00:00:00 2001 From: JK Date: Sun, 24 Nov 2024 17:47:54 -0500 Subject: [PATCH 01/23] Migrate to Microsoft.Extensions.Logging Deprecate old logging system --- AcceptanceTest/ATApplication.cs | 4 +- AcceptanceTest/AcceptanceTest.csproj | 1 + AcceptanceTest/TestBase.cs | 17 ++- Examples/Executor/Examples.Executor.csproj | 4 + Examples/Executor/Program.cs | 8 +- .../Examples.SimpleAcceptor.csproj | 4 + Examples/SimpleAcceptor/Program.cs | 5 +- .../TradeClient/Examples.TradeClient.csproj | 4 + Examples/TradeClient/Program.cs | 7 +- QuickFIXn/AbstractInitiator.cs | 33 +++++- QuickFIXn/AcceptorSocketDescriptor.cs | 6 +- QuickFIXn/ClientHandlerThread.cs | 4 +- QuickFIXn/Logger/FileLogger.cs | 100 ++++++++++++++++++ QuickFIXn/Logger/FileLoggerProvider.cs | 35 ++++++ QuickFIXn/Logger/LogEventIds.cs | 9 ++ QuickFIXn/Logger/LogFactoryAdapter.cs | 74 +++++++++++++ QuickFIXn/Logger/NonSessionFileLogger.cs | 25 +++++ QuickFIXn/Logger/NullLogger.cs | 19 ++++ QuickFIXn/Logger/NullLoggerProvider.cs | 10 ++ QuickFIXn/Logger/ScreenLogger.cs | 43 ++++++++ QuickFIXn/Logger/ScreenLoggerProvider.cs | 55 ++++++++++ QuickFIXn/QuickFix.csproj | 4 + QuickFIXn/Session.cs | 97 +++++++++-------- QuickFIXn/SessionFactory.cs | 11 +- QuickFIXn/SessionState.cs | 11 +- QuickFIXn/SocketInitiatorThread.cs | 8 +- QuickFIXn/SocketReader.cs | 48 +++++---- QuickFIXn/ThreadedSocketAcceptor.cs | 53 ++++++++-- QuickFIXn/ThreadedSocketReactor.cs | 15 ++- QuickFIXn/Transport/SocketInitiator.cs | 29 +++-- QuickFIXn/Transport/SslStreamFactory.cs | 29 +++-- QuickFIXn/Transport/StreamFactory.cs | 6 +- UnitTests/Logger/FileLogTests.cs | 25 ++--- UnitTests/SessionDynamicTest.cs | 8 +- UnitTests/SessionStateTest.cs | 2 +- UnitTests/SessionTest.cs | 10 +- UnitTests/ThreadedSocketAcceptorTests.cs | 3 +- UnitTests/ThreadedSocketReactorTests.cs | 2 +- UnitTests/UnitTests.csproj | 1 + 39 files changed, 665 insertions(+), 164 deletions(-) create mode 100644 QuickFIXn/Logger/FileLogger.cs create mode 100644 QuickFIXn/Logger/FileLoggerProvider.cs create mode 100644 QuickFIXn/Logger/LogEventIds.cs create mode 100644 QuickFIXn/Logger/LogFactoryAdapter.cs create mode 100644 QuickFIXn/Logger/NonSessionFileLogger.cs create mode 100755 QuickFIXn/Logger/NullLogger.cs create mode 100644 QuickFIXn/Logger/NullLoggerProvider.cs create mode 100755 QuickFIXn/Logger/ScreenLogger.cs create mode 100755 QuickFIXn/Logger/ScreenLoggerProvider.cs diff --git a/AcceptanceTest/ATApplication.cs b/AcceptanceTest/ATApplication.cs index 6fff73dd2..7b073597c 100755 --- a/AcceptanceTest/ATApplication.cs +++ b/AcceptanceTest/ATApplication.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Microsoft.Extensions.Logging; using QuickFix; namespace AcceptanceTest @@ -169,7 +170,8 @@ public void FromApp(Message message, SessionID sessionId) } catch (System.Exception e) { - Session.LookupSession(sessionId)?.Log.OnEvent($"Exception during FromApp: {e}\n while processing msg ({message})"); + Session.LookupSession(sessionId)?.Log.Log(LogLevel.Error, e, + "Exception during FromApp: {Error}\n while processing msg ({Message})", e, message); } } diff --git a/AcceptanceTest/AcceptanceTest.csproj b/AcceptanceTest/AcceptanceTest.csproj index 50d1db736..4cfe0ec14 100644 --- a/AcceptanceTest/AcceptanceTest.csproj +++ b/AcceptanceTest/AcceptanceTest.csproj @@ -19,6 +19,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/AcceptanceTest/TestBase.cs b/AcceptanceTest/TestBase.cs index cc9dba3dd..f11da92d0 100644 --- a/AcceptanceTest/TestBase.cs +++ b/AcceptanceTest/TestBase.cs @@ -2,6 +2,7 @@ using QuickFix; using System.IO; using System.Net; +using Microsoft.Extensions.Logging; using QuickFix.Logger; using QuickFix.Store; @@ -23,11 +24,17 @@ public void Setup() var testApp = new ATApplication(); var storeFactory = new MemoryStoreFactory(); - ILogFactory? logFactory = settings.Get().Has("Verbose") && settings.Get().GetBool("Verbose") - ? new FileLogFactory(settings) - : new NullLogFactory(); - - _acceptor = new ThreadedSocketAcceptor(testApp, storeFactory, settings, logFactory); + var loggerFactory = new LoggerFactory(); + if (settings.Get().Has("Verbose") && settings.Get().GetBool("Verbose")) + { + loggerFactory.AddProvider(new FileLoggerProvider(settings)); + } + else + { + loggerFactory.AddProvider(new NullLoggerProvider()); + } + + _acceptor = new ThreadedSocketAcceptor(testApp, storeFactory, settings, loggerFactory); _acceptor.Start(); } diff --git a/Examples/Executor/Examples.Executor.csproj b/Examples/Executor/Examples.Executor.csproj index 0ece262ad..6cf2f7a70 100644 --- a/Examples/Executor/Examples.Executor.csproj +++ b/Examples/Executor/Examples.Executor.csproj @@ -40,4 +40,8 @@ + + + + diff --git a/Examples/Executor/Program.cs b/Examples/Executor/Program.cs index ab7583496..58eab860c 100644 --- a/Examples/Executor/Program.cs +++ b/Examples/Executor/Program.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.Extensions.Logging; using QuickFix; using QuickFix.Logger; using QuickFix.Store; @@ -27,9 +28,10 @@ static void Main(string[] args) SessionSettings settings = new SessionSettings(args[0]); IApplication executorApp = new Executor(); IMessageStoreFactory storeFactory = new FileStoreFactory(settings); - ILogFactory logFactory = new ScreenLogFactory(settings); - //ILogFactory logFactory = new FileLogFactory(settings); - ThreadedSocketAcceptor acceptor = new ThreadedSocketAcceptor(executorApp, storeFactory, settings, logFactory); + var loggerFactory = new LoggerFactory([new ScreenLoggerProvider(settings)]); + // var loggerFactory = new LoggerFactory([new FileLogProvider(settings)]); + ThreadedSocketAcceptor acceptor = + new ThreadedSocketAcceptor(executorApp, storeFactory, settings, loggerFactory); HttpServer srv = new HttpServer(HttpServerPrefix, settings); acceptor.Start(); diff --git a/Examples/SimpleAcceptor/Examples.SimpleAcceptor.csproj b/Examples/SimpleAcceptor/Examples.SimpleAcceptor.csproj index fc954a4b0..bc3ece1f4 100644 --- a/Examples/SimpleAcceptor/Examples.SimpleAcceptor.csproj +++ b/Examples/SimpleAcceptor/Examples.SimpleAcceptor.csproj @@ -34,4 +34,8 @@ + + + + diff --git a/Examples/SimpleAcceptor/Program.cs b/Examples/SimpleAcceptor/Program.cs index 136b637bb..230ec2702 100644 --- a/Examples/SimpleAcceptor/Program.cs +++ b/Examples/SimpleAcceptor/Program.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.Extensions.Logging; using QuickFix; using QuickFix.Logger; using QuickFix.Store; @@ -30,8 +31,8 @@ static void Main(string[] args) SessionSettings settings = new SessionSettings(args[0]); IApplication app = new SimpleAcceptorApp(); IMessageStoreFactory storeFactory = new FileStoreFactory(settings); - ILogFactory logFactory = new FileLogFactory(settings); - IAcceptor acceptor = new ThreadedSocketAcceptor(app, storeFactory, settings, logFactory); + var loggerFactory = new LoggerFactory([new FileLoggerProvider(settings)]); + IAcceptor acceptor = new ThreadedSocketAcceptor(app, storeFactory, settings, loggerFactory); acceptor.Start(); Console.WriteLine("press to quit"); diff --git a/Examples/TradeClient/Examples.TradeClient.csproj b/Examples/TradeClient/Examples.TradeClient.csproj index 738994695..9da5513b0 100644 --- a/Examples/TradeClient/Examples.TradeClient.csproj +++ b/Examples/TradeClient/Examples.TradeClient.csproj @@ -35,4 +35,8 @@ + + + + diff --git a/Examples/TradeClient/Program.cs b/Examples/TradeClient/Program.cs index b40e1913c..44b0db354 100644 --- a/Examples/TradeClient/Program.cs +++ b/Examples/TradeClient/Program.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.Extensions.Logging; using QuickFix.Logger; using QuickFix.Store; @@ -31,9 +32,9 @@ static void Main(string[] args) QuickFix.SessionSettings settings = new QuickFix.SessionSettings(file); TradeClientApp application = new TradeClientApp(); IMessageStoreFactory storeFactory = new FileStoreFactory(settings); - ILogFactory logFactory = new ScreenLogFactory(settings); - //ILogFactory logFactory = new FileLogFactory(settings); - QuickFix.Transport.SocketInitiator initiator = new QuickFix.Transport.SocketInitiator(application, storeFactory, settings, logFactory); + var loggerFactory = new LoggerFactory([new ScreenLoggerProvider(settings)]); + // var loggerFactory = new LoggerFactory([new FileLogProvider(settings)]); + QuickFix.Transport.SocketInitiator initiator = new QuickFix.Transport.SocketInitiator(application, storeFactory, settings, loggerFactory); // this is a developer-test kludge. do not emulate. application.MyInitiator = initiator; diff --git a/QuickFIXn/AbstractInitiator.cs b/QuickFIXn/AbstractInitiator.cs index 7222d43d1..065592315 100644 --- a/QuickFIXn/AbstractInitiator.cs +++ b/QuickFIXn/AbstractInitiator.cs @@ -1,6 +1,8 @@ using System.Threading; using System.Collections.Generic; using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using QuickFix.Logger; using QuickFix.Store; @@ -20,22 +22,43 @@ public abstract class AbstractInitiator : IInitiator private readonly SessionFactory _sessionFactory; private Thread? _thread; - protected readonly NonSessionLog _nonSessionLog; + protected readonly ILogger _nonSessionLog; public bool IsStopped { get; private set; } = true; + [Obsolete] protected AbstractInitiator( IApplication app, IMessageStoreFactory storeFactory, SessionSettings settings, - ILogFactory? logFactoryNullable, - IMessageFactory? messageFactoryNullable) + ILogFactory? logFactoryNullable = null, + IMessageFactory? messageFactoryNullable = null) { _settings = settings; - var logFactory = logFactoryNullable ?? new NullLogFactory(); + ILoggerFactory logFactory = logFactoryNullable is null + ? NullLoggerFactory.Instance + : new LogFactoryAdapter(logFactoryNullable, settings); var msgFactory = messageFactoryNullable ?? new DefaultMessageFactory(); _sessionFactory = new SessionFactory(app, storeFactory, logFactory, msgFactory); - _nonSessionLog = new NonSessionLog(logFactory); + _nonSessionLog = logFactory.CreateLogger("QuickFix"); + + HashSet definedSessions = _settings.GetSessions(); + if (0 == definedSessions.Count) + throw new ConfigError("No sessions defined"); + } + + protected AbstractInitiator( + IApplication app, + IMessageStoreFactory storeFactory, + SessionSettings settings, + ILoggerFactory? logFactoryNullable = null, + IMessageFactory? messageFactoryNullable = null) + { + _settings = settings; + var logFactory = logFactoryNullable ?? new NullLoggerFactory(); + var msgFactory = messageFactoryNullable ?? new DefaultMessageFactory(); + _sessionFactory = new SessionFactory(app, storeFactory, logFactory, msgFactory); + _nonSessionLog = logFactory.CreateLogger("QuickFix"); HashSet definedSessions = _settings.GetSessions(); if (0 == definedSessions.Count) diff --git a/QuickFIXn/AcceptorSocketDescriptor.cs b/QuickFIXn/AcceptorSocketDescriptor.cs index 527ea062b..e7af0da82 100644 --- a/QuickFIXn/AcceptorSocketDescriptor.cs +++ b/QuickFIXn/AcceptorSocketDescriptor.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Net; -using QuickFix.Logger; +using Microsoft.Extensions.Logging; namespace QuickFix { @@ -15,7 +15,7 @@ internal class AcceptorSocketDescriptor public AcceptorSocketDescriptor( IPEndPoint socketEndPoint, SocketSettings socketSettings, - NonSessionLog nonSessionLog) + ILogger nonSessionLog) { Address = socketEndPoint; SocketReactor = new ThreadedSocketReactor(Address, socketSettings, this, nonSessionLog); @@ -26,7 +26,7 @@ public AcceptorSocketDescriptor( IPEndPoint socketEndPoint, SocketSettings socketSettings, SettingsDictionary sessionDict, - NonSessionLog nonSessionLog) : this(socketEndPoint, socketSettings, nonSessionLog) + ILogger nonSessionLog) : this(socketEndPoint, socketSettings, nonSessionLog) { } internal void AcceptSession(Session session) diff --git a/QuickFIXn/ClientHandlerThread.cs b/QuickFIXn/ClientHandlerThread.cs index 323c339cb..3ada2174e 100755 --- a/QuickFIXn/ClientHandlerThread.cs +++ b/QuickFIXn/ClientHandlerThread.cs @@ -1,7 +1,7 @@ using System.Net.Sockets; using System.Threading; using System; -using QuickFix.Logger; +using Microsoft.Extensions.Logging; namespace QuickFix { @@ -36,7 +36,7 @@ internal ClientHandlerThread( long clientId, SocketSettings socketSettings, AcceptorSocketDescriptor? acceptorDescriptor, - NonSessionLog nonSessionLog + ILogger nonSessionLog ) { Id = clientId; _socketReader = new SocketReader(tcpClient, socketSettings, this, acceptorDescriptor, nonSessionLog); diff --git a/QuickFIXn/Logger/FileLogger.cs b/QuickFIXn/Logger/FileLogger.cs new file mode 100644 index 000000000..017c99e16 --- /dev/null +++ b/QuickFIXn/Logger/FileLogger.cs @@ -0,0 +1,100 @@ +using System; +using Microsoft.Extensions.Logging; +using QuickFix.Fields.Converters; +using QuickFix.Util; + +namespace QuickFix.Logger; + +public class FileLogger : ILogger, IDisposable +{ + private readonly object _messagesLock = new(); + private readonly object _eventsLock = new(); + + private System.IO.StreamWriter _messageLog; + private System.IO.StreamWriter _eventLog; + + private readonly string _messageLogFileName; + private readonly string _eventLogFileName; + + /// + /// + /// + /// + /// All back or forward slashes in this path will be converted as needed to the running platform's preferred + /// path separator (i.e. "/" will become "\" on windows, else "\" will become "/" on all other platforms) + /// + /// + internal FileLogger(string fileLogPath, SessionID sessionId) + { + string prefix = Prefix(sessionId); + + string normalizedPath = StringUtil.FixSlashes(fileLogPath); + + if (!System.IO.Directory.Exists(normalizedPath)) + System.IO.Directory.CreateDirectory(normalizedPath); + + _messageLogFileName = System.IO.Path.Combine(normalizedPath, prefix + ".messages.current.log"); + _eventLogFileName = System.IO.Path.Combine(normalizedPath, prefix + ".event.current.log"); + + _messageLog = new System.IO.StreamWriter(_messageLogFileName, true); + _eventLog = new System.IO.StreamWriter(_eventLogFileName, true); + + _messageLog.AutoFlush = true; + _eventLog.AutoFlush = true; + } + + public static string Prefix(SessionID sessionId) + { + System.Text.StringBuilder prefix = new System.Text.StringBuilder(sessionId.BeginString) + .Append('-').Append(sessionId.SenderCompID); + if (SessionID.IsSet(sessionId.SenderSubID)) + prefix.Append('_').Append(sessionId.SenderSubID); + if (SessionID.IsSet(sessionId.SenderLocationID)) + prefix.Append('_').Append(sessionId.SenderLocationID); + prefix.Append('-').Append(sessionId.TargetCompID); + if (SessionID.IsSet(sessionId.TargetSubID)) + prefix.Append('_').Append(sessionId.TargetSubID); + if (SessionID.IsSet(sessionId.TargetLocationID)) + prefix.Append('_').Append(sessionId.TargetLocationID); + + if (SessionID.IsSet(sessionId.SessionQualifier)) + prefix.Append('-').Append(sessionId.SessionQualifier); + + return prefix.ToString(); + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, + Func formatter) + { + if (!IsEnabled(logLevel)) return; + if (eventId == LogEventIds.IncomingMessage || eventId == LogEventIds.OutgoingMessage) + { + lock (_messagesLock) + { + _messageLog.WriteLine( + $"{DateTimeConverter.ToFIX(DateTime.UtcNow, TimeStampPrecision.Millisecond)} : {formatter(state, exception)}"); + } + } + else + { + lock (_eventsLock) + { + _eventLog.WriteLine( + $"{DateTimeConverter.ToFIX(DateTime.UtcNow, TimeStampPrecision.Millisecond)} : {formatter(state, exception)}"); + } + } + } + + public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; + + public IDisposable? BeginScope(TState state) where TState : notnull + { + throw new NotImplementedException(); + } + + public void Dispose() + { + _messageLog.Dispose(); + _eventLog.Dispose(); + } +} \ No newline at end of file diff --git a/QuickFIXn/Logger/FileLoggerProvider.cs b/QuickFIXn/Logger/FileLoggerProvider.cs new file mode 100644 index 000000000..c471a8e15 --- /dev/null +++ b/QuickFIXn/Logger/FileLoggerProvider.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using Microsoft.Extensions.Logging; + +namespace QuickFix.Logger; + +public class FileLoggerProvider : ILoggerProvider +{ + private readonly SessionSettings _settings; + private readonly ConcurrentDictionary _loggers = new(); + + public FileLoggerProvider(SessionSettings settings) + { + _settings = settings; + } + + public ILogger CreateLogger(string categoryName) + { + // category will be "QuickFix" for non-session logger and "QuickFix." for session logger + var sessionIdString = categoryName.Length > "QuickFix.".Length ? categoryName.Substring(9) : ""; + var sessionId = _settings.GetSessions() + .SingleOrDefault(s => + s.ToString().Equals(sessionIdString, StringComparison.InvariantCulture)); + var defaultSessionId = new SessionID("Non", "Session", "Log"); + + return _loggers.GetOrAdd(sessionId ?? defaultSessionId, sessionId is not null + ? new FileLogger(_settings.Get(sessionId).GetString(SessionSettings.FILE_LOG_PATH), sessionId) + : new NonSessionFileLogger(_settings.Get().GetString(SessionSettings.FILE_LOG_PATH), defaultSessionId)); + } + + public void Dispose() + { + } +} \ No newline at end of file diff --git a/QuickFIXn/Logger/LogEventIds.cs b/QuickFIXn/Logger/LogEventIds.cs new file mode 100644 index 000000000..bc845c1c7 --- /dev/null +++ b/QuickFIXn/Logger/LogEventIds.cs @@ -0,0 +1,9 @@ +using Microsoft.Extensions.Logging; + +namespace QuickFix.Logger; + +internal static class LogEventIds +{ + internal static readonly EventId IncomingMessage = 7702; + internal static readonly EventId OutgoingMessage = 7203; +} \ No newline at end of file diff --git a/QuickFIXn/Logger/LogFactoryAdapter.cs b/QuickFIXn/Logger/LogFactoryAdapter.cs new file mode 100644 index 000000000..af54e106d --- /dev/null +++ b/QuickFIXn/Logger/LogFactoryAdapter.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using Microsoft.Extensions.Logging; + +namespace QuickFix.Logger; + +internal class LogFactoryAdapter : ILoggerFactory +{ + private readonly ILogFactory _logFactory; + private readonly SessionSettings _settings; + private readonly ConcurrentDictionary _loggers = new(); + + internal LogFactoryAdapter(ILogFactory logFactory, SessionSettings settings) + { + _logFactory = logFactory; + _settings = settings; + } + + public ILogger CreateLogger(string categoryName) + { + // category will be "QuickFix" for non-session logger and "QuickFix." for session logger + var sessionIdString = categoryName.Length > "QuickFix.".Length ? categoryName.Substring(9) : ""; + var sessionId = _settings.GetSessions() + .SingleOrDefault(s => + s.ToString().Equals(sessionIdString, StringComparison.InvariantCulture)); + var defaultSessionId = new SessionID("Non", "Session", "Log"); + + return _loggers.GetOrAdd(sessionId ?? defaultSessionId, sessionId is not null + ? new LogAdapter(_logFactory.Create(sessionId)) + : new LogAdapter(_logFactory.CreateNonSessionLog())); + } + + public void AddProvider(ILoggerProvider provider) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + } +} + +internal class LogAdapter : ILogger +{ + private readonly ILog _log; + + public LogAdapter(ILog log) + { + _log = log; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, + Func formatter) + { + if (!IsEnabled(logLevel)) return; + if (eventId == LogEventIds.IncomingMessage) + { + _log.OnIncoming(formatter(state, exception)); + } + else if (eventId == LogEventIds.OutgoingMessage) + { + _log.OnOutgoing(formatter(state, exception)); + } + else + { + _log.OnEvent(formatter(state, exception)); + } + } + + public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; + + public IDisposable? BeginScope(TState state) where TState : notnull => default!; +} \ No newline at end of file diff --git a/QuickFIXn/Logger/NonSessionFileLogger.cs b/QuickFIXn/Logger/NonSessionFileLogger.cs new file mode 100644 index 000000000..3caf09384 --- /dev/null +++ b/QuickFIXn/Logger/NonSessionFileLogger.cs @@ -0,0 +1,25 @@ +using System; +using Microsoft.Extensions.Logging; + +namespace QuickFix.Logger; + +/// +/// Like the file logger, but only creates the files on first write +/// +internal class NonSessionFileLogger : ILogger +{ + private readonly Lazy _fileLog; + + internal NonSessionFileLogger(string fileLogPath, SessionID sessionId) + { + _fileLog = new Lazy(() => new FileLogger(fileLogPath, sessionId)); + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, + Func formatter) => + _fileLog.Value.Log(logLevel, eventId, state, exception, formatter); + + public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; + + public IDisposable? BeginScope(TState state) where TState : notnull => _fileLog.Value.BeginScope(state); +} \ No newline at end of file diff --git a/QuickFIXn/Logger/NullLogger.cs b/QuickFIXn/Logger/NullLogger.cs new file mode 100755 index 000000000..c1a025e38 --- /dev/null +++ b/QuickFIXn/Logger/NullLogger.cs @@ -0,0 +1,19 @@ +using System; +using Microsoft.Extensions.Logging; + +namespace QuickFix.Logger; + +/// +/// Log implementation that does not do anything +/// +public sealed class NullLogger : ILogger +{ + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, + Func formatter) + { + } + + public bool IsEnabled(LogLevel logLevel) => false; + + public IDisposable? BeginScope(TState state) where TState : notnull => default!; +} \ No newline at end of file diff --git a/QuickFIXn/Logger/NullLoggerProvider.cs b/QuickFIXn/Logger/NullLoggerProvider.cs new file mode 100644 index 000000000..2b311ac96 --- /dev/null +++ b/QuickFIXn/Logger/NullLoggerProvider.cs @@ -0,0 +1,10 @@ +using Microsoft.Extensions.Logging; + +namespace QuickFix.Logger; + +public class NullLoggerProvider : ILoggerProvider +{ + public void Dispose(){} + + public ILogger CreateLogger(string categoryName) => new NullLogger(); +} \ No newline at end of file diff --git a/QuickFIXn/Logger/ScreenLogger.cs b/QuickFIXn/Logger/ScreenLogger.cs new file mode 100755 index 000000000..28b67a8da --- /dev/null +++ b/QuickFIXn/Logger/ScreenLogger.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.Extensions.Logging; + +namespace QuickFix.Logger; + +/// +/// FIXME - needs to log sessionIDs, timestamps, etc. +/// +public class ScreenLogger : ILogger +{ + private readonly bool _logIncoming; + private readonly bool _logOutgoing; + private readonly bool _logEvent; + + public ScreenLogger(bool logIncoming, bool logOutgoing, bool logEvent) + { + _logIncoming = logIncoming; + _logOutgoing = logOutgoing; + _logEvent = logEvent; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, + Func formatter) + { + if (!IsEnabled(logLevel)) return; + if (eventId == LogEventIds.IncomingMessage && _logIncoming) + { + Console.WriteLine($" {formatter(state, exception).Replace(Message.SOH, '|')}"); + } + else if (eventId == LogEventIds.OutgoingMessage && _logOutgoing) + { + Console.WriteLine($" {formatter(state, exception).Replace(Message.SOH, '|')}"); + } + else if (_logEvent) + { + Console.WriteLine($" {formatter(state, exception)}"); + } + } + + public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; + + public IDisposable? BeginScope(TState state) where TState : notnull => default!; +} \ No newline at end of file diff --git a/QuickFIXn/Logger/ScreenLoggerProvider.cs b/QuickFIXn/Logger/ScreenLoggerProvider.cs new file mode 100755 index 000000000..536fcf6ef --- /dev/null +++ b/QuickFIXn/Logger/ScreenLoggerProvider.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using Microsoft.Extensions.Logging; + +namespace QuickFix.Logger; + +public class ScreenLoggerProvider : ILoggerProvider +{ + private const string SCREEN_LOG_SHOW_INCOMING = "ScreenLogShowIncoming"; + private const string SCREEN_LOG_SHOW_OUTGOING = "ScreenLogShowOutgoing"; + private const string SCREEN_LOG_SHOW_EVENTS = "ScreenLogShowEvents"; + private readonly bool _logIncoming = false; + private readonly bool _logOutgoing = false; + private readonly bool _logEvent = false; + private readonly SessionSettings _settings; + + public ScreenLoggerProvider(SessionSettings settings) + { + _settings = settings; + } + + public ScreenLoggerProvider(bool logIncoming, bool logOutgoing, bool logEvent) + { + _logIncoming = logIncoming; + _logOutgoing = logOutgoing; + _logEvent = logEvent; + _settings = new SessionSettings(); + } + + public ILogger CreateLogger(string categoryName) + { + // category will be "QuickFix" for non-session logger and "QuickFix." for session logger + var sessionIdString = categoryName.Length > "QuickFix.".Length ? categoryName.Substring(9) : ""; + bool logIncoming = _logIncoming; + bool logOutgoing = _logOutgoing; + bool logEvent = _logEvent; + + var sessionId = _settings.GetSessions() + .SingleOrDefault(s => s.ToString().Equals(sessionIdString, StringComparison.InvariantCulture)); + if (sessionId is not null) + { + SettingsDictionary dict = _settings.Get(sessionId); + + logIncoming = _logIncoming || dict.IsBoolPresentAndTrue(SCREEN_LOG_SHOW_INCOMING); + logOutgoing = _logOutgoing || dict.IsBoolPresentAndTrue(SCREEN_LOG_SHOW_OUTGOING); + logEvent = _logEvent || dict.IsBoolPresentAndTrue(SCREEN_LOG_SHOW_EVENTS); + } + + return new ScreenLogger(logIncoming, logOutgoing, logEvent); + } + + public void Dispose() + { + } +} \ No newline at end of file diff --git a/QuickFIXn/QuickFix.csproj b/QuickFIXn/QuickFix.csproj index 8e1f2bd7f..e6a1c9325 100644 --- a/QuickFIXn/QuickFix.csproj +++ b/QuickFIXn/QuickFix.csproj @@ -43,4 +43,8 @@ <_Parameter1>UnitTests + + + + diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index d4e0c1a85..f61c007aa 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading; +using Microsoft.Extensions.Logging; using QuickFix.Fields; using QuickFix.Fields.Converters; using QuickFix.Logger; @@ -36,7 +37,7 @@ public class Session : IDisposable // state public IMessageStore MessageStore => _state.MessageStore; - public ILog Log => _state.Log; + public ILogger Log => _state.Log; public bool IsInitiator => _state.IsInitiator; public bool IsAcceptor => !_state.IsInitiator; public bool IsEnabled => _state.IsEnabled; @@ -225,7 +226,7 @@ public Session( DataDictionaryProvider dataDictProvider, SessionSchedule sessionSchedule, int heartBtInt, - ILogFactory logFactory, + ILoggerFactory loggerFactory, IMessageFactory msgFactory, string senderDefaultApplVerId) { @@ -243,9 +244,9 @@ public Session( ? DataDictionaryProvider.GetApplicationDataDictionary(SenderDefaultApplVerID) : SessionDataDictionary; - ILog log = logFactory.Create(sessId); + var logger = loggerFactory.CreateLogger($"QuickFix.{sessId}"); - _state = new SessionState(isInitiator, log, heartBtInt, storeFactory.Create(sessId)); + _state = new SessionState(isInitiator, logger, heartBtInt, storeFactory.Create(sessId)); // Configuration defaults. // Will be overridden by the SessionFactory with values in the user's configuration. @@ -275,7 +276,7 @@ public Session( } Application.OnCreate(SessionID); - Log.OnEvent("Created session"); + Log.Log(LogLevel.Debug, "Created session"); } #region Static Methods @@ -355,7 +356,7 @@ public bool Send(string message) { if (_responder is null) return false; - Log.OnOutgoing(message); + Log.Log(LogLevel.Information, LogEventIds.OutgoingMessage, "{Message}", message); return _responder.Send(message); } } @@ -389,13 +390,13 @@ public void Disconnect(string reason) { if (_responder is not null) { - Log.OnEvent($"Session {SessionID} disconnecting: {reason}"); + Log.Log(LogLevel.Debug, "Session {SessionID} disconnecting: {Reason}", SessionID, reason); _responder.Disconnect(); _responder = null; } else { - Log.OnEvent($"Session {SessionID} already disconnected: {reason}"); + Log.Log(LogLevel.Debug, "Session {SessionID} already disconnected: {Reason}", SessionID, reason); } if (_state.ReceivedLogon || _state.SentLogon) @@ -444,7 +445,7 @@ public void Next() if (!_state.SentLogout) { - Log.OnEvent("Initiated logout request"); + Log.Log(LogLevel.Debug, "Initiated logout request"); GenerateLogout(_state.LogoutReason); } } @@ -454,9 +455,9 @@ public void Next() if (_state.ShouldSendLogon && IsTimeToGenerateLogon()) { if (GenerateLogon()) - Log.OnEvent("Initiated logon request"); + Log.Log(LogLevel.Debug, "Initiated logon request"); else - Log.OnEvent("Error during logon request initiation"); + Log.Log(LogLevel.Error, "Error during logon request initiation"); } else if (_state.SentLogon && _state.LogonTimedOut()) @@ -487,7 +488,7 @@ public void Next() { GenerateTestRequest("TEST"); _state.TestRequestCounter += 1; - Log.OnEvent("Sent test request TEST"); + Log.Log(LogLevel.Debug, "Sent test request TEST"); } else if (_state.NeedHeartbeat()) { @@ -512,7 +513,7 @@ public void Next(string msgStr) /// private void NextMessage(string msgStr) { - Log.OnIncoming(msgStr); + Log.Log(LogLevel.Information, LogEventIds.IncomingMessage, "{Message}", msgStr); MessageBuilder msgBuilder = new MessageBuilder( msgStr, @@ -595,7 +596,7 @@ internal void Next(MessageBuilder msgBuilder) } catch (InvalidMessage e) { - Log.OnEvent(e.Message); + Log.Log(LogLevel.Information, "{Message}", e.Message); try { @@ -610,7 +611,7 @@ internal void Next(MessageBuilder msgBuilder) catch (TagException e) { if (e.InnerException is not null) - Log.OnEvent(e.InnerException.Message); + Log.Log(LogLevel.Error, "{Message}", e.InnerException.Message); GenerateReject(msgBuilder, e.sessionRejectReason, e.Field); } catch (UnsupportedVersion uvx) @@ -621,19 +622,19 @@ internal void Next(MessageBuilder msgBuilder) } else { - Log.OnEvent(uvx.ToString()); + Log.Log(LogLevel.Error, uvx, "{Message}", uvx.ToString()); GenerateLogout(uvx.Message); _state.IncrNextTargetMsgSeqNum(); } } catch (UnsupportedMessageType e) { - Log.OnEvent("Unsupported message type: " + e.Message); + Log.Log(LogLevel.Error, e, "Unsupported message type: {Message}", e.Message); GenerateBusinessMessageReject(message!, Fields.BusinessRejectReason.UNKNOWN_MESSAGE_TYPE, 0); } catch (FieldNotFoundException e) { - Log.OnEvent("Rejecting invalid message, field not found: " + e.Message); + Log.Log(LogLevel.Information, e, "Rejecting invalid message, field not found: {Message}", e.Message); if (string.CompareOrdinal(SessionID.BeginString, FixValues.BeginString.FIX42) >= 0 && message!.IsApp()) { GenerateBusinessMessageReject(message, Fields.BusinessRejectReason.CONDITIONALLY_REQUIRED_FIELD_MISSING, e.Field); @@ -642,7 +643,7 @@ internal void Next(MessageBuilder msgBuilder) { if (MsgType.LOGON.Equals(msgBuilder.MsgType.Value)) { - Log.OnEvent("Required field missing from logon"); + Log.Log(LogLevel.Warning, "Required field missing from logon"); Disconnect("Required field missing from logon"); } else @@ -664,7 +665,7 @@ protected void NextLogon(Message logon) if (_state.ReceivedReset) { - Log.OnEvent("Sequence numbers reset due to ResetSeqNumFlag=Y"); + Log.Log(LogLevel.Warning, "Sequence numbers reset due to ResetSeqNumFlag=Y"); if (!_state.SentReset) { _state.Reset("Reset requested by counterparty"); @@ -681,19 +682,19 @@ protected void NextLogon(Message logon) if (!IsGoodTime(logon)) { - Log.OnEvent("Logon has bad sending time"); + Log.Log(LogLevel.Error, "Logon has bad sending time"); Disconnect("bad sending time"); return; } _state.ReceivedLogon = true; - Log.OnEvent("Received logon"); + Log.Log(LogLevel.Warning, "Received logon"); if (IsAcceptor) { int heartBtInt = logon.GetInt(Fields.Tags.HeartBtInt); _state.HeartBtInt = heartBtInt; GenerateLogon(logon); - Log.OnEvent($"Responding to logon request; heartbeat is {heartBtInt} seconds"); + Log.Log(LogLevel.Warning, $"Responding to logon request; heartbeat is {heartBtInt} seconds"); } _state.SentReset = false; @@ -731,7 +732,7 @@ protected void NextResendRequest(Message resendReq) { SeqNumType begSeqNo = resendReq.GetULong(Fields.Tags.BeginSeqNo); SeqNumType endSeqNo = resendReq.GetULong(Fields.Tags.EndSeqNo); - Log.OnEvent("Got resend request from " + begSeqNo + " to " + endSeqNo); + Log.Log(LogLevel.Information, "Got resend request from {BeginSeqNo} to {EndSeqNo}", begSeqNo, endSeqNo); if (endSeqNo == 999999 || endSeqNo == 0) { @@ -819,7 +820,7 @@ protected void NextResendRequest(Message resendReq) } catch (Exception e) { - Log.OnEvent("ERROR during resend request " + e.Message); + Log.Log(LogLevel.Error, e, "ERROR during resend request {Message}", e.Message); } } private bool ResendApproved(Message msg, SessionID sessionId) @@ -846,14 +847,14 @@ protected void NextLogout(Message logout) if (!_state.SentLogout) { disconnectReason = "Received logout request"; - Log.OnEvent(disconnectReason); + Log.Log(LogLevel.Debug, "{Message}", disconnectReason); GenerateLogout(logout); - Log.OnEvent("Sending logout response"); + Log.Log(LogLevel.Debug, "Sending logout response"); } else { disconnectReason = "Received logout response"; - Log.OnEvent(disconnectReason); + Log.Log(LogLevel.Debug, "{Message}", disconnectReason); } _state.IncrNextTargetMsgSeqNum(); @@ -881,7 +882,7 @@ protected void NextSequenceReset(Message sequenceReset) if (sequenceReset.IsSetField(Fields.Tags.NewSeqNo)) { SeqNumType newSeqNo = sequenceReset.GetULong(Fields.Tags.NewSeqNo); - Log.OnEvent("Received SequenceReset FROM: " + _state.NextTargetMsgSeqNum + " TO: " + newSeqNo); + Log.Log(LogLevel.Debug, "Received SequenceRequest FROM: {NextTargetMsgSeqNum} TO: {NewSeqNo}", _state.NextTargetMsgSeqNum, newSeqNo); if (newSeqNo > _state.NextTargetMsgSeqNum) { @@ -932,12 +933,14 @@ public bool Verify(Message msg, bool checkTooHigh = true, bool checkTooLow = tru ResendRange range = _state.GetResendRange(); if (msgSeqNum >= range.EndSeqNo) { - Log.OnEvent("ResendRequest for messages FROM: " + range.BeginSeqNo + " TO: " + range.EndSeqNo + " has been satisfied."); + Log.Log(LogLevel.Debug, "ResendRequest for messages FROM: {BeginSeqNo} TO: {EndSeqNo} has been satisfied.", range.BeginSeqNo, range.EndSeqNo); _state.SetResendRange(0, 0); } else if (msgSeqNum >= range.ChunkEndSeqNo) { - Log.OnEvent("Chunked ResendRequest for messages FROM: " + range.BeginSeqNo + " TO: " + range.ChunkEndSeqNo + " has been satisfied."); + Log.Log(LogLevel.Warning, + "Chunked ResendRequest for messages FROM: {BeginSeqNo} TO: {EndSeqNo} has been satisfied.", + range.BeginSeqNo, range.ChunkEndSeqNo); SeqNumType newChunkEndSeqNo = Math.Min(range.EndSeqNo, range.ChunkEndSeqNo + MaxMessagesInResendRequest); GenerateResendRequestRange(msg.Header.GetString(Fields.Tags.BeginString), range.ChunkEndSeqNo + 1, newChunkEndSeqNo); range.ChunkEndSeqNo = newChunkEndSeqNo; @@ -946,7 +949,7 @@ public bool Verify(Message msg, bool checkTooHigh = true, bool checkTooLow = tru if (!IsGoodTime(msg)) { - Log.OnEvent("Sending time accuracy problem"); + Log.Log(LogLevel.Warning, "Sending time accuracy problem"); GenerateReject(msg, FixValues.SessionRejectReason.SENDING_TIME_ACCURACY_PROBLEM); GenerateLogout(); return false; @@ -954,7 +957,7 @@ public bool Verify(Message msg, bool checkTooHigh = true, bool checkTooLow = tru } catch (Exception e) { - Log.OnEvent("Verify failed: " + e.Message); + Log.Log(LogLevel.Error, e, "Verify failed: {Message}", e.Message); Disconnect("Verify failed: " + e.Message); return false; } @@ -1044,7 +1047,7 @@ protected void DoTargetTooHigh(Message msg, SeqNumType msgSeqNum) { string beginString = msg.Header.GetString(Fields.Tags.BeginString); - Log.OnEvent("MsgSeqNum too high, expecting " + _state.NextTargetMsgSeqNum + " but received " + msgSeqNum); + Log.Log(LogLevel.Warning, "MsgSeqNum too high, expecting {NextSeqNum} but received {MsgSeqNum}", _state.NextTargetMsgSeqNum, msgSeqNum); _state.Queue(msgSeqNum, msg); if (_state.ResendRequested()) @@ -1053,7 +1056,7 @@ protected void DoTargetTooHigh(Message msg, SeqNumType msgSeqNum) if (!SendRedundantResendRequests && msgSeqNum >= range.BeginSeqNo) { - Log.OnEvent("Already sent ResendRequest FROM: " + range.BeginSeqNo + " TO: " + range.EndSeqNo + ". Not sending another."); + Log.Log(LogLevel.Debug, "Already sent ResendRequest FROM: {BeginSeqNo} TO: {EndSeqNo}. Not sending another.", range.BeginSeqNo, range.EndSeqNo); return; } } @@ -1133,7 +1136,7 @@ protected void GenerateBusinessMessageReject(Message message, int err, int field reject.SetField(new Text(reason)); - Log.OnEvent("Reject sent for Message: " + msgSeqNum + " Reason:" + reason); + Log.Log(LogLevel.Warning, "Reject sent for Message: {MsgSeqNum} Reason: {Reason}", msgSeqNum, reason); SendRaw(reject, 0); } @@ -1147,11 +1150,11 @@ protected bool GenerateResendRequestRange(string beginString, SeqNumType startSe InitializeHeader(resendRequest); if (SendRaw(resendRequest, 0)) { - Log.OnEvent("Sent ResendRequest FROM: " + startSeqNum + " TO: " + endSeqNum); + Log.Log(LogLevel.Debug, "Sent ResendRequest FROM: {StartSeqNum} TO: {EndSeqNum}", startSeqNum, endSeqNum); return true; } - Log.OnEvent("Error sending ResendRequest (" + startSeqNum + " ," + endSeqNum + ")"); + Log.Log(LogLevel.Error, "Error sending ResendRequest ({StartSeqNum}, {EndSeqNum})", startSeqNum, endSeqNum); return false; } @@ -1277,9 +1280,9 @@ private void ImplGenerateLogout(Message? other = null, string? text = null) { logout.Header.SetField(new Fields.LastMsgSeqNumProcessed(other.Header.GetULong(Tags.MsgSeqNum))); } - catch (FieldNotFoundException) + catch (FieldNotFoundException e) { - Log.OnEvent("Error: No message sequence number: " + other); + Log.Log(LogLevel.Error, e, "Error: No message sequence number: {Other}", other); } } _state.SentLogout = SendRaw(logout, 0); @@ -1335,7 +1338,7 @@ public void GenerateReject(Message message, FixValues.SessionRejectReason reason } catch (Exception ex) { - Log.OnEvent($"Exception while setting RefSeqNum: {ex}"); + Log.Log(LogLevel.Error, ex, "Exception while setting RefSeqNum: {Exception}", ex); } } @@ -1368,12 +1371,12 @@ public void GenerateReject(Message message, FixValues.SessionRejectReason reason else PopulateSessionRejectReason(reject, field, reason.Description, true); - Log.OnEvent("Message " + msgSeqNum + " Rejected: " + reason.Description + " (Field=" + field + ")"); + Log.Log(LogLevel.Warning, "Message {MsgSeqNum} Rejected: {Reason} (Field={Field})", msgSeqNum, reason.Description, field); } else { PopulateRejectReason(reject, reason.Description); - Log.OnEvent("Message " + msgSeqNum + " Rejected: " + reason.Value); + Log.Log(LogLevel.Error, "Message {MsgSeqNum} Rejected: {Reason}", msgSeqNum, reason.Value); } if (!_state.ReceivedLogon) @@ -1478,13 +1481,13 @@ private void GenerateSequenceReset(Message receivedMessage, SeqNumType beginSeqN { sequenceReset.Header.SetField(new Fields.LastMsgSeqNumProcessed(receivedMessage.Header.GetULong(Tags.MsgSeqNum))); } - catch (FieldNotFoundException) + catch (FieldNotFoundException e) { - Log.OnEvent("Error: Received message without MsgSeqNum: " + receivedMessage); + Log.Log(LogLevel.Error, e, "Error: Received message without MsgSeqNum: {ReceivedMessage}", receivedMessage); } } SendRaw(sequenceReset, beginSeqNo); - Log.OnEvent("Sent SequenceReset TO: " + newSeqNo); + Log.Log(LogLevel.Debug, "Sent SequenceReset TO: {NewSeqNo}", newSeqNo); } protected void InsertOrigSendingTime(FieldMap header, DateTime sendingTime) @@ -1507,7 +1510,7 @@ protected bool NextQueued(SeqNumType num) if (msg is not null) { - Log.OnEvent("Processing queued message: " + num); + Log.Log(LogLevel.Debug, "Processing queued message: {Num}", num); string msgType = msg.Header.GetString(Tags.MsgType); if (msgType.Equals(MsgType.LOGON) || msgType.Equals(MsgType.RESEND_REQUEST)) diff --git a/QuickFIXn/SessionFactory.cs b/QuickFIXn/SessionFactory.cs index 867aeb170..b05f663ee 100755 --- a/QuickFIXn/SessionFactory.cs +++ b/QuickFIXn/SessionFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; -using QuickFix.Logger; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using QuickFix.Store; using QuickFix.Util; @@ -13,14 +14,14 @@ public class SessionFactory { protected IApplication _application; protected IMessageStoreFactory _messageStoreFactory; - protected ILogFactory _logFactory; + protected ILoggerFactory _loggerFactory; protected IMessageFactory _messageFactory; protected Dictionary _dictionariesByPath = new(); public SessionFactory( IApplication app, IMessageStoreFactory storeFactory, - ILogFactory? logFactory = null, + ILoggerFactory? loggerFactory = null, IMessageFactory? messageFactory = null) { // TODO: for V2, consider ONLY instantiating MessageFactory in the Create() method, @@ -31,7 +32,7 @@ public SessionFactory( _application = app; _messageStoreFactory = storeFactory; - _logFactory = logFactory ?? new NullLogFactory(); + _loggerFactory = loggerFactory ?? new NullLoggerFactory(); _messageFactory = messageFactory ?? new DefaultMessageFactory(); } @@ -107,7 +108,7 @@ public Session Create(SessionID sessionId, SettingsDictionary settings) dd, new SessionSchedule(settings), heartBtInt, - _logFactory, + _loggerFactory, sessionMsgFactory, senderDefaultApplVerId); diff --git a/QuickFIXn/SessionState.cs b/QuickFIXn/SessionState.cs index 76206f8e6..97462a7eb 100755 --- a/QuickFIXn/SessionState.cs +++ b/QuickFIXn/SessionState.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using QuickFix.Logger; +using Microsoft.Extensions.Logging; using QuickFix.Store; using MessagesBySeqNum = System.Collections.Generic.Dictionary; @@ -46,7 +46,7 @@ public bool IsInitiator public bool ShouldSendLogon => IsInitiator && !SentLogon; - public ILog Log { get; } + public ILogger Log { get; } #endregion @@ -154,9 +154,9 @@ private MessagesBySeqNum MsgQueue #endregion - public SessionState(bool isInitiator, ILog log, int heartBtInt, IMessageStore messageStore) + public SessionState(bool isInitiator, ILogger logger, int heartBtInt, IMessageStore messageStore) { - Log = log; + Log = logger; HeartBtInt = heartBtInt; IsInitiator = isInitiator; _lastReceivedTimeDt = DateTime.UtcNow; @@ -395,7 +395,7 @@ public void Reset(string reason) lock (_sync) { MessageStore.Reset(); - Log.OnEvent("Session reset: " + reason); + Log.Log(LogLevel.Debug, "Session reset: {Reason}", reason); } } @@ -418,7 +418,6 @@ protected virtual void Dispose(bool disposing) if (_disposed) return; if (disposing) { - Log.Dispose(); MessageStore.Dispose(); } _disposed = true; diff --git a/QuickFIXn/SocketInitiatorThread.cs b/QuickFIXn/SocketInitiatorThread.cs index f37669493..b78aa7158 100755 --- a/QuickFIXn/SocketInitiatorThread.cs +++ b/QuickFIXn/SocketInitiatorThread.cs @@ -5,7 +5,7 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -using QuickFix.Logger; +using Microsoft.Extensions.Logging; namespace QuickFix { @@ -16,7 +16,7 @@ public class SocketInitiatorThread : IResponder { public Session Session { get; } public Transport.SocketInitiator Initiator { get; } - public NonSessionLog NonSessionLog { get; } + public ILogger NonSessionLog { get; } public const int BUF_SIZE = 512; @@ -39,7 +39,7 @@ public SocketInitiatorThread( Session session, IPEndPoint socketEndPoint, SocketSettings socketSettings, - NonSessionLog nonSessionLog) + ILogger nonSessionLog) { Initiator = initiator; Session = session; @@ -105,7 +105,7 @@ public bool Read() } catch (Exception e) { - Session.Log.OnEvent(e.ToString()); + Session.Log.Log(LogLevel.Error, e, "{Exception}", e); Disconnect(); } return false; diff --git a/QuickFIXn/SocketReader.cs b/QuickFIXn/SocketReader.cs index 32ea6a332..b92007c3d 100755 --- a/QuickFIXn/SocketReader.cs +++ b/QuickFIXn/SocketReader.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using QuickFix.Logger; namespace QuickFix @@ -19,7 +20,15 @@ public class SocketReader : IDisposable private readonly TcpClient _tcpClient; private readonly ClientHandlerThread _responder; private readonly AcceptorSocketDescriptor? _acceptorDescriptor; - private readonly NonSessionLog _nonSessionLog; + private readonly ILogger _nonSessionLog; + private ILogger UnconditionalLogger + { + get + { + if (_qfSession?.Log is { } logger) return logger; + return _nonSessionLog; + } + } /// /// Keep a task for handling async read @@ -31,7 +40,7 @@ internal SocketReader( SocketSettings settings, ClientHandlerThread responder, AcceptorSocketDescriptor? acceptorDescriptor, - NonSessionLog nonSessionLog) + ILogger nonSessionLog) { _tcpClient = tcpClient; _responder = responder; @@ -124,21 +133,26 @@ private void OnMessageFound(string msg) if (_qfSession is null || IsUnknownSession(_qfSession.SessionID)) { _qfSession = null; - _nonSessionLog.OnEvent("ERROR: Disconnecting; received message for unknown session: " + msg); + _nonSessionLog.Log(LogLevel.Error, + "ERROR: Disconnecting; received message for unknown session: {Message}", msg); DisconnectClient(); return; } if (_qfSession.HasResponder) { - _qfSession.Log.OnIncoming(msg); - _qfSession.Log.OnEvent("Multiple logons/connections for this session are not allowed (" + _tcpClient.Client.RemoteEndPoint + ")"); + _qfSession.Log.Log(LogLevel.Information, LogEventIds.IncomingMessage, "{Message}", msg); + _qfSession.Log.Log(LogLevel.Error, + "Multiple logons/connections for this session are not allowed ({Endpoint})", + _tcpClient.Client.RemoteEndPoint); _qfSession = null; DisconnectClient(); return; } - _qfSession.Log.OnEvent(_qfSession.SessionID + " Socket Reader " + GetHashCode() + " accepting session " + _qfSession.SessionID + " from " + _tcpClient.Client.RemoteEndPoint); + _qfSession.Log.Log(LogLevel.Debug, + "{SessionId} Socket Reader {ReaderId} accepting session {AcceptedSessionId} from {Endpoint}", + _qfSession.SessionID, GetHashCode(), _qfSession.SessionID, _tcpClient.Client.RemoteEndPoint); _qfSession.SetResponder(_responder); } @@ -148,7 +162,8 @@ private void OnMessageFound(string msg) } catch (Exception e) { - _qfSession.Log.OnEvent($"Error on Session '{_qfSession.SessionID}': {e}"); + _qfSession.Log.Log(LogLevel.Error, e, "Error on Session '{SessionId}': {Exception}", + _qfSession.SessionID, e); } } /* @@ -172,12 +187,13 @@ protected void HandleBadMessage(string msg, Exception e) { if (Fields.MsgType.LOGON.Equals(Message.GetMsgType(msg))) { - LogEvent($"ERROR: Invalid LOGON message, disconnecting: {e.Message}"); + UnconditionalLogger.Log(LogLevel.Error, e, "ERROR: Invalid LOGON message, disconnecting: {Message}", + e.Message); DisconnectClient(); } else { - LogEvent($"ERROR: Invalid message: {e.Message}"); + UnconditionalLogger.Log(LogLevel.Error, e, "ERROR: Invalid message: {Message}", e.Message); } } catch (InvalidMessage) @@ -246,7 +262,7 @@ private void HandleExceptionInternal(Session? quickFixSession, Exception cause) break; } - LogEvent($"SocketReader Error: {reason}"); + UnconditionalLogger.Log(LogLevel.Error, realCause, "SocketReader Error: {Reason}", reason); if (disconnectNeeded) { @@ -257,18 +273,6 @@ private void HandleExceptionInternal(Session? quickFixSession, Exception cause) } } - /// - /// Log event to session log if session is known, else to nonSessionLog - /// - /// - private void LogEvent(string s) - { - if(_qfSession is not null) - _qfSession.Log.OnEvent(s); - else - _nonSessionLog.OnEvent(s); - } - public int Send(string data) { byte[] rawData = CharEncoding.DefaultEncoding.GetBytes(data); diff --git a/QuickFIXn/ThreadedSocketAcceptor.cs b/QuickFIXn/ThreadedSocketAcceptor.cs index 2f3e69daf..8acfc14b2 100755 --- a/QuickFIXn/ThreadedSocketAcceptor.cs +++ b/QuickFIXn/ThreadedSocketAcceptor.cs @@ -2,6 +2,8 @@ using System.Linq; using System.Net; using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using QuickFix.Logger; using QuickFix.Store; @@ -20,10 +22,9 @@ public class ThreadedSocketAcceptor : IAcceptor private bool _isStarted = false; private bool _disposed = false; private readonly object _sync = new(); - private readonly NonSessionLog _nonSessionLog; + private readonly ILogger _nonSessionLog; #region Constructors - /// /// Create a ThreadedSocketAcceptor /// @@ -32,6 +33,7 @@ public class ThreadedSocketAcceptor : IAcceptor /// /// If null, a NullFactory will be used. /// If null, a DefaultMessageFactory will be created (using settings parameters) + [Obsolete] public ThreadedSocketAcceptor( IApplication application, IMessageStoreFactory storeFactory, @@ -39,11 +41,47 @@ public ThreadedSocketAcceptor( ILogFactory? logFactory = null, IMessageFactory? messageFactory = null) { - ILogFactory lf = logFactory ?? new NullLogFactory(); + ILoggerFactory lf = logFactory is null + ? NullLoggerFactory.Instance + : new LogFactoryAdapter(logFactory, settings); + IMessageFactory mf = messageFactory ?? new DefaultMessageFactory(); + _settings = settings; + _sessionFactory = new SessionFactory(application, storeFactory, lf, mf); + _nonSessionLog = lf.CreateLogger("QuickFix"); + + try + { + foreach (SessionID sessionId in settings.GetSessions()) + { + SettingsDictionary dict = settings.Get(sessionId); + CreateSession(sessionId, dict); + } + } + catch (Exception e) + { + throw new ConfigError(e.Message, e); + } + } + /// + /// Create a ThreadedSocketAcceptor + /// + /// + /// + /// + /// If null, a NullFactory will be used. + /// If null, a DefaultMessageFactory will be created (using settings parameters) + public ThreadedSocketAcceptor( + IApplication application, + IMessageStoreFactory storeFactory, + SessionSettings settings, + ILoggerFactory? loggerFactory = null, + IMessageFactory? messageFactory = null) + { + ILoggerFactory lf = loggerFactory ?? new NullLoggerFactory(); IMessageFactory mf = messageFactory ?? new DefaultMessageFactory(); _settings = settings; _sessionFactory = new SessionFactory(application, storeFactory, lf, mf); - _nonSessionLog = new NonSessionLog(lf); + _nonSessionLog = lf.CreateLogger("QuickFix"); try { @@ -176,7 +214,8 @@ private void LogoutAllSessions(bool force) } catch (Exception e) { - session.Log.OnEvent($"Error during logout of Session {session.SessionID}: {e.Message}"); + session.Log.Log(LogLevel.Error, e, "Error during logout of Session {SessionID}: {Message}", + session.SessionID, e.Message); } } @@ -191,7 +230,8 @@ private void LogoutAllSessions(bool force) } catch (Exception e) { - session.Log.OnEvent($"Error during disconnect of Session {session.SessionID}: {e.Message}"); + session.Log.Log(LogLevel.Error, e, "Error during disconnect of Session {SessionID}: {Message}", + session.SessionID, e.Message); } } } @@ -274,7 +314,6 @@ public void Stop(bool force) LogoutAllSessions(force); DisposeSessions(); _sessions.Clear(); - _nonSessionLog.Dispose(); _isStarted = false; // FIXME StopSessionTimer(); diff --git a/QuickFIXn/ThreadedSocketReactor.cs b/QuickFIXn/ThreadedSocketReactor.cs index daaceec06..b342f46a4 100755 --- a/QuickFIXn/ThreadedSocketReactor.cs +++ b/QuickFIXn/ThreadedSocketReactor.cs @@ -3,7 +3,7 @@ using System.Net.Sockets; using System.Threading; using System; -using QuickFix.Logger; +using Microsoft.Extensions.Logging; namespace QuickFix { @@ -31,13 +31,13 @@ public State ReactorState private readonly SocketSettings _socketSettings; private readonly IPEndPoint _serverSocketEndPoint; private readonly AcceptorSocketDescriptor? _acceptorSocketDescriptor; - private readonly NonSessionLog _nonSessionLog; + private readonly ILogger _nonSessionLog; internal ThreadedSocketReactor( IPEndPoint serverSocketEndPoint, SocketSettings socketSettings, AcceptorSocketDescriptor? acceptorSocketDescriptor, - NonSessionLog nonSessionLog) + ILogger nonSessionLog) { _socketSettings = socketSettings; _serverSocketEndPoint = serverSocketEndPoint; @@ -211,7 +211,14 @@ private void ShutdownClientHandlerThreads() /// /// private void LogError(string s, Exception? ex = null) { - _nonSessionLog.OnEvent(ex is null ? $"{s}" : $"{s}: {ex}"); + if (ex is null) + { + _nonSessionLog.LogError("{Message}", s); + } + else + { + _nonSessionLog.LogError(ex, "{Message}: {Error}", s, ex); + } } } } diff --git a/QuickFIXn/Transport/SocketInitiator.cs b/QuickFIXn/Transport/SocketInitiator.cs index 30984e86b..1c330b1c3 100644 --- a/QuickFIXn/Transport/SocketInitiator.cs +++ b/QuickFIXn/Transport/SocketInitiator.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Sockets; using System.Threading; +using Microsoft.Extensions.Logging; using QuickFix.Logger; using QuickFix.Store; @@ -26,7 +27,8 @@ public class SocketInitiator : AbstractInitiator private readonly Dictionary _threads = new(); private readonly Dictionary _sessionToHostNum = new(); private readonly object _sync = new(); - + + [Obsolete] public SocketInitiator( IApplication application, IMessageStoreFactory storeFactory, @@ -36,6 +38,15 @@ public SocketInitiator( : base(application, storeFactory, settings, logFactoryNullable, messageFactoryNullable) { } + public SocketInitiator( + IApplication application, + IMessageStoreFactory storeFactory, + SessionSettings settings, + ILoggerFactory? logFactoryNullable = null, + IMessageFactory? messageFactoryNullable = null) + : base(application, storeFactory, settings, logFactoryNullable, messageFactoryNullable) + { } + public static void SocketInitiatorThreadStart(object? socketInitiatorThread) { SocketInitiatorThread? t = socketInitiatorThread as SocketInitiatorThread; @@ -45,7 +56,7 @@ public static void SocketInitiatorThreadStart(object? socketInitiatorThread) { t.Connect(); t.Initiator.SetConnected(t.Session.SessionID); - t.Session.Log.OnEvent("Connection succeeded"); + t.Session.Log.Log(LogLevel.Debug, "Connection succeeded"); t.Session.Next(); while (t.Read()) { } @@ -78,11 +89,13 @@ public static void SocketInitiatorThreadStart(object? socketInitiatorThread) } private static void LogThreadStartConnectionFailed(SocketInitiatorThread t, Exception e) { - if (t.Session.Disposed) { - t.NonSessionLog.OnEvent($"Connection failed [session {t.Session.SessionID}]: {e}"); + if (t.Session.Disposed) + { + t.NonSessionLog.Log(LogLevel.Error, e, "Connection failed [session {SessionID}]: {Message}", + t.Session.SessionID, e); return; } - t.Session.Log.OnEvent($"Connection failed: {e}"); + t.Session.Log.Log(LogLevel.Error, e, "Connection failed: {Error}", e); } private void AddThread(SocketInitiatorThread thread) @@ -185,7 +198,7 @@ protected override void OnStart() } catch (Exception e) { - _nonSessionLog.OnEvent($"Failed to start: {e}"); + _nonSessionLog.Log(LogLevel.Error, e, "Failed to start: {Error}", e); } Thread.Sleep(1 * 1000); @@ -220,7 +233,7 @@ protected override void DoConnect(Session session, SettingsDictionary settings) IPEndPoint socketEndPoint = GetNextSocketEndPoint(session.SessionID, settings); SetPending(session.SessionID); - session.Log.OnEvent($"Connecting to {socketEndPoint.Address} on port {socketEndPoint.Port}"); + session.Log.Log(LogLevel.Debug, "Connecting to {Address} on port {Port}", socketEndPoint.Address, socketEndPoint.Port); //Setup socket settings based on current section var socketSettings = _socketSettings.Clone(); @@ -233,7 +246,7 @@ protected override void DoConnect(Session session, SettingsDictionary settings) AddThread(t); } catch (Exception e) { - session.Log.OnEvent(e.Message); + session.Log.Log(LogLevel.Error, e, "{Exception}", e); } } diff --git a/QuickFIXn/Transport/SslStreamFactory.cs b/QuickFIXn/Transport/SslStreamFactory.cs index b5c5e9d64..6ed6a4ef4 100644 --- a/QuickFIXn/Transport/SslStreamFactory.cs +++ b/QuickFIXn/Transport/SslStreamFactory.cs @@ -4,6 +4,7 @@ using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; +using Microsoft.Extensions.Logging; using QuickFix.Logger; using QuickFix.Util; @@ -15,11 +16,11 @@ namespace QuickFix.Transport; internal sealed class SslStreamFactory { private readonly SocketSettings _socketSettings; - private readonly NonSessionLog _nonSessionLog; + private readonly ILogger _nonSessionLog; private const string CLIENT_AUTHENTICATION_OID = "1.3.6.1.5.5.7.3.2"; private const string SERVER_AUTHENTICATION_OID = "1.3.6.1.5.5.7.3.1"; - public SslStreamFactory(SocketSettings settings, NonSessionLog nonSessionLog) + public SslStreamFactory(SocketSettings settings, ILogger nonSessionLog) { _socketSettings = settings; _nonSessionLog = nonSessionLog; @@ -56,7 +57,8 @@ public Stream CreateClientStreamAndAuthenticate(Stream innerStream) } catch (AuthenticationException ex) { - _nonSessionLog.OnEvent($"Unable to perform authentication against server: {ex.GetFullMessage()}"); + _nonSessionLog.Log(LogLevel.Error, ex, "Unable to perform authentication against server: {Message}", + ex.GetFullMessage()); throw; } @@ -101,7 +103,8 @@ public Stream CreateServerStreamAndAuthenticate(Stream innerStream) } catch (AuthenticationException ex) { - _nonSessionLog.OnEvent($"Unable to perform authentication against server: {ex.GetFullMessage()}"); + _nonSessionLog.Log(LogLevel.Error, ex, "Unable to perform authentication against server: {Message}", + ex.GetFullMessage()); throw; } @@ -171,13 +174,14 @@ private bool VerifyRemoteCertificate( // Validate enhanced key usage if (!ContainsEnhancedKeyUsage(certificate, enhancedKeyUsage)) { var role = enhancedKeyUsage == CLIENT_AUTHENTICATION_OID ? "client" : "server"; - _nonSessionLog.OnEvent( - $"Remote certificate is not intended for {role} authentication: It is missing enhanced key usage {enhancedKeyUsage}"); + _nonSessionLog.Log(LogLevel.Warning, + "Remote certificate is not intended for {Role} authentication: It is missing enhanced key usage {KeyUsage}", + role, enhancedKeyUsage); return false; } if (string.IsNullOrEmpty(_socketSettings.CACertificatePath)) { - _nonSessionLog.OnEvent("CACertificatePath is not specified"); + _nonSessionLog.Log(LogLevel.Warning, "CACertificatePath is not specified"); return false; } @@ -185,9 +189,11 @@ private bool VerifyRemoteCertificate( // If CA Certificate is specified then validate against the CA certificate, otherwise it is validated against the installed certificates X509Certificate2? cert = SslCertCache.LoadCertificate(caCertPath, null); - if (cert is null) { - _nonSessionLog.OnEvent( - $"Certificate '{caCertPath}' could not be loaded from store or path '{Directory.GetCurrentDirectory()}'"); + if (cert is null) + { + _nonSessionLog.Log(LogLevel.Warning, + "Certificate '{CertificatePath}' could not be loaded from store or path '{Directory}'", caCertPath, + Directory.GetCurrentDirectory()); return false; } @@ -212,7 +218,8 @@ private bool VerifyRemoteCertificate( // Any basic authentication check failed, do after checking CA if (sslPolicyErrors != SslPolicyErrors.None) { - _nonSessionLog.OnEvent($"Remote certificate was not recognized as a valid certificate: {sslPolicyErrors}"); + _nonSessionLog.Log(LogLevel.Warning, + "Remote certificate was not recognized as a valid certificate: {Errors}", sslPolicyErrors); return false; } diff --git a/QuickFIXn/Transport/StreamFactory.cs b/QuickFIXn/Transport/StreamFactory.cs index 66117a7d0..ea1f453d1 100644 --- a/QuickFIXn/Transport/StreamFactory.cs +++ b/QuickFIXn/Transport/StreamFactory.cs @@ -4,7 +4,7 @@ using System.Net; using System.Net.Sockets; using System.Text; -using QuickFix.Logger; +using Microsoft.Extensions.Logging; namespace QuickFix.Transport { @@ -66,7 +66,7 @@ internal static class StreamFactory /// The socket settings. /// Logger that is not tied to a particular session /// an opened and initiated stream which can be read and written to - internal static Stream CreateClientStream(IPEndPoint endpoint, SocketSettings settings, NonSessionLog nonSessionLog) + internal static Stream CreateClientStream(IPEndPoint endpoint, SocketSettings settings, ILogger nonSessionLog) { Socket? socket = null; @@ -116,7 +116,7 @@ internal static Stream CreateClientStream(IPEndPoint endpoint, SocketSettings se /// Logger that is not tied to a particular session /// an opened and initiated stream which can be read and written to /// tcp client must be connected in order to get stream;tcpClient - internal static Stream CreateServerStream(TcpClient tcpClient, SocketSettings settings, NonSessionLog nonSessionLog) + internal static Stream CreateServerStream(TcpClient tcpClient, SocketSettings settings, ILogger nonSessionLog) { if (tcpClient.Connected == false) throw new ArgumentException("tcp client must be connected in order to get stream", nameof(tcpClient)); diff --git a/UnitTests/Logger/FileLogTests.cs b/UnitTests/Logger/FileLogTests.cs index 9b0f6f980..5d2bbb26c 100644 --- a/UnitTests/Logger/FileLogTests.cs +++ b/UnitTests/Logger/FileLogTests.cs @@ -1,4 +1,5 @@ using System.IO; +using Microsoft.Extensions.Logging; using NUnit.Framework; using QuickFix.Logger; @@ -7,7 +8,7 @@ namespace UnitTests.Logger; [TestFixture] public class FileLogTests { - private FileLog? _log; + private FileLogger? _log; [SetUp] public void Setup() @@ -26,18 +27,18 @@ public void TestPrefix() QuickFix.SessionID someSessionId = new QuickFix.SessionID("FIX.4.4", "sender", "target"); QuickFix.SessionID someSessionIdWithQualifier = new QuickFix.SessionID("FIX.4.3", "sender", "target", "foo"); - Assert.That(FileLog.Prefix(someSessionId), Is.EqualTo("FIX.4.4-sender-target")); - Assert.That(FileLog.Prefix(someSessionIdWithQualifier), Is.EqualTo("FIX.4.3-sender-target-foo")); + Assert.That(FileLogger.Prefix(someSessionId), Is.EqualTo("FIX.4.4-sender-target")); + Assert.That(FileLogger.Prefix(someSessionIdWithQualifier), Is.EqualTo("FIX.4.3-sender-target-foo")); } [Test] public void TestPrefixForSubsAndLocation() { QuickFix.SessionID sessionIdWithSubsAndLocation = new QuickFix.SessionID("FIX.4.2", "SENDERCOMP", "SENDERSUB", "SENDERLOC", "TARGETCOMP", "TARGETSUB", "TARGETLOC"); - Assert.That(FileLog.Prefix(sessionIdWithSubsAndLocation), Is.EqualTo("FIX.4.2-SENDERCOMP_SENDERSUB_SENDERLOC-TARGETCOMP_TARGETSUB_TARGETLOC")); + Assert.That(FileLogger.Prefix(sessionIdWithSubsAndLocation), Is.EqualTo("FIX.4.2-SENDERCOMP_SENDERSUB_SENDERLOC-TARGETCOMP_TARGETSUB_TARGETLOC")); QuickFix.SessionID sessionIdWithSubsNoLocation = new QuickFix.SessionID("FIX.4.2", "SENDERCOMP", "SENDERSUB", "TARGETCOMP", "TARGETSUB"); - Assert.That(FileLog.Prefix(sessionIdWithSubsNoLocation), Is.EqualTo("FIX.4.2-SENDERCOMP_SENDERSUB-TARGETCOMP_TARGETSUB")); + Assert.That(FileLogger.Prefix(sessionIdWithSubsNoLocation), Is.EqualTo("FIX.4.2-SENDERCOMP_SENDERSUB-TARGETCOMP_TARGETSUB")); } [Test] @@ -57,12 +58,12 @@ public void TestGeneratedFileName() settings.Set(sessionId, config); - FileLogFactory factory = new FileLogFactory(settings); - _log = (FileLog)factory.Create(sessionId); + var fileLogProvider = new FileLoggerProvider(settings); + _log = (FileLogger) fileLogProvider.CreateLogger($"QuickFix.{sessionId}"); - _log.OnEvent("some event"); - _log.OnIncoming("some incoming"); - _log.OnOutgoing("some outgoing"); + _log.Log(LogLevel.Debug, "some event"); + _log.Log(LogLevel.Information, LogEventIds.IncomingMessage, "some incoming"); + _log.Log(LogLevel.Information, LogEventIds.OutgoingMessage, "some outgoing"); Assert.That(File.Exists(Path.Combine(logDirectory, "FIX.4.2-SENDERCOMP-TARGETCOMP.event.current.log"))); Assert.That(File.Exists(Path.Combine(logDirectory, "FIX.4.2-SENDERCOMP-TARGETCOMP.messages.current.log"))); @@ -82,8 +83,8 @@ public void TestThrowsIfNoConfig() QuickFix.SessionSettings settings = new QuickFix.SessionSettings(); settings.Set(sessionId, config); - FileLogFactory factory = new FileLogFactory(settings); + var loggerFactory = new LoggerFactory([new FileLoggerProvider(settings)]); - Assert.Throws(delegate { factory.Create(sessionId); }); + Assert.Throws(() => loggerFactory.CreateLogger($"QuickFix.{sessionId}")); } } diff --git a/UnitTests/SessionDynamicTest.cs b/UnitTests/SessionDynamicTest.cs index 5db1598b1..9fbb29684 100644 --- a/UnitTests/SessionDynamicTest.cs +++ b/UnitTests/SessionDynamicTest.cs @@ -5,7 +5,7 @@ using System.Net; using System.Net.Sockets; using System.Text.RegularExpressions; - +using Microsoft.Extensions.Logging; using NUnit.Framework; using QuickFix; using QuickFix.Logger; @@ -127,13 +127,13 @@ private void StartEngine(bool initiator, bool twoSessions = false) defaults.SetString(SessionSettings.SOCKET_ACCEPT_PORT, AcceptPort.ToString()); settings.Set(defaults); - ILogFactory logFactory = new FileLogFactory(settings); + var loggerFactory = new LoggerFactory([new FileLoggerProvider(settings)]); if (initiator) { defaults.SetString(SessionSettings.RECONNECT_INTERVAL, "1"); settings.Set(CreateSessionId(StaticInitiatorCompId), CreateSessionConfig(true)); - _initiator = new SocketInitiator(application, storeFactory, settings, logFactory); + _initiator = new SocketInitiator(application, storeFactory, settings, loggerFactory); _initiator.Start(); } else @@ -151,7 +151,7 @@ private void StartEngine(bool initiator, bool twoSessions = false) settings.Set(id, conf); } - _acceptor = new ThreadedSocketAcceptor(application, storeFactory, settings, logFactory); + _acceptor = new ThreadedSocketAcceptor(application, storeFactory, settings, loggerFactory); _acceptor.Start(); } } diff --git a/UnitTests/SessionStateTest.cs b/UnitTests/SessionStateTest.cs index 90f912333..d195eaffc 100755 --- a/UnitTests/SessionStateTest.cs +++ b/UnitTests/SessionStateTest.cs @@ -151,7 +151,7 @@ public void ThreadSafeSetAndGet() { FileStore store = (FileStore)factory.Create(sessionId); - NullLog log = new NullLog(); + NullLogger log = new NullLogger(); //Set up sessionstate SessionState state = new SessionState(true, log, 1, store); diff --git a/UnitTests/SessionTest.cs b/UnitTests/SessionTest.cs index afe1762f7..d485c7df0 100755 --- a/UnitTests/SessionTest.cs +++ b/UnitTests/SessionTest.cs @@ -4,6 +4,7 @@ using System.Text.RegularExpressions; using NUnit.Framework; using System.Threading; +using Microsoft.Extensions.Logging; using QuickFix.Logger; using QuickFix.Store; @@ -39,17 +40,17 @@ public void Setup() _config.SetString(QuickFix.SessionSettings.END_TIME, "00:00:00"); _settings.Set(_sessionId, _config); - var logFactory = new NullLogFactory(); // use QuickFix.ScreenLogFactory(settings) if you need to see output + var loggerFactory = new LoggerFactory([new NullLoggerProvider()]); // use QuickFix.ScreenLogFactory(settings) if you need to see output // acceptor _session = new QuickFix.Session(false, _application, new MemoryStoreFactory(), _sessionId, - new QuickFix.DataDictionaryProvider(),new QuickFix.SessionSchedule(_config), 0, logFactory, new QuickFix.DefaultMessageFactory(), "blah"); + new QuickFix.DataDictionaryProvider(),new QuickFix.SessionSchedule(_config), 0, loggerFactory, new QuickFix.DefaultMessageFactory(), "blah"); _session.SetResponder(_responder); _session.CheckLatency = false; // initiator _session2 = new QuickFix.Session(true, _application, new MemoryStoreFactory(), new QuickFix.SessionID("FIX.4.2", "OTHER_SENDER", "OTHER_TARGET"), - new QuickFix.DataDictionaryProvider(), new QuickFix.SessionSchedule(_config), 0, logFactory, new QuickFix.DefaultMessageFactory(), "blah"); + new QuickFix.DataDictionaryProvider(), new QuickFix.SessionSchedule(_config), 0, loggerFactory, new QuickFix.DefaultMessageFactory(), "blah"); _session2.SetResponder(_responder); _session2.CheckLatency = false; @@ -758,7 +759,8 @@ public void TestApplicationExtension() { var mockApp = new SessionTestSupport.MockApplicationExt(); _session = new QuickFix.Session(true, mockApp, new MemoryStoreFactory(), _sessionId, - new QuickFix.DataDictionaryProvider(), new QuickFix.SessionSchedule(_config), 0, new NullLogFactory(), new QuickFix.DefaultMessageFactory(), "blah"); + new QuickFix.DataDictionaryProvider(), new QuickFix.SessionSchedule(_config), 0, + new LoggerFactory([new NullLoggerProvider()]), new QuickFix.DefaultMessageFactory(), "blah"); _session.SetResponder(_responder); _session.CheckLatency = false; diff --git a/UnitTests/ThreadedSocketAcceptorTests.cs b/UnitTests/ThreadedSocketAcceptorTests.cs index 1f334b31c..a05f39cf1 100644 --- a/UnitTests/ThreadedSocketAcceptorTests.cs +++ b/UnitTests/ThreadedSocketAcceptorTests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Text; +using Microsoft.Extensions.Logging; using NUnit.Framework; using QuickFix; using QuickFix.Logger; @@ -42,7 +43,7 @@ private static ThreadedSocketAcceptor CreateAcceptor() new NullApplication(), new FileStoreFactory(settings), settings, - new FileLogFactory(settings)); + new LoggerFactory([new FileLoggerProvider(settings)])); } [Test] diff --git a/UnitTests/ThreadedSocketReactorTests.cs b/UnitTests/ThreadedSocketReactorTests.cs index 199304490..cfb58cca9 100644 --- a/UnitTests/ThreadedSocketReactorTests.cs +++ b/UnitTests/ThreadedSocketReactorTests.cs @@ -49,7 +49,7 @@ public void TestStartOnBusyPort() new IPEndPoint(IPAddress.Loopback, port), settings, acceptorSocketDescriptor: null, - new NonSessionLog(new ScreenLogFactory(true, true, true))); + new ScreenLogger(true, true, true)); var stdOut = GetStdOut(); var ex = Assert.Throws(delegate { testingObject.Run(); })!; diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 92f40b7ca..443e7bd12 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -18,6 +18,7 @@ + From e130f42fc8fac5876da2a0f7e95c84768a9e2563 Mon Sep 17 00:00:00 2001 From: JK Date: Wed, 27 Nov 2024 11:39:24 -0500 Subject: [PATCH 02/23] Mark old logger obsolete --- QuickFIXn/AbstractInitiator.cs | 2 +- QuickFIXn/Logger/CompositeLog.cs | 1 + QuickFIXn/Logger/CompositeLogFactory.cs | 4 +++- QuickFIXn/Logger/FileLog.cs | 1 + QuickFIXn/Logger/FileLogFactory.cs | 5 ++++- QuickFIXn/Logger/ILog.cs | 1 + QuickFIXn/Logger/ILogFactory.cs | 5 ++++- QuickFIXn/Logger/NonSessionLog.cs | 3 +++ QuickFIXn/Logger/NullLog.cs | 3 +++ QuickFIXn/Logger/NullLogFactory.cs | 5 ++++- QuickFIXn/Logger/ScreenLog.cs | 5 ++++- QuickFIXn/Logger/ScreenLogFactory.cs | 5 ++++- QuickFIXn/ThreadedSocketAcceptor.cs | 2 +- QuickFIXn/Transport/SocketInitiator.cs | 2 +- 14 files changed, 35 insertions(+), 9 deletions(-) diff --git a/QuickFIXn/AbstractInitiator.cs b/QuickFIXn/AbstractInitiator.cs index 065592315..4a3a97006 100644 --- a/QuickFIXn/AbstractInitiator.cs +++ b/QuickFIXn/AbstractInitiator.cs @@ -26,7 +26,7 @@ public abstract class AbstractInitiator : IInitiator public bool IsStopped { get; private set; } = true; - [Obsolete] + [Obsolete("Use \"Microsoft.Extensions.Logging.ILoggerFactory\" instead of \"QuickFix.Logger.ILogFactory\".")] protected AbstractInitiator( IApplication app, IMessageStoreFactory storeFactory, diff --git a/QuickFIXn/Logger/CompositeLog.cs b/QuickFIXn/Logger/CompositeLog.cs index f204fc9dc..2c7f2d637 100644 --- a/QuickFIXn/Logger/CompositeLog.cs +++ b/QuickFIXn/Logger/CompositeLog.cs @@ -5,6 +5,7 @@ namespace QuickFix.Logger; /// /// File log implementation /// +[Obsolete("Use Microsoft.Extensions.Logging instead.")] internal class CompositeLog : ILog { private readonly ILog[] _logs; diff --git a/QuickFIXn/Logger/CompositeLogFactory.cs b/QuickFIXn/Logger/CompositeLogFactory.cs index 9a51d14df..4d11665eb 100644 --- a/QuickFIXn/Logger/CompositeLogFactory.cs +++ b/QuickFIXn/Logger/CompositeLogFactory.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; namespace QuickFix.Logger; @@ -6,6 +7,7 @@ namespace QuickFix.Logger; /// Allows multiple log factories to be used with QuickFIX/N. /// For example, you could log events to the console and also log all events and messages to a file. /// +[Obsolete("Use Microsoft.Extensions.Logging instead")] public class CompositeLogFactory : ILogFactory { private readonly ILogFactory[] _factories; diff --git a/QuickFIXn/Logger/FileLog.cs b/QuickFIXn/Logger/FileLog.cs index 2041ed650..d0166535a 100755 --- a/QuickFIXn/Logger/FileLog.cs +++ b/QuickFIXn/Logger/FileLog.cs @@ -7,6 +7,7 @@ namespace QuickFix.Logger; /// /// File log implementation /// +[Obsolete("Use Microsoft.Extensions.Logging instead")] public class FileLog : ILog { private readonly object _sync = new(); diff --git a/QuickFIXn/Logger/FileLogFactory.cs b/QuickFIXn/Logger/FileLogFactory.cs index fc9bfc568..b3051103e 100755 --- a/QuickFIXn/Logger/FileLogFactory.cs +++ b/QuickFIXn/Logger/FileLogFactory.cs @@ -1,8 +1,11 @@ -namespace QuickFix.Logger; +using System; + +namespace QuickFix.Logger; /// /// Creates a message store that stores messages in a file /// +[Obsolete("Use Microsoft.Extensions.Logging instead")] public class FileLogFactory : ILogFactory { private readonly SessionSettings _settings; diff --git a/QuickFIXn/Logger/ILog.cs b/QuickFIXn/Logger/ILog.cs index c77166dbc..c4ffafabb 100755 --- a/QuickFIXn/Logger/ILog.cs +++ b/QuickFIXn/Logger/ILog.cs @@ -5,6 +5,7 @@ namespace QuickFix.Logger; /// /// Session log for messages and events /// +[Obsolete("Use Microsoft.Extensions.Logging instead")] public interface ILog : IDisposable { /// diff --git a/QuickFIXn/Logger/ILogFactory.cs b/QuickFIXn/Logger/ILogFactory.cs index 231f1d566..89ba074b3 100755 --- a/QuickFIXn/Logger/ILogFactory.cs +++ b/QuickFIXn/Logger/ILogFactory.cs @@ -1,8 +1,11 @@ -namespace QuickFix.Logger; +using System; + +namespace QuickFix.Logger; /// /// Creates a log instance /// +[Obsolete("Use Microsoft.Extensions.Logging instead.")] public interface ILogFactory { /// diff --git a/QuickFIXn/Logger/NonSessionLog.cs b/QuickFIXn/Logger/NonSessionLog.cs index 2fa3ae5d4..40370b521 100644 --- a/QuickFIXn/Logger/NonSessionLog.cs +++ b/QuickFIXn/Logger/NonSessionLog.cs @@ -1,9 +1,12 @@ +using System; + namespace QuickFix.Logger; /// /// A logger that can be used when the calling logic cannot identify a session (which is rare). /// Does not create a log artifact until first write. /// +[Obsolete("Use Microsoft.Extensions.Logging instead.")] public class NonSessionLog : System.IDisposable { private readonly ILogFactory _logFactory; diff --git a/QuickFIXn/Logger/NullLog.cs b/QuickFIXn/Logger/NullLog.cs index cccf6cdac..381257e95 100755 --- a/QuickFIXn/Logger/NullLog.cs +++ b/QuickFIXn/Logger/NullLog.cs @@ -1,9 +1,12 @@  +using System; + namespace QuickFix.Logger; /// /// Log implementation that does not do anything /// +[Obsolete("Use Microsoft.Extensions.Logging instead.")] public sealed class NullLog : ILog { #region ILog Members diff --git a/QuickFIXn/Logger/NullLogFactory.cs b/QuickFIXn/Logger/NullLogFactory.cs index 1b0e74f97..b446679cf 100644 --- a/QuickFIXn/Logger/NullLogFactory.cs +++ b/QuickFIXn/Logger/NullLogFactory.cs @@ -1,5 +1,8 @@ -namespace QuickFix.Logger; +using System; +namespace QuickFix.Logger; + +[Obsolete("Use Microsoft.Extensions.Logging instead.")] public class NullLogFactory : ILogFactory { public NullLogFactory() { } diff --git a/QuickFIXn/Logger/ScreenLog.cs b/QuickFIXn/Logger/ScreenLog.cs index dd44516fc..0b0904d65 100755 --- a/QuickFIXn/Logger/ScreenLog.cs +++ b/QuickFIXn/Logger/ScreenLog.cs @@ -1,8 +1,11 @@ -namespace QuickFix.Logger; +using System; + +namespace QuickFix.Logger; /// /// FIXME - needs to log sessionIDs, timestamps, etc. /// +[Obsolete("Use Microsoft.Extensions.Logging instead.")] public class ScreenLog : ILog { private readonly object _sync = new (); diff --git a/QuickFIXn/Logger/ScreenLogFactory.cs b/QuickFIXn/Logger/ScreenLogFactory.cs index 2e426ac9c..771e855bb 100755 --- a/QuickFIXn/Logger/ScreenLogFactory.cs +++ b/QuickFIXn/Logger/ScreenLogFactory.cs @@ -1,5 +1,8 @@ -namespace QuickFix.Logger; +using System; +namespace QuickFix.Logger; + +[Obsolete("Use Microsoft.Extensions.Logging instead.")] public class ScreenLogFactory : ILogFactory { private const string SCREEN_LOG_SHOW_INCOMING = "ScreenLogShowIncoming"; diff --git a/QuickFIXn/ThreadedSocketAcceptor.cs b/QuickFIXn/ThreadedSocketAcceptor.cs index 8acfc14b2..f91fdd6c1 100755 --- a/QuickFIXn/ThreadedSocketAcceptor.cs +++ b/QuickFIXn/ThreadedSocketAcceptor.cs @@ -33,7 +33,7 @@ public class ThreadedSocketAcceptor : IAcceptor /// /// If null, a NullFactory will be used. /// If null, a DefaultMessageFactory will be created (using settings parameters) - [Obsolete] + [Obsolete("Use \"Microsoft.Extensions.Logging.ILoggerFactory\" instead of \"QuickFix.Logger.ILogFactory\".")] public ThreadedSocketAcceptor( IApplication application, IMessageStoreFactory storeFactory, diff --git a/QuickFIXn/Transport/SocketInitiator.cs b/QuickFIXn/Transport/SocketInitiator.cs index 1c330b1c3..66ec081f3 100644 --- a/QuickFIXn/Transport/SocketInitiator.cs +++ b/QuickFIXn/Transport/SocketInitiator.cs @@ -28,7 +28,7 @@ public class SocketInitiator : AbstractInitiator private readonly Dictionary _sessionToHostNum = new(); private readonly object _sync = new(); - [Obsolete] + [Obsolete("Use \"Microsoft.Extensions.Logging.ILoggerFactory\" instead of \"QuickFix.Logger.ILogFactory\".")] public SocketInitiator( IApplication application, IMessageStoreFactory storeFactory, From 581d65166e4bfabc1c51b8945a818f40b958e1bf Mon Sep 17 00:00:00 2001 From: JK Date: Wed, 27 Nov 2024 12:54:33 -0500 Subject: [PATCH 03/23] Make transitional classes obsolete --- Examples/Executor/Program.cs | 8 ++++++-- Examples/SimpleAcceptor/Program.cs | 5 ++++- Examples/TradeClient/Program.cs | 7 +++++-- QuickFIXn/Logger/FileLoggerProvider.cs | 3 +++ QuickFIXn/Logger/NullLoggerProvider.cs | 6 +++++- QuickFIXn/Logger/ScreenLoggerProvider.cs | 3 +++ 6 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Examples/Executor/Program.cs b/Examples/Executor/Program.cs index 58eab860c..3263745e1 100644 --- a/Examples/Executor/Program.cs +++ b/Examples/Executor/Program.cs @@ -28,8 +28,12 @@ static void Main(string[] args) SessionSettings settings = new SessionSettings(args[0]); IApplication executorApp = new Executor(); IMessageStoreFactory storeFactory = new FileStoreFactory(settings); - var loggerFactory = new LoggerFactory([new ScreenLoggerProvider(settings)]); - // var loggerFactory = new LoggerFactory([new FileLogProvider(settings)]); + var loggerFactory = LoggerFactory.Create(builder => + { + builder.SetMinimumLevel(LogLevel.Trace); + builder.AddProvider(new ScreenLoggerProvider(settings)); + builder.AddProvider(new FileLoggerProvider(settings)); + }); ThreadedSocketAcceptor acceptor = new ThreadedSocketAcceptor(executorApp, storeFactory, settings, loggerFactory); HttpServer srv = new HttpServer(HttpServerPrefix, settings); diff --git a/Examples/SimpleAcceptor/Program.cs b/Examples/SimpleAcceptor/Program.cs index 230ec2702..ed7a460ee 100644 --- a/Examples/SimpleAcceptor/Program.cs +++ b/Examples/SimpleAcceptor/Program.cs @@ -31,7 +31,10 @@ static void Main(string[] args) SessionSettings settings = new SessionSettings(args[0]); IApplication app = new SimpleAcceptorApp(); IMessageStoreFactory storeFactory = new FileStoreFactory(settings); - var loggerFactory = new LoggerFactory([new FileLoggerProvider(settings)]); + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddProvider(new FileLoggerProvider(settings)); + }); IAcceptor acceptor = new ThreadedSocketAcceptor(app, storeFactory, settings, loggerFactory); acceptor.Start(); diff --git a/Examples/TradeClient/Program.cs b/Examples/TradeClient/Program.cs index 44b0db354..9af620761 100644 --- a/Examples/TradeClient/Program.cs +++ b/Examples/TradeClient/Program.cs @@ -32,8 +32,11 @@ static void Main(string[] args) QuickFix.SessionSettings settings = new QuickFix.SessionSettings(file); TradeClientApp application = new TradeClientApp(); IMessageStoreFactory storeFactory = new FileStoreFactory(settings); - var loggerFactory = new LoggerFactory([new ScreenLoggerProvider(settings)]); - // var loggerFactory = new LoggerFactory([new FileLogProvider(settings)]); + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddProvider(new ScreenLoggerProvider(settings)); + // builder.AddProvider(new FileLogProvider(settings)); + }); QuickFix.Transport.SocketInitiator initiator = new QuickFix.Transport.SocketInitiator(application, storeFactory, settings, loggerFactory); // this is a developer-test kludge. do not emulate. diff --git a/QuickFIXn/Logger/FileLoggerProvider.cs b/QuickFIXn/Logger/FileLoggerProvider.cs index c471a8e15..a517458c0 100644 --- a/QuickFIXn/Logger/FileLoggerProvider.cs +++ b/QuickFIXn/Logger/FileLoggerProvider.cs @@ -5,6 +5,9 @@ namespace QuickFix.Logger; +[Obsolete("This class is provided to ease migration from the old logging system to Microsoft.Extensions.Logging." + + "It is an attempt to maintain the behavior of the previous FileLog and FileLogFactory while plugging into the Microsoft.Extensions.Logging ecosystem. " + + "Consider using the more robust logging options available in the .NET ecosystem, like the MS Console logging provider, Serilog and NLog.")] public class FileLoggerProvider : ILoggerProvider { private readonly SessionSettings _settings; diff --git a/QuickFIXn/Logger/NullLoggerProvider.cs b/QuickFIXn/Logger/NullLoggerProvider.cs index 2b311ac96..29946e056 100644 --- a/QuickFIXn/Logger/NullLoggerProvider.cs +++ b/QuickFIXn/Logger/NullLoggerProvider.cs @@ -1,7 +1,11 @@ -using Microsoft.Extensions.Logging; +using System; +using Microsoft.Extensions.Logging; namespace QuickFix.Logger; +[Obsolete("This class is provided to ease migration from the old logging system to Microsoft.Extensions.Logging." + + "It is an attempt to maintain the behavior of the previous NullLog and NullLogFactory while plugging into the Microsoft.Extensions.Logging ecosystem. " + + "Consider using the more robust logging options available in the .NET ecosystem, like the MS Console logging provider, Serilog and NLog.")] public class NullLoggerProvider : ILoggerProvider { public void Dispose(){} diff --git a/QuickFIXn/Logger/ScreenLoggerProvider.cs b/QuickFIXn/Logger/ScreenLoggerProvider.cs index 536fcf6ef..dba56df8e 100755 --- a/QuickFIXn/Logger/ScreenLoggerProvider.cs +++ b/QuickFIXn/Logger/ScreenLoggerProvider.cs @@ -4,6 +4,9 @@ namespace QuickFix.Logger; +[Obsolete("This class is provided to ease migration from the old logging system to Microsoft.Extensions.Logging." + + "It is an attempt to maintain the behavior of the previous ScreenLog and ScreenLogFactory while plugging into the Microsoft.Extensions.Logging ecosystem. " + + "Consider using the more robust logging options available in the .NET ecosystem, like the MS Console logging provider, Serilog and NLog.")] public class ScreenLoggerProvider : ILoggerProvider { private const string SCREEN_LOG_SHOW_INCOMING = "ScreenLogShowIncoming"; From ed7f5919de21c2224ced8693bdeeac133742a2a4 Mon Sep 17 00:00:00 2001 From: JK Date: Wed, 27 Nov 2024 15:07:32 -0500 Subject: [PATCH 04/23] Add message type to log scope --- QuickFIXn/Logger/FileLogger.cs | 5 +---- QuickFIXn/Session.cs | 32 +++++++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/QuickFIXn/Logger/FileLogger.cs b/QuickFIXn/Logger/FileLogger.cs index 017c99e16..000a87f2f 100644 --- a/QuickFIXn/Logger/FileLogger.cs +++ b/QuickFIXn/Logger/FileLogger.cs @@ -87,10 +87,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; - public IDisposable? BeginScope(TState state) where TState : notnull - { - throw new NotImplementedException(); - } + public IDisposable? BeginScope(TState state) where TState : notnull => default!; public void Dispose() { diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index f61c007aa..f8156fea1 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -349,14 +349,22 @@ public virtual bool Send(Message message) /// Sends a message /// /// + /// /// - public bool Send(string message) + public bool Send(string message, string messageType) { lock (_sync) { if (_responder is null) return false; - Log.Log(LogLevel.Information, LogEventIds.OutgoingMessage, "{Message}", message); + using (Log.BeginScope(new Dictionary + { + {"MessageType", messageType} + })) + { + Log.Log(LogLevel.Information, LogEventIds.OutgoingMessage, "{Message}", message); + } + return _responder.Send(message); } } @@ -513,7 +521,21 @@ public void Next(string msgStr) /// private void NextMessage(string msgStr) { - Log.Log(LogLevel.Information, LogEventIds.IncomingMessage, "{Message}", msgStr); + try + { + var messageType = Message.IdentifyType(msgStr); + using (Log.BeginScope(new Dictionary + { + {"MessageType", messageType.Value} + })) + { + Log.Log(LogLevel.Information, LogEventIds.IncomingMessage, "{Message}", msgStr); + } + } + catch (Exception) + { + Log.Log(LogLevel.Information, LogEventIds.IncomingMessage, "{Message}", msgStr); + } MessageBuilder msgBuilder = new MessageBuilder( msgStr, @@ -789,7 +811,7 @@ protected void NextResendRequest(Message resendReq) { GenerateSequenceReset(resendReq, begin, msgSeqNum); } - Send(msg.ConstructString()); + Send(msg.ConstructString(), msg.Header.GetString(Tags.MsgType)); begin = 0; } current = msgSeqNum + 1; @@ -1572,7 +1594,7 @@ protected bool SendRaw(Message message, SeqNumType seqNum) string messageString = message.ConstructString(); if (0 == seqNum) Persist(message, messageString); - return Send(messageString); + return Send(messageString, message.Header.GetString(Tags.MsgType)); } } From 57d7ae36f199b7ada62abf2f0b0c6dd20b0b4ba6 Mon Sep 17 00:00:00 2001 From: JK Date: Tue, 10 Dec 2024 12:33:22 -0500 Subject: [PATCH 05/23] Forward to single constructor --- QuickFIXn/AbstractInitiator.cs | 16 ++++------------ QuickFIXn/ThreadedSocketAcceptor.cs | 26 +++++--------------------- 2 files changed, 9 insertions(+), 33 deletions(-) diff --git a/QuickFIXn/AbstractInitiator.cs b/QuickFIXn/AbstractInitiator.cs index 4a3a97006..7c17ac390 100644 --- a/QuickFIXn/AbstractInitiator.cs +++ b/QuickFIXn/AbstractInitiator.cs @@ -32,19 +32,11 @@ protected AbstractInitiator( IMessageStoreFactory storeFactory, SessionSettings settings, ILogFactory? logFactoryNullable = null, - IMessageFactory? messageFactoryNullable = null) - { - _settings = settings; - ILoggerFactory logFactory = logFactoryNullable is null + IMessageFactory? messageFactoryNullable = null) : this(app, storeFactory, settings, + logFactoryNullable is null ? NullLoggerFactory.Instance - : new LogFactoryAdapter(logFactoryNullable, settings); - var msgFactory = messageFactoryNullable ?? new DefaultMessageFactory(); - _sessionFactory = new SessionFactory(app, storeFactory, logFactory, msgFactory); - _nonSessionLog = logFactory.CreateLogger("QuickFix"); - - HashSet definedSessions = _settings.GetSessions(); - if (0 == definedSessions.Count) - throw new ConfigError("No sessions defined"); + : new LogFactoryAdapter(logFactoryNullable, settings), messageFactoryNullable) + { } protected AbstractInitiator( diff --git a/QuickFIXn/ThreadedSocketAcceptor.cs b/QuickFIXn/ThreadedSocketAcceptor.cs index f91fdd6c1..0f3b2e340 100755 --- a/QuickFIXn/ThreadedSocketAcceptor.cs +++ b/QuickFIXn/ThreadedSocketAcceptor.cs @@ -39,29 +39,12 @@ public ThreadedSocketAcceptor( IMessageStoreFactory storeFactory, SessionSettings settings, ILogFactory? logFactory = null, - IMessageFactory? messageFactory = null) + IMessageFactory? messageFactory = null) : this(application, storeFactory, settings, + logFactory is null ? NullLoggerFactory.Instance : new LogFactoryAdapter(logFactory, settings), + messageFactory) { - ILoggerFactory lf = logFactory is null - ? NullLoggerFactory.Instance - : new LogFactoryAdapter(logFactory, settings); - IMessageFactory mf = messageFactory ?? new DefaultMessageFactory(); - _settings = settings; - _sessionFactory = new SessionFactory(application, storeFactory, lf, mf); - _nonSessionLog = lf.CreateLogger("QuickFix"); - - try - { - foreach (SessionID sessionId in settings.GetSessions()) - { - SettingsDictionary dict = settings.Get(sessionId); - CreateSession(sessionId, dict); - } - } - catch (Exception e) - { - throw new ConfigError(e.Message, e); - } } + /// /// Create a ThreadedSocketAcceptor /// @@ -214,6 +197,7 @@ private void LogoutAllSessions(bool force) } catch (Exception e) { + session.Log.Log(LogLevel.Critical, new EventId(), "", new Exception(), (a, b) => ""); session.Log.Log(LogLevel.Error, e, "Error during logout of Session {SessionID}: {Message}", session.SessionID, e.Message); } From d6b4535f5268c91b8838d84f12b0cf1af9a4e20c Mon Sep 17 00:00:00 2001 From: JK Date: Tue, 10 Dec 2024 13:22:24 -0500 Subject: [PATCH 06/23] Delete nulllogger and nullloggerprovider --- AcceptanceTest/TestBase.cs | 3 ++- QuickFIXn/Logger/NullLogger.cs | 19 ------------------- QuickFIXn/Logger/NullLoggerProvider.cs | 14 -------------- QuickFIXn/SessionFactory.cs | 2 +- UnitTests/SessionStateTest.cs | 9 +++------ UnitTests/SessionTest.cs | 6 +++--- 6 files changed, 9 insertions(+), 44 deletions(-) delete mode 100755 QuickFIXn/Logger/NullLogger.cs delete mode 100644 QuickFIXn/Logger/NullLoggerProvider.cs diff --git a/AcceptanceTest/TestBase.cs b/AcceptanceTest/TestBase.cs index f11da92d0..b7166d9b2 100644 --- a/AcceptanceTest/TestBase.cs +++ b/AcceptanceTest/TestBase.cs @@ -3,6 +3,7 @@ using System.IO; using System.Net; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using QuickFix.Logger; using QuickFix.Store; @@ -31,7 +32,7 @@ public void Setup() } else { - loggerFactory.AddProvider(new NullLoggerProvider()); + loggerFactory.AddProvider(NullLoggerProvider.Instance); } _acceptor = new ThreadedSocketAcceptor(testApp, storeFactory, settings, loggerFactory); diff --git a/QuickFIXn/Logger/NullLogger.cs b/QuickFIXn/Logger/NullLogger.cs deleted file mode 100755 index c1a025e38..000000000 --- a/QuickFIXn/Logger/NullLogger.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using Microsoft.Extensions.Logging; - -namespace QuickFix.Logger; - -/// -/// Log implementation that does not do anything -/// -public sealed class NullLogger : ILogger -{ - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, - Func formatter) - { - } - - public bool IsEnabled(LogLevel logLevel) => false; - - public IDisposable? BeginScope(TState state) where TState : notnull => default!; -} \ No newline at end of file diff --git a/QuickFIXn/Logger/NullLoggerProvider.cs b/QuickFIXn/Logger/NullLoggerProvider.cs deleted file mode 100644 index 29946e056..000000000 --- a/QuickFIXn/Logger/NullLoggerProvider.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using Microsoft.Extensions.Logging; - -namespace QuickFix.Logger; - -[Obsolete("This class is provided to ease migration from the old logging system to Microsoft.Extensions.Logging." + - "It is an attempt to maintain the behavior of the previous NullLog and NullLogFactory while plugging into the Microsoft.Extensions.Logging ecosystem. " + - "Consider using the more robust logging options available in the .NET ecosystem, like the MS Console logging provider, Serilog and NLog.")] -public class NullLoggerProvider : ILoggerProvider -{ - public void Dispose(){} - - public ILogger CreateLogger(string categoryName) => new NullLogger(); -} \ No newline at end of file diff --git a/QuickFIXn/SessionFactory.cs b/QuickFIXn/SessionFactory.cs index b05f663ee..83f408b52 100755 --- a/QuickFIXn/SessionFactory.cs +++ b/QuickFIXn/SessionFactory.cs @@ -32,7 +32,7 @@ public SessionFactory( _application = app; _messageStoreFactory = storeFactory; - _loggerFactory = loggerFactory ?? new NullLoggerFactory(); + _loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; _messageFactory = messageFactory ?? new DefaultMessageFactory(); } diff --git a/UnitTests/SessionStateTest.cs b/UnitTests/SessionStateTest.cs index d195eaffc..83e713470 100755 --- a/UnitTests/SessionStateTest.cs +++ b/UnitTests/SessionStateTest.cs @@ -1,11 +1,10 @@ -using System; -using NUnit.Framework; +using NUnit.Framework; using QuickFix; using System.Collections; using System.Collections.Generic; using System.IO; using System.Threading; -using QuickFix.Logger; +using Microsoft.Extensions.Logging.Abstractions; using QuickFix.Store; namespace UnitTests @@ -151,10 +150,8 @@ public void ThreadSafeSetAndGet() { FileStore store = (FileStore)factory.Create(sessionId); - NullLogger log = new NullLogger(); - //Set up sessionstate - SessionState state = new SessionState(true, log, 1, store); + SessionState state = new SessionState(true, NullLogger.Instance, 1, store); Hashtable errorsTable = Hashtable.Synchronized(new Hashtable());//used in more than 1 thread at a time Hashtable setTable = new Hashtable(1000);//only used in 1 thread at a time diff --git a/UnitTests/SessionTest.cs b/UnitTests/SessionTest.cs index d485c7df0..fce5be3ae 100755 --- a/UnitTests/SessionTest.cs +++ b/UnitTests/SessionTest.cs @@ -5,7 +5,7 @@ using NUnit.Framework; using System.Threading; using Microsoft.Extensions.Logging; -using QuickFix.Logger; +using Microsoft.Extensions.Logging.Abstractions; using QuickFix.Store; namespace UnitTests; @@ -40,7 +40,7 @@ public void Setup() _config.SetString(QuickFix.SessionSettings.END_TIME, "00:00:00"); _settings.Set(_sessionId, _config); - var loggerFactory = new LoggerFactory([new NullLoggerProvider()]); // use QuickFix.ScreenLogFactory(settings) if you need to see output + var loggerFactory = new LoggerFactory([NullLoggerProvider.Instance]); // use QuickFix.ScreenLogFactory(settings) if you need to see output // acceptor _session = new QuickFix.Session(false, _application, new MemoryStoreFactory(), _sessionId, @@ -760,7 +760,7 @@ public void TestApplicationExtension() var mockApp = new SessionTestSupport.MockApplicationExt(); _session = new QuickFix.Session(true, mockApp, new MemoryStoreFactory(), _sessionId, new QuickFix.DataDictionaryProvider(), new QuickFix.SessionSchedule(_config), 0, - new LoggerFactory([new NullLoggerProvider()]), new QuickFix.DefaultMessageFactory(), "blah"); + new LoggerFactory([NullLoggerProvider.Instance]), new QuickFix.DefaultMessageFactory(), "blah"); _session.SetResponder(_responder); _session.CheckLatency = false; From 3235611acd91518197e3869a84e8a6d25e42da1f Mon Sep 17 00:00:00 2001 From: JK Date: Tue, 10 Dec 2024 13:51:06 -0500 Subject: [PATCH 07/23] Relax MEL version requirements to allow v6 to 8 --- QuickFIXn/QuickFix.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QuickFIXn/QuickFix.csproj b/QuickFIXn/QuickFix.csproj index e6a1c9325..e82d837ed 100644 --- a/QuickFIXn/QuickFix.csproj +++ b/QuickFIXn/QuickFix.csproj @@ -45,6 +45,6 @@ - + From f66e5576477b5dad6b81e4a484930a238f5a7500 Mon Sep 17 00:00:00 2001 From: JK Date: Tue, 10 Dec 2024 17:22:18 -0500 Subject: [PATCH 08/23] Undo public API change in Send(string message) --- QuickFIXn/Session.cs | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index f8156fea1..0bcb171dd 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -349,20 +349,24 @@ public virtual bool Send(Message message) /// Sends a message /// /// - /// /// - public bool Send(string message, string messageType) + public bool Send(string message) { lock (_sync) { if (_responder is null) return false; - using (Log.BeginScope(new Dictionary - { - {"MessageType", messageType} - })) + + const LogLevel messagesLogLevel = LogLevel.Information; + if (Log.IsEnabled(messagesLogLevel)) { - Log.Log(LogLevel.Information, LogEventIds.OutgoingMessage, "{Message}", message); + using (Log.BeginScope(new Dictionary + { + {"MessageType", Message.GetMsgType(message)} + })) + { + Log.Log(messagesLogLevel, LogEventIds.OutgoingMessage, "{Message}", message); + } } return _responder.Send(message); @@ -521,20 +525,23 @@ public void Next(string msgStr) /// private void NextMessage(string msgStr) { + const LogLevel messageLogLevel = LogLevel.Information; try { - var messageType = Message.IdentifyType(msgStr); - using (Log.BeginScope(new Dictionary - { - {"MessageType", messageType.Value} - })) + if (Log.IsEnabled(messageLogLevel)) { - Log.Log(LogLevel.Information, LogEventIds.IncomingMessage, "{Message}", msgStr); + using (Log.BeginScope(new Dictionary + { + {"MessageType", Message.GetMsgType(msgStr)} + })) + { + Log.Log(messageLogLevel, LogEventIds.IncomingMessage, "{Message}", msgStr); + } } } catch (Exception) { - Log.Log(LogLevel.Information, LogEventIds.IncomingMessage, "{Message}", msgStr); + Log.Log(messageLogLevel, LogEventIds.IncomingMessage, "{Message}", msgStr); } MessageBuilder msgBuilder = new MessageBuilder( @@ -811,7 +818,7 @@ protected void NextResendRequest(Message resendReq) { GenerateSequenceReset(resendReq, begin, msgSeqNum); } - Send(msg.ConstructString(), msg.Header.GetString(Tags.MsgType)); + Send(msg.ConstructString()); begin = 0; } current = msgSeqNum + 1; @@ -1594,7 +1601,7 @@ protected bool SendRaw(Message message, SeqNumType seqNum) string messageString = message.ConstructString(); if (0 == seqNum) Persist(message, messageString); - return Send(messageString, message.Header.GetString(Tags.MsgType)); + return Send(messageString); } } From 67e7fee4ef79bb569d17f4981d3c4d75fd27f1ef Mon Sep 17 00:00:00 2001 From: JK Date: Tue, 10 Dec 2024 19:41:06 -0500 Subject: [PATCH 09/23] Merge transitional classes into existing log implementations --- QuickFIXn/Logger/FileLog.cs | 29 ++++++- QuickFIXn/Logger/FileLogger.cs | 97 ------------------------ QuickFIXn/Logger/FileLoggerProvider.cs | 2 +- QuickFIXn/Logger/NonSessionFileLogger.cs | 4 +- QuickFIXn/Logger/ScreenLog.cs | 34 ++++++--- QuickFIXn/Logger/ScreenLogger.cs | 43 ----------- QuickFIXn/Logger/ScreenLoggerProvider.cs | 2 +- UnitTests/Logger/FileLogTests.cs | 12 +-- UnitTests/ThreadedSocketReactorTests.cs | 2 +- 9 files changed, 62 insertions(+), 163 deletions(-) delete mode 100644 QuickFIXn/Logger/FileLogger.cs delete mode 100755 QuickFIXn/Logger/ScreenLogger.cs diff --git a/QuickFIXn/Logger/FileLog.cs b/QuickFIXn/Logger/FileLog.cs index d0166535a..093bb100e 100755 --- a/QuickFIXn/Logger/FileLog.cs +++ b/QuickFIXn/Logger/FileLog.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.Extensions.Logging; using QuickFix.Fields.Converters; using QuickFix.Util; @@ -8,7 +9,7 @@ namespace QuickFix.Logger; /// File log implementation /// [Obsolete("Use Microsoft.Extensions.Logging instead")] -public class FileLog : ILog +public class FileLog : ILog, ILogger { private readonly object _sync = new(); @@ -120,6 +121,32 @@ public void OnEvent(string s) } } + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, + Func formatter) + { + if (!IsEnabled(logLevel)) return; + if (eventId == LogEventIds.IncomingMessage || eventId == LogEventIds.OutgoingMessage) + { + lock (_sync) + { + _messageLog.WriteLine( + $"{DateTimeConverter.ToFIX(DateTime.UtcNow, TimeStampPrecision.Millisecond)} : {formatter(state, exception)}"); + } + } + else + { + lock (_sync) + { + _eventLog.WriteLine( + $"{DateTimeConverter.ToFIX(DateTime.UtcNow, TimeStampPrecision.Millisecond)} : {formatter(state, exception)}"); + } + } + } + + public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; + + public IDisposable BeginScope(TState state) where TState : notnull => default!; + #endregion #region IDisposable Members diff --git a/QuickFIXn/Logger/FileLogger.cs b/QuickFIXn/Logger/FileLogger.cs deleted file mode 100644 index 000a87f2f..000000000 --- a/QuickFIXn/Logger/FileLogger.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using Microsoft.Extensions.Logging; -using QuickFix.Fields.Converters; -using QuickFix.Util; - -namespace QuickFix.Logger; - -public class FileLogger : ILogger, IDisposable -{ - private readonly object _messagesLock = new(); - private readonly object _eventsLock = new(); - - private System.IO.StreamWriter _messageLog; - private System.IO.StreamWriter _eventLog; - - private readonly string _messageLogFileName; - private readonly string _eventLogFileName; - - /// - /// - /// - /// - /// All back or forward slashes in this path will be converted as needed to the running platform's preferred - /// path separator (i.e. "/" will become "\" on windows, else "\" will become "/" on all other platforms) - /// - /// - internal FileLogger(string fileLogPath, SessionID sessionId) - { - string prefix = Prefix(sessionId); - - string normalizedPath = StringUtil.FixSlashes(fileLogPath); - - if (!System.IO.Directory.Exists(normalizedPath)) - System.IO.Directory.CreateDirectory(normalizedPath); - - _messageLogFileName = System.IO.Path.Combine(normalizedPath, prefix + ".messages.current.log"); - _eventLogFileName = System.IO.Path.Combine(normalizedPath, prefix + ".event.current.log"); - - _messageLog = new System.IO.StreamWriter(_messageLogFileName, true); - _eventLog = new System.IO.StreamWriter(_eventLogFileName, true); - - _messageLog.AutoFlush = true; - _eventLog.AutoFlush = true; - } - - public static string Prefix(SessionID sessionId) - { - System.Text.StringBuilder prefix = new System.Text.StringBuilder(sessionId.BeginString) - .Append('-').Append(sessionId.SenderCompID); - if (SessionID.IsSet(sessionId.SenderSubID)) - prefix.Append('_').Append(sessionId.SenderSubID); - if (SessionID.IsSet(sessionId.SenderLocationID)) - prefix.Append('_').Append(sessionId.SenderLocationID); - prefix.Append('-').Append(sessionId.TargetCompID); - if (SessionID.IsSet(sessionId.TargetSubID)) - prefix.Append('_').Append(sessionId.TargetSubID); - if (SessionID.IsSet(sessionId.TargetLocationID)) - prefix.Append('_').Append(sessionId.TargetLocationID); - - if (SessionID.IsSet(sessionId.SessionQualifier)) - prefix.Append('-').Append(sessionId.SessionQualifier); - - return prefix.ToString(); - } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, - Func formatter) - { - if (!IsEnabled(logLevel)) return; - if (eventId == LogEventIds.IncomingMessage || eventId == LogEventIds.OutgoingMessage) - { - lock (_messagesLock) - { - _messageLog.WriteLine( - $"{DateTimeConverter.ToFIX(DateTime.UtcNow, TimeStampPrecision.Millisecond)} : {formatter(state, exception)}"); - } - } - else - { - lock (_eventsLock) - { - _eventLog.WriteLine( - $"{DateTimeConverter.ToFIX(DateTime.UtcNow, TimeStampPrecision.Millisecond)} : {formatter(state, exception)}"); - } - } - } - - public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; - - public IDisposable? BeginScope(TState state) where TState : notnull => default!; - - public void Dispose() - { - _messageLog.Dispose(); - _eventLog.Dispose(); - } -} \ No newline at end of file diff --git a/QuickFIXn/Logger/FileLoggerProvider.cs b/QuickFIXn/Logger/FileLoggerProvider.cs index a517458c0..d81ad984f 100644 --- a/QuickFIXn/Logger/FileLoggerProvider.cs +++ b/QuickFIXn/Logger/FileLoggerProvider.cs @@ -28,7 +28,7 @@ public ILogger CreateLogger(string categoryName) var defaultSessionId = new SessionID("Non", "Session", "Log"); return _loggers.GetOrAdd(sessionId ?? defaultSessionId, sessionId is not null - ? new FileLogger(_settings.Get(sessionId).GetString(SessionSettings.FILE_LOG_PATH), sessionId) + ? new FileLog(_settings.Get(sessionId).GetString(SessionSettings.FILE_LOG_PATH), sessionId) : new NonSessionFileLogger(_settings.Get().GetString(SessionSettings.FILE_LOG_PATH), defaultSessionId)); } diff --git a/QuickFIXn/Logger/NonSessionFileLogger.cs b/QuickFIXn/Logger/NonSessionFileLogger.cs index 3caf09384..f5a79f174 100644 --- a/QuickFIXn/Logger/NonSessionFileLogger.cs +++ b/QuickFIXn/Logger/NonSessionFileLogger.cs @@ -8,11 +8,11 @@ namespace QuickFix.Logger; /// internal class NonSessionFileLogger : ILogger { - private readonly Lazy _fileLog; + private readonly Lazy _fileLog; internal NonSessionFileLogger(string fileLogPath, SessionID sessionId) { - _fileLog = new Lazy(() => new FileLogger(fileLogPath, sessionId)); + _fileLog = new Lazy(() => new FileLog(fileLogPath, sessionId)); } public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, diff --git a/QuickFIXn/Logger/ScreenLog.cs b/QuickFIXn/Logger/ScreenLog.cs index 0b0904d65..d364520f1 100755 --- a/QuickFIXn/Logger/ScreenLog.cs +++ b/QuickFIXn/Logger/ScreenLog.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.Extensions.Logging; namespace QuickFix.Logger; @@ -6,7 +7,7 @@ namespace QuickFix.Logger; /// FIXME - needs to log sessionIDs, timestamps, etc. /// [Obsolete("Use Microsoft.Extensions.Logging instead.")] -public class ScreenLog : ILog +public class ScreenLog : ILog, ILogger { private readonly object _sync = new (); private readonly bool _logIncoming; @@ -59,16 +60,27 @@ public void OnEvent(string s) } #endregion - #region IDisposable implementation - public void Dispose() + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, + Func formatter) { - Dispose(true); - System.GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - // Nothing to dispose of... + if (!IsEnabled(logLevel)) return; + if (eventId == LogEventIds.IncomingMessage && _logIncoming) + { + Console.WriteLine($" {formatter(state, exception).Replace(Message.SOH, '|')}"); + } + else if (eventId == LogEventIds.OutgoingMessage && _logOutgoing) + { + Console.WriteLine($" {formatter(state, exception).Replace(Message.SOH, '|')}"); + } + else if (_logEvent) + { + Console.WriteLine($" {formatter(state, exception)}"); + } } - ~ScreenLog() => Dispose(false); - #endregion + + public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; + + public IDisposable BeginScope(TState state) where TState : notnull => default!; + + public void Dispose(){} } diff --git a/QuickFIXn/Logger/ScreenLogger.cs b/QuickFIXn/Logger/ScreenLogger.cs deleted file mode 100755 index 28b67a8da..000000000 --- a/QuickFIXn/Logger/ScreenLogger.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using Microsoft.Extensions.Logging; - -namespace QuickFix.Logger; - -/// -/// FIXME - needs to log sessionIDs, timestamps, etc. -/// -public class ScreenLogger : ILogger -{ - private readonly bool _logIncoming; - private readonly bool _logOutgoing; - private readonly bool _logEvent; - - public ScreenLogger(bool logIncoming, bool logOutgoing, bool logEvent) - { - _logIncoming = logIncoming; - _logOutgoing = logOutgoing; - _logEvent = logEvent; - } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, - Func formatter) - { - if (!IsEnabled(logLevel)) return; - if (eventId == LogEventIds.IncomingMessage && _logIncoming) - { - Console.WriteLine($" {formatter(state, exception).Replace(Message.SOH, '|')}"); - } - else if (eventId == LogEventIds.OutgoingMessage && _logOutgoing) - { - Console.WriteLine($" {formatter(state, exception).Replace(Message.SOH, '|')}"); - } - else if (_logEvent) - { - Console.WriteLine($" {formatter(state, exception)}"); - } - } - - public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; - - public IDisposable? BeginScope(TState state) where TState : notnull => default!; -} \ No newline at end of file diff --git a/QuickFIXn/Logger/ScreenLoggerProvider.cs b/QuickFIXn/Logger/ScreenLoggerProvider.cs index dba56df8e..ecb1f02ce 100755 --- a/QuickFIXn/Logger/ScreenLoggerProvider.cs +++ b/QuickFIXn/Logger/ScreenLoggerProvider.cs @@ -49,7 +49,7 @@ public ILogger CreateLogger(string categoryName) logEvent = _logEvent || dict.IsBoolPresentAndTrue(SCREEN_LOG_SHOW_EVENTS); } - return new ScreenLogger(logIncoming, logOutgoing, logEvent); + return new ScreenLog(logIncoming, logOutgoing, logEvent); } public void Dispose() diff --git a/UnitTests/Logger/FileLogTests.cs b/UnitTests/Logger/FileLogTests.cs index 5d2bbb26c..1416834f6 100644 --- a/UnitTests/Logger/FileLogTests.cs +++ b/UnitTests/Logger/FileLogTests.cs @@ -8,7 +8,7 @@ namespace UnitTests.Logger; [TestFixture] public class FileLogTests { - private FileLogger? _log; + private FileLog? _log; [SetUp] public void Setup() @@ -27,18 +27,18 @@ public void TestPrefix() QuickFix.SessionID someSessionId = new QuickFix.SessionID("FIX.4.4", "sender", "target"); QuickFix.SessionID someSessionIdWithQualifier = new QuickFix.SessionID("FIX.4.3", "sender", "target", "foo"); - Assert.That(FileLogger.Prefix(someSessionId), Is.EqualTo("FIX.4.4-sender-target")); - Assert.That(FileLogger.Prefix(someSessionIdWithQualifier), Is.EqualTo("FIX.4.3-sender-target-foo")); + Assert.That(FileLog.Prefix(someSessionId), Is.EqualTo("FIX.4.4-sender-target")); + Assert.That(FileLog.Prefix(someSessionIdWithQualifier), Is.EqualTo("FIX.4.3-sender-target-foo")); } [Test] public void TestPrefixForSubsAndLocation() { QuickFix.SessionID sessionIdWithSubsAndLocation = new QuickFix.SessionID("FIX.4.2", "SENDERCOMP", "SENDERSUB", "SENDERLOC", "TARGETCOMP", "TARGETSUB", "TARGETLOC"); - Assert.That(FileLogger.Prefix(sessionIdWithSubsAndLocation), Is.EqualTo("FIX.4.2-SENDERCOMP_SENDERSUB_SENDERLOC-TARGETCOMP_TARGETSUB_TARGETLOC")); + Assert.That(FileLog.Prefix(sessionIdWithSubsAndLocation), Is.EqualTo("FIX.4.2-SENDERCOMP_SENDERSUB_SENDERLOC-TARGETCOMP_TARGETSUB_TARGETLOC")); QuickFix.SessionID sessionIdWithSubsNoLocation = new QuickFix.SessionID("FIX.4.2", "SENDERCOMP", "SENDERSUB", "TARGETCOMP", "TARGETSUB"); - Assert.That(FileLogger.Prefix(sessionIdWithSubsNoLocation), Is.EqualTo("FIX.4.2-SENDERCOMP_SENDERSUB-TARGETCOMP_TARGETSUB")); + Assert.That(FileLog.Prefix(sessionIdWithSubsNoLocation), Is.EqualTo("FIX.4.2-SENDERCOMP_SENDERSUB-TARGETCOMP_TARGETSUB")); } [Test] @@ -59,7 +59,7 @@ public void TestGeneratedFileName() settings.Set(sessionId, config); var fileLogProvider = new FileLoggerProvider(settings); - _log = (FileLogger) fileLogProvider.CreateLogger($"QuickFix.{sessionId}"); + _log = (FileLog) fileLogProvider.CreateLogger($"QuickFix.{sessionId}"); _log.Log(LogLevel.Debug, "some event"); _log.Log(LogLevel.Information, LogEventIds.IncomingMessage, "some incoming"); diff --git a/UnitTests/ThreadedSocketReactorTests.cs b/UnitTests/ThreadedSocketReactorTests.cs index cfb58cca9..05c81fdb9 100644 --- a/UnitTests/ThreadedSocketReactorTests.cs +++ b/UnitTests/ThreadedSocketReactorTests.cs @@ -49,7 +49,7 @@ public void TestStartOnBusyPort() new IPEndPoint(IPAddress.Loopback, port), settings, acceptorSocketDescriptor: null, - new ScreenLogger(true, true, true)); + new ScreenLog(true, true, true)); var stdOut = GetStdOut(); var ex = Assert.Throws(delegate { testingObject.Run(); })!; From b3ad725ab2bb091678845047b109dff227838e06 Mon Sep 17 00:00:00 2001 From: JK Date: Wed, 11 Dec 2024 20:29:35 -0500 Subject: [PATCH 10/23] Remove stray log --- QuickFIXn/ThreadedSocketAcceptor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/QuickFIXn/ThreadedSocketAcceptor.cs b/QuickFIXn/ThreadedSocketAcceptor.cs index 0f3b2e340..f6ea09a96 100755 --- a/QuickFIXn/ThreadedSocketAcceptor.cs +++ b/QuickFIXn/ThreadedSocketAcceptor.cs @@ -197,7 +197,6 @@ private void LogoutAllSessions(bool force) } catch (Exception e) { - session.Log.Log(LogLevel.Critical, new EventId(), "", new Exception(), (a, b) => ""); session.Log.Log(LogLevel.Error, e, "Error during logout of Session {SessionID}: {Message}", session.SessionID, e.Message); } From 231b79095dd3599d9171816bffe879e3c027b677 Mon Sep 17 00:00:00 2001 From: JK Date: Wed, 11 Dec 2024 21:49:47 -0500 Subject: [PATCH 11/23] Use NullLoggerProvider.Instance instead of constructing one --- QuickFIXn/AbstractInitiator.cs | 2 +- QuickFIXn/ThreadedSocketAcceptor.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/QuickFIXn/AbstractInitiator.cs b/QuickFIXn/AbstractInitiator.cs index 7c17ac390..e45016737 100644 --- a/QuickFIXn/AbstractInitiator.cs +++ b/QuickFIXn/AbstractInitiator.cs @@ -47,7 +47,7 @@ protected AbstractInitiator( IMessageFactory? messageFactoryNullable = null) { _settings = settings; - var logFactory = logFactoryNullable ?? new NullLoggerFactory(); + var logFactory = logFactoryNullable ?? NullLoggerFactory.Instance; var msgFactory = messageFactoryNullable ?? new DefaultMessageFactory(); _sessionFactory = new SessionFactory(app, storeFactory, logFactory, msgFactory); _nonSessionLog = logFactory.CreateLogger("QuickFix"); diff --git a/QuickFIXn/ThreadedSocketAcceptor.cs b/QuickFIXn/ThreadedSocketAcceptor.cs index f6ea09a96..76ae4e1ae 100755 --- a/QuickFIXn/ThreadedSocketAcceptor.cs +++ b/QuickFIXn/ThreadedSocketAcceptor.cs @@ -60,7 +60,7 @@ public ThreadedSocketAcceptor( ILoggerFactory? loggerFactory = null, IMessageFactory? messageFactory = null) { - ILoggerFactory lf = loggerFactory ?? new NullLoggerFactory(); + ILoggerFactory lf = loggerFactory ?? NullLoggerFactory.Instance; IMessageFactory mf = messageFactory ?? new DefaultMessageFactory(); _settings = settings; _sessionFactory = new SessionFactory(application, storeFactory, lf, mf); From e21307ab86ccc47ff72797c5036a09be704f8974 Mon Sep 17 00:00:00 2001 From: JK Date: Thu, 12 Dec 2024 06:56:17 -0500 Subject: [PATCH 12/23] Use delegate overload for GetOrAdd --- QuickFIXn/Logger/FileLoggerProvider.cs | 6 +++--- QuickFIXn/Logger/LogFactoryAdapter.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/QuickFIXn/Logger/FileLoggerProvider.cs b/QuickFIXn/Logger/FileLoggerProvider.cs index d81ad984f..e6f19cf13 100644 --- a/QuickFIXn/Logger/FileLoggerProvider.cs +++ b/QuickFIXn/Logger/FileLoggerProvider.cs @@ -27,9 +27,9 @@ public ILogger CreateLogger(string categoryName) s.ToString().Equals(sessionIdString, StringComparison.InvariantCulture)); var defaultSessionId = new SessionID("Non", "Session", "Log"); - return _loggers.GetOrAdd(sessionId ?? defaultSessionId, sessionId is not null - ? new FileLog(_settings.Get(sessionId).GetString(SessionSettings.FILE_LOG_PATH), sessionId) - : new NonSessionFileLogger(_settings.Get().GetString(SessionSettings.FILE_LOG_PATH), defaultSessionId)); + return _loggers.GetOrAdd(sessionId ?? defaultSessionId, sId => sessionId is not null + ? new FileLog(_settings.Get(sId).GetString(SessionSettings.FILE_LOG_PATH), sId) + : new NonSessionFileLogger(_settings.Get().GetString(SessionSettings.FILE_LOG_PATH), sId)); } public void Dispose() diff --git a/QuickFIXn/Logger/LogFactoryAdapter.cs b/QuickFIXn/Logger/LogFactoryAdapter.cs index af54e106d..8772e92f2 100644 --- a/QuickFIXn/Logger/LogFactoryAdapter.cs +++ b/QuickFIXn/Logger/LogFactoryAdapter.cs @@ -26,8 +26,8 @@ public ILogger CreateLogger(string categoryName) s.ToString().Equals(sessionIdString, StringComparison.InvariantCulture)); var defaultSessionId = new SessionID("Non", "Session", "Log"); - return _loggers.GetOrAdd(sessionId ?? defaultSessionId, sessionId is not null - ? new LogAdapter(_logFactory.Create(sessionId)) + return _loggers.GetOrAdd(sessionId ?? defaultSessionId, sid => sessionId is not null + ? new LogAdapter(_logFactory.Create(sid)) : new LogAdapter(_logFactory.CreateNonSessionLog())); } From 2ed24b59af28000ea8dec931474a466f2b3d141c Mon Sep 17 00:00:00 2001 From: JK Date: Thu, 12 Dec 2024 07:05:50 -0500 Subject: [PATCH 13/23] Ignore nullability error while implementing ILogger.BeginScope --- QuickFIXn/Logger/FileLog.cs | 2 ++ QuickFIXn/Logger/LogFactoryAdapter.cs | 4 +++- QuickFIXn/Logger/NonSessionFileLogger.cs | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/QuickFIXn/Logger/FileLog.cs b/QuickFIXn/Logger/FileLog.cs index 093bb100e..d88ca61ef 100755 --- a/QuickFIXn/Logger/FileLog.cs +++ b/QuickFIXn/Logger/FileLog.cs @@ -145,7 +145,9 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; +#pragma warning disable CS8633 public IDisposable BeginScope(TState state) where TState : notnull => default!; +#pragma warning restore CS8633 #endregion diff --git a/QuickFIXn/Logger/LogFactoryAdapter.cs b/QuickFIXn/Logger/LogFactoryAdapter.cs index 8772e92f2..473821474 100644 --- a/QuickFIXn/Logger/LogFactoryAdapter.cs +++ b/QuickFIXn/Logger/LogFactoryAdapter.cs @@ -70,5 +70,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; - public IDisposable? BeginScope(TState state) where TState : notnull => default!; +#pragma warning disable CS8633 + public IDisposable BeginScope(TState state) where TState : notnull => default!; +#pragma warning restore CS8633 } \ No newline at end of file diff --git a/QuickFIXn/Logger/NonSessionFileLogger.cs b/QuickFIXn/Logger/NonSessionFileLogger.cs index f5a79f174..6de1da4ca 100644 --- a/QuickFIXn/Logger/NonSessionFileLogger.cs +++ b/QuickFIXn/Logger/NonSessionFileLogger.cs @@ -21,5 +21,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; - public IDisposable? BeginScope(TState state) where TState : notnull => _fileLog.Value.BeginScope(state); +#pragma warning disable CS8633 + public IDisposable BeginScope(TState state) where TState : notnull => _fileLog.Value.BeginScope(state); +#pragma warning restore CS8633 } \ No newline at end of file From 9ad26d3fee6f7c671401f55a10c3c0b59878f944 Mon Sep 17 00:00:00 2001 From: JK Date: Thu, 12 Dec 2024 16:19:21 -0500 Subject: [PATCH 14/23] Ignore nullability error while implementing ILogger.BeginScope --- QuickFIXn/Logger/ScreenLog.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/QuickFIXn/Logger/ScreenLog.cs b/QuickFIXn/Logger/ScreenLog.cs index d364520f1..bc83177a1 100755 --- a/QuickFIXn/Logger/ScreenLog.cs +++ b/QuickFIXn/Logger/ScreenLog.cs @@ -80,7 +80,9 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; +#pragma warning disable CS8633 public IDisposable BeginScope(TState state) where TState : notnull => default!; +#pragma warning restore CS8633 public void Dispose(){} } From 40f336ef6389febc6fde287531da5c0c10647461 Mon Sep 17 00:00:00 2001 From: JK Date: Thu, 12 Dec 2024 18:29:22 -0500 Subject: [PATCH 15/23] Make sure owned logging classes are disposed. Should fix System.IO.IOExceptions related to locked files on windows --- AcceptanceTest/TestBase.cs | 10 ++++++---- Examples/Executor/Program.cs | 2 +- Examples/SimpleAcceptor/Program.cs | 2 +- Examples/TradeClient/Program.cs | 2 +- QuickFIXn/AbstractInitiator.cs | 9 +++++++++ QuickFIXn/Logger/FileLoggerProvider.cs | 11 +++++++++++ QuickFIXn/Logger/LogFactoryAdapter.cs | 15 ++++++++++++++- QuickFIXn/Logger/NonSessionFileLogger.cs | 7 ++++++- QuickFIXn/ThreadedSocketAcceptor.cs | 10 +++++++++- UnitTests/Logger/FileLogTests.cs | 2 +- UnitTests/SessionDynamicTest.cs | 9 ++++++--- UnitTests/ThreadedSocketAcceptorTests.cs | 19 ++++++++----------- 12 files changed, 73 insertions(+), 25 deletions(-) diff --git a/AcceptanceTest/TestBase.cs b/AcceptanceTest/TestBase.cs index b7166d9b2..53cf98ceb 100644 --- a/AcceptanceTest/TestBase.cs +++ b/AcceptanceTest/TestBase.cs @@ -13,6 +13,7 @@ public abstract class TestBase { private int _port; private ThreadedSocketAcceptor _acceptor; + private LoggerFactory? _loggerFactory; protected abstract SessionSettings Settings { get; } @@ -25,17 +26,17 @@ public void Setup() var testApp = new ATApplication(); var storeFactory = new MemoryStoreFactory(); - var loggerFactory = new LoggerFactory(); + _loggerFactory = new LoggerFactory(); if (settings.Get().Has("Verbose") && settings.Get().GetBool("Verbose")) { - loggerFactory.AddProvider(new FileLoggerProvider(settings)); + _loggerFactory.AddProvider(new FileLoggerProvider(settings)); } else { - loggerFactory.AddProvider(NullLoggerProvider.Instance); + _loggerFactory.AddProvider(NullLoggerProvider.Instance); } - _acceptor = new ThreadedSocketAcceptor(testApp, storeFactory, settings, loggerFactory); + _acceptor = new ThreadedSocketAcceptor(testApp, storeFactory, settings, _loggerFactory); _acceptor.Start(); } @@ -44,6 +45,7 @@ public void Setup() public void TearDown() { _acceptor?.Dispose(); + _loggerFactory?.Dispose(); } protected void RunTest(string definitionPath) diff --git a/Examples/Executor/Program.cs b/Examples/Executor/Program.cs index 3263745e1..dabe621fc 100644 --- a/Examples/Executor/Program.cs +++ b/Examples/Executor/Program.cs @@ -28,7 +28,7 @@ static void Main(string[] args) SessionSettings settings = new SessionSettings(args[0]); IApplication executorApp = new Executor(); IMessageStoreFactory storeFactory = new FileStoreFactory(settings); - var loggerFactory = LoggerFactory.Create(builder => + using var loggerFactory = LoggerFactory.Create(builder => { builder.SetMinimumLevel(LogLevel.Trace); builder.AddProvider(new ScreenLoggerProvider(settings)); diff --git a/Examples/SimpleAcceptor/Program.cs b/Examples/SimpleAcceptor/Program.cs index ed7a460ee..3d9c6c6f1 100644 --- a/Examples/SimpleAcceptor/Program.cs +++ b/Examples/SimpleAcceptor/Program.cs @@ -31,7 +31,7 @@ static void Main(string[] args) SessionSettings settings = new SessionSettings(args[0]); IApplication app = new SimpleAcceptorApp(); IMessageStoreFactory storeFactory = new FileStoreFactory(settings); - var loggerFactory = LoggerFactory.Create(builder => + using var loggerFactory = LoggerFactory.Create(builder => { builder.AddProvider(new FileLoggerProvider(settings)); }); diff --git a/Examples/TradeClient/Program.cs b/Examples/TradeClient/Program.cs index 9af620761..8a5f4063e 100644 --- a/Examples/TradeClient/Program.cs +++ b/Examples/TradeClient/Program.cs @@ -32,7 +32,7 @@ static void Main(string[] args) QuickFix.SessionSettings settings = new QuickFix.SessionSettings(file); TradeClientApp application = new TradeClientApp(); IMessageStoreFactory storeFactory = new FileStoreFactory(settings); - var loggerFactory = LoggerFactory.Create(builder => + using var loggerFactory = LoggerFactory.Create(builder => { builder.AddProvider(new ScreenLoggerProvider(settings)); // builder.AddProvider(new FileLogProvider(settings)); diff --git a/QuickFIXn/AbstractInitiator.cs b/QuickFIXn/AbstractInitiator.cs index e45016737..96be67d30 100644 --- a/QuickFIXn/AbstractInitiator.cs +++ b/QuickFIXn/AbstractInitiator.cs @@ -23,6 +23,7 @@ public abstract class AbstractInitiator : IInitiator private Thread? _thread; protected readonly ILogger _nonSessionLog; + private readonly ILoggerFactory? _loggerFactoryToDispose; public bool IsStopped { get; private set; } = true; @@ -48,6 +49,12 @@ protected AbstractInitiator( { _settings = settings; var logFactory = logFactoryNullable ?? NullLoggerFactory.Instance; + if (logFactory is LogFactoryAdapter) + { + // LogFactoryAdapter is internal, and can only have been created in the obsolete ctor, and must be + // disposed by us later. This should be removed eventually together with the old ILog and ILogFactory. + _loggerFactoryToDispose = logFactory; + } var msgFactory = messageFactoryNullable ?? new DefaultMessageFactory(); _sessionFactory = new SessionFactory(app, storeFactory, logFactory, msgFactory); _nonSessionLog = logFactory.CreateLogger("QuickFix"); @@ -226,6 +233,8 @@ public void Stop(bool force) _connected.Clear(); _disconnected.Clear(); } + + _loggerFactoryToDispose?.Dispose(); } public bool IsLoggedOn diff --git a/QuickFIXn/Logger/FileLoggerProvider.cs b/QuickFIXn/Logger/FileLoggerProvider.cs index e6f19cf13..453ca0a60 100644 --- a/QuickFIXn/Logger/FileLoggerProvider.cs +++ b/QuickFIXn/Logger/FileLoggerProvider.cs @@ -34,5 +34,16 @@ public ILogger CreateLogger(string categoryName) public void Dispose() { + foreach (var (_, logger) in _loggers) + { + try + { + if (logger is IDisposable disposable) + { + disposable.Dispose(); + } + } + catch { } + } } } \ No newline at end of file diff --git a/QuickFIXn/Logger/LogFactoryAdapter.cs b/QuickFIXn/Logger/LogFactoryAdapter.cs index 473821474..874ff05a2 100644 --- a/QuickFIXn/Logger/LogFactoryAdapter.cs +++ b/QuickFIXn/Logger/LogFactoryAdapter.cs @@ -5,7 +5,7 @@ namespace QuickFix.Logger; -internal class LogFactoryAdapter : ILoggerFactory +internal class LogFactoryAdapter : ILoggerFactory, IDisposable { private readonly ILogFactory _logFactory; private readonly SessionSettings _settings; @@ -38,6 +38,19 @@ public void AddProvider(ILoggerProvider provider) public void Dispose() { + foreach (var (_, logger) in _loggers) + { + if (logger is IDisposable disposable) + { + try + { + disposable.Dispose(); + } + catch + { + } + } + } } } diff --git a/QuickFIXn/Logger/NonSessionFileLogger.cs b/QuickFIXn/Logger/NonSessionFileLogger.cs index 6de1da4ca..013e923af 100644 --- a/QuickFIXn/Logger/NonSessionFileLogger.cs +++ b/QuickFIXn/Logger/NonSessionFileLogger.cs @@ -6,7 +6,7 @@ namespace QuickFix.Logger; /// /// Like the file logger, but only creates the files on first write /// -internal class NonSessionFileLogger : ILogger +internal class NonSessionFileLogger : ILogger, IDisposable { private readonly Lazy _fileLog; @@ -24,4 +24,9 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except #pragma warning disable CS8633 public IDisposable BeginScope(TState state) where TState : notnull => _fileLog.Value.BeginScope(state); #pragma warning restore CS8633 + + public void Dispose() + { + if (_fileLog.IsValueCreated) _fileLog.Value.Dispose(); + } } \ No newline at end of file diff --git a/QuickFIXn/ThreadedSocketAcceptor.cs b/QuickFIXn/ThreadedSocketAcceptor.cs index 76ae4e1ae..fbc739ea1 100755 --- a/QuickFIXn/ThreadedSocketAcceptor.cs +++ b/QuickFIXn/ThreadedSocketAcceptor.cs @@ -23,6 +23,7 @@ public class ThreadedSocketAcceptor : IAcceptor private bool _disposed = false; private readonly object _sync = new(); private readonly ILogger _nonSessionLog; + private readonly ILoggerFactory? _loggerFactoryToDispose; #region Constructors /// @@ -60,7 +61,13 @@ public ThreadedSocketAcceptor( ILoggerFactory? loggerFactory = null, IMessageFactory? messageFactory = null) { - ILoggerFactory lf = loggerFactory ?? NullLoggerFactory.Instance; + var lf = loggerFactory ?? NullLoggerFactory.Instance; + if (lf is LogFactoryAdapter) + { + // LogFactoryAdapter is internal, and can only have been created in the obsolete ctor, and must be + // disposed by us later. This should be removed eventually together with the old ILog and ILogFactory. + _loggerFactoryToDispose = lf; + } IMessageFactory mf = messageFactory ?? new DefaultMessageFactory(); _settings = settings; _sessionFactory = new SessionFactory(application, storeFactory, lf, mf); @@ -298,6 +305,7 @@ public void Stop(bool force) DisposeSessions(); _sessions.Clear(); _isStarted = false; + _loggerFactoryToDispose?.Dispose(); // FIXME StopSessionTimer(); // FIXME Session.UnregisterSessions(GetSessions()); diff --git a/UnitTests/Logger/FileLogTests.cs b/UnitTests/Logger/FileLogTests.cs index 1416834f6..d671f0066 100644 --- a/UnitTests/Logger/FileLogTests.cs +++ b/UnitTests/Logger/FileLogTests.cs @@ -83,7 +83,7 @@ public void TestThrowsIfNoConfig() QuickFix.SessionSettings settings = new QuickFix.SessionSettings(); settings.Set(sessionId, config); - var loggerFactory = new LoggerFactory([new FileLoggerProvider(settings)]); + using var loggerFactory = new LoggerFactory([new FileLoggerProvider(settings)]); Assert.Throws(() => loggerFactory.CreateLogger($"QuickFix.{sessionId}")); } diff --git a/UnitTests/SessionDynamicTest.cs b/UnitTests/SessionDynamicTest.cs index 9fbb29684..88d768268 100644 --- a/UnitTests/SessionDynamicTest.cs +++ b/UnitTests/SessionDynamicTest.cs @@ -73,6 +73,7 @@ public SocketState(Socket s) private string _logPath = "unset"; private SocketInitiator? _initiator; private ThreadedSocketAcceptor? _acceptor; + private ILoggerFactory? _loggerFactory; private Dictionary _sessions = new(); private HashSet _loggedOnCompIDs = new(); private Socket? _listenSocket; @@ -127,13 +128,13 @@ private void StartEngine(bool initiator, bool twoSessions = false) defaults.SetString(SessionSettings.SOCKET_ACCEPT_PORT, AcceptPort.ToString()); settings.Set(defaults); - var loggerFactory = new LoggerFactory([new FileLoggerProvider(settings)]); + _loggerFactory = new LoggerFactory([new FileLoggerProvider(settings)]); if (initiator) { defaults.SetString(SessionSettings.RECONNECT_INTERVAL, "1"); settings.Set(CreateSessionId(StaticInitiatorCompId), CreateSessionConfig(true)); - _initiator = new SocketInitiator(application, storeFactory, settings, loggerFactory); + _initiator = new SocketInitiator(application, storeFactory, settings, _loggerFactory); _initiator.Start(); } else @@ -151,7 +152,7 @@ private void StartEngine(bool initiator, bool twoSessions = false) settings.Set(id, conf); } - _acceptor = new ThreadedSocketAcceptor(application, storeFactory, settings, loggerFactory); + _acceptor = new ThreadedSocketAcceptor(application, storeFactory, settings, _loggerFactory); _acceptor.Start(); } } @@ -361,9 +362,11 @@ public void TearDown() _listenSocket?.Close(); _initiator?.Stop(true); _acceptor?.Stop(true); + _loggerFactory?.Dispose(); _initiator = null; _acceptor = null; + _loggerFactory = null; Thread.Sleep(500); ClearLogs(); diff --git a/UnitTests/ThreadedSocketAcceptorTests.cs b/UnitTests/ThreadedSocketAcceptorTests.cs index a05f39cf1..77c6d039b 100644 --- a/UnitTests/ThreadedSocketAcceptorTests.cs +++ b/UnitTests/ThreadedSocketAcceptorTests.cs @@ -36,16 +36,6 @@ private static SessionSettings CreateSettings() return new SessionSettings(new StringReader(Config)); } - private static ThreadedSocketAcceptor CreateAcceptor() - { - var settings = CreateSettings(); - return new ThreadedSocketAcceptor( - new NullApplication(), - new FileStoreFactory(settings), - settings, - new LoggerFactory([new FileLoggerProvider(settings)])); - } - [Test] public void TestRecreation() { @@ -56,7 +46,14 @@ public void TestRecreation() private static void StartStopAcceptor() { - var acceptor = CreateAcceptor(); + var settings = CreateSettings(); + using var lf = new LoggerFactory([new FileLoggerProvider(settings)]); + + var acceptor = new ThreadedSocketAcceptor( + new NullApplication(), + new FileStoreFactory(settings), + settings, + lf); acceptor.Start(); acceptor.Dispose(); } From 841fcee2ecee34fa87fbd803b782d5380030132a Mon Sep 17 00:00:00 2001 From: JK Date: Thu, 12 Dec 2024 18:44:43 -0500 Subject: [PATCH 16/23] Naming --- QuickFIXn/AbstractInitiator.cs | 14 ++++++++------ QuickFIXn/ThreadedSocketAcceptor.cs | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/QuickFIXn/AbstractInitiator.cs b/QuickFIXn/AbstractInitiator.cs index 96be67d30..eea21ef9c 100644 --- a/QuickFIXn/AbstractInitiator.cs +++ b/QuickFIXn/AbstractInitiator.cs @@ -23,7 +23,7 @@ public abstract class AbstractInitiator : IInitiator private Thread? _thread; protected readonly ILogger _nonSessionLog; - private readonly ILoggerFactory? _loggerFactoryToDispose; + private readonly LogFactoryAdapter? _logFactoryAdapter; public bool IsStopped { get; private set; } = true; @@ -49,11 +49,13 @@ protected AbstractInitiator( { _settings = settings; var logFactory = logFactoryNullable ?? NullLoggerFactory.Instance; - if (logFactory is LogFactoryAdapter) + if (logFactory is LogFactoryAdapter lfa) { - // LogFactoryAdapter is internal, and can only have been created in the obsolete ctor, and must be - // disposed by us later. This should be removed eventually together with the old ILog and ILogFactory. - _loggerFactoryToDispose = logFactory; + // LogFactoryAdapter is only ever created in the constructor marked obsolete, which means we own it and + // must save a ref to it so we can dispose it later. Any other ILoggerFactory is owned by someone else + // so we'll leave the dispose up to them. This should be removed eventually together with the old ILog + // and ILogFactory. + _logFactoryAdapter = lfa; } var msgFactory = messageFactoryNullable ?? new DefaultMessageFactory(); _sessionFactory = new SessionFactory(app, storeFactory, logFactory, msgFactory); @@ -234,7 +236,7 @@ public void Stop(bool force) _disconnected.Clear(); } - _loggerFactoryToDispose?.Dispose(); + _logFactoryAdapter?.Dispose(); } public bool IsLoggedOn diff --git a/QuickFIXn/ThreadedSocketAcceptor.cs b/QuickFIXn/ThreadedSocketAcceptor.cs index fbc739ea1..b6bab13be 100755 --- a/QuickFIXn/ThreadedSocketAcceptor.cs +++ b/QuickFIXn/ThreadedSocketAcceptor.cs @@ -23,7 +23,7 @@ public class ThreadedSocketAcceptor : IAcceptor private bool _disposed = false; private readonly object _sync = new(); private readonly ILogger _nonSessionLog; - private readonly ILoggerFactory? _loggerFactoryToDispose; + private readonly LogFactoryAdapter? _logFactoryAdapter; #region Constructors /// @@ -62,11 +62,13 @@ public ThreadedSocketAcceptor( IMessageFactory? messageFactory = null) { var lf = loggerFactory ?? NullLoggerFactory.Instance; - if (lf is LogFactoryAdapter) + if (lf is LogFactoryAdapter lfa) { - // LogFactoryAdapter is internal, and can only have been created in the obsolete ctor, and must be - // disposed by us later. This should be removed eventually together with the old ILog and ILogFactory. - _loggerFactoryToDispose = lf; + // LogFactoryAdapter is only ever created in the constructor marked obsolete, which means we own it and + // must save a ref to it so we can dispose it later. Any other ILoggerFactory is owned by someone else + // so we'll leave the dispose up to them. This should be removed eventually together with the old ILog + // and ILogFactory. + _logFactoryAdapter = lfa; } IMessageFactory mf = messageFactory ?? new DefaultMessageFactory(); _settings = settings; @@ -305,7 +307,7 @@ public void Stop(bool force) DisposeSessions(); _sessions.Clear(); _isStarted = false; - _loggerFactoryToDispose?.Dispose(); + _logFactoryAdapter?.Dispose(); // FIXME StopSessionTimer(); // FIXME Session.UnregisterSessions(GetSessions()); From ab4737e1bf29c48b1ffbf9ad9c56b99e222453eb Mon Sep 17 00:00:00 2001 From: JK Date: Fri, 13 Dec 2024 14:02:32 -0500 Subject: [PATCH 17/23] Set min version of MEL.Abstractions to v6 and no upper limit --- QuickFIXn/QuickFix.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QuickFIXn/QuickFix.csproj b/QuickFIXn/QuickFix.csproj index e82d837ed..4ca97ccba 100644 --- a/QuickFIXn/QuickFix.csproj +++ b/QuickFIXn/QuickFix.csproj @@ -45,6 +45,6 @@ - + From 4590b62bc9f1772fe93b7f4d7285a7927088af96 Mon Sep 17 00:00:00 2001 From: JK Date: Sun, 15 Dec 2024 16:27:46 -0500 Subject: [PATCH 18/23] Use LoggerFactory constructor that disposes ILoggerProviders --- UnitTests/SessionDynamicTest.cs | 3 ++- UnitTests/ThreadedSocketAcceptorTests.cs | 9 +++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/UnitTests/SessionDynamicTest.cs b/UnitTests/SessionDynamicTest.cs index 88d768268..47fc42fed 100644 --- a/UnitTests/SessionDynamicTest.cs +++ b/UnitTests/SessionDynamicTest.cs @@ -128,7 +128,8 @@ private void StartEngine(bool initiator, bool twoSessions = false) defaults.SetString(SessionSettings.SOCKET_ACCEPT_PORT, AcceptPort.ToString()); settings.Set(defaults); - _loggerFactory = new LoggerFactory([new FileLoggerProvider(settings)]); + _loggerFactory = new LoggerFactory(); + _loggerFactory.AddProvider(new FileLoggerProvider(settings)); if (initiator) { diff --git a/UnitTests/ThreadedSocketAcceptorTests.cs b/UnitTests/ThreadedSocketAcceptorTests.cs index 77c6d039b..6219f3ae1 100644 --- a/UnitTests/ThreadedSocketAcceptorTests.cs +++ b/UnitTests/ThreadedSocketAcceptorTests.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; +using System.IO; using Microsoft.Extensions.Logging; using NUnit.Framework; using QuickFix; @@ -47,7 +43,8 @@ public void TestRecreation() private static void StartStopAcceptor() { var settings = CreateSettings(); - using var lf = new LoggerFactory([new FileLoggerProvider(settings)]); + using var lf = new LoggerFactory(); + lf.AddProvider(new FileLoggerProvider(settings)); var acceptor = new ThreadedSocketAcceptor( new NullApplication(), From 283a3b88ed59a4b84465cba4536bf2db1779c205 Mon Sep 17 00:00:00 2001 From: JK Date: Sun, 5 Jan 2025 15:38:35 -0500 Subject: [PATCH 19/23] Set ILogger.BeginScope type to match net6 and not later. Clears nullability warning CS8633 --- QuickFIXn/Logger/FileLog.cs | 4 +--- QuickFIXn/Logger/LogFactoryAdapter.cs | 4 +--- QuickFIXn/Logger/NonSessionFileLogger.cs | 4 +--- QuickFIXn/Logger/ScreenLog.cs | 4 +--- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/QuickFIXn/Logger/FileLog.cs b/QuickFIXn/Logger/FileLog.cs index d88ca61ef..4729d1910 100755 --- a/QuickFIXn/Logger/FileLog.cs +++ b/QuickFIXn/Logger/FileLog.cs @@ -145,9 +145,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; -#pragma warning disable CS8633 - public IDisposable BeginScope(TState state) where TState : notnull => default!; -#pragma warning restore CS8633 + public IDisposable BeginScope(TState state) => default!; #endregion diff --git a/QuickFIXn/Logger/LogFactoryAdapter.cs b/QuickFIXn/Logger/LogFactoryAdapter.cs index 874ff05a2..ca120a301 100644 --- a/QuickFIXn/Logger/LogFactoryAdapter.cs +++ b/QuickFIXn/Logger/LogFactoryAdapter.cs @@ -83,7 +83,5 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; -#pragma warning disable CS8633 - public IDisposable BeginScope(TState state) where TState : notnull => default!; -#pragma warning restore CS8633 + public IDisposable BeginScope(TState state) => default!; } \ No newline at end of file diff --git a/QuickFIXn/Logger/NonSessionFileLogger.cs b/QuickFIXn/Logger/NonSessionFileLogger.cs index 013e923af..fa9f63cba 100644 --- a/QuickFIXn/Logger/NonSessionFileLogger.cs +++ b/QuickFIXn/Logger/NonSessionFileLogger.cs @@ -21,9 +21,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; -#pragma warning disable CS8633 - public IDisposable BeginScope(TState state) where TState : notnull => _fileLog.Value.BeginScope(state); -#pragma warning restore CS8633 + public IDisposable BeginScope(TState state) => _fileLog.Value.BeginScope(state); public void Dispose() { diff --git a/QuickFIXn/Logger/ScreenLog.cs b/QuickFIXn/Logger/ScreenLog.cs index bc83177a1..829228580 100755 --- a/QuickFIXn/Logger/ScreenLog.cs +++ b/QuickFIXn/Logger/ScreenLog.cs @@ -80,9 +80,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; -#pragma warning disable CS8633 - public IDisposable BeginScope(TState state) where TState : notnull => default!; -#pragma warning restore CS8633 + public IDisposable BeginScope(TState state) => default!; public void Dispose(){} } From 05c650b21eeab8a45ba9a0c768f105f48404791a Mon Sep 17 00:00:00 2001 From: JK Date: Sun, 5 Jan 2025 17:38:11 -0500 Subject: [PATCH 20/23] Make NonSessionFileLogger obsolete --- QuickFIXn/Logger/NonSessionFileLogger.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/QuickFIXn/Logger/NonSessionFileLogger.cs b/QuickFIXn/Logger/NonSessionFileLogger.cs index fa9f63cba..9ffcb8a57 100644 --- a/QuickFIXn/Logger/NonSessionFileLogger.cs +++ b/QuickFIXn/Logger/NonSessionFileLogger.cs @@ -6,6 +6,7 @@ namespace QuickFix.Logger; /// /// Like the file logger, but only creates the files on first write /// +[Obsolete("Use Microsoft.Extensions.Logging instead")] internal class NonSessionFileLogger : ILogger, IDisposable { private readonly Lazy _fileLog; From d145d73764dc7c5071d7af560be829fd64d46e50 Mon Sep 17 00:00:00 2001 From: JK Date: Wed, 8 Jan 2025 19:31:55 -0500 Subject: [PATCH 21/23] Refactor to set proper categories Don't use string parsing to decide whether to return session/non session log Remove iloggerproviders that were added before --- AcceptanceTest/TestBase.cs | 18 ++---- Examples/Executor/Examples.Executor.csproj | 1 + Examples/Executor/Program.cs | 4 +- .../Examples.SimpleAcceptor.csproj | 1 + Examples/SimpleAcceptor/Program.cs | 3 +- .../TradeClient/Examples.TradeClient.csproj | 1 + Examples/TradeClient/Program.cs | 4 +- QuickFIXn/AbstractInitiator.cs | 30 ++++++--- QuickFIXn/AcceptorSocketDescriptor.cs | 8 +-- QuickFIXn/ClientHandlerThread.cs | 6 +- QuickFIXn/Logger/FileLog.cs | 29 +-------- QuickFIXn/Logger/FileLoggerProvider.cs | 49 --------------- QuickFIXn/Logger/IQuickFixLoggerFactory.cs | 61 +++++++++++++++++++ QuickFIXn/Logger/LogFactoryAdapter.cs | 47 +++++++------- QuickFIXn/Logger/NonSessionFileLogger.cs | 31 ---------- QuickFIXn/Logger/ScreenLog.cs | 25 +------- QuickFIXn/Logger/ScreenLoggerProvider.cs | 58 ------------------ QuickFIXn/Session.cs | 4 +- QuickFIXn/SessionFactory.cs | 9 ++- QuickFIXn/SocketInitiatorThread.cs | 9 ++- QuickFIXn/SocketReader.cs | 6 +- QuickFIXn/ThreadedSocketAcceptor.cs | 27 +++++--- QuickFIXn/ThreadedSocketReactor.cs | 9 ++- QuickFIXn/Transport/SocketInitiator.cs | 15 +++-- QuickFIXn/Transport/SslStreamFactory.cs | 4 +- QuickFIXn/Transport/StreamFactory.cs | 14 ++--- UnitTests/Logger/FileLogTests.cs | 15 +++-- UnitTests/SessionDynamicTest.cs | 11 +--- UnitTests/SessionTest.cs | 11 ++-- UnitTests/ThreadedSocketAcceptorTests.cs | 3 +- UnitTests/ThreadedSocketReactorTests.cs | 2 +- 31 files changed, 199 insertions(+), 316 deletions(-) delete mode 100644 QuickFIXn/Logger/FileLoggerProvider.cs create mode 100644 QuickFIXn/Logger/IQuickFixLoggerFactory.cs delete mode 100644 QuickFIXn/Logger/NonSessionFileLogger.cs delete mode 100755 QuickFIXn/Logger/ScreenLoggerProvider.cs diff --git a/AcceptanceTest/TestBase.cs b/AcceptanceTest/TestBase.cs index 53cf98ceb..cc9dba3dd 100644 --- a/AcceptanceTest/TestBase.cs +++ b/AcceptanceTest/TestBase.cs @@ -2,8 +2,6 @@ using QuickFix; using System.IO; using System.Net; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using QuickFix.Logger; using QuickFix.Store; @@ -13,7 +11,6 @@ public abstract class TestBase { private int _port; private ThreadedSocketAcceptor _acceptor; - private LoggerFactory? _loggerFactory; protected abstract SessionSettings Settings { get; } @@ -26,17 +23,11 @@ public void Setup() var testApp = new ATApplication(); var storeFactory = new MemoryStoreFactory(); - _loggerFactory = new LoggerFactory(); - if (settings.Get().Has("Verbose") && settings.Get().GetBool("Verbose")) - { - _loggerFactory.AddProvider(new FileLoggerProvider(settings)); - } - else - { - _loggerFactory.AddProvider(NullLoggerProvider.Instance); - } + ILogFactory? logFactory = settings.Get().Has("Verbose") && settings.Get().GetBool("Verbose") + ? new FileLogFactory(settings) + : new NullLogFactory(); - _acceptor = new ThreadedSocketAcceptor(testApp, storeFactory, settings, _loggerFactory); + _acceptor = new ThreadedSocketAcceptor(testApp, storeFactory, settings, logFactory); _acceptor.Start(); } @@ -45,7 +36,6 @@ public void Setup() public void TearDown() { _acceptor?.Dispose(); - _loggerFactory?.Dispose(); } protected void RunTest(string definitionPath) diff --git a/Examples/Executor/Examples.Executor.csproj b/Examples/Executor/Examples.Executor.csproj index 6cf2f7a70..604c22ecd 100644 --- a/Examples/Executor/Examples.Executor.csproj +++ b/Examples/Executor/Examples.Executor.csproj @@ -42,6 +42,7 @@ + diff --git a/Examples/Executor/Program.cs b/Examples/Executor/Program.cs index dabe621fc..e6cc5134b 100644 --- a/Examples/Executor/Program.cs +++ b/Examples/Executor/Program.cs @@ -1,7 +1,6 @@ using System; using Microsoft.Extensions.Logging; using QuickFix; -using QuickFix.Logger; using QuickFix.Store; namespace Executor @@ -31,8 +30,7 @@ static void Main(string[] args) using var loggerFactory = LoggerFactory.Create(builder => { builder.SetMinimumLevel(LogLevel.Trace); - builder.AddProvider(new ScreenLoggerProvider(settings)); - builder.AddProvider(new FileLoggerProvider(settings)); + builder.AddConsole(); }); ThreadedSocketAcceptor acceptor = new ThreadedSocketAcceptor(executorApp, storeFactory, settings, loggerFactory); diff --git a/Examples/SimpleAcceptor/Examples.SimpleAcceptor.csproj b/Examples/SimpleAcceptor/Examples.SimpleAcceptor.csproj index bc3ece1f4..47185044d 100644 --- a/Examples/SimpleAcceptor/Examples.SimpleAcceptor.csproj +++ b/Examples/SimpleAcceptor/Examples.SimpleAcceptor.csproj @@ -36,6 +36,7 @@ + diff --git a/Examples/SimpleAcceptor/Program.cs b/Examples/SimpleAcceptor/Program.cs index 3d9c6c6f1..089883f9d 100644 --- a/Examples/SimpleAcceptor/Program.cs +++ b/Examples/SimpleAcceptor/Program.cs @@ -1,7 +1,6 @@ using System; using Microsoft.Extensions.Logging; using QuickFix; -using QuickFix.Logger; using QuickFix.Store; namespace SimpleAcceptor @@ -33,7 +32,7 @@ static void Main(string[] args) IMessageStoreFactory storeFactory = new FileStoreFactory(settings); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddProvider(new FileLoggerProvider(settings)); + builder.AddConsole(); }); IAcceptor acceptor = new ThreadedSocketAcceptor(app, storeFactory, settings, loggerFactory); diff --git a/Examples/TradeClient/Examples.TradeClient.csproj b/Examples/TradeClient/Examples.TradeClient.csproj index 9da5513b0..e65bf64a1 100644 --- a/Examples/TradeClient/Examples.TradeClient.csproj +++ b/Examples/TradeClient/Examples.TradeClient.csproj @@ -37,6 +37,7 @@ + diff --git a/Examples/TradeClient/Program.cs b/Examples/TradeClient/Program.cs index 8a5f4063e..c6ebe9ce6 100644 --- a/Examples/TradeClient/Program.cs +++ b/Examples/TradeClient/Program.cs @@ -1,6 +1,5 @@ using System; using Microsoft.Extensions.Logging; -using QuickFix.Logger; using QuickFix.Store; namespace TradeClient @@ -34,8 +33,7 @@ static void Main(string[] args) IMessageStoreFactory storeFactory = new FileStoreFactory(settings); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddProvider(new ScreenLoggerProvider(settings)); - // builder.AddProvider(new FileLogProvider(settings)); + builder.AddConsole(); }); QuickFix.Transport.SocketInitiator initiator = new QuickFix.Transport.SocketInitiator(application, storeFactory, settings, loggerFactory); diff --git a/QuickFIXn/AbstractInitiator.cs b/QuickFIXn/AbstractInitiator.cs index eea21ef9c..017224bfe 100644 --- a/QuickFIXn/AbstractInitiator.cs +++ b/QuickFIXn/AbstractInitiator.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using QuickFix.Logger; using QuickFix.Store; @@ -22,7 +21,7 @@ public abstract class AbstractInitiator : IInitiator private readonly SessionFactory _sessionFactory; private Thread? _thread; - protected readonly ILogger _nonSessionLog; + protected readonly IQuickFixLoggerFactory LoggerFactory; private readonly LogFactoryAdapter? _logFactoryAdapter; public bool IsStopped { get; private set; } = true; @@ -35,8 +34,8 @@ protected AbstractInitiator( ILogFactory? logFactoryNullable = null, IMessageFactory? messageFactoryNullable = null) : this(app, storeFactory, settings, logFactoryNullable is null - ? NullLoggerFactory.Instance - : new LogFactoryAdapter(logFactoryNullable, settings), messageFactoryNullable) + ? NullQuickFixLoggerFactory.Instance + : new LogFactoryAdapter(logFactoryNullable), messageFactoryNullable) { } @@ -44,22 +43,33 @@ protected AbstractInitiator( IApplication app, IMessageStoreFactory storeFactory, SessionSettings settings, - ILoggerFactory? logFactoryNullable = null, + ILoggerFactory? loggerFactoryNullable = null, + IMessageFactory? messageFactoryNullable = null) : this(app, storeFactory, settings, + loggerFactoryNullable is null + ? NullQuickFixLoggerFactory.Instance + : new MelQuickFixLoggerFactory(loggerFactoryNullable), messageFactoryNullable) + { + } + + private AbstractInitiator( + IApplication app, + IMessageStoreFactory storeFactory, + SessionSettings settings, + IQuickFixLoggerFactory loggerFactory, IMessageFactory? messageFactoryNullable = null) { _settings = settings; - var logFactory = logFactoryNullable ?? NullLoggerFactory.Instance; - if (logFactory is LogFactoryAdapter lfa) + if (loggerFactory is LogFactoryAdapter lfa) { // LogFactoryAdapter is only ever created in the constructor marked obsolete, which means we own it and - // must save a ref to it so we can dispose it later. Any other ILoggerFactory is owned by someone else + // must save a ref to it so we can dispose it later. Any other loggerFactory is owned by someone else // so we'll leave the dispose up to them. This should be removed eventually together with the old ILog // and ILogFactory. _logFactoryAdapter = lfa; } var msgFactory = messageFactoryNullable ?? new DefaultMessageFactory(); - _sessionFactory = new SessionFactory(app, storeFactory, logFactory, msgFactory); - _nonSessionLog = logFactory.CreateLogger("QuickFix"); + _sessionFactory = new SessionFactory(app, storeFactory, loggerFactory, msgFactory); + LoggerFactory = loggerFactory; HashSet definedSessions = _settings.GetSessions(); if (0 == definedSessions.Count) diff --git a/QuickFIXn/AcceptorSocketDescriptor.cs b/QuickFIXn/AcceptorSocketDescriptor.cs index e7af0da82..7cdabc8b6 100644 --- a/QuickFIXn/AcceptorSocketDescriptor.cs +++ b/QuickFIXn/AcceptorSocketDescriptor.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Net; -using Microsoft.Extensions.Logging; +using QuickFix.Logger; namespace QuickFix { @@ -15,10 +15,10 @@ internal class AcceptorSocketDescriptor public AcceptorSocketDescriptor( IPEndPoint socketEndPoint, SocketSettings socketSettings, - ILogger nonSessionLog) + IQuickFixLoggerFactory loggerFactory) { Address = socketEndPoint; - SocketReactor = new ThreadedSocketReactor(Address, socketSettings, this, nonSessionLog); + SocketReactor = new ThreadedSocketReactor(Address, socketSettings, this, loggerFactory); } [Obsolete("Param 'sessionDict' is unused. Use the alt constructor without it.")] @@ -26,7 +26,7 @@ public AcceptorSocketDescriptor( IPEndPoint socketEndPoint, SocketSettings socketSettings, SettingsDictionary sessionDict, - ILogger nonSessionLog) : this(socketEndPoint, socketSettings, nonSessionLog) + IQuickFixLoggerFactory nonSessionLog) : this(socketEndPoint, socketSettings, nonSessionLog) { } internal void AcceptSession(Session session) diff --git a/QuickFIXn/ClientHandlerThread.cs b/QuickFIXn/ClientHandlerThread.cs index 3ada2174e..6945b2a03 100755 --- a/QuickFIXn/ClientHandlerThread.cs +++ b/QuickFIXn/ClientHandlerThread.cs @@ -1,7 +1,7 @@ using System.Net.Sockets; using System.Threading; using System; -using Microsoft.Extensions.Logging; +using QuickFix.Logger; namespace QuickFix { @@ -36,10 +36,10 @@ internal ClientHandlerThread( long clientId, SocketSettings socketSettings, AcceptorSocketDescriptor? acceptorDescriptor, - ILogger nonSessionLog + IQuickFixLoggerFactory loggerFactory ) { Id = clientId; - _socketReader = new SocketReader(tcpClient, socketSettings, this, acceptorDescriptor, nonSessionLog); + _socketReader = new SocketReader(tcpClient, socketSettings, this, acceptorDescriptor, loggerFactory); } public void Start() diff --git a/QuickFIXn/Logger/FileLog.cs b/QuickFIXn/Logger/FileLog.cs index 4729d1910..d0166535a 100755 --- a/QuickFIXn/Logger/FileLog.cs +++ b/QuickFIXn/Logger/FileLog.cs @@ -1,5 +1,4 @@ using System; -using Microsoft.Extensions.Logging; using QuickFix.Fields.Converters; using QuickFix.Util; @@ -9,7 +8,7 @@ namespace QuickFix.Logger; /// File log implementation /// [Obsolete("Use Microsoft.Extensions.Logging instead")] -public class FileLog : ILog, ILogger +public class FileLog : ILog { private readonly object _sync = new(); @@ -121,32 +120,6 @@ public void OnEvent(string s) } } - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, - Func formatter) - { - if (!IsEnabled(logLevel)) return; - if (eventId == LogEventIds.IncomingMessage || eventId == LogEventIds.OutgoingMessage) - { - lock (_sync) - { - _messageLog.WriteLine( - $"{DateTimeConverter.ToFIX(DateTime.UtcNow, TimeStampPrecision.Millisecond)} : {formatter(state, exception)}"); - } - } - else - { - lock (_sync) - { - _eventLog.WriteLine( - $"{DateTimeConverter.ToFIX(DateTime.UtcNow, TimeStampPrecision.Millisecond)} : {formatter(state, exception)}"); - } - } - } - - public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; - - public IDisposable BeginScope(TState state) => default!; - #endregion #region IDisposable Members diff --git a/QuickFIXn/Logger/FileLoggerProvider.cs b/QuickFIXn/Logger/FileLoggerProvider.cs deleted file mode 100644 index 453ca0a60..000000000 --- a/QuickFIXn/Logger/FileLoggerProvider.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; -using Microsoft.Extensions.Logging; - -namespace QuickFix.Logger; - -[Obsolete("This class is provided to ease migration from the old logging system to Microsoft.Extensions.Logging." + - "It is an attempt to maintain the behavior of the previous FileLog and FileLogFactory while plugging into the Microsoft.Extensions.Logging ecosystem. " + - "Consider using the more robust logging options available in the .NET ecosystem, like the MS Console logging provider, Serilog and NLog.")] -public class FileLoggerProvider : ILoggerProvider -{ - private readonly SessionSettings _settings; - private readonly ConcurrentDictionary _loggers = new(); - - public FileLoggerProvider(SessionSettings settings) - { - _settings = settings; - } - - public ILogger CreateLogger(string categoryName) - { - // category will be "QuickFix" for non-session logger and "QuickFix." for session logger - var sessionIdString = categoryName.Length > "QuickFix.".Length ? categoryName.Substring(9) : ""; - var sessionId = _settings.GetSessions() - .SingleOrDefault(s => - s.ToString().Equals(sessionIdString, StringComparison.InvariantCulture)); - var defaultSessionId = new SessionID("Non", "Session", "Log"); - - return _loggers.GetOrAdd(sessionId ?? defaultSessionId, sId => sessionId is not null - ? new FileLog(_settings.Get(sId).GetString(SessionSettings.FILE_LOG_PATH), sId) - : new NonSessionFileLogger(_settings.Get().GetString(SessionSettings.FILE_LOG_PATH), sId)); - } - - public void Dispose() - { - foreach (var (_, logger) in _loggers) - { - try - { - if (logger is IDisposable disposable) - { - disposable.Dispose(); - } - } - catch { } - } - } -} \ No newline at end of file diff --git a/QuickFIXn/Logger/IQuickFixLoggerFactory.cs b/QuickFIXn/Logger/IQuickFixLoggerFactory.cs new file mode 100644 index 000000000..e08fdc849 --- /dev/null +++ b/QuickFIXn/Logger/IQuickFixLoggerFactory.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace QuickFix.Logger; + +public interface IQuickFixLoggerFactory +{ + public ILogger CreateSessionLogger(SessionID sessionId); + public ILogger CreateNonSessionLogger(); +} + +internal class MelQuickFixLoggerFactory : IQuickFixLoggerFactory +{ + private readonly ILoggerFactory _loggerFactory; + + public MelQuickFixLoggerFactory(ILoggerFactory loggerFactory) + { + _loggerFactory = loggerFactory; + } + + /// + /// Creates a new instance using the Session ID. + /// + /// The that was created + public ILogger CreateSessionLogger(SessionID sessionId) => + _loggerFactory.CreateLogger($"QuickFix.SessionLogs.{GetCategoryFromSessionId(sessionId)}"); + + /// + /// Creates a new instance using the full name of the given type. + /// + /// The type + /// The that was created + public ILogger CreateNonSessionLogger() => _loggerFactory.CreateLogger(); + + private static string GetCategoryFromSessionId(SessionID sessionId) + { + System.Text.StringBuilder category = new System.Text.StringBuilder(sessionId.BeginString) + .Append('-').Append(sessionId.SenderCompID); + if (SessionID.IsSet(sessionId.SenderSubID)) + category.Append('_').Append(sessionId.SenderSubID); + if (SessionID.IsSet(sessionId.SenderLocationID)) + category.Append('_').Append(sessionId.SenderLocationID); + category.Append('-').Append(sessionId.TargetCompID); + if (SessionID.IsSet(sessionId.TargetSubID)) + category.Append('_').Append(sessionId.TargetSubID); + if (SessionID.IsSet(sessionId.TargetLocationID)) + category.Append('_').Append(sessionId.TargetLocationID); + + if (SessionID.IsSet(sessionId.SessionQualifier)) + category.Append('-').Append(sessionId.SessionQualifier); + + return category.ToString(); + } +} + +internal class NullQuickFixLoggerFactory : IQuickFixLoggerFactory +{ + public ILogger CreateSessionLogger(SessionID sessionId) => NullLogger.Instance; + public ILogger CreateNonSessionLogger() => NullLogger.Instance; + public static NullQuickFixLoggerFactory Instance = new NullQuickFixLoggerFactory(); +} \ No newline at end of file diff --git a/QuickFIXn/Logger/LogFactoryAdapter.cs b/QuickFIXn/Logger/LogFactoryAdapter.cs index ca120a301..081238081 100644 --- a/QuickFIXn/Logger/LogFactoryAdapter.cs +++ b/QuickFIXn/Logger/LogFactoryAdapter.cs @@ -1,40 +1,25 @@ using System; using System.Collections.Concurrent; -using System.Linq; using Microsoft.Extensions.Logging; namespace QuickFix.Logger; -internal class LogFactoryAdapter : ILoggerFactory, IDisposable +internal class LogFactoryAdapter : IQuickFixLoggerFactory, IDisposable { private readonly ILogFactory _logFactory; - private readonly SessionSettings _settings; private readonly ConcurrentDictionary _loggers = new(); + private readonly Lazy _nonSessionLogger; - internal LogFactoryAdapter(ILogFactory logFactory, SessionSettings settings) + internal LogFactoryAdapter(ILogFactory logFactory) { _logFactory = logFactory; - _settings = settings; + _nonSessionLogger = new Lazy(() => new LogAdapter(_logFactory.CreateNonSessionLog())); } - public ILogger CreateLogger(string categoryName) - { - // category will be "QuickFix" for non-session logger and "QuickFix." for session logger - var sessionIdString = categoryName.Length > "QuickFix.".Length ? categoryName.Substring(9) : ""; - var sessionId = _settings.GetSessions() - .SingleOrDefault(s => - s.ToString().Equals(sessionIdString, StringComparison.InvariantCulture)); - var defaultSessionId = new SessionID("Non", "Session", "Log"); - - return _loggers.GetOrAdd(sessionId ?? defaultSessionId, sid => sessionId is not null - ? new LogAdapter(_logFactory.Create(sid)) - : new LogAdapter(_logFactory.CreateNonSessionLog())); - } + public ILogger CreateSessionLogger(SessionID sessionId) => + _loggers.GetOrAdd(sessionId, sId => new LogAdapter(_logFactory.Create(sId))); - public void AddProvider(ILoggerProvider provider) - { - throw new NotImplementedException(); - } + public ILogger CreateNonSessionLogger() => _nonSessionLogger.Value; public void Dispose() { @@ -51,10 +36,21 @@ public void Dispose() } } } + + try + { + if (_nonSessionLogger.IsValueCreated && _nonSessionLogger.Value is IDisposable disposable) + { + disposable.Dispose(); + } + } + catch + { + } } } -internal class LogAdapter : ILogger +internal class LogAdapter : ILogger, IDisposable { private readonly ILog _log; @@ -84,4 +80,9 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; public IDisposable BeginScope(TState state) => default!; + + public void Dispose() + { + _log.Dispose(); + } } \ No newline at end of file diff --git a/QuickFIXn/Logger/NonSessionFileLogger.cs b/QuickFIXn/Logger/NonSessionFileLogger.cs deleted file mode 100644 index 9ffcb8a57..000000000 --- a/QuickFIXn/Logger/NonSessionFileLogger.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using Microsoft.Extensions.Logging; - -namespace QuickFix.Logger; - -/// -/// Like the file logger, but only creates the files on first write -/// -[Obsolete("Use Microsoft.Extensions.Logging instead")] -internal class NonSessionFileLogger : ILogger, IDisposable -{ - private readonly Lazy _fileLog; - - internal NonSessionFileLogger(string fileLogPath, SessionID sessionId) - { - _fileLog = new Lazy(() => new FileLog(fileLogPath, sessionId)); - } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, - Func formatter) => - _fileLog.Value.Log(logLevel, eventId, state, exception, formatter); - - public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; - - public IDisposable BeginScope(TState state) => _fileLog.Value.BeginScope(state); - - public void Dispose() - { - if (_fileLog.IsValueCreated) _fileLog.Value.Dispose(); - } -} \ No newline at end of file diff --git a/QuickFIXn/Logger/ScreenLog.cs b/QuickFIXn/Logger/ScreenLog.cs index 829228580..551a8898f 100755 --- a/QuickFIXn/Logger/ScreenLog.cs +++ b/QuickFIXn/Logger/ScreenLog.cs @@ -1,5 +1,4 @@ using System; -using Microsoft.Extensions.Logging; namespace QuickFix.Logger; @@ -7,7 +6,7 @@ namespace QuickFix.Logger; /// FIXME - needs to log sessionIDs, timestamps, etc. /// [Obsolete("Use Microsoft.Extensions.Logging instead.")] -public class ScreenLog : ILog, ILogger +public class ScreenLog : ILog { private readonly object _sync = new (); private readonly bool _logIncoming; @@ -60,27 +59,5 @@ public void OnEvent(string s) } #endregion - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, - Func formatter) - { - if (!IsEnabled(logLevel)) return; - if (eventId == LogEventIds.IncomingMessage && _logIncoming) - { - Console.WriteLine($" {formatter(state, exception).Replace(Message.SOH, '|')}"); - } - else if (eventId == LogEventIds.OutgoingMessage && _logOutgoing) - { - Console.WriteLine($" {formatter(state, exception).Replace(Message.SOH, '|')}"); - } - else if (_logEvent) - { - Console.WriteLine($" {formatter(state, exception)}"); - } - } - - public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; - - public IDisposable BeginScope(TState state) => default!; - public void Dispose(){} } diff --git a/QuickFIXn/Logger/ScreenLoggerProvider.cs b/QuickFIXn/Logger/ScreenLoggerProvider.cs deleted file mode 100755 index ecb1f02ce..000000000 --- a/QuickFIXn/Logger/ScreenLoggerProvider.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Linq; -using Microsoft.Extensions.Logging; - -namespace QuickFix.Logger; - -[Obsolete("This class is provided to ease migration from the old logging system to Microsoft.Extensions.Logging." + - "It is an attempt to maintain the behavior of the previous ScreenLog and ScreenLogFactory while plugging into the Microsoft.Extensions.Logging ecosystem. " + - "Consider using the more robust logging options available in the .NET ecosystem, like the MS Console logging provider, Serilog and NLog.")] -public class ScreenLoggerProvider : ILoggerProvider -{ - private const string SCREEN_LOG_SHOW_INCOMING = "ScreenLogShowIncoming"; - private const string SCREEN_LOG_SHOW_OUTGOING = "ScreenLogShowOutgoing"; - private const string SCREEN_LOG_SHOW_EVENTS = "ScreenLogShowEvents"; - private readonly bool _logIncoming = false; - private readonly bool _logOutgoing = false; - private readonly bool _logEvent = false; - private readonly SessionSettings _settings; - - public ScreenLoggerProvider(SessionSettings settings) - { - _settings = settings; - } - - public ScreenLoggerProvider(bool logIncoming, bool logOutgoing, bool logEvent) - { - _logIncoming = logIncoming; - _logOutgoing = logOutgoing; - _logEvent = logEvent; - _settings = new SessionSettings(); - } - - public ILogger CreateLogger(string categoryName) - { - // category will be "QuickFix" for non-session logger and "QuickFix." for session logger - var sessionIdString = categoryName.Length > "QuickFix.".Length ? categoryName.Substring(9) : ""; - bool logIncoming = _logIncoming; - bool logOutgoing = _logOutgoing; - bool logEvent = _logEvent; - - var sessionId = _settings.GetSessions() - .SingleOrDefault(s => s.ToString().Equals(sessionIdString, StringComparison.InvariantCulture)); - if (sessionId is not null) - { - SettingsDictionary dict = _settings.Get(sessionId); - - logIncoming = _logIncoming || dict.IsBoolPresentAndTrue(SCREEN_LOG_SHOW_INCOMING); - logOutgoing = _logOutgoing || dict.IsBoolPresentAndTrue(SCREEN_LOG_SHOW_OUTGOING); - logEvent = _logEvent || dict.IsBoolPresentAndTrue(SCREEN_LOG_SHOW_EVENTS); - } - - return new ScreenLog(logIncoming, logOutgoing, logEvent); - } - - public void Dispose() - { - } -} \ No newline at end of file diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index 0bcb171dd..9cb10c33f 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -226,7 +226,7 @@ public Session( DataDictionaryProvider dataDictProvider, SessionSchedule sessionSchedule, int heartBtInt, - ILoggerFactory loggerFactory, + IQuickFixLoggerFactory loggerFactory, IMessageFactory msgFactory, string senderDefaultApplVerId) { @@ -244,7 +244,7 @@ public Session( ? DataDictionaryProvider.GetApplicationDataDictionary(SenderDefaultApplVerID) : SessionDataDictionary; - var logger = loggerFactory.CreateLogger($"QuickFix.{sessId}"); + var logger = loggerFactory.CreateSessionLogger(sessId); _state = new SessionState(isInitiator, logger, heartBtInt, storeFactory.Create(sessId)); diff --git a/QuickFIXn/SessionFactory.cs b/QuickFIXn/SessionFactory.cs index 83f408b52..cf9cd7b7c 100755 --- a/QuickFIXn/SessionFactory.cs +++ b/QuickFIXn/SessionFactory.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; +using QuickFix.Logger; using QuickFix.Store; using QuickFix.Util; @@ -14,14 +13,14 @@ public class SessionFactory { protected IApplication _application; protected IMessageStoreFactory _messageStoreFactory; - protected ILoggerFactory _loggerFactory; + protected IQuickFixLoggerFactory _loggerFactory; protected IMessageFactory _messageFactory; protected Dictionary _dictionariesByPath = new(); public SessionFactory( IApplication app, IMessageStoreFactory storeFactory, - ILoggerFactory? loggerFactory = null, + IQuickFixLoggerFactory? loggerFactory = null, IMessageFactory? messageFactory = null) { // TODO: for V2, consider ONLY instantiating MessageFactory in the Create() method, @@ -32,7 +31,7 @@ public SessionFactory( _application = app; _messageStoreFactory = storeFactory; - _loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; + _loggerFactory = loggerFactory ?? NullQuickFixLoggerFactory.Instance; _messageFactory = messageFactory ?? new DefaultMessageFactory(); } diff --git a/QuickFIXn/SocketInitiatorThread.cs b/QuickFIXn/SocketInitiatorThread.cs index b78aa7158..3872d9696 100755 --- a/QuickFIXn/SocketInitiatorThread.cs +++ b/QuickFIXn/SocketInitiatorThread.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using QuickFix.Logger; namespace QuickFix { @@ -28,6 +29,7 @@ public class SocketInitiatorThread : IResponder private readonly IPEndPoint _socketEndPoint; private readonly SocketSettings _socketSettings; private bool _isDisconnectRequested = false; + private readonly IQuickFixLoggerFactory _loggerFactory; /// /// Keep a task for handling async read @@ -39,11 +41,12 @@ public SocketInitiatorThread( Session session, IPEndPoint socketEndPoint, SocketSettings socketSettings, - ILogger nonSessionLog) + IQuickFixLoggerFactory loggerFactory) { Initiator = initiator; Session = session; - NonSessionLog = nonSessionLog; + _loggerFactory = loggerFactory; + NonSessionLog = _loggerFactory.CreateNonSessionLogger(); _socketEndPoint = socketEndPoint; _socketSettings = socketSettings; } @@ -81,7 +84,7 @@ public void Connect() /// Stream representing the (network)connection to the other party protected virtual Stream SetupStream() { - return Transport.StreamFactory.CreateClientStream(_socketEndPoint, _socketSettings, NonSessionLog); + return Transport.StreamFactory.CreateClientStream(_socketEndPoint, _socketSettings, _loggerFactory); } public bool Read() diff --git a/QuickFIXn/SocketReader.cs b/QuickFIXn/SocketReader.cs index b92007c3d..26277f22f 100755 --- a/QuickFIXn/SocketReader.cs +++ b/QuickFIXn/SocketReader.cs @@ -40,13 +40,13 @@ internal SocketReader( SocketSettings settings, ClientHandlerThread responder, AcceptorSocketDescriptor? acceptorDescriptor, - ILogger nonSessionLog) + IQuickFixLoggerFactory loggerFactory) { _tcpClient = tcpClient; _responder = responder; _acceptorDescriptor = acceptorDescriptor; - _stream = Transport.StreamFactory.CreateServerStream(tcpClient, settings, nonSessionLog); - _nonSessionLog = nonSessionLog; + _stream = Transport.StreamFactory.CreateServerStream(tcpClient, settings, loggerFactory); + _nonSessionLog = loggerFactory.CreateNonSessionLogger(); } public void Read() diff --git a/QuickFIXn/ThreadedSocketAcceptor.cs b/QuickFIXn/ThreadedSocketAcceptor.cs index b6bab13be..7e0ec44e5 100755 --- a/QuickFIXn/ThreadedSocketAcceptor.cs +++ b/QuickFIXn/ThreadedSocketAcceptor.cs @@ -3,7 +3,6 @@ using System.Net; using System; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using QuickFix.Logger; using QuickFix.Store; @@ -22,7 +21,7 @@ public class ThreadedSocketAcceptor : IAcceptor private bool _isStarted = false; private bool _disposed = false; private readonly object _sync = new(); - private readonly ILogger _nonSessionLog; + private readonly IQuickFixLoggerFactory _loggerFactory; private readonly LogFactoryAdapter? _logFactoryAdapter; #region Constructors @@ -41,7 +40,7 @@ public ThreadedSocketAcceptor( SessionSettings settings, ILogFactory? logFactory = null, IMessageFactory? messageFactory = null) : this(application, storeFactory, settings, - logFactory is null ? NullLoggerFactory.Instance : new LogFactoryAdapter(logFactory, settings), + logFactory is null ? NullQuickFixLoggerFactory.Instance : new LogFactoryAdapter(logFactory), messageFactory) { } @@ -59,21 +58,31 @@ public ThreadedSocketAcceptor( IMessageStoreFactory storeFactory, SessionSettings settings, ILoggerFactory? loggerFactory = null, + IMessageFactory? messageFactory = null) : this(application, storeFactory, settings, + loggerFactory is null ? NullQuickFixLoggerFactory.Instance : new MelQuickFixLoggerFactory(loggerFactory), + messageFactory) + { + } + + private ThreadedSocketAcceptor( + IApplication application, + IMessageStoreFactory storeFactory, + SessionSettings settings, + IQuickFixLoggerFactory loggerFactory, IMessageFactory? messageFactory = null) { - var lf = loggerFactory ?? NullLoggerFactory.Instance; - if (lf is LogFactoryAdapter lfa) + if (loggerFactory is LogFactoryAdapter lfa) { // LogFactoryAdapter is only ever created in the constructor marked obsolete, which means we own it and - // must save a ref to it so we can dispose it later. Any other ILoggerFactory is owned by someone else + // must save a ref to it so we can dispose it later. Any other loggerFactory is owned by someone else // so we'll leave the dispose up to them. This should be removed eventually together with the old ILog // and ILogFactory. _logFactoryAdapter = lfa; } IMessageFactory mf = messageFactory ?? new DefaultMessageFactory(); _settings = settings; - _sessionFactory = new SessionFactory(application, storeFactory, lf, mf); - _nonSessionLog = lf.CreateLogger("QuickFix"); + _sessionFactory = new SessionFactory(application, storeFactory, loggerFactory, mf); + _loggerFactory = loggerFactory; try { @@ -124,7 +133,7 @@ private AcceptorSocketDescriptor GetAcceptorSocketDescriptor(SettingsDictionary if (!_socketDescriptorForAddress.TryGetValue(socketEndPoint, out var descriptor)) { - descriptor = new AcceptorSocketDescriptor(socketEndPoint, socketSettings, _nonSessionLog); + descriptor = new AcceptorSocketDescriptor(socketEndPoint, socketSettings, _loggerFactory); _socketDescriptorForAddress[socketEndPoint] = descriptor; } diff --git a/QuickFIXn/ThreadedSocketReactor.cs b/QuickFIXn/ThreadedSocketReactor.cs index b342f46a4..04d0c4d30 100755 --- a/QuickFIXn/ThreadedSocketReactor.cs +++ b/QuickFIXn/ThreadedSocketReactor.cs @@ -4,6 +4,7 @@ using System.Threading; using System; using Microsoft.Extensions.Logging; +using QuickFix.Logger; namespace QuickFix { @@ -32,18 +33,20 @@ public State ReactorState private readonly IPEndPoint _serverSocketEndPoint; private readonly AcceptorSocketDescriptor? _acceptorSocketDescriptor; private readonly ILogger _nonSessionLog; + private readonly IQuickFixLoggerFactory _loggerFactory; internal ThreadedSocketReactor( IPEndPoint serverSocketEndPoint, SocketSettings socketSettings, AcceptorSocketDescriptor? acceptorSocketDescriptor, - ILogger nonSessionLog) + IQuickFixLoggerFactory loggerFactory) { _socketSettings = socketSettings; _serverSocketEndPoint = serverSocketEndPoint; _tcpListener = new TcpListener(_serverSocketEndPoint); _acceptorSocketDescriptor = acceptorSocketDescriptor; - _nonSessionLog = nonSessionLog; + _loggerFactory = loggerFactory; + _nonSessionLog = loggerFactory.CreateNonSessionLogger(); } public void Start() @@ -115,7 +118,7 @@ public void Run() { ApplySocketOptions(client, _socketSettings); ClientHandlerThread t = new ClientHandlerThread( - client, _nextClientId++, _socketSettings, _acceptorSocketDescriptor, _nonSessionLog); + client, _nextClientId++, _socketSettings, _acceptorSocketDescriptor, _loggerFactory); t.Exited += OnClientHandlerThreadExited; lock (_sync) { diff --git a/QuickFIXn/Transport/SocketInitiator.cs b/QuickFIXn/Transport/SocketInitiator.cs index 8224ca60c..95ffd7985 100644 --- a/QuickFIXn/Transport/SocketInitiator.cs +++ b/QuickFIXn/Transport/SocketInitiator.cs @@ -23,6 +23,7 @@ public class SocketInitiator : AbstractInitiator private readonly Dictionary _threads = new(); private readonly Dictionary _sessionToHostNum = new(); private readonly object _sync = new(); + private readonly ILogger _nonSessionLog; [Obsolete("Use \"Microsoft.Extensions.Logging.ILoggerFactory\" instead of \"QuickFix.Logger.ILogFactory\".")] public SocketInitiator( @@ -32,16 +33,20 @@ public SocketInitiator( ILogFactory? logFactoryNullable = null, IMessageFactory? messageFactoryNullable = null) : base(application, storeFactory, settings, logFactoryNullable, messageFactoryNullable) - { } + { + _nonSessionLog = LoggerFactory.CreateNonSessionLogger(); + } public SocketInitiator( IApplication application, IMessageStoreFactory storeFactory, SessionSettings settings, - ILoggerFactory? logFactoryNullable = null, + ILoggerFactory? loggerFactoryNullable = null, IMessageFactory? messageFactoryNullable = null) - : base(application, storeFactory, settings, logFactoryNullable, messageFactoryNullable) - { } + : base(application, storeFactory, settings, loggerFactoryNullable, messageFactoryNullable) + { + _nonSessionLog = LoggerFactory.CreateNonSessionLogger(); + } public static void SocketInitiatorThreadStart(object? socketInitiatorThread) { @@ -237,7 +242,7 @@ protected override void DoConnect(Session session, SettingsDictionary settings) // Create a Ssl-SocketInitiatorThread if a certificate is given SocketInitiatorThread t = new SocketInitiatorThread( - this, session, socketEndPoint, socketSettings, _nonSessionLog); + this, session, socketEndPoint, socketSettings, LoggerFactory); t.Start(); AddThread(t); } diff --git a/QuickFIXn/Transport/SslStreamFactory.cs b/QuickFIXn/Transport/SslStreamFactory.cs index 6ed6a4ef4..5dbdeea9f 100644 --- a/QuickFIXn/Transport/SslStreamFactory.cs +++ b/QuickFIXn/Transport/SslStreamFactory.cs @@ -20,10 +20,10 @@ internal sealed class SslStreamFactory private const string CLIENT_AUTHENTICATION_OID = "1.3.6.1.5.5.7.3.2"; private const string SERVER_AUTHENTICATION_OID = "1.3.6.1.5.5.7.3.1"; - public SslStreamFactory(SocketSettings settings, ILogger nonSessionLog) + public SslStreamFactory(SocketSettings settings, IQuickFixLoggerFactory loggerFactory) { _socketSettings = settings; - _nonSessionLog = nonSessionLog; + _nonSessionLog = loggerFactory.CreateNonSessionLogger(); } /// diff --git a/QuickFIXn/Transport/StreamFactory.cs b/QuickFIXn/Transport/StreamFactory.cs index 6787187b5..04a1e98f7 100644 --- a/QuickFIXn/Transport/StreamFactory.cs +++ b/QuickFIXn/Transport/StreamFactory.cs @@ -4,7 +4,7 @@ using System.Net; using System.Net.Sockets; using System.Text; -using Microsoft.Extensions.Logging; +using QuickFix.Logger; namespace QuickFix.Transport { @@ -64,9 +64,9 @@ internal static class StreamFactory /// /// The endpoint. /// The socket settings. - /// Logger that is not tied to a particular session + /// /// an opened and initiated stream which can be read and written to - internal static Stream CreateClientStream(IPEndPoint endpoint, SocketSettings settings, ILogger nonSessionLog) + internal static Stream CreateClientStream(IPEndPoint endpoint, SocketSettings settings, IQuickFixLoggerFactory loggerFactory) { Socket? socket = null; @@ -107,7 +107,7 @@ internal static Stream CreateClientStream(IPEndPoint endpoint, SocketSettings se Stream stream = new NetworkStream(socket, true); if (settings.UseSSL) - stream = new SslStreamFactory(settings, nonSessionLog).CreateClientStreamAndAuthenticate(stream); + stream = new SslStreamFactory(settings, loggerFactory).CreateClientStreamAndAuthenticate(stream); return stream; } @@ -117,10 +117,10 @@ internal static Stream CreateClientStream(IPEndPoint endpoint, SocketSettings se /// /// The TCP client. /// The socket settings. - /// Logger that is not tied to a particular session + /// /// an opened and initiated stream which can be read and written to /// tcp client must be connected in order to get stream;tcpClient - internal static Stream CreateServerStream(TcpClient tcpClient, SocketSettings settings, ILogger nonSessionLog) + internal static Stream CreateServerStream(TcpClient tcpClient, SocketSettings settings, IQuickFixLoggerFactory loggerFactory) { if (tcpClient.Connected == false) throw new ArgumentException("tcp client must be connected in order to get stream", nameof(tcpClient)); @@ -128,7 +128,7 @@ internal static Stream CreateServerStream(TcpClient tcpClient, SocketSettings se Stream stream = tcpClient.GetStream(); if (settings.UseSSL) { - stream = new SslStreamFactory(settings, nonSessionLog).CreateServerStreamAndAuthenticate(stream); + stream = new SslStreamFactory(settings, loggerFactory).CreateServerStreamAndAuthenticate(stream); } return stream; diff --git a/UnitTests/Logger/FileLogTests.cs b/UnitTests/Logger/FileLogTests.cs index d671f0066..c65cda93c 100644 --- a/UnitTests/Logger/FileLogTests.cs +++ b/UnitTests/Logger/FileLogTests.cs @@ -1,5 +1,4 @@ using System.IO; -using Microsoft.Extensions.Logging; using NUnit.Framework; using QuickFix.Logger; @@ -58,12 +57,12 @@ public void TestGeneratedFileName() settings.Set(sessionId, config); - var fileLogProvider = new FileLoggerProvider(settings); - _log = (FileLog) fileLogProvider.CreateLogger($"QuickFix.{sessionId}"); + FileLogFactory fileLogFactory = new FileLogFactory(settings); + _log = (FileLog) fileLogFactory.Create(sessionId); - _log.Log(LogLevel.Debug, "some event"); - _log.Log(LogLevel.Information, LogEventIds.IncomingMessage, "some incoming"); - _log.Log(LogLevel.Information, LogEventIds.OutgoingMessage, "some outgoing"); + _log.OnEvent("some event"); + _log.OnIncoming("some incoming"); + _log.OnOutgoing("some outgoing"); Assert.That(File.Exists(Path.Combine(logDirectory, "FIX.4.2-SENDERCOMP-TARGETCOMP.event.current.log"))); Assert.That(File.Exists(Path.Combine(logDirectory, "FIX.4.2-SENDERCOMP-TARGETCOMP.messages.current.log"))); @@ -83,8 +82,8 @@ public void TestThrowsIfNoConfig() QuickFix.SessionSettings settings = new QuickFix.SessionSettings(); settings.Set(sessionId, config); - using var loggerFactory = new LoggerFactory([new FileLoggerProvider(settings)]); + FileLogFactory factory = new FileLogFactory(settings); - Assert.Throws(() => loggerFactory.CreateLogger($"QuickFix.{sessionId}")); + Assert.Throws(delegate { factory.Create(sessionId); }); } } diff --git a/UnitTests/SessionDynamicTest.cs b/UnitTests/SessionDynamicTest.cs index 47fc42fed..864fc563c 100644 --- a/UnitTests/SessionDynamicTest.cs +++ b/UnitTests/SessionDynamicTest.cs @@ -5,7 +5,6 @@ using System.Net; using System.Net.Sockets; using System.Text.RegularExpressions; -using Microsoft.Extensions.Logging; using NUnit.Framework; using QuickFix; using QuickFix.Logger; @@ -73,7 +72,6 @@ public SocketState(Socket s) private string _logPath = "unset"; private SocketInitiator? _initiator; private ThreadedSocketAcceptor? _acceptor; - private ILoggerFactory? _loggerFactory; private Dictionary _sessions = new(); private HashSet _loggedOnCompIDs = new(); private Socket? _listenSocket; @@ -128,14 +126,13 @@ private void StartEngine(bool initiator, bool twoSessions = false) defaults.SetString(SessionSettings.SOCKET_ACCEPT_PORT, AcceptPort.ToString()); settings.Set(defaults); - _loggerFactory = new LoggerFactory(); - _loggerFactory.AddProvider(new FileLoggerProvider(settings)); + ILogFactory logFactory = new FileLogFactory(settings); if (initiator) { defaults.SetString(SessionSettings.RECONNECT_INTERVAL, "1"); settings.Set(CreateSessionId(StaticInitiatorCompId), CreateSessionConfig(true)); - _initiator = new SocketInitiator(application, storeFactory, settings, _loggerFactory); + _initiator = new SocketInitiator(application, storeFactory, settings, logFactory); _initiator.Start(); } else @@ -153,7 +150,7 @@ private void StartEngine(bool initiator, bool twoSessions = false) settings.Set(id, conf); } - _acceptor = new ThreadedSocketAcceptor(application, storeFactory, settings, _loggerFactory); + _acceptor = new ThreadedSocketAcceptor(application, storeFactory, settings, logFactory); _acceptor.Start(); } } @@ -363,11 +360,9 @@ public void TearDown() _listenSocket?.Close(); _initiator?.Stop(true); _acceptor?.Stop(true); - _loggerFactory?.Dispose(); _initiator = null; _acceptor = null; - _loggerFactory = null; Thread.Sleep(500); ClearLogs(); diff --git a/UnitTests/SessionTest.cs b/UnitTests/SessionTest.cs index fce5be3ae..0ee94bfbd 100755 --- a/UnitTests/SessionTest.cs +++ b/UnitTests/SessionTest.cs @@ -4,8 +4,7 @@ using System.Text.RegularExpressions; using NUnit.Framework; using System.Threading; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; +using QuickFix.Logger; using QuickFix.Store; namespace UnitTests; @@ -40,17 +39,17 @@ public void Setup() _config.SetString(QuickFix.SessionSettings.END_TIME, "00:00:00"); _settings.Set(_sessionId, _config); - var loggerFactory = new LoggerFactory([NullLoggerProvider.Instance]); // use QuickFix.ScreenLogFactory(settings) if you need to see output + var logFactory = new LogFactoryAdapter(new NullLogFactory()); // use QuickFix.ScreenLogFactory(settings) if you need to see output // acceptor _session = new QuickFix.Session(false, _application, new MemoryStoreFactory(), _sessionId, - new QuickFix.DataDictionaryProvider(),new QuickFix.SessionSchedule(_config), 0, loggerFactory, new QuickFix.DefaultMessageFactory(), "blah"); + new QuickFix.DataDictionaryProvider(),new QuickFix.SessionSchedule(_config), 0, logFactory, new QuickFix.DefaultMessageFactory(), "blah"); _session.SetResponder(_responder); _session.CheckLatency = false; // initiator _session2 = new QuickFix.Session(true, _application, new MemoryStoreFactory(), new QuickFix.SessionID("FIX.4.2", "OTHER_SENDER", "OTHER_TARGET"), - new QuickFix.DataDictionaryProvider(), new QuickFix.SessionSchedule(_config), 0, loggerFactory, new QuickFix.DefaultMessageFactory(), "blah"); + new QuickFix.DataDictionaryProvider(), new QuickFix.SessionSchedule(_config), 0, logFactory, new QuickFix.DefaultMessageFactory(), "blah"); _session2.SetResponder(_responder); _session2.CheckLatency = false; @@ -760,7 +759,7 @@ public void TestApplicationExtension() var mockApp = new SessionTestSupport.MockApplicationExt(); _session = new QuickFix.Session(true, mockApp, new MemoryStoreFactory(), _sessionId, new QuickFix.DataDictionaryProvider(), new QuickFix.SessionSchedule(_config), 0, - new LoggerFactory([NullLoggerProvider.Instance]), new QuickFix.DefaultMessageFactory(), "blah"); + NullQuickFixLoggerFactory.Instance, new QuickFix.DefaultMessageFactory(), "blah"); _session.SetResponder(_responder); _session.CheckLatency = false; diff --git a/UnitTests/ThreadedSocketAcceptorTests.cs b/UnitTests/ThreadedSocketAcceptorTests.cs index 6219f3ae1..0361f3d1c 100644 --- a/UnitTests/ThreadedSocketAcceptorTests.cs +++ b/UnitTests/ThreadedSocketAcceptorTests.cs @@ -43,8 +43,7 @@ public void TestRecreation() private static void StartStopAcceptor() { var settings = CreateSettings(); - using var lf = new LoggerFactory(); - lf.AddProvider(new FileLoggerProvider(settings)); + var lf = new FileLogFactory(settings); var acceptor = new ThreadedSocketAcceptor( new NullApplication(), diff --git a/UnitTests/ThreadedSocketReactorTests.cs b/UnitTests/ThreadedSocketReactorTests.cs index 05c81fdb9..e08a412f0 100644 --- a/UnitTests/ThreadedSocketReactorTests.cs +++ b/UnitTests/ThreadedSocketReactorTests.cs @@ -49,7 +49,7 @@ public void TestStartOnBusyPort() new IPEndPoint(IPAddress.Loopback, port), settings, acceptorSocketDescriptor: null, - new ScreenLog(true, true, true)); + new LogFactoryAdapter(new ScreenLogFactory(true, true, true))); var stdOut = GetStdOut(); var ex = Assert.Throws(delegate { testingObject.Run(); })!; From 0f52aa03af7e03a4b9b9f65ad96083a5c64b2a47 Mon Sep 17 00:00:00 2001 From: JK Date: Thu, 9 Jan 2025 08:58:06 -0500 Subject: [PATCH 22/23] Make log levels make sense --- QuickFIXn/Logger/IQuickFixLoggerFactory.cs | 2 +- QuickFIXn/Session.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/QuickFIXn/Logger/IQuickFixLoggerFactory.cs b/QuickFIXn/Logger/IQuickFixLoggerFactory.cs index e08fdc849..28a5435f4 100644 --- a/QuickFIXn/Logger/IQuickFixLoggerFactory.cs +++ b/QuickFIXn/Logger/IQuickFixLoggerFactory.cs @@ -57,5 +57,5 @@ internal class NullQuickFixLoggerFactory : IQuickFixLoggerFactory { public ILogger CreateSessionLogger(SessionID sessionId) => NullLogger.Instance; public ILogger CreateNonSessionLogger() => NullLogger.Instance; - public static NullQuickFixLoggerFactory Instance = new NullQuickFixLoggerFactory(); + public static readonly NullQuickFixLoggerFactory Instance = new NullQuickFixLoggerFactory(); } \ No newline at end of file diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index 9cb10c33f..984a86907 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -672,7 +672,7 @@ internal void Next(MessageBuilder msgBuilder) { if (MsgType.LOGON.Equals(msgBuilder.MsgType.Value)) { - Log.Log(LogLevel.Warning, "Required field missing from logon"); + Log.Log(LogLevel.Error, "Required field missing from logon"); Disconnect("Required field missing from logon"); } else @@ -694,7 +694,7 @@ protected void NextLogon(Message logon) if (_state.ReceivedReset) { - Log.Log(LogLevel.Warning, "Sequence numbers reset due to ResetSeqNumFlag=Y"); + Log.Log(LogLevel.Debug, "Sequence numbers reset due to ResetSeqNumFlag=Y"); if (!_state.SentReset) { _state.Reset("Reset requested by counterparty"); @@ -717,13 +717,13 @@ protected void NextLogon(Message logon) } _state.ReceivedLogon = true; - Log.Log(LogLevel.Warning, "Received logon"); + Log.Log(LogLevel.Debug, "Received logon"); if (IsAcceptor) { int heartBtInt = logon.GetInt(Fields.Tags.HeartBtInt); _state.HeartBtInt = heartBtInt; GenerateLogon(logon); - Log.Log(LogLevel.Warning, $"Responding to logon request; heartbeat is {heartBtInt} seconds"); + Log.Log(LogLevel.Debug, $"Responding to logon request; heartbeat is {heartBtInt} seconds"); } _state.SentReset = false; @@ -967,7 +967,7 @@ public bool Verify(Message msg, bool checkTooHigh = true, bool checkTooLow = tru } else if (msgSeqNum >= range.ChunkEndSeqNo) { - Log.Log(LogLevel.Warning, + Log.Log(LogLevel.Debug, "Chunked ResendRequest for messages FROM: {BeginSeqNo} TO: {EndSeqNo} has been satisfied.", range.BeginSeqNo, range.ChunkEndSeqNo); SeqNumType newChunkEndSeqNo = Math.Min(range.EndSeqNo, range.ChunkEndSeqNo + MaxMessagesInResendRequest); @@ -978,7 +978,7 @@ public bool Verify(Message msg, bool checkTooHigh = true, bool checkTooLow = tru if (!IsGoodTime(msg)) { - Log.Log(LogLevel.Warning, "Sending time accuracy problem"); + Log.Log(LogLevel.Error, "Sending time accuracy problem"); GenerateReject(msg, FixValues.SessionRejectReason.SENDING_TIME_ACCURACY_PROBLEM); GenerateLogout(); return false; @@ -1165,7 +1165,7 @@ protected void GenerateBusinessMessageReject(Message message, int err, int field reject.SetField(new Text(reason)); - Log.Log(LogLevel.Warning, "Reject sent for Message: {MsgSeqNum} Reason: {Reason}", msgSeqNum, reason); + Log.Log(LogLevel.Debug, "Reject sent for Message: {MsgSeqNum} Reason: {Reason}", msgSeqNum, reason); SendRaw(reject, 0); } From cc7cc9e5a76d7037260f1644256e955db24cfbde Mon Sep 17 00:00:00 2001 From: JK Date: Thu, 9 Jan 2025 09:01:22 -0500 Subject: [PATCH 23/23] Add serilog example project --- Examples/Standalone/SerilogLog/AppSettings.cs | 6 + .../SerilogLog/FixBackgroundService.cs | 60 ++++++++ Examples/Standalone/SerilogLog/Program.cs | 74 +++++++++ Examples/Standalone/SerilogLog/README.md | 35 ----- Examples/Standalone/SerilogLog/Serilog.cfg | 33 ++++ Examples/Standalone/SerilogLog/SerilogLog.cs | 142 ------------------ .../Standalone/SerilogLog/SerilogLog.csproj | 21 ++- Examples/Standalone/SerilogLog/SerilogLog.sln | 31 ---- .../SerilogLog/SerilogLogFactory.cs | 39 ----- .../SerilogLog/UnitTests/SerilogLogTests.cs | 136 ----------------- .../SerilogLog/UnitTests/UnitTests.csproj | 26 ---- .../Standalone/SerilogLog/appsettings.json | 3 + QuickFIXn.sln | 18 +++ 13 files changed, 207 insertions(+), 417 deletions(-) create mode 100644 Examples/Standalone/SerilogLog/AppSettings.cs create mode 100644 Examples/Standalone/SerilogLog/FixBackgroundService.cs create mode 100644 Examples/Standalone/SerilogLog/Program.cs delete mode 100644 Examples/Standalone/SerilogLog/README.md create mode 100644 Examples/Standalone/SerilogLog/Serilog.cfg delete mode 100644 Examples/Standalone/SerilogLog/SerilogLog.cs delete mode 100644 Examples/Standalone/SerilogLog/SerilogLog.sln delete mode 100644 Examples/Standalone/SerilogLog/SerilogLogFactory.cs delete mode 100644 Examples/Standalone/SerilogLog/UnitTests/SerilogLogTests.cs delete mode 100644 Examples/Standalone/SerilogLog/UnitTests/UnitTests.csproj create mode 100644 Examples/Standalone/SerilogLog/appsettings.json diff --git a/Examples/Standalone/SerilogLog/AppSettings.cs b/Examples/Standalone/SerilogLog/AppSettings.cs new file mode 100644 index 000000000..799490adb --- /dev/null +++ b/Examples/Standalone/SerilogLog/AppSettings.cs @@ -0,0 +1,6 @@ +namespace SerilogLog; + +public class AppSettings +{ + public required string ConfigFilePath { get; set; } +} \ No newline at end of file diff --git a/Examples/Standalone/SerilogLog/FixBackgroundService.cs b/Examples/Standalone/SerilogLog/FixBackgroundService.cs new file mode 100644 index 000000000..7ef167b84 --- /dev/null +++ b/Examples/Standalone/SerilogLog/FixBackgroundService.cs @@ -0,0 +1,60 @@ +using Microsoft.Extensions.Options; +using QuickFix; +using QuickFix.Store; +using QuickFix.Transport; + +namespace SerilogLog; + +public class FixBackgroundService(IOptions appSettings, ILoggerFactory loggerFactory) : IHostedService, IApplication +{ + private SocketInitiator? _initiator; + + public Task StartAsync(CancellationToken cancellationToken) + { + var settings = new SessionSettings(appSettings.Value.ConfigFilePath); + + var storeFactory = new FileStoreFactory(settings); + _initiator = new SocketInitiator( + this, + storeFactory, + settings, + loggerFactory); + + _initiator.Start(); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _initiator?.Stop(); + return Task.CompletedTask; + } + + public void ToAdmin(Message message, SessionID sessionId) + { + } + + public void FromAdmin(Message message, SessionID sessionId) + { + } + + public void ToApp(Message message, SessionID sessionId) + { + } + + public void FromApp(Message message, SessionID sessionId) + { + } + + public void OnCreate(SessionID sessionId) + { + } + + public void OnLogout(SessionID sessionId) + { + } + + public void OnLogon(SessionID sessionId) + { + } +} \ No newline at end of file diff --git a/Examples/Standalone/SerilogLog/Program.cs b/Examples/Standalone/SerilogLog/Program.cs new file mode 100644 index 000000000..653f839ee --- /dev/null +++ b/Examples/Standalone/SerilogLog/Program.cs @@ -0,0 +1,74 @@ +using Serilog; +using Serilog.Core; +using Serilog.Filters; + +namespace SerilogLog; + +class Program +{ + static async Task Main(string[] args) + { + Log.Logger = new LoggerConfiguration() + .Enrich.FromLogContext() + .MinimumLevel.Debug() + .WriteTo.Console() + .WriteTo.Logger(l => l + .Filter + .ByIncludingOnly(Matching.FromSource("QuickFix.SessionLogs")) + .Filter + .ByIncludingOnly(Matching.WithProperty("MessageType")) + .WriteTo.Map(Constants.SourceContextPropertyName, "", (source, lc) => + { + var fileName = GetSessionIdFromSourceContext(source); + lc.File($"log/{fileName}.messages.log", rollingInterval: RollingInterval.Minute, outputTemplate:"{Message:lj}{NewLine}"); + }) + ) + .WriteTo.Logger(l => l + .Filter + .ByIncludingOnly(Matching.FromSource("QuickFix.SessionLogs")) + .Filter + .ByExcluding(Matching.WithProperty("MessageType")) + .WriteTo.Map(Constants.SourceContextPropertyName, "", (source, lc) => + { + var fileName = GetSessionIdFromSourceContext(source); + lc.File($"log/{fileName}.event.log", rollingInterval: RollingInterval.Minute, outputTemplate:"{Message:lj}{NewLine}"); + }) + ) + .WriteTo.Logger(l => l + .Filter + .ByIncludingOnly(Matching.FromSource("QuickFix")) + .Filter + .ByExcluding(Matching.FromSource("QuickFix.SessionLogs")) + .WriteTo.File("log/Non-Session-Log.event.current.log") + ) + .CreateLogger(); + + try + { + Log.Information("Starting host"); + + var builder = Host.CreateApplicationBuilder(args); + builder.Services.AddHostedService(); + builder.Services.AddSerilog(); + builder.Services.Configure(builder.Configuration); + var app = builder.Build(); + + await app.RunAsync(); + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly"); + } + finally + { + await Log.CloseAndFlushAsync(); + } + } + + private static string GetSessionIdFromSourceContext(string sourceContext) + { + // Log source is "QuickFix.SessionLogs.", this returns + var i = sourceContext.IndexOf('.', sourceContext.IndexOf('.') + 1) + 1; + return sourceContext[i..]; + } +} diff --git a/Examples/Standalone/SerilogLog/README.md b/Examples/Standalone/SerilogLog/README.md deleted file mode 100644 index 2a30dd399..000000000 --- a/Examples/Standalone/SerilogLog/README.md +++ /dev/null @@ -1,35 +0,0 @@ -Sample QuickFix.ILog Implementation -=================================== - -This is an example of enriching QuickFIX/N logging using a 3rd party NuGet -(Serilog.Sinks.File in this case). Features added are rolling log files -and limited total log size. - -The solution contains 2 projects: - -1. The SerilogLog is a sample implementation of ILog and ILogFactory. - _NOTE: this is a sample, **NOT** a finished ready-to-use product. - Follow comments in the source code to adjust the example to - your actual requirements._ - -2. The UnitTests are proof that limits for log size are working. - -The projects are dependent on QuickFix.dll (ILog and ILogFactory interfaces). -Assuming the standard directory structure, the reference path is: - - \QuickFIXn\bin\Debug\net6.0\QuickFix.dll - -You need to build QuickFIXn release configuration to have the DLL available. -Alternatively, you can change the reference in the both projects. - -If you copy/paste the source code in your project, you need to have -references to QuickFix.dll and NuGet Serilog.Sinks.File installed. - -Usage example: - - ILogFactory logFactory = new SerilogLogFactory(settings); - ThreadedSocketAcceptor _acceptor = new ThreadedSocketAcceptor(executorApp, - storeFactory, settings, logFactory); - -All sessions created by this `_acceptor` will be using SerilogLog -for messages and events logging. diff --git a/Examples/Standalone/SerilogLog/Serilog.cfg b/Examples/Standalone/SerilogLog/Serilog.cfg new file mode 100644 index 000000000..e382f7a4a --- /dev/null +++ b/Examples/Standalone/SerilogLog/Serilog.cfg @@ -0,0 +1,33 @@ +[DEFAULT] +ConnectionType=initiator +ReconnectInterval=2 +FileStorePath=store +StartTime=00:00:00 +EndTime=00:00:00 +UseDataDictionary=Y +SocketConnectHost=127.0.0.1 +SocketConnectPort=5001 +SocketIgnoreProxy=Y +ResetOnDisconnect=Y +FileLogPath=log +LogoutTimeout=5 +ResetOnLogon=Y +HeartBtInt=10 + +[SESSION] +BeginString=FIX.4.0 +SenderCompID=CLIENT1 +TargetCompID=EXECUTOR +DataDictionary=../../../spec/fix/FIX40.xml + +[SESSION] +BeginString=FIX.4.4 +SenderCompID=CLIENT1 +TargetCompID=EXECUTOR +DataDictionary=../../../spec/fix/FIX44.xml + +[SESSION] +BeginString=FIX.4.4 +SenderCompID=CLIENT2 +TargetCompID=EXECUTOR +DataDictionary=../../../spec/fix/FIX44.xml diff --git a/Examples/Standalone/SerilogLog/SerilogLog.cs b/Examples/Standalone/SerilogLog/SerilogLog.cs deleted file mode 100644 index a67e36751..000000000 --- a/Examples/Standalone/SerilogLog/SerilogLog.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using Serilog; - -namespace SerilogLog -{ - /// - /// Sample implementation of QuickFix.ILog - /// featuring rolling log files and limited total log size. - /// - public class SerilogLog : QuickFix.ILog - { - private Serilog.Core.Logger _messageLog; - private Serilog.Core.Logger _eventLog; - private string _pathPrefix; - - public long? MessagesFileSizeLimitBytes { get; } - public int? MessagesFilesLimit { get; } - public long? EventsFileSizeLimitBytes { get; } - public int? EventsFilesLimit { get; } - - /// - /// Feel free to add / change logging parameters - /// based on the actual requirements - /// - /// All loges are stored in this folder - /// SessionID of the - /// null => no limit - /// null => no limit - /// null => no limit - /// null => no limit - public SerilogLog(string fileLogPath, QuickFix.SessionID sessionID, - long? messagesFileSizeLimitBytes, int? messagesFilesLimit, - long? eventsFileSizeLimitBytes, int? eventsFilesLimit) - { - MessagesFileSizeLimitBytes = messagesFileSizeLimitBytes; - MessagesFilesLimit = messagesFilesLimit; - EventsFileSizeLimitBytes = eventsFileSizeLimitBytes; - EventsFilesLimit = eventsFilesLimit; - _pathPrefix = System.IO.Path.Combine(fileLogPath, QuickFix.FileStore.Prefix(sessionID)); - CreateLoggers(); - } - - /// - /// Here is the place to customize logging parameters as needed - /// - private void CreateLoggers() - { - string messagesFilePath = _pathPrefix + @".messages.log"; - // You can replace Serilog with any other library of your choice or your own implementation - _messageLog = new LoggerConfiguration() - .WriteTo.File(messagesFilePath, - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} : {Message}\n", - // the special log features are defined in the below parameters - rollOnFileSizeLimit: true, - fileSizeLimitBytes: MessagesFileSizeLimitBytes, - retainedFileCountLimit: MessagesFilesLimit) - .CreateLogger(); - string eventsFilePath = _pathPrefix + @".events.log"; - _eventLog = new LoggerConfiguration() - .WriteTo.File(eventsFilePath, - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} : {Message}\n", - rollOnFileSizeLimit: true, - fileSizeLimitBytes: EventsFileSizeLimitBytes, - retainedFileCountLimit: EventsFilesLimit) - .CreateLogger(); - } - - private readonly object _sync = new object(); - public void Clear() - { - lock (_sync) - { - DisposeLoggers(); - CreateLoggers(); - } - } - - private void DisposeLoggers() - { - _eventLog?.Dispose(); - _eventLog = null; - _messageLog?.Dispose(); - _messageLog = null; - } - - - public void OnEvent(string s) - { - - lock (_sync) - { - DisposedCheck(); - _eventLog.Information(s); - } - } - - public void OnIncoming(string msg) - { - lock (_sync) - { - DisposedCheck(); - _messageLog.Information(msg); - } - } - - public void OnOutgoing(string msg) - { - lock (_sync) - { - DisposedCheck(); - _messageLog.Information(msg); - - } - } - - private void DisposedCheck() - { - if (_disposed) - throw new System.ObjectDisposedException(this.GetType().Name); - } - - #region IDisposable - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool _disposed = false; - protected virtual void Dispose(bool disposing) - { - if (_disposed) return; - if (disposing) - { - DisposeLoggers(); - } - _disposed = true; - } - ~SerilogLog() => Dispose(false); - #endregion - } -} diff --git a/Examples/Standalone/SerilogLog/SerilogLog.csproj b/Examples/Standalone/SerilogLog/SerilogLog.csproj index b174430ef..070daf1e7 100644 --- a/Examples/Standalone/SerilogLog/SerilogLog.csproj +++ b/Examples/Standalone/SerilogLog/SerilogLog.csproj @@ -1,23 +1,28 @@ - + + Exe net8.0 + enable + enable - - - + + + + + - + - - ..\..\..\QuickFIXn\bin\Debug\net6.0\QuickFix.dll - + + PreserveNewest + diff --git a/Examples/Standalone/SerilogLog/SerilogLog.sln b/Examples/Standalone/SerilogLog/SerilogLog.sln deleted file mode 100644 index 90baac7ca..000000000 --- a/Examples/Standalone/SerilogLog/SerilogLog.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30011.22 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SerilogLog", "SerilogLog.csproj", "{A4A2FF68-F81B-4154-8FF7-EA9DD98D36C4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "UnitTests\UnitTests.csproj", "{3B3366EC-8410-4165-B684-E094F950A314}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A4A2FF68-F81B-4154-8FF7-EA9DD98D36C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A4A2FF68-F81B-4154-8FF7-EA9DD98D36C4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4A2FF68-F81B-4154-8FF7-EA9DD98D36C4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A4A2FF68-F81B-4154-8FF7-EA9DD98D36C4}.Release|Any CPU.Build.0 = Release|Any CPU - {3B3366EC-8410-4165-B684-E094F950A314}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3B3366EC-8410-4165-B684-E094F950A314}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3B3366EC-8410-4165-B684-E094F950A314}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3B3366EC-8410-4165-B684-E094F950A314}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {5855979E-90D5-464E-8136-5B45DF13BC50} - EndGlobalSection -EndGlobal diff --git a/Examples/Standalone/SerilogLog/SerilogLogFactory.cs b/Examples/Standalone/SerilogLog/SerilogLogFactory.cs deleted file mode 100644 index 899943989..000000000 --- a/Examples/Standalone/SerilogLog/SerilogLogFactory.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using QuickFix; - -namespace SerilogLog -{ - /// - /// Sample implementation of QuickFix.ILog for SerilogLog. - /// Usage example: - /// ILogFactory logFactory = new SerilogLogFactory(settings); - /// ThreadedSocketAcceptor _acceptor = new ThreadedSocketAcceptor(executorApp, storeFactory, settings, logFactory); - /// - all sessions created by this acceptor will be using SerilogLog for messages and events logging - /// - public class SerilogLogFactory : QuickFix.ILogFactory - { - SessionSettings _settings; - - /// - /// Feel free to add other logging parameters - /// based on the actual requirements, e.g. limits to be set in `public ILog Create` - /// - public SerilogLogFactory(SessionSettings settings) - { - _settings = settings; - } - - public ILog Create(SessionID sessionID) - { - // Here you can also change logging parameters depending on sessionID value. - // I.e. if you have separate sessions for streaming prices and for trading, you can set some max size for the prices log and no limit (null) for the trading log. - // You can even create NullLog for the prices, if no log is required, to avoid file I/O operations and so to improve performance. - return new SerilogLog(_settings.Get(sessionID).GetString(SessionSettings.FILE_LOG_PATH), - sessionID, - // change below to your actual requirements - 1024*1024, 10, 1024*1024, 2); - } - } -} diff --git a/Examples/Standalone/SerilogLog/UnitTests/SerilogLogTests.cs b/Examples/Standalone/SerilogLog/UnitTests/SerilogLogTests.cs deleted file mode 100644 index ad0323663..000000000 --- a/Examples/Standalone/SerilogLog/UnitTests/SerilogLogTests.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System.Diagnostics; -using System.IO; -using System.Text; -using NUnit.Framework; - -namespace UnitTests -{ - public class SerilogLogTests - { - SerilogLog.SerilogLog log; - string logPath; - - [SetUp] - public void Setup() - { - logPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "log"); - if (Directory.Exists(logPath)) Directory.Delete(logPath, true); - } - - [TearDown] - public void Teardown() - { - log?.Dispose(); - log = null; - if (Directory.Exists(logPath)) Directory.Delete(logPath, true); - } - - [Test] - public void TestEventsLogSizeLimit() - { - const string TextTestMessage = "Test message."; - - QuickFix.SessionID sessionID = new QuickFix.SessionID("FIX.4.2", "SENDERCOMP", "TARGETCOMP"); - long fileSizeLimitBytes = 1024; - int filesLimit = 2; - log = new SerilogLog.SerilogLog(logPath, sessionID, fileSizeLimitBytes, filesLimit, fileSizeLimitBytes, filesLimit); - string testMessage = TextTestMessage; - int testMessageSize = Encoding.UTF8.GetByteCount($"2020-11-11 21:45:47.153 +02:00 : 00000: {TextTestMessage}\n"); - testMessage = TextTestMessage; - long writtenBytes = 0; - long line = 0; - long limitBytes = fileSizeLimitBytes * filesLimit; - while (writtenBytes < limitBytes) - { - log.OnEvent($"{++line:D5}: {testMessage}"); - writtenBytes += testMessageSize; - Debug.Print($"Written bytes: {writtenBytes}"); - } - Debug.Print($"Limit passed, writing over the limit"); - for (int i = 0; i < 2; ++i) - { - log.OnEvent("Over size limit text!"); - } - long dirSize = DirectorySize(logPath); - Debug.Print($"Calculated {dirSize} bytes for {logPath}"); - Assert.IsTrue(dirSize <= limitBytes, $"Size {dirSize} unexpectedly exceeds limit {limitBytes}"); - } - - - [Test] - public void TestMessagesLogSizeLimit() - { - const string TextTestMessage = "Test message."; - - QuickFix.SessionID sessionID = new QuickFix.SessionID("FIX.4.2", "SENDERCOMP", "TARGETCOMP"); - long fileSizeLimitBytes = 1024; - int filesLimit = 2; - log = new SerilogLog.SerilogLog(logPath, sessionID, fileSizeLimitBytes, filesLimit, fileSizeLimitBytes, filesLimit); - string testMessage = TextTestMessage; - int testMessageSize = Encoding.UTF8.GetByteCount($"2020-11-11 21:45:47.153 +02:00 : 00000: {TextTestMessage}\n"); - testMessage = TextTestMessage; - long writtenBytes = 0; - long line = 0; - long limitBytes = fileSizeLimitBytes * filesLimit; - while (writtenBytes < limitBytes) - { - log.OnIncoming($"{++line:D5}: {testMessage}"); - writtenBytes += testMessageSize; - Debug.Print($"Written bytes: {writtenBytes}"); - } - Debug.Print($"Limit passed, writing over the limit"); - for (int i = 0; i < 2; ++i) - { - log.OnOutgoing("Over size limit text!"); - } - long dirSize = DirectorySize(logPath); - Debug.Print($"Calculated {dirSize} bytes for {logPath}"); - Assert.IsTrue(dirSize <= limitBytes, $"Size {dirSize} unexpectedly exceeds limit {limitBytes}"); - } - - - [Test] - public void TestMessagesNoLimit() - { - const string TextTestMessage = "Test message."; - - QuickFix.SessionID sessionID = new QuickFix.SessionID("FIX.4.2", "SENDERCOMP", "TARGETCOMP"); - log = new SerilogLog.SerilogLog(logPath, sessionID, null, null, null, null); - string testMessage = TextTestMessage; - int testMessageSize = Encoding.UTF8.GetByteCount($"2020-11-11 21:45:47.153 +02:00 : 00000: {TextTestMessage}\n"); - testMessage = TextTestMessage; - long writtenBytes = 0; - long line = 0; - long limitBytes = testMessageSize * 100; - while (writtenBytes < limitBytes) - { - log.OnIncoming($"{++line:D5}: {testMessage}"); - writtenBytes += testMessageSize; - Debug.Print($"Total written bytes: {writtenBytes}"); - } - Debug.Print($"Limit passed, writing over the limit"); - for (int i = 0; i < 2; ++i) - { - log.OnOutgoing($"{++line:D5}: {testMessage}"); - writtenBytes += testMessageSize; - Debug.Print($"Total written bytes: {writtenBytes}"); - } - long dirSize = DirectorySize(logPath); - Debug.Print($"Calculated {dirSize} bytes for {logPath}"); - Assert.IsTrue(dirSize >= limitBytes, $"Size {dirSize} is unexpectedly less then written {limitBytes}"); - } - - private static long DirectorySize(string directoryPath) - { - long size = 0; - // Add file sizes. - foreach(string fileName in Directory.EnumerateFiles(directoryPath)) - { - string filePath = Path.Combine(directoryPath, fileName); - FileInfo fi = new FileInfo(filePath); - size += fi.Length; - } - return size; - } - } -} \ No newline at end of file diff --git a/Examples/Standalone/SerilogLog/UnitTests/UnitTests.csproj b/Examples/Standalone/SerilogLog/UnitTests/UnitTests.csproj deleted file mode 100644 index 7c9470191..000000000 --- a/Examples/Standalone/SerilogLog/UnitTests/UnitTests.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - net8.0 - - false - - - - - - - - - - - - - - - ..\..\..\..\QuickFIXn\bin\Debug\net6.0\QuickFix.dll - - - - - diff --git a/Examples/Standalone/SerilogLog/appsettings.json b/Examples/Standalone/SerilogLog/appsettings.json new file mode 100644 index 000000000..9ae187196 --- /dev/null +++ b/Examples/Standalone/SerilogLog/appsettings.json @@ -0,0 +1,3 @@ +{ + "ConfigFilePath": "Serilog.cfg" +} \ No newline at end of file diff --git a/QuickFIXn.sln b/QuickFIXn.sln index a7bb35ac6..a4caa6420 100644 --- a/QuickFIXn.sln +++ b/QuickFIXn.sln @@ -48,6 +48,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickFix.FIXT11", "Messages EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AcceptanceTest", "AcceptanceTest\AcceptanceTest.csproj", "{A6E267D7-CF1E-4C28-BB72-8F004E4375FC}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{80B846FF-780C-4D04-ADF9-BAED00CCC403}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Standalone", "Standalone", "{31A77EBD-3A37-4C51-8124-0E6FF68BD890}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SerilogLog", "Examples\Standalone\SerilogLog\SerilogLog.csproj", "{8F64D9A5-0E63-4705-B37B-346F6D2C0B82}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -192,6 +198,14 @@ Global {A6E267D7-CF1E-4C28-BB72-8F004E4375FC}.Release|Any CPU.Build.0 = Release|Any CPU {A6E267D7-CF1E-4C28-BB72-8F004E4375FC}.Release|x64.ActiveCfg = Release|Any CPU {A6E267D7-CF1E-4C28-BB72-8F004E4375FC}.Release|x64.Build.0 = Release|Any CPU + {8F64D9A5-0E63-4705-B37B-346F6D2C0B82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F64D9A5-0E63-4705-B37B-346F6D2C0B82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F64D9A5-0E63-4705-B37B-346F6D2C0B82}.Debug|x64.ActiveCfg = Debug|Any CPU + {8F64D9A5-0E63-4705-B37B-346F6D2C0B82}.Debug|x64.Build.0 = Debug|Any CPU + {8F64D9A5-0E63-4705-B37B-346F6D2C0B82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F64D9A5-0E63-4705-B37B-346F6D2C0B82}.Release|Any CPU.Build.0 = Release|Any CPU + {8F64D9A5-0E63-4705-B37B-346F6D2C0B82}.Release|x64.ActiveCfg = Release|Any CPU + {8F64D9A5-0E63-4705-B37B-346F6D2C0B82}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -199,4 +213,8 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4BE64B0D-00E7-4D43-8578-311E1F10FD80} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {31A77EBD-3A37-4C51-8124-0E6FF68BD890} = {80B846FF-780C-4D04-ADF9-BAED00CCC403} + {8F64D9A5-0E63-4705-B37B-346F6D2C0B82} = {31A77EBD-3A37-4C51-8124-0E6FF68BD890} + EndGlobalSection EndGlobal