From 985f7e6dfa03206bd6b72c2918ca4b88a4c37984 Mon Sep 17 00:00:00 2001 From: Zach Montoya Date: Wed, 21 Jun 2023 08:30:05 -0700 Subject: [PATCH] Add initial MySql.Data instrumentation for the affected 8.0.33 nuget package where source instrumentation does not work --- .../ExecuteReaderAsyncIntegration.cs | 89 ++++++++++++++ .../MySqlData/IMySqlCommand.cs | 23 ++++ .../MySqlData/IMySqlConnection.cs | 22 ++++ .../IMySqlConnectionStringBuilder.cs | 28 +++++ .../MySqlData/MySqlDataCommon.cs | 111 ++++++++++++++++++ .../TestApplication.MySqlData.csproj | 4 +- 6 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/ExecuteReaderAsyncIntegration.cs create mode 100644 src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/IMySqlCommand.cs create mode 100644 src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/IMySqlConnection.cs create mode 100644 src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/IMySqlConnectionStringBuilder.cs create mode 100644 src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/MySqlDataCommon.cs diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/ExecuteReaderAsyncIntegration.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/ExecuteReaderAsyncIntegration.cs new file mode 100644 index 0000000000..aa0d3d096c --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/ExecuteReaderAsyncIntegration.cs @@ -0,0 +1,89 @@ +// +// Copyright The 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. +// + +#if NET6_0_OR_GREATER + +using System.Data; +using System.Threading; +using OpenTelemetry.AutoInstrumentation.CallTarget; +using OpenTelemetry.AutoInstrumentation.Util; + +namespace OpenTelemetry.AutoInstrumentation.Instrumentations.MySqlData; + +/// +/// MySql.Data.MySqlClient.MySqlCommand.ExecuteReaderAsyncIntegration calltarget instrumentation +/// +[InstrumentMethod( + assemblyName: "MySql.Data", + typeName: "MySql.Data.MySqlClient.MySqlCommand", + methodName: "ExecuteReaderAsync", + returnTypeName: "System.Threading.Tasks.Task`1", + parameterTypeNames: new[] { "System.Data.CommandBehavior", ClrNames.Bool, ClrNames.CancellationToken }, + minimumVersion: "8.0.33", + maximumVersion: "8.65535.65535", + integrationName: "MySqlData", + type: InstrumentationType.Trace)] +public static class ExecuteReaderAsyncIntegration +{ + /// + /// OnMethodBegin callback + /// + /// Type of the target + /// Instance value, aka `this` of the instrumented method. + /// The provided CommandBehavior value. + /// Indicates whether to run the query asynchronously + /// The provided CancellationToken value. + /// CallTarget state value + internal static CallTargetState OnMethodBegin(TTarget instance, CommandBehavior commandBehavior, bool execAsync, CancellationToken cancellationToken) + where TTarget : IMySqlCommand + { + return new CallTargetState(activity: MySqlDataCommon.CreateActivity(instance), state: instance!); + } + + /// + /// OnAsyncMethodEnd callback + /// + /// Type of the target + /// Type of the result value + /// Instance value, aka `this` of the instrumented method. + /// The result value. + /// Exception instance in case the original code threw an exception. + /// Calltarget state value + /// A response value, in an async scenario will be T of Task of T + internal static TResult OnAsyncMethodEnd(TTarget instance, TResult result, Exception? exception, in CallTargetState state) + { + var activity = state.Activity; + if (activity is null) + { + return result; + } + + try + { + if (exception != null) + { + activity.SetException(exception); + } + } + finally + { + activity.Dispose(); + } + + return result; + } +} +#endif diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/IMySqlCommand.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/IMySqlCommand.cs new file mode 100644 index 0000000000..7b7207d0ac --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/IMySqlCommand.cs @@ -0,0 +1,23 @@ +// +// Copyright The 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. +// + +using System.Data; + +namespace OpenTelemetry.AutoInstrumentation.Instrumentations.MySqlData; +internal interface IMySqlCommand +{ + IMySqlConnection? SqlConnection { get; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/IMySqlConnection.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/IMySqlConnection.cs new file mode 100644 index 0000000000..fa770a7a2e --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/IMySqlConnection.cs @@ -0,0 +1,22 @@ +// +// Copyright The 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. +// + +namespace OpenTelemetry.AutoInstrumentation.Instrumentations.MySqlData; + +internal interface IMySqlConnection +{ + IMySqlConnectionStringBuilder Settings { get; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/IMySqlConnectionStringBuilder.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/IMySqlConnectionStringBuilder.cs new file mode 100644 index 0000000000..211d50a320 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/IMySqlConnectionStringBuilder.cs @@ -0,0 +1,28 @@ +// +// Copyright The 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. +// + +namespace OpenTelemetry.AutoInstrumentation.Instrumentations.MySqlData; + +internal interface IMySqlConnectionStringBuilder +{ + string Database { get; } + + uint Port { get; } + + string Server { get; } + + string UserID { get; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/MySqlDataCommon.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/MySqlDataCommon.cs new file mode 100644 index 0000000000..b25b792215 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/MySqlData/MySqlDataCommon.cs @@ -0,0 +1,111 @@ +// +// Copyright The 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. +// + +using System.Data; +using System.Diagnostics; + +namespace OpenTelemetry.AutoInstrumentation.Instrumentations.MySqlData; + +internal class MySqlDataCommon +{ + internal const string DbName = "db.name"; + internal const string DbSystem = "db.system"; + internal const string DbStatement = "db.statement"; + internal const string DbUser = "db.user"; + + internal const string NetPeerIp = "net.peer.ip"; + internal const string NetPeerPort = "net.peer.port"; + internal const string NetPeerName = "net.peer.name"; + + internal const string PeerService = "peer.service"; + + internal const string MysqlDatabaseSystemName = "mysql"; + internal static readonly ActivitySource ActivitySource = new ActivitySource( + "OpenTelemetry.Instrumentation.MySqlData", Constants.Tracer.Version); // In the library, it uses the version of the instrumentation library, e.g. "1.0.0.7" + + internal static readonly IEnumerable> CreationTags = new[] + { + new KeyValuePair(DbSystem, MysqlDatabaseSystemName), + }; + + internal static Activity? CreateActivity(TCommand command) + where TCommand : IMySqlCommand + { + var activity = ActivitySource.StartActivity("OpenTelemetry.Instrumentation.MySqlData.Execute", ActivityKind.Client, Activity.Current?.Context ?? default, CreationTags); + if (activity is null) + { + return null; + } + + if (activity.IsAllDataRequested) + { + // Figure out how to get the options, if possible + /* + if (this.options.SetDbStatement) + { + activity.SetTag(DbStatement, command.CommandText); + } + */ + + if (command.SqlConnection?.Settings is not null) + { + activity.DisplayName = command.SqlConnection.Settings.Database; + activity.SetTag(DbName, command.SqlConnection.Settings.Database); + + AddConnectionLevelDetailsToActivity(command.SqlConnection.Settings, activity); + } + } + + return activity; + } + + internal static void StopActivity(Activity activity) + { + if (activity.Source != ActivitySource) + { + return; + } + + activity.Stop(); + } + + private static void AddConnectionLevelDetailsToActivity(IMySqlConnectionStringBuilder dataSource, Activity activity) + { + // Figure out how to get the options, if possible + /* + if (!this.options.EnableConnectionLevelAttributes) + { + activity.SetTag(PeerService, dataSource.Server); + } + else + { + var uriHostNameType = Uri.CheckHostName(dataSource.Server); + + if (uriHostNameType == UriHostNameType.IPv4 || uriHostNameType == UriHostNameType.IPv6) + { + activity.SetTag(NetPeerIp, dataSource.Server); + } + else + { + activity.SetTag(NetPeerName, dataSource.Server); + } + + activity.SetTag(NetPeerPort, dataSource.Port); + activity.SetTag(DbUser, dataSource.UserID); + } + */ + } +} diff --git a/test/test-applications/integrations/TestApplication.MySqlData/TestApplication.MySqlData.csproj b/test/test-applications/integrations/TestApplication.MySqlData/TestApplication.MySqlData.csproj index d45bc2664e..fe59d1b029 100644 --- a/test/test-applications/integrations/TestApplication.MySqlData/TestApplication.MySqlData.csproj +++ b/test/test-applications/integrations/TestApplication.MySqlData/TestApplication.MySqlData.csproj @@ -5,7 +5,9 @@ - + + +