@@ -26,6 +26,18 @@ internal sealed class SqlClientDiagnosticListener : ListenerHandler
2626 public const string SqlDataWriteCommandError = "System.Data.SqlClient.WriteCommandError" ;
2727 public const string SqlMicrosoftWriteCommandError = "Microsoft.Data.SqlClient.WriteCommandError" ;
2828
29+ private static readonly string [ ] SharedTagNames =
30+ [
31+ SemanticConventions . AttributeDbSystem ,
32+ SemanticConventions . AttributeDbCollectionName ,
33+ SemanticConventions . AttributeDbNamespace ,
34+ SemanticConventions . AttributeDbResponseStatusCode ,
35+ SemanticConventions . AttributeDbOperationName ,
36+ SemanticConventions . AttributeErrorType ,
37+ SemanticConventions . AttributeServerPort ,
38+ SemanticConventions . AttributeServerAddress ,
39+ ] ;
40+
2941 private readonly PropertyFetcher < object > commandFetcher = new ( "Command" ) ;
3042 private readonly PropertyFetcher < object > connectionFetcher = new ( "Connection" ) ;
3143 private readonly PropertyFetcher < string > dataSourceFetcher = new ( "DataSource" ) ;
@@ -34,6 +46,7 @@ internal sealed class SqlClientDiagnosticListener : ListenerHandler
3446 private readonly PropertyFetcher < object > commandTextFetcher = new ( "CommandText" ) ;
3547 private readonly PropertyFetcher < Exception > exceptionFetcher = new ( "Exception" ) ;
3648 private readonly PropertyFetcher < int > exceptionNumberFetcher = new ( "Number" ) ;
49+ private readonly AsyncLocal < long > beginTimestamp = new ( ) ;
3750
3851 public SqlClientDiagnosticListener ( string sourceName )
3952 : base ( sourceName )
@@ -44,7 +57,7 @@ public SqlClientDiagnosticListener(string sourceName)
4457
4558 public override void OnEventWritten ( string name , object ? payload )
4659 {
47- if ( SqlClientInstrumentation . TracingHandles == 0 )
60+ if ( SqlClientInstrumentation . TracingHandles == 0 && SqlClientInstrumentation . MetricHandles == 0 )
4861 {
4962 return ;
5063 }
@@ -77,6 +90,7 @@ public override void OnEventWritten(string name, object? payload)
7790 if ( activity == null )
7891 {
7992 // There is no listener or it decided not to sample the current request.
93+ this . beginTimestamp . Value = Stopwatch . GetTimestamp ( ) ;
8094 return ;
8195 }
8296
@@ -162,15 +176,18 @@ public override void OnEventWritten(string name, object? payload)
162176 if ( activity == null )
163177 {
164178 SqlClientInstrumentationEventSource . Log . NullActivity ( name ) ;
179+ this . RecordDuration ( null , payload ) ;
165180 return ;
166181 }
167182
168183 if ( activity . Source != SqlActivitySourceHelper . ActivitySource )
169184 {
185+ this . RecordDuration ( null , payload ) ;
170186 return ;
171187 }
172188
173189 activity . Stop ( ) ;
190+ this . RecordDuration ( activity , payload ) ;
174191 }
175192
176193 break ;
@@ -180,11 +197,13 @@ public override void OnEventWritten(string name, object? payload)
180197 if ( activity == null )
181198 {
182199 SqlClientInstrumentationEventSource . Log . NullActivity ( name ) ;
200+ this . RecordDuration ( null , payload ) ;
183201 return ;
184202 }
185203
186204 if ( activity . Source != SqlActivitySourceHelper . ActivitySource )
187205 {
206+ this . RecordDuration ( null , payload ) ;
188207 return ;
189208 }
190209
@@ -217,6 +236,7 @@ public override void OnEventWritten(string name, object? payload)
217236 finally
218237 {
219238 activity . Stop ( ) ;
239+ this . RecordDuration ( activity , payload , hasError : true ) ;
220240 }
221241 }
222242
@@ -225,5 +245,84 @@ public override void OnEventWritten(string name, object? payload)
225245 break ;
226246 }
227247 }
248+
249+ private void RecordDuration ( Activity ? activity , object ? payload , bool hasError = false )
250+ {
251+ if ( SqlClientInstrumentation . MetricHandles == 0 )
252+ {
253+ return ;
254+ }
255+
256+ TagList tags = default ( TagList ) ;
257+
258+ if ( activity != null && activity . IsAllDataRequested )
259+ {
260+ foreach ( var name in SharedTagNames )
261+ {
262+ var value = activity . GetTagItem ( name ) ;
263+ if ( value != null )
264+ {
265+ tags . Add ( name , value ) ;
266+ }
267+ }
268+ }
269+ else if ( payload != null )
270+ {
271+ if ( this . commandFetcher . TryFetch ( payload , out var command ) && command != null &&
272+ this . connectionFetcher . TryFetch ( command , out var connection ) )
273+ {
274+ this . databaseFetcher . TryFetch ( connection , out var databaseName ) ;
275+ this . dataSourceFetcher . TryFetch ( connection , out var dataSource ) ;
276+
277+ var connectionTags = SqlActivitySourceHelper . GetTagListFromConnectionInfo (
278+ dataSource ,
279+ databaseName ,
280+ SqlClientInstrumentation . TracingOptions ,
281+ out _ ) ;
282+
283+ foreach ( var tag in connectionTags )
284+ {
285+ tags . Add ( tag . Key , tag . Value ) ;
286+ }
287+
288+ if ( this . commandTypeFetcher . TryFetch ( command , out var commandType ) &&
289+ commandType == CommandType . StoredProcedure )
290+ {
291+ if ( this . commandTextFetcher . TryFetch ( command , out var commandText ) )
292+ {
293+ tags . Add ( SemanticConventions . AttributeDbOperationName , "EXECUTE" ) ;
294+ tags . Add ( SemanticConventions . AttributeDbCollectionName , commandText ) ;
295+ }
296+ }
297+ }
298+
299+ if ( hasError )
300+ {
301+ if ( this . exceptionFetcher . TryFetch ( payload , out var exception ) && exception != null )
302+ {
303+ tags . Add ( SemanticConventions . AttributeErrorType , exception . GetType ( ) . FullName ) ;
304+
305+ if ( this . exceptionNumberFetcher . TryFetch ( exception , out var exceptionNumber ) )
306+ {
307+ tags . Add ( SemanticConventions . AttributeDbResponseStatusCode , exceptionNumber . ToString ( CultureInfo . InvariantCulture ) ) ;
308+ }
309+ }
310+ }
311+ }
312+
313+ var duration = activity ? . Duration . TotalSeconds ?? this . CalculateDurationFromTimestamp ( ) ;
314+ SqlActivitySourceHelper . DbClientOperationDuration . Record ( duration , tags ) ;
315+ }
316+
317+ private double CalculateDurationFromTimestamp ( )
318+ {
319+ var timestampToTicks = TimeSpan . TicksPerSecond / ( double ) Stopwatch . Frequency ;
320+ var begin = this . beginTimestamp . Value ;
321+ var end = Stopwatch . GetTimestamp ( ) ;
322+ var delta = end - begin ;
323+ var ticks = ( long ) ( timestampToTicks * delta ) ;
324+ var duration = new TimeSpan ( ticks ) ;
325+ return duration . TotalSeconds ;
326+ }
228327}
229328#endif
0 commit comments