Skip to content

Commit 0b365bf

Browse files
authored
Add additional connection and request logging to client (#2047)
1 parent 0f9300c commit 0b365bf

File tree

4 files changed

+94
-18
lines changed

4 files changed

+94
-18
lines changed

src/Grpc.Net.Client/Balancer/Internal/BalancerHttpHandler.cs

+34-3
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
using System;
2121
using System.Diagnostics;
2222
using System.IO;
23+
using System.Net;
2324
using System.Net.Http;
2425
using System.Threading;
2526
using System.Threading.Tasks;
2627
using Grpc.Shared;
28+
using Microsoft.Extensions.Logging;
2729

2830
namespace Grpc.Net.Client.Balancer.Internal;
2931

@@ -37,11 +39,13 @@ internal class BalancerHttpHandler : DelegatingHandler
3739
internal const string IsSocketsHttpHandlerSetupKey = "IsSocketsHttpHandlerSetup";
3840

3941
private readonly ConnectionManager _manager;
42+
private readonly ILogger _logger;
4043

4144
public BalancerHttpHandler(HttpMessageHandler innerHandler, ConnectionManager manager)
4245
: base(innerHandler)
4346
{
4447
_manager = manager;
48+
_logger = manager.LoggerFactory.CreateLogger<BalancerHttpHandler>();
4549
}
4650

4751
internal static bool IsSocketsHttpHandlerSetup(SocketsHttpHandler socketsHttpHandler)
@@ -54,7 +58,9 @@ value is bool isEnabled &&
5458
}
5559
}
5660

57-
internal static void ConfigureSocketsHttpHandlerSetup(SocketsHttpHandler socketsHttpHandler)
61+
internal static void ConfigureSocketsHttpHandlerSetup(
62+
SocketsHttpHandler socketsHttpHandler,
63+
Func<SocketsHttpConnectionContext, CancellationToken, ValueTask<Stream>> connectCallback)
5864
{
5965
// We're modifying the SocketsHttpHandler and nothing prevents two threads from creating a
6066
// channel with the same handler on different threads.
@@ -67,15 +73,17 @@ internal static void ConfigureSocketsHttpHandlerSetup(SocketsHttpHandler sockets
6773
{
6874
Debug.Assert(socketsHttpHandler.ConnectCallback == null, "ConnectCallback should be null to get to this point.");
6975

70-
socketsHttpHandler.ConnectCallback = OnConnect;
76+
socketsHttpHandler.ConnectCallback = connectCallback;
7177
socketsHttpHandler.Properties[IsSocketsHttpHandlerSetupKey] = true;
7278
}
7379
}
7480
}
7581

7682
#if NET5_0_OR_GREATER
77-
private static async ValueTask<Stream> OnConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
83+
internal async ValueTask<Stream> OnConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
7884
{
85+
Log.StartingConnectCallback(_logger, context.DnsEndPoint);
86+
7987
if (!context.InitialRequestMessage.TryGetOption<Subchannel>(SubchannelKey, out var subchannel))
8088
{
8189
throw new InvalidOperationException($"Unable to get subchannel from {nameof(HttpRequestMessage)}.");
@@ -133,6 +141,7 @@ protected override async Task<HttpResponseMessage> SendAsync(
133141
request.SetOption(CurrentAddressKey, address);
134142
#endif
135143

144+
Log.SendingRequest(_logger, request.RequestUri);
136145
var responseMessageTask = base.SendAsync(request, cancellationToken);
137146
result.SubchannelCallTracker?.Start();
138147

@@ -161,5 +170,27 @@ protected override async Task<HttpResponseMessage> SendAsync(
161170
throw;
162171
}
163172
}
173+
174+
internal static class Log
175+
{
176+
private static readonly Action<ILogger, Uri, Exception?> _sendingRequest =
177+
LoggerMessage.Define<Uri>(LogLevel.Trace, new EventId(1, "SendingRequest"), "Sending request {RequestUri}.");
178+
179+
private static readonly Action<ILogger, string, Exception?> _startingConnectCallback =
180+
LoggerMessage.Define<string>(LogLevel.Trace, new EventId(2, "StartingConnectCallback"), "Starting connect callback for {Endpoint}.");
181+
182+
public static void SendingRequest(ILogger logger, Uri requestUri)
183+
{
184+
_sendingRequest(logger, requestUri, null);
185+
}
186+
187+
public static void StartingConnectCallback(ILogger logger, DnsEndPoint endpoint)
188+
{
189+
if (logger.IsEnabled(LogLevel.Trace))
190+
{
191+
_startingConnectCallback(logger, $"{endpoint.Host}:{endpoint.Port}", null);
192+
}
193+
}
194+
}
164195
}
165196
#endif

src/Grpc.Net.Client/Balancer/Internal/SocketConnectivitySubchannelTransport.cs

+36-10
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,9 @@ public SocketConnectivitySubchannelTransport(
8080
_socketConnectedTimer = new Timer(OnCheckSocketConnection, state: null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
8181
}
8282

83-
public object Lock => _subchannel.Lock;
83+
private object Lock => _subchannel.Lock;
8484
public BalancerAddress? CurrentAddress => _currentAddress;
8585
public TimeSpan? ConnectTimeout { get; }
86-
public bool HasStream { get; }
8786

8887
// For testing. Take a copy under lock for thread-safety.
8988
internal IReadOnlyList<ActiveStream> GetActiveStreams()
@@ -264,13 +263,21 @@ public async ValueTask<Stream> GetStreamAsync(BalancerAddress address, Cancellat
264263
Socket? socket = null;
265264
lock (Lock)
266265
{
267-
if (_initialSocket != null &&
268-
_initialSocketAddress != null &&
269-
Equals(_initialSocketAddress, address))
266+
if (_initialSocket != null)
270267
{
268+
var socketAddressMatch = Equals(_initialSocketAddress, address);
269+
271270
socket = _initialSocket;
272271
_initialSocket = null;
273272
_initialSocketAddress = null;
273+
274+
// Double check the address matches the socket address and only use socket on match.
275+
// Not sure if this is possible in practice, but better safe than sorry.
276+
if (!socketAddressMatch)
277+
{
278+
socket.Dispose();
279+
socket = null;
280+
}
274281
}
275282
}
276283

@@ -288,6 +295,8 @@ public async ValueTask<Stream> GetStreamAsync(BalancerAddress address, Cancellat
288295

289296
if (socket == null)
290297
{
298+
SocketConnectivitySubchannelTransportLog.ConnectingOnCreateStream(_logger, _subchannel.Id, address);
299+
291300
socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
292301
await socket.ConnectAsync(address.EndPoint, cancellationToken).ConfigureAwait(false);
293302
}
@@ -300,6 +309,7 @@ public async ValueTask<Stream> GetStreamAsync(BalancerAddress address, Cancellat
300309
lock (Lock)
301310
{
302311
_activeStreams.Add(new ActiveStream(address, socket, stream));
312+
SocketConnectivitySubchannelTransportLog.StreamCreated(_logger, _subchannel.Id, address, _activeStreams.Count);
303313
}
304314

305315
return stream;
@@ -331,7 +341,7 @@ private void OnStreamDisposed(Stream streamWrapper)
331341
if (t.Stream == streamWrapper)
332342
{
333343
_activeStreams.RemoveAt(i);
334-
SocketConnectivitySubchannelTransportLog.DisposingStream(_logger, _subchannel.Id, t.Address);
344+
SocketConnectivitySubchannelTransportLog.DisposingStream(_logger, _subchannel.Id, t.Address, _activeStreams.Count);
335345

336346
// If the last active streams is removed then there is no active connection.
337347
disconnect = _activeStreams.Count == 0;
@@ -399,15 +409,21 @@ internal static class SocketConnectivitySubchannelTransportLog
399409
private static readonly Action<ILogger, int, BalancerAddress, Exception?> _creatingStream =
400410
LoggerMessage.Define<int, BalancerAddress>(LogLevel.Trace, new EventId(7, "CreatingStream"), "Subchannel id '{SubchannelId}' creating stream for {Address}.");
401411

402-
private static readonly Action<ILogger, int, BalancerAddress, Exception?> _disposingStream =
403-
LoggerMessage.Define<int, BalancerAddress>(LogLevel.Trace, new EventId(8, "DisposingStream"), "Subchannel id '{SubchannelId}' disposing stream for {Address}.");
412+
private static readonly Action<ILogger, int, BalancerAddress, int, Exception?> _disposingStream =
413+
LoggerMessage.Define<int, BalancerAddress, int>(LogLevel.Trace, new EventId(8, "DisposingStream"), "Subchannel id '{SubchannelId}' disposing stream for {Address}. Transport has {ActiveStreams} active streams.");
404414

405415
private static readonly Action<ILogger, int, Exception?> _disposingTransport =
406416
LoggerMessage.Define<int>(LogLevel.Trace, new EventId(9, "DisposingTransport"), "Subchannel id '{SubchannelId}' disposing transport.");
407417

408418
private static readonly Action<ILogger, int, Exception> _errorOnDisposingStream =
409419
LoggerMessage.Define<int>(LogLevel.Error, new EventId(10, "ErrorOnDisposingStream"), "Subchannel id '{SubchannelId}' unexpected error when reacting to transport stream dispose.");
410420

421+
private static readonly Action<ILogger, int, BalancerAddress, Exception?> _connectingOnCreateStream =
422+
LoggerMessage.Define<int, BalancerAddress>(LogLevel.Trace, new EventId(11, "ConnectingOnCreateStream"), "Subchannel id '{SubchannelId}' doesn't have a connected socket available. Connecting new stream socket for {Address}.");
423+
424+
private static readonly Action<ILogger, int, BalancerAddress, int, Exception?> _streamCreated =
425+
LoggerMessage.Define<int, BalancerAddress, int>(LogLevel.Trace, new EventId(12, "StreamCreated"), "Subchannel id '{SubchannelId}' created stream for {Address}. Transport has {ActiveStreams} active streams.");
426+
411427
public static void ConnectingSocket(ILogger logger, int subchannelId, BalancerAddress address)
412428
{
413429
_connectingSocket(logger, subchannelId, address, null);
@@ -443,9 +459,9 @@ public static void CreatingStream(ILogger logger, int subchannelId, BalancerAddr
443459
_creatingStream(logger, subchannelId, address, null);
444460
}
445461

446-
public static void DisposingStream(ILogger logger, int subchannelId, BalancerAddress address)
462+
public static void DisposingStream(ILogger logger, int subchannelId, BalancerAddress address, int activeStreams)
447463
{
448-
_disposingStream(logger, subchannelId, address, null);
464+
_disposingStream(logger, subchannelId, address, activeStreams, null);
449465
}
450466

451467
public static void DisposingTransport(ILogger logger, int subchannelId)
@@ -457,6 +473,16 @@ public static void ErrorOnDisposingStream(ILogger logger, int subchannelId, Exce
457473
{
458474
_errorOnDisposingStream(logger, subchannelId, ex);
459475
}
476+
477+
public static void ConnectingOnCreateStream(ILogger logger, int subchannelId, BalancerAddress address)
478+
{
479+
_connectingOnCreateStream(logger, subchannelId, address, null);
480+
}
481+
482+
public static void StreamCreated(ILogger logger, int subchannelId, BalancerAddress address, int activeStreams)
483+
{
484+
_streamCreated(logger, subchannelId, address, activeStreams, null);
485+
}
460486
}
461487
#endif
462488
#endif

src/Grpc.Net.Client/Balancer/Internal/StreamWrapper.cs

+20-2
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,18 @@
1818

1919
#if SUPPORT_LOAD_BALANCING
2020
using System;
21+
using System.Diagnostics;
2122
using System.IO;
2223
using System.Threading;
2324
using System.Threading.Tasks;
2425

2526
namespace Grpc.Net.Client.Balancer.Internal;
2627

27-
internal class StreamWrapper : Stream
28+
internal sealed class StreamWrapper : Stream
2829
{
2930
private readonly Stream _inner;
3031
private readonly Action<Stream> _onDispose;
32+
private bool _disposed;
3133

3234
public StreamWrapper(Stream inner, Action<Stream> onDispose)
3335
{
@@ -86,13 +88,29 @@ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken
8688
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) =>
8789
_inner.CopyToAsync(destination, bufferSize, cancellationToken);
8890

91+
public override async ValueTask DisposeAsync()
92+
{
93+
await base.DisposeAsync().ConfigureAwait(false);
94+
95+
// Avoid invoking dispose callback multiple times.
96+
if (_disposed)
97+
{
98+
_onDispose(this);
99+
await _inner.DisposeAsync().ConfigureAwait(false);
100+
_disposed = true;
101+
}
102+
}
103+
89104
protected override void Dispose(bool disposing)
90105
{
91106
base.Dispose(disposing);
92-
if (disposing)
107+
108+
// Avoid invoking dispose callback multiple times.
109+
if (disposing && !_disposed)
93110
{
94111
_onDispose(this);
95112
_inner.Dispose();
113+
_disposed = true;
96114
}
97115
}
98116
}

src/Grpc.Net.Client/GrpcChannel.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -456,15 +456,16 @@ private HttpMessageInvoker CreateInternalHttpInvoker(HttpMessageHandler? handler
456456
#endif
457457

458458
#if SUPPORT_LOAD_BALANCING
459+
BalancerHttpHandler balancerHttpHandler;
460+
handler = balancerHttpHandler = new BalancerHttpHandler(handler, ConnectionManager);
461+
459462
if (HttpHandlerType == HttpHandlerType.SocketsHttpHandler)
460463
{
461464
var socketsHttpHandler = HttpRequestHelpers.GetHttpHandlerType<SocketsHttpHandler>(handler);
462465
CompatibilityHelpers.Assert(socketsHttpHandler != null, "Should have handler with this handler type.");
463466

464-
BalancerHttpHandler.ConfigureSocketsHttpHandlerSetup(socketsHttpHandler);
467+
BalancerHttpHandler.ConfigureSocketsHttpHandlerSetup(socketsHttpHandler, balancerHttpHandler.OnConnect);
465468
}
466-
467-
handler = new BalancerHttpHandler(handler, ConnectionManager);
468469
#endif
469470

470471
// Use HttpMessageInvoker instead of HttpClient because it is faster

0 commit comments

Comments
 (0)