Skip to content

Commit 3692305

Browse files
authored
Merge pull request #1545 from mysql-net/tracing-options
Add options to configure tracing. Fixes #1524
2 parents 97b776b + bbd66bb commit 3692305

7 files changed

+108
-2
lines changed

src/MySqlConnector/Core/ResultSet.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ public async Task ReadResultSetHeaderAsync(IOBehavior ioBehavior)
166166
ContainsCommandParameters = true;
167167
WarningCount = 0;
168168
State = ResultSetState.ReadResultSetHeader;
169-
if (DataReader.Activity is { IsAllDataRequested: true })
169+
if (DataReader.Activity is { IsAllDataRequested: true } && (Command?.Connection!.MySqlDataSource?.TracingOptions.EnableResultSetHeaderEvent ?? MySqlConnectorTracingOptions.Default.EnableResultSetHeaderEvent))
170170
DataReader.Activity.AddEvent(new ActivityEvent("read-result-set-header"));
171171
break;
172172
}

src/MySqlConnector/MySqlConnection.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1138,6 +1138,8 @@ private async ValueTask<ServerSession> CreateSessionAsync(ConnectionPool? pool,
11381138

11391139
internal IPEndPoint? SessionEndPoint => m_session!.IPEndPoint;
11401140

1141+
internal MySqlDataSource? MySqlDataSource => m_dataSource;
1142+
11411143
internal void SetState(ConnectionState newState)
11421144
{
11431145
if (m_connectionState != newState)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace MySqlConnector;
2+
3+
internal sealed class MySqlConnectorTracingOptions
4+
{
5+
public bool EnableResultSetHeaderEvent { get; set; }
6+
7+
public static MySqlConnectorTracingOptions Default { get; } = new()
8+
{
9+
EnableResultSetHeaderEvent = true,
10+
};
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace MySqlConnector;
2+
3+
public sealed class MySqlConnectorTracingOptionsBuilder
4+
{
5+
/// <summary>
6+
/// Gets or sets a value indicating whether to enable the "time-to-first-read" event.
7+
/// Default is true to preserve existing behavior.
8+
/// </summary>
9+
public MySqlConnectorTracingOptionsBuilder EnableResultSetHeaderEvent(bool enable = true)
10+
{
11+
m_enableResultSetHeaderEvent = enable;
12+
return this;
13+
}
14+
15+
internal MySqlConnectorTracingOptions Build() =>
16+
new()
17+
{
18+
EnableResultSetHeaderEvent = m_enableResultSetHeaderEvent,
19+
};
20+
21+
private bool m_enableResultSetHeaderEvent = MySqlConnectorTracingOptions.Default.EnableResultSetHeaderEvent;
22+
}

src/MySqlConnector/MySqlDataSource.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ public sealed class MySqlDataSource : DbDataSource
1919
/// <param name="connectionString">The connection string for the MySQL Server. This parameter is required.</param>
2020
/// <exception cref="ArgumentNullException">Thrown if <paramref name="connectionString"/> is <c>null</c>.</exception>
2121
public MySqlDataSource(string connectionString)
22-
: this(connectionString ?? throw new ArgumentNullException(nameof(connectionString)), MySqlConnectorLoggingConfiguration.NullConfiguration, null, null, null, null, default, default, default, default)
22+
: this(connectionString ?? throw new ArgumentNullException(nameof(connectionString)), MySqlConnectorLoggingConfiguration.NullConfiguration, null, null, null, null, null, default, default, default, default)
2323
{
2424
}
2525

2626
internal MySqlDataSource(string connectionString,
2727
MySqlConnectorLoggingConfiguration loggingConfiguration,
28+
MySqlConnectorTracingOptions? tracingOptions,
2829
string? name,
2930
Func<X509CertificateCollection, ValueTask>? clientCertificatesCallback,
3031
RemoteCertificateValidationCallback? remoteCertificateValidationCallback,
@@ -36,6 +37,7 @@ internal MySqlDataSource(string connectionString,
3637
{
3738
m_connectionString = connectionString;
3839
LoggingConfiguration = loggingConfiguration;
40+
TracingOptions = tracingOptions ?? MySqlConnectorTracingOptions.Default;
3941
Name = name;
4042
m_clientCertificatesCallback = clientCertificatesCallback;
4143
m_remoteCertificateValidationCallback = remoteCertificateValidationCallback;
@@ -202,6 +204,8 @@ private async Task RefreshPassword()
202204

203205
internal MySqlConnectorLoggingConfiguration LoggingConfiguration { get; }
204206

207+
internal MySqlConnectorTracingOptions TracingOptions { get; }
208+
205209
internal string? Name { get; }
206210

207211
private string ProvidePasswordFromField(MySqlProvidePasswordContext context) => m_password!;

src/MySqlConnector/MySqlDataSourceBuilder.cs

+25
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,23 @@ public MySqlDataSourceBuilder(string? connectionString = null)
2121
ConnectionStringBuilder = new(connectionString ?? "");
2222
}
2323

24+
/// <summary>
25+
/// Configures OpenTelemetry tracing options.
26+
/// </summary>
27+
/// <returns>This builder, so that method calls can be chained.</returns>
28+
public MySqlDataSourceBuilder ConfigureTracing(Action<MySqlConnectorTracingOptionsBuilder> configureAction)
29+
{
30+
#if NET6_0_OR_GREATER
31+
ArgumentNullException.ThrowIfNull(configureAction);
32+
#else
33+
if (configureAction is null)
34+
throw new ArgumentNullException(nameof(configureAction));
35+
#endif
36+
m_tracingOptionsBuilderCallbacks ??= [];
37+
m_tracingOptionsBuilderCallbacks.Add(configureAction);
38+
return this;
39+
}
40+
2441
/// <summary>
2542
/// Sets the <see cref="ILoggerFactory"/> that will be used for logging.
2643
/// </summary>
@@ -107,8 +124,15 @@ public MySqlDataSourceBuilder UseConnectionOpenedCallback(MySqlConnectionOpenedC
107124
public MySqlDataSource Build()
108125
{
109126
var loggingConfiguration = m_loggerFactory is null ? MySqlConnectorLoggingConfiguration.NullConfiguration : new(m_loggerFactory);
127+
128+
var tracingOptionsBuilder = new MySqlConnectorTracingOptionsBuilder();
129+
foreach (var callback in m_tracingOptionsBuilderCallbacks ?? (IEnumerable<Action<MySqlConnectorTracingOptionsBuilder>>) [])
130+
callback.Invoke(tracingOptionsBuilder);
131+
var tracingOptions = tracingOptionsBuilder.Build();
132+
110133
return new(ConnectionStringBuilder.ConnectionString,
111134
loggingConfiguration,
135+
tracingOptions,
112136
m_name,
113137
m_clientCertificatesCallback,
114138
m_remoteCertificateValidationCallback,
@@ -135,4 +159,5 @@ public MySqlDataSource Build()
135159
private TimeSpan m_periodicPasswordProviderSuccessRefreshInterval;
136160
private TimeSpan m_periodicPasswordProviderFailureRefreshInterval;
137161
private MySqlConnectionOpenedCallback? m_connectionOpenedCallback;
162+
private List<Action<MySqlConnectorTracingOptionsBuilder>>? m_tracingOptionsBuilderCallbacks;
138163
}

tests/IntegrationTests/ActivityTests.cs

+42
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,48 @@ public void SelectTags()
140140
AssertTag(activity.Tags, "db.statement", "SELECT 1;");
141141
}
142142

143+
[Theory]
144+
[InlineData(true)]
145+
[InlineData(false)]
146+
public void ReadResultSetHeaderEvent(bool enableEvent)
147+
{
148+
var dataSourceBuilder = new MySqlDataSourceBuilder(AppConfig.ConnectionString)
149+
.ConfigureTracing(o => o.EnableResultSetHeaderEvent(enableEvent));
150+
using var dataSource = dataSourceBuilder.Build();
151+
using var connection = dataSource.OpenConnection();
152+
153+
using var parentActivity = new Activity(nameof(ReadResultSetHeaderEvent));
154+
parentActivity.Start();
155+
156+
Activity activity = null;
157+
using var listener = new ActivityListener
158+
{
159+
ShouldListenTo = x => x.Name == "MySqlConnector",
160+
Sample = (ref ActivityCreationOptions<ActivityContext> options) =>
161+
options.TraceId == parentActivity.TraceId ? ActivitySamplingResult.AllData : ActivitySamplingResult.None,
162+
ActivityStopped = x => activity = x,
163+
};
164+
ActivitySource.AddActivityListener(listener);
165+
166+
using (var command = new MySqlCommand("SELECT 1;", connection))
167+
{
168+
command.ExecuteScalar();
169+
}
170+
171+
Assert.NotNull(activity);
172+
Assert.Equal(ActivityKind.Client, activity.Kind);
173+
Assert.Equal("Execute", activity.OperationName);
174+
if (enableEvent)
175+
{
176+
var activityEvent = Assert.Single(activity.Events);
177+
Assert.Equal("read-result-set-header", activityEvent.Name);
178+
}
179+
else
180+
{
181+
Assert.Empty(activity.Events);
182+
}
183+
}
184+
143185
private void AssertTags(IEnumerable<KeyValuePair<string, string>> tags, MySqlConnectionStringBuilder csb)
144186
{
145187
AssertTag(tags, "db.system", "mysql");

0 commit comments

Comments
 (0)