From dbabd7678d2290f1aef44815fe96b4310cd0c3e6 Mon Sep 17 00:00:00 2001
From: Thomas Stringer
Date: Tue, 22 Mar 2016 20:35:21 -0400
Subject: [PATCH] Added diagnostics to sqlclient
---
.../src/System.Data.SqlClient.csproj | 3 +-
.../SqlClientDiagnosticListenerExtensions.cs | 74 +++
.../src/System/Data/SqlClient/SqlCommand.cs | 110 +++-
.../System/Data/SqlClient/SqlConnection.cs | 11 +-
src/System.Data.SqlClient/src/project.json | 8 +-
.../tests/FunctionalTests/DiagnosticTest.cs | 490 ++++++++++++++++++
.../FakeDiagnosticListenerObserver.cs | 72 +++
.../System.Data.SqlClient.Tests.csproj | 6 +-
.../tests/FunctionalTests/project.json | 4 +
9 files changed, 766 insertions(+), 12 deletions(-)
create mode 100644 src/System.Data.SqlClient/src/System/Data/SqlClient/SqlClientDiagnosticListenerExtensions.cs
create mode 100644 src/System.Data.SqlClient/tests/FunctionalTests/DiagnosticTest.cs
create mode 100644 src/System.Data.SqlClient/tests/FunctionalTests/FakeDiagnosticListenerObserver.cs
diff --git a/src/System.Data.SqlClient/src/System.Data.SqlClient.csproj b/src/System.Data.SqlClient/src/System.Data.SqlClient.csproj
index 81fa44a90fbc..b55914ae8409 100644
--- a/src/System.Data.SqlClient/src/System.Data.SqlClient.csproj
+++ b/src/System.Data.SqlClient/src/System.Data.SqlClient.csproj
@@ -1,4 +1,4 @@
-
+
Windows_Debug
@@ -88,6 +88,7 @@
+
diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlClientDiagnosticListenerExtensions.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlClientDiagnosticListenerExtensions.cs
new file mode 100644
index 000000000000..e2038e3bce57
--- /dev/null
+++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlClientDiagnosticListenerExtensions.cs
@@ -0,0 +1,74 @@
+using System.Collections;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System.Data.SqlClient
+{
+ ///
+ /// Extension methods on the DiagnosticListener class to log SqlCommand data
+ ///
+ internal static class SqlClientDiagnosticListenerExtensions
+ {
+ public const string DiagnosticListenerName = "SqlClientDiagnosticListener";
+
+ private const string SqlClientPrefix = "System.Data.SqlClient.";
+ public const string SqlBeforeExecuteCommand = SqlClientPrefix + nameof(WriteCommandBefore);
+ public const string SqlAfterExecuteCommand = SqlClientPrefix + nameof(WriteCommandAfter);
+ public const string SqlErrorExecuteCommand = SqlClientPrefix + nameof(WriteCommandError);
+
+ public static Guid WriteCommandBefore(this DiagnosticListener @this, SqlCommand sqlCommand, [CallerMemberName] string operation = "")
+ {
+ if (@this.IsEnabled(SqlBeforeExecuteCommand))
+ {
+ Guid operationId = Guid.NewGuid();
+
+ @this.Write(
+ SqlBeforeExecuteCommand,
+ new
+ {
+ OperationId = operationId,
+ Operation = operation,
+ Command = sqlCommand
+ });
+
+ return operationId;
+ }
+ else
+ return Guid.Empty;
+ }
+
+ public static void WriteCommandAfter(this DiagnosticListener @this, Guid operationId, SqlCommand sqlCommand, [CallerMemberName] string operation = "")
+ {
+ if (@this.IsEnabled(SqlAfterExecuteCommand))
+ {
+ @this.Write(
+ SqlAfterExecuteCommand,
+ new
+ {
+ OperationId = operationId,
+ Operation = operation,
+ Command = sqlCommand,
+ Statistics = sqlCommand.Statistics?.GetDictionary(),
+ Timestamp = Stopwatch.GetTimestamp()
+ });
+ }
+ }
+
+ public static void WriteCommandError(this DiagnosticListener @this, Guid operationId, SqlCommand sqlCommand, Exception ex, [CallerMemberName] string operation = "")
+ {
+ if (@this.IsEnabled(SqlErrorExecuteCommand))
+ {
+ @this.Write(
+ SqlErrorExecuteCommand,
+ new
+ {
+ OperationId = operationId,
+ Operation = operation,
+ Command = sqlCommand,
+ Exception = ex,
+ Timestamp = Stopwatch.GetTimestamp()
+ });
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs
index db105b91b3e7..37e31c9ba0b1 100644
--- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs
+++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs
@@ -23,6 +23,9 @@ public sealed class SqlCommand : DbCommand
private UpdateRowSource _updatedRowSource = UpdateRowSource.Both;
private bool _designTimeInvisible;
+ private readonly static DiagnosticListener _diagnosticListener = new DiagnosticListener(SqlClientDiagnosticListenerExtensions.DiagnosticListenerName);
+ private bool _parentOperationStarted = false;
+
// Prepare
// Against 7.0 Serve a prepare/unprepare requires an extra roundtrip to the server.
//
@@ -290,7 +293,8 @@ internal SqlStatistics Statistics
{
if (null != _activeConnection)
{
- if (_activeConnection.StatisticsEnabled)
+ if (_activeConnection.StatisticsEnabled ||
+ _diagnosticListener.IsEnabled(SqlClientDiagnosticListenerExtensions.SqlAfterExecuteCommand))
{
return _activeConnection.Statistics;
}
@@ -738,9 +742,11 @@ override public object ExecuteScalar()
// between entry into Execute* API and the thread obtaining the stateObject.
_pendingCancel = false;
- SqlStatistics statistics = null;
-
+ Guid operationId = _diagnosticListener.WriteCommandBefore(this);
+ SqlStatistics statistics = null;
+
+ Exception e = null;
try
{
statistics = SqlStatistics.StartTimer(Statistics);
@@ -748,9 +754,23 @@ override public object ExecuteScalar()
ds = RunExecuteReader(0, RunBehavior.ReturnImmediately, returnStream: true);
return CompleteExecuteScalar(ds, false);
}
+ catch (Exception ex)
+ {
+ e = ex;
+ throw;
+ }
finally
{
SqlStatistics.StopTimer(statistics);
+
+ if (e != null)
+ {
+ _diagnosticListener.WriteCommandError(operationId, this, e);
+ }
+ else
+ {
+ _diagnosticListener.WriteCommandAfter(operationId, this);
+ }
}
}
@@ -790,16 +810,34 @@ override public int ExecuteNonQuery()
// between entry into Execute* API and the thread obtaining the stateObject.
_pendingCancel = false;
+ Guid operationId = _diagnosticListener.WriteCommandBefore(this);
+
SqlStatistics statistics = null;
+
+ Exception e = null;
try
{
statistics = SqlStatistics.StartTimer(Statistics);
InternalExecuteNonQuery(completion: null, sendToPipe: false, timeout: CommandTimeout);
return _rowsAffected;
}
+ catch (Exception ex)
+ {
+ e = ex;
+ throw;
+ }
finally
{
SqlStatistics.StopTimer(statistics);
+
+ if (e != null)
+ {
+ _diagnosticListener.WriteCommandError(operationId, this, e);
+ }
+ else
+ {
+ _diagnosticListener.WriteCommandAfter(operationId, this);
+ }
}
}
@@ -1091,7 +1129,11 @@ public XmlReader ExecuteXmlReader()
// between entry into Execute* API and the thread obtaining the stateObject.
_pendingCancel = false;
+ Guid operationId = _diagnosticListener.WriteCommandBefore(this);
+
SqlStatistics statistics = null;
+
+ Exception e = null;
try
{
statistics = SqlStatistics.StartTimer(Statistics);
@@ -1101,9 +1143,23 @@ public XmlReader ExecuteXmlReader()
ds = RunExecuteReader(CommandBehavior.SequentialAccess, RunBehavior.ReturnImmediately, returnStream: true);
return CompleteXmlReader(ds);
}
+ catch (Exception ex)
+ {
+ e = ex;
+ throw;
+ }
finally
{
SqlStatistics.StopTimer(statistics);
+
+ if (e != null)
+ {
+ _diagnosticListener.WriteCommandError(operationId, this, e);
+ }
+ else
+ {
+ _diagnosticListener.WriteCommandAfter(operationId, this);
+ }
}
}
@@ -1286,16 +1342,33 @@ override protected DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
// between entry into Execute* API and the thread obtaining the stateObject.
_pendingCancel = false;
- SqlStatistics statistics = null;
+ Guid operationId = _diagnosticListener.WriteCommandBefore(this);
+ SqlStatistics statistics = null;
+
+ Exception e = null;
try
{
statistics = SqlStatistics.StartTimer(Statistics);
return RunExecuteReader(behavior, RunBehavior.ReturnImmediately, returnStream: true);
}
+ catch (Exception ex)
+ {
+ e = ex;
+ throw;
+ }
finally
{
SqlStatistics.StopTimer(statistics);
+
+ if (e != null)
+ {
+ _diagnosticListener.WriteCommandError(operationId, this, e);
+ }
+ else
+ {
+ _diagnosticListener.WriteCommandAfter(operationId, this);
+ }
}
}
@@ -1438,6 +1511,8 @@ private SqlDataReader InternalEndExecuteReader(IAsyncResult asyncResult, string
public override Task ExecuteNonQueryAsync(CancellationToken cancellationToken)
{
+ Guid operationId = _diagnosticListener.WriteCommandBefore(this);
+
TaskCompletionSource source = new TaskCompletionSource();
CancellationTokenRegistration registration = new CancellationTokenRegistration();
@@ -1462,6 +1537,7 @@ public override Task ExecuteNonQueryAsync(CancellationToken cancellationTok
if (t.IsFaulted)
{
Exception e = t.Exception.InnerException;
+ _diagnosticListener.WriteCommandError(operationId, this, e);
source.SetException(e);
}
else
@@ -1474,11 +1550,13 @@ public override Task ExecuteNonQueryAsync(CancellationToken cancellationTok
{
source.SetResult(t.Result);
}
+ _diagnosticListener.WriteCommandAfter(operationId, this);
}
}, TaskScheduler.Default);
}
catch (Exception e)
{
+ _diagnosticListener.WriteCommandError(operationId, this, e);
source.SetException(e);
}
@@ -1514,6 +1592,10 @@ protected override Task ExecuteDbDataReaderAsync(CommandBehavior b
new public Task ExecuteReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
{
+ Guid operationId;
+ if (!_parentOperationStarted)
+ operationId = _diagnosticListener.WriteCommandBefore(this);
+
TaskCompletionSource source = new TaskCompletionSource();
CancellationTokenRegistration registration = new CancellationTokenRegistration();
@@ -1538,6 +1620,8 @@ protected override Task ExecuteDbDataReaderAsync(CommandBehavior b
if (t.IsFaulted)
{
Exception e = t.Exception.InnerException;
+ if (!_parentOperationStarted)
+ _diagnosticListener.WriteCommandError(operationId, this, e);
source.SetException(e);
}
else
@@ -1550,11 +1634,16 @@ protected override Task ExecuteDbDataReaderAsync(CommandBehavior b
{
source.SetResult(t.Result);
}
+ if (!_parentOperationStarted)
+ _diagnosticListener.WriteCommandAfter(operationId, this);
}
}, TaskScheduler.Default);
}
catch (Exception e)
{
+ if (!_parentOperationStarted)
+ _diagnosticListener.WriteCommandError(operationId, this, e);
+
source.SetException(e);
}
@@ -1563,6 +1652,9 @@ protected override Task ExecuteDbDataReaderAsync(CommandBehavior b
public override Task