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..53cf98ceb 100644 --- a/AcceptanceTest/TestBase.cs +++ b/AcceptanceTest/TestBase.cs @@ -2,6 +2,8 @@ using QuickFix; using System.IO; using System.Net; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using QuickFix.Logger; using QuickFix.Store; @@ -11,6 +13,7 @@ public abstract class TestBase { private int _port; private ThreadedSocketAcceptor _acceptor; + private LoggerFactory? _loggerFactory; protected abstract SessionSettings Settings { get; } @@ -23,11 +26,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(); + _loggerFactory = new LoggerFactory(); + if (settings.Get().Has("Verbose") && settings.Get().GetBool("Verbose")) + { + _loggerFactory.AddProvider(new FileLoggerProvider(settings)); + } + else + { + _loggerFactory.AddProvider(NullLoggerProvider.Instance); + } - _acceptor = new ThreadedSocketAcceptor(testApp, storeFactory, settings, logFactory); + _acceptor = new ThreadedSocketAcceptor(testApp, storeFactory, settings, _loggerFactory); _acceptor.Start(); } @@ -36,6 +45,7 @@ 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 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..dabe621fc 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,14 @@ 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); + using 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); 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..3d9c6c6f1 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,11 @@ 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); + using var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddProvider(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..8a5f4063e 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,12 @@ 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); + using 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. application.MyInitiator = initiator; diff --git a/QuickFIXn/AbstractInitiator.cs b/QuickFIXn/AbstractInitiator.cs index 7222d43d1..eea21ef9c 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,44 @@ public abstract class AbstractInitiator : IInitiator private readonly SessionFactory _sessionFactory; private Thread? _thread; - protected readonly NonSessionLog _nonSessionLog; + protected readonly ILogger _nonSessionLog; + private readonly LogFactoryAdapter? _logFactoryAdapter; public bool IsStopped { get; private set; } = true; + [Obsolete("Use \"Microsoft.Extensions.Logging.ILoggerFactory\" instead of \"QuickFix.Logger.ILogFactory\".")] protected AbstractInitiator( IApplication app, IMessageStoreFactory storeFactory, SessionSettings settings, - ILogFactory? logFactoryNullable, - IMessageFactory? messageFactoryNullable) + ILogFactory? logFactoryNullable = null, + IMessageFactory? messageFactoryNullable = null) : this(app, storeFactory, settings, + logFactoryNullable is null + ? NullLoggerFactory.Instance + : new LogFactoryAdapter(logFactoryNullable, settings), messageFactoryNullable) + { + } + + protected AbstractInitiator( + IApplication app, + IMessageStoreFactory storeFactory, + SessionSettings settings, + ILoggerFactory? logFactoryNullable = null, + IMessageFactory? messageFactoryNullable = null) { _settings = settings; - var logFactory = logFactoryNullable ?? new NullLogFactory(); + var logFactory = logFactoryNullable ?? NullLoggerFactory.Instance; + if (logFactory 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 + // 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 = new NonSessionLog(logFactory); + _nonSessionLog = logFactory.CreateLogger("QuickFix"); HashSet definedSessions = _settings.GetSessions(); if (0 == definedSessions.Count) @@ -211,6 +235,8 @@ public void Stop(bool force) _connected.Clear(); _disconnected.Clear(); } + + _logFactoryAdapter?.Dispose(); } public bool IsLoggedOn 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/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..d88ca61ef 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; @@ -7,7 +8,8 @@ namespace QuickFix.Logger; /// /// File log implementation /// -public class FileLog : ILog +[Obsolete("Use Microsoft.Extensions.Logging instead")] +public class FileLog : ILog, ILogger { private readonly object _sync = new(); @@ -119,6 +121,34 @@ 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; + +#pragma warning disable CS8633 + public IDisposable BeginScope(TState state) where TState : notnull => default!; +#pragma warning restore CS8633 + #endregion #region IDisposable Members 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/FileLoggerProvider.cs b/QuickFIXn/Logger/FileLoggerProvider.cs new file mode 100644 index 000000000..453ca0a60 --- /dev/null +++ b/QuickFIXn/Logger/FileLoggerProvider.cs @@ -0,0 +1,49 @@ +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/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/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..874ff05a2 --- /dev/null +++ b/QuickFIXn/Logger/LogFactoryAdapter.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using Microsoft.Extensions.Logging; + +namespace QuickFix.Logger; + +internal class LogFactoryAdapter : ILoggerFactory, IDisposable +{ + 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, sid => sessionId is not null + ? new LogAdapter(_logFactory.Create(sid)) + : new LogAdapter(_logFactory.CreateNonSessionLog())); + } + + public void AddProvider(ILoggerProvider provider) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + foreach (var (_, logger) in _loggers) + { + if (logger is IDisposable disposable) + { + try + { + disposable.Dispose(); + } + catch + { + } + } + } + } +} + +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; + +#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 new file mode 100644 index 000000000..013e923af --- /dev/null +++ b/QuickFIXn/Logger/NonSessionFileLogger.cs @@ -0,0 +1,32 @@ +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, 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; + +#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/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..bc83177a1 100755 --- a/QuickFIXn/Logger/ScreenLog.cs +++ b/QuickFIXn/Logger/ScreenLog.cs @@ -1,9 +1,13 @@ -namespace QuickFix.Logger; +using System; +using Microsoft.Extensions.Logging; + +namespace QuickFix.Logger; /// /// FIXME - needs to log sessionIDs, timestamps, etc. /// -public class ScreenLog : ILog +[Obsolete("Use Microsoft.Extensions.Logging instead.")] +public class ScreenLog : ILog, ILogger { private readonly object _sync = new (); private readonly bool _logIncoming; @@ -56,16 +60,29 @@ public void OnEvent(string s) } #endregion - #region IDisposable implementation - public void Dispose() - { - Dispose(true); - System.GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, + Func formatter) { - // 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; + +#pragma warning disable CS8633 + public IDisposable BeginScope(TState state) where TState : notnull => default!; +#pragma warning restore CS8633 + + public void Dispose(){} } 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/Logger/ScreenLoggerProvider.cs b/QuickFIXn/Logger/ScreenLoggerProvider.cs new file mode 100755 index 000000000..ecb1f02ce --- /dev/null +++ b/QuickFIXn/Logger/ScreenLoggerProvider.cs @@ -0,0 +1,58 @@ +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/QuickFix.csproj b/QuickFIXn/QuickFix.csproj index 8e1f2bd7f..e82d837ed 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..0bcb171dd 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,19 @@ public bool Send(string message) { if (_responder is null) return false; - Log.OnOutgoing(message); + + const LogLevel messagesLogLevel = LogLevel.Information; + if (Log.IsEnabled(messagesLogLevel)) + { + using (Log.BeginScope(new Dictionary + { + {"MessageType", Message.GetMsgType(message)} + })) + { + Log.Log(messagesLogLevel, LogEventIds.OutgoingMessage, "{Message}", message); + } + } + return _responder.Send(message); } } @@ -389,13 +402,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 +457,7 @@ public void Next() if (!_state.SentLogout) { - Log.OnEvent("Initiated logout request"); + Log.Log(LogLevel.Debug, "Initiated logout request"); GenerateLogout(_state.LogoutReason); } } @@ -454,9 +467,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 +500,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 +525,24 @@ public void Next(string msgStr) /// private void NextMessage(string msgStr) { - Log.OnIncoming(msgStr); + const LogLevel messageLogLevel = LogLevel.Information; + try + { + if (Log.IsEnabled(messageLogLevel)) + { + using (Log.BeginScope(new Dictionary + { + {"MessageType", Message.GetMsgType(msgStr)} + })) + { + Log.Log(messageLogLevel, LogEventIds.IncomingMessage, "{Message}", msgStr); + } + } + } + catch (Exception) + { + Log.Log(messageLogLevel, LogEventIds.IncomingMessage, "{Message}", msgStr); + } MessageBuilder msgBuilder = new MessageBuilder( msgStr, @@ -595,7 +625,7 @@ internal void Next(MessageBuilder msgBuilder) } catch (InvalidMessage e) { - Log.OnEvent(e.Message); + Log.Log(LogLevel.Information, "{Message}", e.Message); try { @@ -610,7 +640,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 +651,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 +672,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 +694,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 +711,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 +761,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 +849,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 +876,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 +911,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 +962,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 +978,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 +986,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 +1076,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 +1085,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 +1165,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 +1179,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 +1309,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 +1367,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 +1400,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 +1510,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 +1539,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..83f408b52 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 ?? NullLoggerFactory.Instance; _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..b6bab13be 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,10 @@ 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; + private readonly LogFactoryAdapter? _logFactoryAdapter; #region Constructors - /// /// Create a ThreadedSocketAcceptor /// @@ -32,18 +34,46 @@ public class ThreadedSocketAcceptor : IAcceptor /// /// If null, a NullFactory will be used. /// If null, a DefaultMessageFactory will be created (using settings parameters) + [Obsolete("Use \"Microsoft.Extensions.Logging.ILoggerFactory\" instead of \"QuickFix.Logger.ILogFactory\".")] public ThreadedSocketAcceptor( IApplication application, IMessageStoreFactory storeFactory, SessionSettings settings, ILogFactory? logFactory = null, + IMessageFactory? messageFactory = null) : this(application, storeFactory, settings, + logFactory is null ? NullLoggerFactory.Instance : new LogFactoryAdapter(logFactory, settings), + messageFactory) + { + } + + /// + /// 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) { - ILogFactory lf = logFactory ?? new NullLogFactory(); + var lf = loggerFactory ?? NullLoggerFactory.Instance; + if (lf 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 + // 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 = new NonSessionLog(lf); + _nonSessionLog = lf.CreateLogger("QuickFix"); try { @@ -176,7 +206,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 +222,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,8 +306,8 @@ public void Stop(bool force) LogoutAllSessions(force); DisposeSessions(); _sessions.Clear(); - _nonSessionLog.Dispose(); _isStarted = false; + _logFactoryAdapter?.Dispose(); // FIXME StopSessionTimer(); // FIXME Session.UnregisterSessions(GetSessions()); 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..66ec081f3 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("Use \"Microsoft.Extensions.Logging.ILoggerFactory\" instead of \"QuickFix.Logger.ILogFactory\".")] 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..d671f0066 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; @@ -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 = (FileLog) 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); + using 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..88d768268 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; @@ -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); - ILogFactory logFactory = new FileLogFactory(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, logFactory); + _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, logFactory); + _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/SessionStateTest.cs b/UnitTests/SessionStateTest.cs index 90f912333..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); - NullLog log = new NullLog(); - //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 afe1762f7..fce5be3ae 100755 --- a/UnitTests/SessionTest.cs +++ b/UnitTests/SessionTest.cs @@ -4,7 +4,8 @@ using System.Text.RegularExpressions; using NUnit.Framework; using System.Threading; -using QuickFix.Logger; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using QuickFix.Store; namespace UnitTests; @@ -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([NullLoggerProvider.Instance]); // 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([NullLoggerProvider.Instance]), new QuickFix.DefaultMessageFactory(), "blah"); _session.SetResponder(_responder); _session.CheckLatency = false; diff --git a/UnitTests/ThreadedSocketAcceptorTests.cs b/UnitTests/ThreadedSocketAcceptorTests.cs index 1f334b31c..77c6d039b 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; @@ -35,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 FileLogFactory(settings)); - } - [Test] public void TestRecreation() { @@ -55,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(); } diff --git a/UnitTests/ThreadedSocketReactorTests.cs b/UnitTests/ThreadedSocketReactorTests.cs index 199304490..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 NonSessionLog(new ScreenLogFactory(true, true, true))); + new ScreenLog(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 @@ +