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();
+ }
+ }
+}