Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Sql Collector (Part 2) #536

Merged
merged 8 commits into from
Mar 17, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Myget feeds:
| Package | MyGet (CI) | NuGet (releases) |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| ASP.NET Core | [![MyGet Nightly][OpenTelemetry-collect-aspnetcore-myget-image]][OpenTelemetry-collect-aspnetcore-myget-url] | [![NuGet Release][OpenTelemetry-collect-aspnetcore-nuget-image]][OpenTelemetry-collect-aspnetcore-nuget-url] |
| .NET Core HttpClient & Azure SDKs | [![MyGet Nightly][OpenTelemetry-collect-deps-myget-image]][OpenTelemetry-collect-deps-myget-url] | [![NuGet Release][OpenTelemetry-collect-deps-nuget-image]][OpenTelemetry-collect-deps-nuget-url] |
| .NET Core HttpClient, SqlClient, & Azure SDKs | [![MyGet Nightly][OpenTelemetry-collect-deps-myget-image]][OpenTelemetry-collect-deps-myget-url] | [![NuGet Release][OpenTelemetry-collect-deps-nuget-image]][OpenTelemetry-collect-deps-nuget-url] |
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
| StackExchange.Redis | [![MyGet Nightly][OpenTelemetry-collect-stackexchange-redis-myget-image]][OpenTelemetry-collect-stackexchange-redis-myget-url] | [![NuGet Release][OpenTelemetry-collect-stackexchange-redis-nuget-image]][OpenTelemetry-collect-stackexchange-redis-nuget-url] |

### Exporters Packages
Expand Down
30 changes: 20 additions & 10 deletions src/OpenTelemetry.Api/Trace/SpanAttributeConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,27 @@

namespace OpenTelemetry.Trace
{
internal static class SpanAttributeConstants
/// <summary>
/// Defines well-known span attribute keys.
/// </summary>
public static class SpanAttributeConstants
{
public static readonly string ComponentKey = "component";
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public const string ComponentKey = "component";
public const string PeerServiceKey = "peer.service";

public static readonly string HttpMethodKey = "http.method";
public static readonly string HttpStatusCodeKey = "http.status_code";
public static readonly string HttpUserAgentKey = "http.user_agent";
public static readonly string HttpPathKey = "http.path";
public static readonly string HttpHostKey = "http.host";
public static readonly string HttpUrlKey = "http.url";
public static readonly string HttpRouteKey = "http.route";
public static readonly string HttpFlavorKey = "http.flavor";
public const string HttpMethodKey = "http.method";
public const string HttpStatusCodeKey = "http.status_code";
public const string HttpUserAgentKey = "http.user_agent";
public const string HttpPathKey = "http.path";
public const string HttpHostKey = "http.host";
public const string HttpUrlKey = "http.url";
public const string HttpRouteKey = "http.route";
public const string HttpFlavorKey = "http.flavor";

public const string DatabaseTypeKey = "db.type";
public const string DatabaseInstanceKey = "db.instance";
public const string DatabaseStatementKey = "db.statement";
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}
}
54 changes: 53 additions & 1 deletion src/OpenTelemetry.Api/Trace/SpanExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ public static TelemetrySpan PutComponentAttribute(this TelemetrySpan span, strin
return span;
}

/// <summary>
/// Helper method that populates span properties from component
/// to https://github.com/open-telemetry/opentelemetry-specification/blob/2316771e7e0ca3bfe9b2286d13e3a41ded6b8858/specification/data-span-general.md.
/// </summary>
/// <param name="span">Span to fill out.</param>
/// <param name="peerService">Peer service.</param>
/// <returns>Span with populated http method properties.</returns>
public static TelemetrySpan PutPeerServiceAttribute(this TelemetrySpan span, string peerService)
{
span.SetAttribute(SpanAttributeConstants.PeerServiceKey, peerService);
return span;
}

/// <summary>
/// Helper method that populates span properties from http method according
/// to https://github.com/open-telemetry/opentelemetry-specification/blob/2316771e7e0ca3bfe9b2286d13e3a41ded6b8858/specification/data-http.md.
Expand Down Expand Up @@ -208,11 +221,50 @@ public static TelemetrySpan PutHttpStatusCode(this TelemetrySpan span, int statu
/// </summary>
/// <param name="span">Span to fill out.</param>
/// <param name="flavor">HTTP version.</param>
/// <returns>Span with populated request size properties.</returns>
/// <returns>Span with populated properties.</returns>
public static TelemetrySpan PutHttpFlavorAttribute(this TelemetrySpan span, string flavor)
{
span.SetAttribute(SpanAttributeConstants.HttpFlavorKey, flavor);
return span;
}

/// <summary>
/// Helper method that populates database type
/// to https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-database.md.
/// </summary>
/// <param name="span">Span to fill out.</param>
/// <param name="type">Database type.</param>
/// <returns>Span with populated properties.</returns>
public static TelemetrySpan PutDatabaseTypeAttribute(this TelemetrySpan span, string type)
{
span.SetAttribute(SpanAttributeConstants.DatabaseTypeKey, type);
return span;
}

/// <summary>
/// Helper method that populates database type
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
/// to https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-database.md.
/// </summary>
/// <param name="span">Span to fill out.</param>
/// <param name="instance">Database instance.</param>
/// <returns>Span with populated properties.</returns>
public static TelemetrySpan PutDatabaseInstanceAttribute(this TelemetrySpan span, string instance)
{
span.SetAttribute(SpanAttributeConstants.DatabaseInstanceKey, instance);
return span;
}

/// <summary>
/// Helper method that populates database statement
/// to https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-database.md.
/// </summary>
/// <param name="span">Span to fill out.</param>
/// <param name="statement">Database type.</param>
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
/// <returns>Span with populated properties.</returns>
public static TelemetrySpan PutDatabaseStatementAttribute(this TelemetrySpan span, string statement)
{
span.SetAttribute(SpanAttributeConstants.DatabaseStatementKey, statement);
return span;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// <copyright file="SqlClientDiagnosticListener.cs" company="OpenTelemetry Authors">
// Copyright 2018, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
using System.Diagnostics;
using OpenTelemetry.Trace;

namespace OpenTelemetry.Collector.Dependencies.Implementation
{
internal class SqlClientDiagnosticListener : ListenerHandler
{
internal const string SqlDataBeforeExecuteCommand = "System.Data.SqlClient.WriteCommandBefore";
internal const string SqlMicrosoftBeforeExecuteCommand = "Microsoft.Data.SqlClient.WriteCommandBefore";

internal const string SqlDataAfterExecuteCommand = "System.Data.SqlClient.WriteCommandAfter";
internal const string SqlMicrosoftAfterExecuteCommand = "Microsoft.Data.SqlClient.WriteCommandAfter";

internal const string SqlDataWriteCommandError = "System.Data.SqlClient.WriteCommandError";
internal const string SqlMicrosoftWriteCommandError = "Microsoft.Data.SqlClient.WriteCommandError";

private readonly PropertyFetcher commandFetcher = new PropertyFetcher("Command");
private readonly PropertyFetcher connectionFetcher = new PropertyFetcher("Connection");
private readonly PropertyFetcher dataSourceFetcher = new PropertyFetcher("DataSource");
private readonly PropertyFetcher databaseFetcher = new PropertyFetcher("Database");
private readonly PropertyFetcher commandTextFetcher = new PropertyFetcher("CommandText");
private readonly PropertyFetcher exceptionFetcher = new PropertyFetcher("Exception");

public SqlClientDiagnosticListener(string sourceName, Tracer tracer)
: base(sourceName, tracer)
{
}

public override void OnStartActivity(Activity activity, object payload)
{
}

public override void OnCustom(string name, Activity activity, object payload)
{
switch (name)
{
case SqlDataBeforeExecuteCommand:
case SqlMicrosoftBeforeExecuteCommand:
{
var command = this.commandFetcher.Fetch(payload);

if (command == null)
{
CollectorEventSource.Log.NullPayload(nameof(SqlClientDiagnosticListener) + name);
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
return;
}

var connection = this.connectionFetcher.Fetch(command);
var database = this.databaseFetcher.Fetch(connection);

this.Tracer.StartActiveSpan((string)database, SpanKind.Client, out var span);

if (span.IsRecording)
{
var dataSource = this.dataSourceFetcher.Fetch(connection);
var commandText = this.commandTextFetcher.Fetch(command);

span.PutComponentAttribute("sql");

span.PutDatabaseTypeAttribute("sql");
span.PutDatabaseInstanceAttribute((string)database);
span.PutDatabaseStatementAttribute((string)commandText);
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
span.PutPeerServiceAttribute((string)dataSource);
}
}

break;
case SqlDataAfterExecuteCommand:
case SqlMicrosoftAfterExecuteCommand:
{
var span = this.Tracer.CurrentSpan;

if (span == null || !span.Context.IsValid)
{
CollectorEventSource.Log.NullOrBlankSpan(nameof(SqlClientDiagnosticListener) + name);
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
return;
}

span.End();
}

break;
case SqlDataWriteCommandError:
case SqlMicrosoftWriteCommandError:
{
var span = this.Tracer.CurrentSpan;

if (span == null || !span.Context.IsValid)
{
CollectorEventSource.Log.NullOrBlankSpan(nameof(SqlClientDiagnosticListener) + name);
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
return;
}

if (span.IsRecording)
{
if (!(this.exceptionFetcher.Fetch(payload) is Exception exception))
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
{
CollectorEventSource.Log.NullPayload(nameof(SqlClientDiagnosticListener) + name);
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
return;
}

span.Status = Status.Unknown.WithDescription(exception.Message);
}
}

break;
}
}
}
}
50 changes: 50 additions & 0 deletions src/OpenTelemetry.Collector.Dependencies/SqlClientCollector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// <copyright file="SqlClientCollector.cs" company="OpenTelemetry Authors">
// Copyright 2018, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
using OpenTelemetry.Collector.Dependencies.Implementation;
using OpenTelemetry.Trace;

namespace OpenTelemetry.Collector.Dependencies
{
/// <summary>
/// SqlClient collector.
/// </summary>
public class SqlClientCollector : IDisposable
{
internal const string SqlClientDiagnosticListenerName = "SqlClientDiagnosticListener";

private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber;

/// <summary>
/// Initializes a new instance of the <see cref="SqlClientCollector"/> class.
/// </summary>
/// <param name="tracer">Tracer to record traced with.</param>
public SqlClientCollector(Tracer tracer)
{
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(
name => new SqlClientDiagnosticListener(name, tracer),
listener => listener.Name == SqlClientDiagnosticListenerName,
null);
this.diagnosticSourceSubscriber.Subscribe();
}

/// <inheritdoc/>
public void Dispose()
{
this.diagnosticSourceSubscriber?.Dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public static TracerBuilder AddDependencyCollector(this TracerBuilder builder)
return builder
.AddCollector((t) => new AzureClientsCollector(t))
.AddCollector((t) => new AzurePipelineCollector(t))
.AddCollector((t) => new HttpClientCollector(t));
.AddCollector((t) => new HttpClientCollector(t))
.AddCollector((t) => new SqlClientCollector(t));
}

/// <summary>
Expand All @@ -66,7 +67,8 @@ public static TracerBuilder AddDependencyCollector(this TracerBuilder builder, A
return builder
.AddCollector((t) => new AzureClientsCollector(t))
.AddCollector((t) => new AzurePipelineCollector(t))
.AddCollector((t) => new HttpClientCollector(t, options));
.AddCollector((t) => new HttpClientCollector(t, options))
.AddCollector((t) => new SqlClientCollector(t));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ internal static class JaegerConversionExtensions

private static readonly Dictionary<string, int> PeerServiceKeyResolutionDictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
["peer.service"] = 0, // peer.service primary.
[SpanAttributeConstants.PeerServiceKey] = 0, // peer.service primary.
MikeGoldsmith marked this conversation as resolved.
Show resolved Hide resolved
["net.peer.name"] = 1, // peer.service first alternative.
["peer.hostname"] = 2, // peer.service second alternative.
["peer.address"] = 2, // peer.service second alternative.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ internal static class ZipkinConversionExtensions

private static readonly Dictionary<string, int> RemoteEndpointServiceNameKeyResolutionDictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
["net.peer.name"] = 0, // RemoteEndpoint.ServiceName primary.
["peer.service"] = 0, // RemoteEndpoint.ServiceName primary.
["peer.hostname"] = 1, // RemoteEndpoint.ServiceName alternative.
["peer.address"] = 1, // RemoteEndpoint.ServiceName alternative.
["http.host"] = 2, // RemoteEndpoint.ServiceName for Http.
["db.instance"] = 2, // RemoteEndpoint.ServiceName for Redis.
[SpanAttributeConstants.PeerServiceKey] = 0, // RemoteEndpoint.ServiceName primary.
MikeGoldsmith marked this conversation as resolved.
Show resolved Hide resolved
["net.peer.name"] = 1, // RemoteEndpoint.ServiceName first alternative.
["peer.hostname"] = 2, // RemoteEndpoint.ServiceName second alternative.
["peer.address"] = 2, // RemoteEndpoint.ServiceName second alternative.
["http.host"] = 3, // RemoteEndpoint.ServiceName for Http.
["db.instance"] = 4, // RemoteEndpoint.ServiceName for Redis.
};

private static readonly ConcurrentDictionary<string, ZipkinEndpoint> LocalEndpointCache = new ConcurrentDictionary<string, ZipkinEndpoint>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" Version="1.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Moq" Version="4.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
Expand Down
Loading