diff --git a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj index 66c10683b87266..6d9e6416ce67cb 100644 --- a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj +++ b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj @@ -14,6 +14,7 @@ + diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs index 6bddfe34d96aba..e75f084e1c9a61 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Tracing; using System.Globalization; using System.IO; using System.Net.Internals; @@ -2071,6 +2072,8 @@ private bool CanUseConnectEx(EndPoint remoteEP) internal IAsyncResult UnsafeBeginConnect(EndPoint remoteEP, AsyncCallback? callback, object? state, bool flowContext = false) { + if (SocketsTelemetry.Log.IsEnabled()) SocketsTelemetry.Log.ConnectStart(remoteEP); + if (CanUseConnectEx(remoteEP)) { return BeginConnectEx(remoteEP, flowContext, callback, state); @@ -2320,6 +2323,8 @@ asyncResult as MultipleAddressConnectAsyncResult ?? Exception? ex = castedAsyncResult.Result as Exception; if (ex != null || (SocketError)castedAsyncResult.ErrorCode != SocketError.Success) { + if (SocketsTelemetry.Log.IsEnabled()) SocketsTelemetry.Log.ConnectFailedAndStop((SocketError)castedAsyncResult.ErrorCode, ex?.Message); + if (ex == null) { SocketError errorCode = (SocketError)castedAsyncResult.ErrorCode; @@ -2334,6 +2339,8 @@ asyncResult as MultipleAddressConnectAsyncResult ?? ExceptionDispatchInfo.Throw(ex); } + if (SocketsTelemetry.Log.IsEnabled()) SocketsTelemetry.Log.ConnectStop(); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Connected(this, LocalEndPoint, RemoteEndPoint); } @@ -4121,11 +4128,15 @@ static void InitializeSocketsCore() private void DoConnect(EndPoint endPointSnapshot, Internals.SocketAddress socketAddress) { + if (SocketsTelemetry.Log.IsEnabled()) SocketsTelemetry.Log.ConnectStart(socketAddress); + SocketError errorCode = SocketPal.Connect(_handle, socketAddress.Buffer, socketAddress.Size); // Throw an appropriate SocketException if the native call fails. if (errorCode != SocketError.Success) { + if (SocketsTelemetry.Log.IsEnabled()) SocketsTelemetry.Log.ConnectFailedAndStop(errorCode, null); + UpdateConnectSocketErrorForDisposed(ref errorCode); // Update the internal state of this socket according to the error before throwing. SocketException socketException = SocketExceptionFactory.CreateSocketException((int)errorCode, endPointSnapshot); @@ -4134,6 +4145,8 @@ private void DoConnect(EndPoint endPointSnapshot, Internals.SocketAddress socket throw socketException; } + if (SocketsTelemetry.Log.IsEnabled()) SocketsTelemetry.Log.ConnectStop(); + if (_rightEndPoint == null) { // Save a copy of the EndPoint so we can use it for Create(). diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs index 118371df52f16a..213cb30909b99a 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.Tracing; using System.Runtime.InteropServices; using System.Threading; @@ -553,6 +554,9 @@ internal void StartOperationConnect(MultipleConnectAsync? multipleConnect, bool _multipleConnect = multipleConnect; _connectSocket = null; _userSocket = userSocket; + + // Log only the actual connect operation to a remote endpoint. + if (SocketsTelemetry.Log.IsEnabled() && multipleConnect == null) SocketsTelemetry.Log.ConnectStart(_socketAddress!); } internal void CancelConnectAsync() @@ -566,6 +570,8 @@ internal void CancelConnectAsync() } else { + if (SocketsTelemetry.Log.IsEnabled()) SocketsTelemetry.Log.ConnectCanceledAndStop(); + // Otherwise we're doing a normal ConnectAsync - cancel it by closing the socket. // _currentSocket will only be null if _multipleConnect was set, so we don't have to check. if (_currentSocket == null) @@ -581,6 +587,11 @@ internal void FinishOperationSyncFailure(SocketError socketError, int bytesTrans { SetResults(socketError, bytesTransferred, flags); + if (SocketsTelemetry.Log.IsEnabled() && _multipleConnect == null && _completedOperation == SocketAsyncOperation.Connect) + { + SocketsTelemetry.Log.ConnectFailedAndStop(socketError, null); + } + // This will be null if we're doing a static ConnectAsync to a DnsEndPoint with AddressFamily.Unspecified; // the attempt socket will be closed anyways, so not updating the state is OK. // If we're doing a static ConnectAsync to an IPEndPoint, we need to dispose @@ -719,12 +730,16 @@ internal void FinishOperationSyncSuccess(int bytesTransferred, SocketFlags flags catch (ObjectDisposedException) { } } + if (SocketsTelemetry.Log.IsEnabled()) SocketsTelemetry.Log.ConnectStop(); + // Mark socket connected. _currentSocket!.SetToConnected(); _connectSocket = _currentSocket; } else { + if (SocketsTelemetry.Log.IsEnabled()) SocketsTelemetry.Log.ConnectFailedAndStop(socketError, null); + SetResults(socketError, bytesTransferred, flags); _currentSocket!.UpdateStatusAfterSocketError(socketError); } diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketsTelemetry.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketsTelemetry.cs new file mode 100644 index 00000000000000..7f0e2a78eb749a --- /dev/null +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketsTelemetry.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.Tracing; + +namespace System.Net.Sockets +{ + [EventSource(Name = "System.Net.Sockets")] + internal sealed class SocketsTelemetry : EventSource + { + public static readonly SocketsTelemetry Log = new SocketsTelemetry(); + + [Event(1, Level = EventLevel.Informational)] + public void ConnectStart(string? address) + { + if (IsEnabled(EventLevel.Informational, EventKeywords.All)) + { + WriteEvent(eventId: 1, address ?? ""); + } + } + + [Event(2, Level = EventLevel.Informational)] + public void ConnectStop() + { + if (IsEnabled(EventLevel.Informational, EventKeywords.All)) + { + WriteEvent(eventId: 2); + } + } + + [Event(3, Level = EventLevel.Error)] + public void ConnectFailed(SocketError error, string? exceptionMessage) + { + if (IsEnabled(EventLevel.Error, EventKeywords.All)) + { + WriteEvent(eventId: 3, (int)error, exceptionMessage ?? string.Empty); + } + } + + [Event(4, Level = EventLevel.Warning)] + public void ConnectCanceled() + { + if (IsEnabled(EventLevel.Warning, EventKeywords.All)) + { + WriteEvent(eventId: 4); + } + } + + [NonEvent] + public void ConnectStart(Internals.SocketAddress address) + { + ConnectStart(address.ToString()); + } + + [NonEvent] + public void ConnectStart(EndPoint address) + { + ConnectStart(address.ToString()); + } + + [NonEvent] + public void ConnectCanceledAndStop() + { + ConnectCanceled(); + ConnectStop(); + } + + [NonEvent] + public void ConnectFailedAndStop(SocketError error, string? exceptionMessage) + { + ConnectFailed(error, exceptionMessage); + ConnectStop(); + } + } +} diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj b/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj index 6a6d63b4c6a4f6..73e4a078e1b7c0 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj @@ -39,6 +39,7 @@ + diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/TelemetryTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/TelemetryTest.cs new file mode 100644 index 00000000000000..33529f980d225f --- /dev/null +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/TelemetryTest.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Concurrent; +using System.Diagnostics.Tracing; +using Microsoft.DotNet.RemoteExecutor; +using Xunit; +using Xunit.Abstractions; + +namespace System.Net.Sockets.Tests +{ + public class TelemetryTest + { + public readonly ITestOutputHelper _output; + + public TelemetryTest(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public static void EventSource_ExistsWithCorrectId() + { + Type esType = typeof(Socket).Assembly.GetType("System.Net.Sockets.SocketsTelemetry", throwOnError: true, ignoreCase: false); + Assert.NotNull(esType); + + Assert.Equal("System.Net.Sockets", EventSource.GetName(esType)); + Assert.Equal(Guid.Parse("d5b2e7d4-b6ec-50ae-7cde-af89427ad21f"), EventSource.GetGuid(esType)); + + Assert.NotEmpty(EventSource.GenerateManifest(esType, esType.Assembly.Location)); + } + + [OuterLoop] + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void EventSource_EventsRaisedAsExpected() + { + RemoteExecutor.Invoke(() => + { + using (var listener = new TestEventListener("System.Net.Sockets", EventLevel.Verbose)) + { + var events = new ConcurrentQueue(); + listener.RunWithCallback(events.Enqueue, () => + { + // Invoke several tests to execute code paths while tracing is enabled + + new SendReceiveSync(null).SendRecv_Stream_TCP(IPAddress.Loopback, false).GetAwaiter(); + new SendReceiveSync(null).SendRecv_Stream_TCP(IPAddress.Loopback, true).GetAwaiter(); + + new SendReceiveTask(null).SendRecv_Stream_TCP(IPAddress.Loopback, false).GetAwaiter(); + new SendReceiveTask(null).SendRecv_Stream_TCP(IPAddress.Loopback, true).GetAwaiter(); + + new SendReceiveEap(null).SendRecv_Stream_TCP(IPAddress.Loopback, false).GetAwaiter(); + new SendReceiveEap(null).SendRecv_Stream_TCP(IPAddress.Loopback, true).GetAwaiter(); + + new SendReceiveApm(null).SendRecv_Stream_TCP(IPAddress.Loopback, false).GetAwaiter(); + new SendReceiveApm(null).SendRecv_Stream_TCP(IPAddress.Loopback, true).GetAwaiter(); + + new NetworkStreamTest().CopyToAsync_AllDataCopied(4096, true).GetAwaiter().GetResult(); + new NetworkStreamTest().Timeout_ValidData_Roundtrips().GetAwaiter().GetResult(); + }); + Assert.DoesNotContain(events, ev => ev.EventId == 0); // errors from the EventSource itself + Assert.InRange(events.Count, 1, int.MaxValue); + } + }).Dispose(); + } + } +}