Skip to content

Commit da08664

Browse files
author
Chao
committed
demo
1 parent 11d3518 commit da08664

File tree

6 files changed

+167
-0
lines changed

6 files changed

+167
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Build and Publish
2+
3+
on:
4+
pull_request:
5+
6+
jobs:
7+
build-and-publish:
8+
runs-on: ubuntu-latest
9+
permissions:
10+
contents: read
11+
packages: write
12+
steps:
13+
- name: Check out code
14+
uses: actions/checkout@v4 # v4.2.2
15+
16+
- name: Setup .NET
17+
uses: actions/setup-dotnet@v4 # v4.3.1
18+
with:
19+
dotnet-version: '9.0.x'
20+
21+
- name: Restore dependencies
22+
run: dotnet restore
23+
24+
- name: Build solution
25+
run: dotnet build --configuration Release --no-restore
26+
27+
- name: Publish to GitHub Packages
28+
run: |
29+
dotnet nuget add source --username dashbase --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/dashbase/index.json"
30+
dotnet pack --configuration Release || true
31+
dotnet nuget push 'src/OpenTelemetry.Instrumentation.SqlClient/bin/Release/OpenTelemetry.Instrumentation.SqlClient.0.0.0-alpha.0.nupkg' --source github

src/OpenTelemetry.Instrumentation.SqlClient/.publicApi/PublicAPI.Unshipped.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.Rec
99
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.SetDbStatementForText.get -> bool
1010
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.SetDbStatementForText.set -> void
1111
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.SqlClientTraceInstrumentationOptions() -> void
12+
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.ContextPropagationLevel.get -> string!
13+
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.ContextPropagationLevel.set -> void
14+
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.ServiceName.get -> string?
15+
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.ServiceName.set -> void
1216
OpenTelemetry.Metrics.SqlClientMeterProviderBuilderExtensions
1317
OpenTelemetry.Trace.TracerProviderBuilderExtensions
1418
static OpenTelemetry.Metrics.SqlClientMeterProviderBuilderExtensions.AddSqlClientInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder) -> OpenTelemetry.Metrics.MeterProviderBuilder!

src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientDiagnosticListener.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Diagnostics.CodeAnalysis;
99
#endif
1010
using System.Globalization;
11+
using System.Text;
1112
using OpenTelemetry.Trace;
1213

1314
namespace OpenTelemetry.Instrumentation.SqlClient.Implementation;
@@ -26,6 +27,9 @@ internal sealed class SqlClientDiagnosticListener : ListenerHandler
2627
public const string SqlDataWriteCommandError = "System.Data.SqlClient.WriteCommandError";
2728
public const string SqlMicrosoftWriteCommandError = "Microsoft.Data.SqlClient.WriteCommandError";
2829

30+
private const string ContextInfoParameterName = "@opentelemetry_traceparent";
31+
private const string SetContextSql = $"set context_info {ContextInfoParameterName}";
32+
2933
private readonly PropertyFetcher<object> commandFetcher = new("Command");
3034
private readonly PropertyFetcher<object> connectionFetcher = new("Connection");
3135
private readonly PropertyFetcher<string> dataSourceFetcher = new("DataSource");
@@ -64,6 +68,13 @@ public override void OnEventWritten(string name, object? payload)
6468
return;
6569
}
6670

71+
// skip if this is an injected query
72+
if (options.ContextPropagationLevel == SqlClientTraceInstrumentationOptions.ContextPropagationLevelTrace &&
73+
command is IDbCommand { CommandType: CommandType.Text, CommandText: SetContextSql })
74+
{
75+
return;
76+
}
77+
6778
_ = this.connectionFetcher.TryFetch(command, out var connection);
6879
_ = this.databaseFetcher.TryFetch(connection, out var databaseName);
6980
_ = this.dataSourceFetcher.TryFetch(connection, out var dataSource);
@@ -82,6 +93,25 @@ public override void OnEventWritten(string name, object? payload)
8293
return;
8394
}
8495

96+
if (options.ContextPropagationLevel == SqlClientTraceInstrumentationOptions.ContextPropagationLevelTrace &&
97+
command is IDbCommand { CommandType: CommandType.Text, Connection.State: ConnectionState.Open } iDbCommand)
98+
{
99+
var setContextCommand = iDbCommand.Connection.CreateCommand();
100+
setContextCommand.CommandText = SetContextSql;
101+
setContextCommand.CommandType = CommandType.Text;
102+
var parameter = setContextCommand.CreateParameter();
103+
parameter.ParameterName = ContextInfoParameterName;
104+
105+
var tracedflags = (activity.ActivityTraceFlags & ActivityTraceFlags.Recorded) != 0 ? "01" : "00";
106+
var traceparent = $"00-{activity.TraceId.ToHexString()}-{activity.SpanId.ToHexString()}-{tracedflags}";
107+
108+
parameter.DbType = DbType.Binary;
109+
parameter.Value = Encoding.UTF8.GetBytes(traceparent);
110+
setContextCommand.Parameters.Add(parameter);
111+
112+
setContextCommand.ExecuteNonQuery();
113+
}
114+
85115
if (activity.IsAllDataRequested)
86116
{
87117
try
@@ -125,6 +155,26 @@ public override void OnEventWritten(string name, object? payload)
125155
break;
126156

127157
case CommandType.Text:
158+
// This section is not for production use. DEMO only
159+
if (options.ContextPropagationLevel == SqlClientTraceInstrumentationOptions
160+
.ContextPropagationLevelTrace &&
161+
command is IDbCommand and not
162+
{
163+
CommandText: SetContextSql
164+
})
165+
{
166+
var idbCommand = (IDbCommand)command;
167+
168+
// TODO: this service name is not correct, to fix it with correct one.
169+
var serviceName = options.ServiceName ?? "UnknownService";
170+
Dictionary<string, string> params4 = new Dictionary<string, string> { { "service.name", serviceName } };
171+
string encoded = SqlCommenter.EncodeParams(params4);
172+
string comment = SqlCommenter.CreateComment(encoded);
173+
174+
// TODO: we need to check if the command text is already set with a comment.
175+
idbCommand.CommandText = $"{comment} {idbCommand.CommandText}";
176+
}
177+
128178
if (options.SetDbStatementForText)
129179
{
130180
var sqlStatementInfo = SqlProcessor.GetSanitizedSql(commandText);
@@ -168,6 +218,15 @@ public override void OnEventWritten(string name, object? payload)
168218
case SqlDataAfterExecuteCommand:
169219
case SqlMicrosoftAfterExecuteCommand:
170220
{
221+
_ = this.commandFetcher.TryFetch(payload, out var command);
222+
223+
// skip if this is an injected query
224+
if (options.ContextPropagationLevel == SqlClientTraceInstrumentationOptions.ContextPropagationLevelTrace &&
225+
command is IDbCommand { CommandType: CommandType.Text, CommandText: SetContextSql })
226+
{
227+
return;
228+
}
229+
171230
if (activity == null)
172231
{
173232
SqlClientInstrumentationEventSource.Log.NullActivity(name);
@@ -189,6 +248,15 @@ public override void OnEventWritten(string name, object? payload)
189248
case SqlDataWriteCommandError:
190249
case SqlMicrosoftWriteCommandError:
191250
{
251+
_ = this.commandFetcher.TryFetch(payload, out var command);
252+
253+
// skip if this is an injected query
254+
if (options.ContextPropagationLevel == SqlClientTraceInstrumentationOptions.ContextPropagationLevelTrace &&
255+
command is IDbCommand { CommandType: CommandType.Text, CommandText: SetContextSql })
256+
{
257+
return;
258+
}
259+
192260
if (activity == null)
193261
{
194262
SqlClientInstrumentationEventSource.Log.NullActivity(name);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System.Net;
5+
6+
namespace OpenTelemetry.Instrumentation.SqlClient.Implementation;
7+
8+
using System.Collections.Generic;
9+
10+
internal sealed class SqlCommenter
11+
{
12+
public static string EncodeParams(Dictionary<string, string> parameters)
13+
{
14+
List<string> encodedParams = new List<string>();
15+
16+
foreach (var kvp in parameters)
17+
{
18+
string? encodedKey = WebUtility.UrlEncode(kvp.Key);
19+
string? encodedValue = WebUtility.UrlEncode(kvp.Value);
20+
encodedParams.Add($"{encodedKey}='{encodedValue}'");
21+
}
22+
23+
return string.Join(",", encodedParams);
24+
}
25+
26+
public static string CreateComment(string encodedParams)
27+
{
28+
if (string.IsNullOrEmpty(encodedParams))
29+
{
30+
return string.Empty; // Or "/* */"
31+
}
32+
33+
return $"/*{encodedParams}*/";
34+
}
35+
}

src/OpenTelemetry.Instrumentation.SqlClient/SqlClientTraceInstrumentationOptions.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ namespace OpenTelemetry.Instrumentation.SqlClient;
1717
/// </remarks>
1818
public class SqlClientTraceInstrumentationOptions
1919
{
20+
/// <summary>
21+
/// Flag to send traceparent information to SQL Server.
22+
/// </summary>
23+
internal const string ContextPropagationLevelTrace = "trace";
24+
25+
/// <summary>
26+
/// Flag to disable sending trace information to SQL Server.
27+
/// </summary>
28+
internal const string ContextPropagationDisabled = "disabled";
29+
2030
/// <summary>
2131
/// Initializes a new instance of the <see cref="SqlClientTraceInstrumentationOptions"/> class.
2232
/// </summary>
@@ -30,6 +40,7 @@ internal SqlClientTraceInstrumentationOptions(IConfiguration configuration)
3040
var databaseSemanticConvention = GetSemanticConventionOptIn(configuration);
3141
this.EmitOldAttributes = databaseSemanticConvention.HasFlag(DatabaseSemanticConvention.Old);
3242
this.EmitNewAttributes = databaseSemanticConvention.HasFlag(DatabaseSemanticConvention.New);
43+
this.ContextPropagationLevel = ContextPropagationDisabled;
3344
}
3445

3546
/// <summary>
@@ -63,6 +74,23 @@ internal SqlClientTraceInstrumentationOptions(IConfiguration configuration)
6374
/// </remarks>
6475
public bool SetDbStatementForText { get; set; }
6576

77+
/// <summary>
78+
/// Gets or sets a value indicating whether to send trace information to SQL Server database.
79+
/// Optional values:
80+
/// <see langword="trace"/>:
81+
/// Send traceparent information to SQL Server.
82+
/// <see langword="disabled"/>:
83+
/// Disable sending trace information to SQL Server.
84+
/// Default value: <see landword="disabled"/>.
85+
/// </summary>
86+
public string ContextPropagationLevel { get; set; }
87+
88+
/// <summary>
89+
/// Gets or sets a service name we will attach to the query via SQL commenter,
90+
/// only works when <see cref="ContextPropagationLevel"/> is enabled.
91+
/// </summary>
92+
public string? ServiceName { get; set; }
93+
6694
/// <summary>
6795
/// Gets or sets an action to enrich an <see cref="Activity"/> with the
6896
/// raw <c>SqlCommand</c> object.

test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlClientIntegrationTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public void SuccessfulCommandTest(
5454
{
5555
options.SetDbStatementForText = captureTextCommandContent;
5656
options.RecordException = recordException;
57+
options.ContextPropagationLevel = SqlClientTraceInstrumentationOptions.ContextPropagationLevelTrace;
5758
if (shouldEnrich)
5859
{
5960
options.Enrich = SqlClientTests.ActivityEnrichment;

0 commit comments

Comments
 (0)