This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 84dfe6d6f12 IGNITE-27129 .NET: Improve logging in tests (#7034)
84dfe6d6f12 is described below

commit 84dfe6d6f12acdd85a6859fae131df6c03a19cfa
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Fri Nov 21 17:00:05 2025 +0200

    IGNITE-27129 .NET: Improve logging in tests (#7034)
    
    * Improve `ConsoleLogger` to collect messages from all threads during a 
given test and flush in `TearDown`
    * Improve response log messages - include duration and op code
---
 .../Apache.Ignite.Tests/Compute/ComputeTests.cs    |  4 ++-
 .../dotnet/Apache.Ignite.Tests/ConsoleLogger.cs    | 34 ++++++++++++++-------
 .../dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs  | 22 ++++++++++----
 .../dotnet/Apache.Ignite.Tests/LoggingTests.cs     |  2 +-
 .../PartitionAwarenessRealClusterTests.cs          |  4 ++-
 .../dotnet/Apache.Ignite.Tests/TestUtils.cs        |  6 +++-
 .../dotnet/Apache.Ignite/Internal/ClientSocket.cs  | 35 +++++++++++++++-------
 .../dotnet/Apache.Ignite/Internal/LogMessages.cs   |  4 +--
 8 files changed, 80 insertions(+), 31 deletions(-)

diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
index 0f9bf019b98..823b3509769 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
@@ -34,6 +34,7 @@ namespace Apache.Ignite.Tests.Compute
     using Internal.Compute;
     using Internal.Network;
     using Internal.Proto;
+    using Microsoft.Extensions.Logging;
     using Network;
     using NodaTime;
     using NUnit.Framework;
@@ -295,8 +296,9 @@ namespace Apache.Ignite.Tests.Compute
         [TestCase(11, 4)]
         public async Task TestExecuteColocated(long key, int nodeIdx)
         {
+            using var loggerFactory = new ConsoleLogger(LogLevel.Trace);
             var proxies = GetProxies();
-            using var client = await 
IgniteClient.StartAsync(GetConfig(proxies));
+            using var client = await 
IgniteClient.StartAsync(GetConfig(proxies, loggerFactory));
             client.WaitForConnections(proxies.Count);
 
             var keyTuple = new IgniteTuple { [KeyCol] = key };
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/ConsoleLogger.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/ConsoleLogger.cs
index e51e7a657df..d1d47dd0016 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/ConsoleLogger.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/ConsoleLogger.cs
@@ -18,31 +18,35 @@
 namespace Apache.Ignite.Tests;
 
 using System;
+using System.Collections.Concurrent;
 using System.Globalization;
 using System.Text;
 using Microsoft.Extensions.Logging;
 
 /// <summary>
-/// Console logger for tests. We don't use <see 
cref="Microsoft.Extensions.Logging.Console.ConsoleLogger"/> because it is 
asynchronous,
-/// which means the log messages may not correspond to the current test.
+/// Console logger for tests.
 /// </summary>
 public class ConsoleLogger : ILogger, ILoggerFactory
 {
     private readonly string _categoryName;
     private readonly LogLevel _minLevel;
+    private readonly ConcurrentQueue<string> _entries;
 
     public ConsoleLogger(LogLevel minLevel)
-        : this(string.Empty, minLevel)
+        : this(new(), string.Empty, minLevel)
     {
         // No-op.
     }
 
-    public ConsoleLogger(string categoryName, LogLevel minLevel)
+    private ConsoleLogger(ConcurrentQueue<string> entries, string 
categoryName, LogLevel minLevel)
     {
+        _entries = entries;
         _categoryName = categoryName;
         _minLevel = minLevel;
     }
 
+    public bool AutoFlush { get; set; }
+
     public void Log<TState>(
         LogLevel logLevel,
         EventId eventId,
@@ -69,7 +73,20 @@ public class ConsoleLogger : ILogger, ILoggerFactory
             sb.AppendFormat(CultureInfo.InvariantCulture, " (exception: {0})", 
exception);
         }
 
-        Console.WriteLine(sb.ToString());
+        _entries.Enqueue(sb.ToString());
+
+        if (AutoFlush)
+        {
+            Flush();
+        }
+    }
+
+    public void Flush()
+    {
+        while (_entries.TryDequeue(out var s))
+        {
+            Console.WriteLine(s);
+        }
     }
 
     public bool IsEnabled(LogLevel logLevel) => logLevel >= _minLevel;
@@ -78,12 +95,9 @@ public class ConsoleLogger : ILogger, ILoggerFactory
         where TState : notnull
         => new DisposeAction(() => { });
 
-    public void Dispose()
-    {
-        // No-op.
-    }
+    public void Dispose() => Flush();
 
-    public ILogger CreateLogger(string categoryName) => new 
ConsoleLogger(categoryName, _minLevel);
+    public ILogger CreateLogger(string categoryName) => new 
ConsoleLogger(_entries, categoryName, _minLevel);
 
     public void AddProvider(ILoggerProvider provider) => throw new 
NotSupportedException();
 
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs
index dd6ff0b1277..dd9e1ca80ba 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs
@@ -69,6 +69,8 @@ namespace Apache.Ignite.Tests
 
         private TestEventListener _eventListener = null!;
 
+        private ConsoleLogger _logger = null!;
+
         static IgniteTestsBase()
         {
             ServerNode = JavaServer.StartAsync().GetAwaiter().GetResult();
@@ -100,8 +102,9 @@ namespace Apache.Ignite.Tests
         public async Task OneTimeSetUp()
         {
             _eventListener = new TestEventListener();
+            _logger = new ConsoleLogger(LogLevel.Trace);
 
-            Client = await IgniteClient.StartAsync(GetConfig());
+            Client = await IgniteClient.StartAsync(GetConfig(_logger));
 
             Table = (await Client.Tables.GetTableAsync(TableName))!;
             TupleView = Table.RecordBinaryView;
@@ -117,6 +120,8 @@ namespace Apache.Ignite.Tests
             var tableAllColumnsSql = await 
Client.Tables.GetTableAsync(TableAllColumnsSqlName);
             PocoAllColumnsSqlView = 
tableAllColumnsSql!.GetRecordView<PocoAllColumnsSql>();
             PocoAllColumnsSqlNullableView = 
tableAllColumnsSql.GetRecordView<PocoAllColumnsSqlNullable>();
+
+            _logger.Flush();
         }
 
         [OneTimeTearDown]
@@ -130,11 +135,13 @@ namespace Apache.Ignite.Tests
             CheckPooledBufferLeak();
 
             _eventListener.Dispose();
+            _logger.Dispose();
         }
 
         [SetUp]
         public void SetUp()
         {
+            _logger.Flush();
             Console.WriteLine("SetUp: " + 
TestContext.CurrentContext.Test.Name);
             TestUtils.CheckByteArrayPoolLeak();
         }
@@ -142,6 +149,9 @@ namespace Apache.Ignite.Tests
         [TearDown]
         public void TearDown()
         {
+            // Flush here so events from all threads are captured as current 
test output.
+            _logger.Flush();
+
             Console.WriteLine("TearDown start: " + 
TestContext.CurrentContext.Test.Name);
 
             _disposables.ForEach(x => x.Dispose());
@@ -149,6 +159,8 @@ namespace Apache.Ignite.Tests
 
             CheckPooledBufferLeak();
 
+            _logger.Flush();
+
             Console.WriteLine("TearDown end: " + 
TestContext.CurrentContext.Test.Name);
         }
 
@@ -180,20 +192,20 @@ namespace Apache.Ignite.Tests
 
         protected static ValPoco GetValPoco(string? val) => new() { Val = val 
};
 
-        protected static IgniteClientConfiguration GetConfig() => new()
+        protected static IgniteClientConfiguration GetConfig(ILoggerFactory? 
loggerFactory = null) => new()
         {
             Endpoints =
             {
                 "127.0.0.1:" + ServerNode.Port,
                 "127.0.0.1:" + (ServerNode.Port + 1)
             },
-            LoggerFactory = TestUtils.GetConsoleLoggerFactory(LogLevel.Trace)
+            LoggerFactory = loggerFactory ?? 
TestUtils.GetConsoleLoggerFactory(LogLevel.Trace)
         };
 
-        protected static IgniteClientConfiguration 
GetConfig(IEnumerable<IgniteProxy> proxies) =>
+        protected static IgniteClientConfiguration 
GetConfig(IEnumerable<IgniteProxy> proxies, ILoggerFactory loggerFactory) =>
             new(proxies.Select(x => x.Endpoint).ToArray())
             {
-                LoggerFactory = 
TestUtils.GetConsoleLoggerFactory(LogLevel.Trace)
+                LoggerFactory = loggerFactory
             };
 
         protected List<IgniteProxy> GetProxies()
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/LoggingTests.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/LoggingTests.cs
index 14304e9ad58..9140a590ba5 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/LoggingTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/LoggingTests.cs
@@ -65,7 +65,7 @@ public class LoggingTests
         StringAssert.Contains("[Debug] Connection established", log);
         StringAssert.Contains("[Debug] Handshake succeeded [remoteAddress=[", 
log);
         StringAssert.Contains("[Trace] Sending request [requestId=1, 
op=TablesGet, remoteAddress=", log);
-        StringAssert.Contains("[Trace] Received response [requestId=1, 
flags=PartitionAssignmentChanged, remoteAddress=", log);
+        StringAssert.Contains("[Trace] Received response [requestId=1, 
op=TablesGet, flags=PartitionAssignmentChanged, remoteAddress=", log);
         StringAssert.Contains("op=SqlExec", log);
         StringAssert.Contains("[Debug] Connection closed gracefully", log);
         StringAssert.DoesNotContain("[Error]", log);
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessRealClusterTests.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessRealClusterTests.cs
index d4fd6e32e47..1a4cea36428 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessRealClusterTests.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessRealClusterTests.cs
@@ -23,6 +23,7 @@ using Compute;
 using Ignite.Compute;
 using Ignite.Table;
 using Internal.Proto;
+using Microsoft.Extensions.Logging;
 using NUnit.Framework;
 
 /// <summary>
@@ -39,8 +40,9 @@ public class PartitionAwarenessRealClusterTests : 
IgniteTestsBase
     [Test]
     public async Task TestPutRoutesRequestToPrimaryNode([Values(true, false)] 
bool withTx)
     {
+        using var loggerFactory = new ConsoleLogger(LogLevel.Trace);
         var proxies = GetProxies();
-        using var client = await IgniteClient.StartAsync(GetConfig(proxies));
+        using var client = await IgniteClient.StartAsync(GetConfig(proxies, 
loggerFactory));
         var recordView = (await 
client.Tables.GetTableAsync(TableName))!.RecordBinaryView;
 
         client.WaitForConnections(proxies.Count);
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/TestUtils.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/TestUtils.cs
index 96a59ccd879..3729ba48132 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/TestUtils.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/TestUtils.cs
@@ -101,7 +101,11 @@ namespace Apache.Ignite.Tests
         public static bool IsRecordClass(this Type type) =>
             type.GetMethods().Any(m => m.Name == "<Clone>$" && m.ReturnType == 
type);
 
-        public static ILoggerFactory GetConsoleLoggerFactory(LogLevel 
minLevel) => new ConsoleLogger(minLevel);
+        public static ILoggerFactory GetConsoleLoggerFactory(LogLevel 
minLevel, bool autoFlush = true) =>
+            new ConsoleLogger(minLevel)
+            {
+                AutoFlush = autoFlush
+            };
 
         public static void CheckByteArrayPoolLeak(int timeoutMs = 1000)
         {
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs 
b/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs
index 8210f364397..38a61e817bc 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs
@@ -75,7 +75,7 @@ namespace Apache.Ignite.Internal
         private readonly IgniteClientConfigurationInternal _config;
 
         /** Current async operations, map from request id. */
-        private readonly ConcurrentDictionary<long, 
TaskCompletionSource<PooledBuffer>> _requests = new();
+        private readonly ConcurrentDictionary<long, PendingRequest> _requests 
= new();
 
         /** Current notification handlers, map from request id. */
         private readonly ConcurrentDictionary<long, NotificationHandler> 
_notificationHandlers = new();
@@ -684,7 +684,7 @@ namespace Apache.Ignite.Internal
 
             var requestId = Interlocked.Increment(ref _requestId);
             var taskCompletionSource = new 
TaskCompletionSource<PooledBuffer>();
-            _requests[requestId] = taskCompletionSource;
+            _requests[requestId] = new PendingRequest(taskCompletionSource, 
Stopwatch.GetTimestamp(), clientOp);
 
             NotificationHandler? notificationHandler = null;
             if (expectNotifications)
@@ -903,12 +903,19 @@ namespace Apache.Ignite.Internal
             var requestId = reader.ReadInt64();
             var flags = (ResponseFlags)reader.ReadInt32();
 
-            _logger.LogReceivedResponseTrace(requestId, flags, 
ConnectionContext.ClusterNode.Address);
+            var isServerOp = (flags & ResponseFlags.ServerOp) != 0;
+            var isError = (flags & ResponseFlags.Error) != 0;
+            var isNotification = (flags & ResponseFlags.Notification) != 0;
+
+            if (isServerOp || isNotification)
+            {
+                _logger.LogReceivedResponseTrace(requestId, ClientOp.None, 
flags,  ConnectionContext.ClusterNode.Address, null);
+            }
 
             HandlePartitionAssignmentChange(flags, ref reader);
             HandleObservableTimestamp(ref reader);
 
-            if ((flags & ResponseFlags.ServerOp) != 0)
+            if (isServerOp)
             {
                 Debug.Assert((flags & ResponseFlags.Error) == 0, "Server op 
should not have an exception.");
                 var serverOp = (ServerOp)reader.ReadInt32();
@@ -917,15 +924,15 @@ namespace Apache.Ignite.Internal
                 return QueueServerOp(requestId, serverOp, response);
             }
 
-            var exception = (flags & ResponseFlags.Error) != 0 ? ReadError(ref 
reader) : null;
+            var exception = isError ? ReadError(ref reader) : null;
             response.Position += reader.Consumed;
 
-            if ((flags & ResponseFlags.Notification) != 0)
+            if (isNotification)
             {
                 return HandleNotification(requestId, exception, response);
             }
 
-            if (!_requests.TryRemove(requestId, out var taskCompletionSource))
+            if (!_requests.TryRemove(requestId, out var pendingReq))
             {
                 var message = $"Unexpected response ID ({requestId}) received 
from the server " +
                               
$"[remoteAddress={ConnectionContext.ClusterNode.Address}], closing the socket.";
@@ -934,19 +941,22 @@ namespace Apache.Ignite.Internal
                 throw new 
IgniteClientConnectionException(ErrorGroups.Client.Protocol, message);
             }
 
+            _logger.LogReceivedResponseTrace(
+                requestId, pendingReq.Op, flags,  
ConnectionContext.ClusterNode.Address, 
Stopwatch.GetElapsedTime(pendingReq.StartTs));
+
             Metrics.RequestsActiveDecrement();
 
             if (exception != null)
             {
                 AddFailedRequest();
 
-                taskCompletionSource.TrySetException(exception);
+                pendingReq.TaskCompletionSource.TrySetException(exception);
                 return false;
             }
 
             Metrics.RequestsCompleted.Add(1, MetricsContext.Tags);
 
-            return taskCompletionSource.TrySetResult(response);
+            return pendingReq.TaskCompletionSource.TrySetResult(response);
         }
 
         /// <summary>
@@ -1061,7 +1071,7 @@ namespace Apache.Ignite.Internal
                     {
                         if (_requests.TryRemove(reqId, out var req))
                         {
-                            req.TrySetException(ex);
+                            req.TaskCompletionSource.TrySetException(ex);
                             Metrics.RequestsActiveDecrement();
                         }
                     }
@@ -1085,5 +1095,10 @@ namespace Apache.Ignite.Internal
                 }
             }
         }
+
+        private readonly record struct PendingRequest(
+            TaskCompletionSource<PooledBuffer> TaskCompletionSource,
+            long StartTs,
+            ClientOp Op);
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/LogMessages.cs 
b/modules/platforms/dotnet/Apache.Ignite/Internal/LogMessages.cs
index de6ff5e182d..e7ddc7aa75b 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/LogMessages.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/LogMessages.cs
@@ -200,11 +200,11 @@ internal static partial class LogMessages
         this ILogger logger, string retrying, int op, ClientOp opType, int 
attempt, string lastErrorMessage);
 
     [LoggerMessage(
-        Message = "Received response [requestId={RequestId}, flags={Flags}, 
remoteAddress={RemoteAddress}]",
+        Message = "Received response [requestId={RequestId}, op={Op}, 
flags={Flags}, remoteAddress={RemoteAddress}, duration={Duration}]",
         Level = LogLevel.Trace,
         EventId = 1027)]
     internal static partial void LogReceivedResponseTrace(
-        this ILogger logger, long requestId, ResponseFlags flags, EndPoint 
remoteAddress);
+        this ILogger logger, long requestId, ClientOp op, ResponseFlags flags, 
EndPoint remoteAddress, TimeSpan? duration);
 
     [LoggerMessage(
         Message = "Failed to send server op response [requestId={RequestId}, 
message={Message}]",

Reply via email to