From 3a56580fa4e5fb012cfcfe2a13236b14b690082e Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 20 Mar 2024 17:29:52 -0700 Subject: [PATCH 01/11] Remove dependency from OpenTelemetry.Instrumentation.SqlClient --- Directory.Build.props | 1 + Directory.Packages.props | 3 +- THIRD-PARTY-NOTICES.TXT | 17 + .../Aspire.Microsoft.Data.SqlClient.csproj | 8 +- ...osoft.EntityFrameworkCore.SqlServer.csproj | 6 +- .../GlobalSuppressions.cs | 11 + .../Implementation/SqlActivitySourceHelper.cs | 30 ++ .../SqlClientDiagnosticListener.cs | 206 ++++++++++++ .../SqlClientInstrumentationEventSource.cs | 85 +++++ .../SqlEventSourceListener.netfx.cs | 191 +++++++++++ .../SqlClientInstrumentation.cs | 72 +++++ .../SqlClientTraceInstrumentationOptions.cs | 303 ++++++++++++++++++ .../TracerProviderBuilderExtensions.cs | 82 +++++ .../DiagnosticSourceListener.cs | 49 +++ .../DiagnosticSourceSubscriber.cs | 105 ++++++ .../ListenerHandler.cs | 40 +++ .../PropertyFetcher.cs | 218 +++++++++++++ .../ExceptionExtensions.cs | 32 ++ src/Vendoring/OpenTelemetry.Shared/Guard.cs | 203 ++++++++++++ .../SemanticConventions.cs | 121 +++++++ src/Vendoring/README.md | 30 ++ 21 files changed, 1808 insertions(+), 5 deletions(-) create mode 100644 src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/GlobalSuppressions.cs create mode 100644 src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlActivitySourceHelper.cs create mode 100644 src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientDiagnosticListener.cs create mode 100644 src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientInstrumentationEventSource.cs create mode 100644 src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlEventSourceListener.netfx.cs create mode 100644 src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/SqlClientInstrumentation.cs create mode 100644 src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/SqlClientTraceInstrumentationOptions.cs create mode 100644 src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/TracerProviderBuilderExtensions.cs create mode 100644 src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/DiagnosticSourceListener.cs create mode 100644 src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/DiagnosticSourceSubscriber.cs create mode 100644 src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/ListenerHandler.cs create mode 100644 src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/PropertyFetcher.cs create mode 100644 src/Vendoring/OpenTelemetry.Shared/ExceptionExtensions.cs create mode 100644 src/Vendoring/OpenTelemetry.Shared/Guard.cs create mode 100644 src/Vendoring/OpenTelemetry.Shared/SemanticConventions.cs create mode 100644 src/Vendoring/README.md diff --git a/Directory.Build.props b/Directory.Build.props index 905d563de5..da9f230fce 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,6 +8,7 @@ MIT $(MSBuildThisFileDirectory)/src/Shared/ $(MSBuildThisFileDirectory)/tests/Shared/ + $(MSBuildThisFileDirectory)/src/Vendoring/ $(PackageIconFullPath) diff --git a/Directory.Packages.props b/Directory.Packages.props index c14ddfc084..4f86d4cd8c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -113,7 +113,6 @@ - @@ -143,4 +142,4 @@ - \ No newline at end of file + diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index d33d10864f..7b5fdf7df4 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -57,3 +57,20 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +License notice for OpenTelemetry .NET (https://github.com/open-telemetry/opentelemetry-dotnet) +---------------------------------------------------------------------------------------------- + +Copyright 2023 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. diff --git a/src/Components/Aspire.Microsoft.Data.SqlClient/Aspire.Microsoft.Data.SqlClient.csproj b/src/Components/Aspire.Microsoft.Data.SqlClient/Aspire.Microsoft.Data.SqlClient.csproj index aa88db8e37..e7145bc927 100644 --- a/src/Components/Aspire.Microsoft.Data.SqlClient/Aspire.Microsoft.Data.SqlClient.csproj +++ b/src/Components/Aspire.Microsoft.Data.SqlClient/Aspire.Microsoft.Data.SqlClient.csproj @@ -15,15 +15,19 @@ + + + + + - + - diff --git a/src/Components/Aspire.Microsoft.EntityFrameworkCore.SqlServer/Aspire.Microsoft.EntityFrameworkCore.SqlServer.csproj b/src/Components/Aspire.Microsoft.EntityFrameworkCore.SqlServer/Aspire.Microsoft.EntityFrameworkCore.SqlServer.csproj index 2e31191058..ed6d88bf28 100644 --- a/src/Components/Aspire.Microsoft.EntityFrameworkCore.SqlServer/Aspire.Microsoft.EntityFrameworkCore.SqlServer.csproj +++ b/src/Components/Aspire.Microsoft.EntityFrameworkCore.SqlServer/Aspire.Microsoft.EntityFrameworkCore.SqlServer.csproj @@ -16,13 +16,17 @@ + + + + + - diff --git a/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/GlobalSuppressions.cs b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/GlobalSuppressions.cs new file mode 100644 index 0000000000..3e28ac51f5 --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/GlobalSuppressions.cs @@ -0,0 +1,11 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0028:Simplify collection initialization", Justification = "Vendor code", Scope = "namespaceanddescendants", Target = "~N:OpenTelemetry.Instrumentation.SqlClient")] +[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Vendor code", Scope = "namespaceanddescendants", Target = "~N:OpenTelemetry.Instrumentation")] +[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Vendor code", Scope = "namespaceanddescendants", Target = "~N:OpenTelemetry.Instrumentation.SqlClient")] +[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Vendor code", Scope = "namespaceanddescendants", Target = "~N:OpenTelemetry.Instrumentation")] diff --git a/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlActivitySourceHelper.cs b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlActivitySourceHelper.cs new file mode 100644 index 0000000000..1c83e1e581 --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlActivitySourceHelper.cs @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#nullable disable + +using System.Diagnostics; +using System.Reflection; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Instrumentation.SqlClient.Implementation; + +/// +/// Helper class to hold common properties used by both SqlClientDiagnosticListener on .NET Core +/// and SqlEventSourceListener on .NET Framework. +/// +internal sealed class SqlActivitySourceHelper +{ + public const string MicrosoftSqlServerDatabaseSystemName = "mssql"; + + public static readonly AssemblyName AssemblyName = typeof(SqlActivitySourceHelper).Assembly.GetName(); + public static readonly string ActivitySourceName = AssemblyName.Name; + public static readonly Version Version = AssemblyName.Version; + public static readonly ActivitySource ActivitySource = new(ActivitySourceName, Version.ToString()); + public static readonly string ActivityName = ActivitySourceName + ".Execute"; + + public static readonly IEnumerable> CreationTags = new[] + { + new KeyValuePair(SemanticConventions.AttributeDbSystem, MicrosoftSqlServerDatabaseSystemName), + }; +} diff --git a/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientDiagnosticListener.cs b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientDiagnosticListener.cs new file mode 100644 index 0000000000..ad42830bb0 --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientDiagnosticListener.cs @@ -0,0 +1,206 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#nullable disable + +#if !NETFRAMEWORK +using System.Data; +using System.Diagnostics; +using OpenTelemetry.Trace; +#if NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif + +namespace OpenTelemetry.Instrumentation.SqlClient.Implementation; + +#if NET6_0_OR_GREATER +[RequiresUnreferencedCode(SqlClientInstrumentation.SqlClientTrimmingUnsupportedMessage)] +#endif +internal sealed class SqlClientDiagnosticListener : ListenerHandler +{ + public const string SqlDataBeforeExecuteCommand = "System.Data.SqlClient.WriteCommandBefore"; + public const string SqlMicrosoftBeforeExecuteCommand = "Microsoft.Data.SqlClient.WriteCommandBefore"; + + public const string SqlDataAfterExecuteCommand = "System.Data.SqlClient.WriteCommandAfter"; + public const string SqlMicrosoftAfterExecuteCommand = "Microsoft.Data.SqlClient.WriteCommandAfter"; + + public const string SqlDataWriteCommandError = "System.Data.SqlClient.WriteCommandError"; + public const string SqlMicrosoftWriteCommandError = "Microsoft.Data.SqlClient.WriteCommandError"; + + private readonly PropertyFetcher commandFetcher = new("Command"); + private readonly PropertyFetcher connectionFetcher = new("Connection"); + private readonly PropertyFetcher dataSourceFetcher = new("DataSource"); + private readonly PropertyFetcher databaseFetcher = new("Database"); + private readonly PropertyFetcher commandTypeFetcher = new("CommandType"); + private readonly PropertyFetcher commandTextFetcher = new("CommandText"); + private readonly PropertyFetcher exceptionFetcher = new("Exception"); + private readonly SqlClientTraceInstrumentationOptions options; + + public SqlClientDiagnosticListener(string sourceName, SqlClientTraceInstrumentationOptions options) + : base(sourceName) + { + this.options = options ?? new SqlClientTraceInstrumentationOptions(); + } + + public override bool SupportsNullActivity => true; + + public override void OnEventWritten(string name, object payload) + { + var activity = Activity.Current; + switch (name) + { + case SqlDataBeforeExecuteCommand: + case SqlMicrosoftBeforeExecuteCommand: + { + // SqlClient does not create an Activity. So the activity coming in here will be null or the root span. + activity = SqlActivitySourceHelper.ActivitySource.StartActivity( + SqlActivitySourceHelper.ActivityName, + ActivityKind.Client, + default(ActivityContext), + SqlActivitySourceHelper.CreationTags); + + if (activity == null) + { + // There is no listener or it decided not to sample the current request. + return; + } + + _ = this.commandFetcher.TryFetch(payload, out var command); + if (command == null) + { + SqlClientInstrumentationEventSource.Log.NullPayload(nameof(SqlClientDiagnosticListener), name); + activity.Stop(); + return; + } + + if (activity.IsAllDataRequested) + { + try + { + if (this.options.Filter?.Invoke(command) == false) + { + SqlClientInstrumentationEventSource.Log.CommandIsFilteredOut(activity.OperationName); + activity.IsAllDataRequested = false; + activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; + return; + } + } + catch (Exception ex) + { + SqlClientInstrumentationEventSource.Log.CommandFilterException(ex); + activity.IsAllDataRequested = false; + activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; + return; + } + + _ = this.connectionFetcher.TryFetch(command, out var connection); + _ = this.databaseFetcher.TryFetch(connection, out var database); + + activity.DisplayName = (string)database; + + _ = this.dataSourceFetcher.TryFetch(connection, out var dataSource); + _ = this.commandTextFetcher.TryFetch(command, out var commandText); + + activity.SetTag(SemanticConventions.AttributeDbName, (string)database); + + this.options.AddConnectionLevelDetailsToActivity((string)dataSource, activity); + + if (this.commandTypeFetcher.TryFetch(command, out CommandType commandType)) + { + switch (commandType) + { + case CommandType.StoredProcedure: + if (this.options.SetDbStatementForStoredProcedure) + { + activity.SetTag(SemanticConventions.AttributeDbStatement, (string)commandText); + } + + break; + + case CommandType.Text: + if (this.options.SetDbStatementForText) + { + activity.SetTag(SemanticConventions.AttributeDbStatement, (string)commandText); + } + + break; + + case CommandType.TableDirect: + break; + } + } + + try + { + this.options.Enrich?.Invoke(activity, "OnCustom", command); + } + catch (Exception ex) + { + SqlClientInstrumentationEventSource.Log.EnrichmentException(ex); + } + } + } + + break; + case SqlDataAfterExecuteCommand: + case SqlMicrosoftAfterExecuteCommand: + { + if (activity == null) + { + SqlClientInstrumentationEventSource.Log.NullActivity(name); + return; + } + + if (activity.Source != SqlActivitySourceHelper.ActivitySource) + { + return; + } + + activity.Stop(); + } + + break; + case SqlDataWriteCommandError: + case SqlMicrosoftWriteCommandError: + { + if (activity == null) + { + SqlClientInstrumentationEventSource.Log.NullActivity(name); + return; + } + + if (activity.Source != SqlActivitySourceHelper.ActivitySource) + { + return; + } + + try + { + if (activity.IsAllDataRequested) + { + if (this.exceptionFetcher.TryFetch(payload, out Exception exception) && exception != null) + { + activity.SetStatus(ActivityStatusCode.Error, exception.Message); + + if (this.options.RecordException) + { + activity.RecordException(exception); + } + } + else + { + SqlClientInstrumentationEventSource.Log.NullPayload(nameof(SqlClientDiagnosticListener), name); + } + } + } + finally + { + activity.Stop(); + } + } + + break; + } + } +} +#endif diff --git a/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientInstrumentationEventSource.cs b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientInstrumentationEventSource.cs new file mode 100644 index 0000000000..ccd86471d7 --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientInstrumentationEventSource.cs @@ -0,0 +1,85 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Diagnostics.Tracing; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Instrumentation.SqlClient.Implementation; + +/// +/// EventSource events emitted from the project. +/// +[EventSource(Name = "OpenTelemetry-Instrumentation-SqlClient")] +internal sealed class SqlClientInstrumentationEventSource : EventSource +{ + public static SqlClientInstrumentationEventSource Log = new(); + + [NonEvent] + public void UnknownErrorProcessingEvent(string handlerName, string eventName, Exception ex) + { + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) + { + this.UnknownErrorProcessingEvent(handlerName, eventName, ex.ToInvariantString()); + } + } + + [Event(1, Message = "Unknown error processing event '{1}' from handler '{0}', Exception: {2}", Level = EventLevel.Error)] + public void UnknownErrorProcessingEvent(string handlerName, string eventName, string ex) + { + this.WriteEvent(1, handlerName, eventName, ex); + } + + [Event(2, Message = "Current Activity is NULL in the '{0}' callback. Span will not be recorded.", Level = EventLevel.Warning)] + public void NullActivity(string eventName) + { + this.WriteEvent(2, eventName); + } + + [Event(3, Message = "Payload is NULL in event '{1}' from handler '{0}', span will not be recorded.", Level = EventLevel.Warning)] + public void NullPayload(string handlerName, string eventName) + { + this.WriteEvent(3, handlerName, eventName); + } + + [Event(4, Message = "Payload is invalid in event '{1}' from handler '{0}', span will not be recorded.", Level = EventLevel.Warning)] + public void InvalidPayload(string handlerName, string eventName) + { + this.WriteEvent(4, handlerName, eventName); + } + + [NonEvent] + public void EnrichmentException(Exception ex) + { + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) + { + this.EnrichmentException(ex.ToInvariantString()); + } + } + + [Event(5, Message = "Enrichment threw exception. Exception {0}.", Level = EventLevel.Error)] + public void EnrichmentException(string exception) + { + this.WriteEvent(5, exception); + } + + [Event(6, Message = "Command is filtered out. Activity {0}", Level = EventLevel.Verbose)] + public void CommandIsFilteredOut(string activityName) + { + this.WriteEvent(6, activityName); + } + + [NonEvent] + public void CommandFilterException(Exception ex) + { + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) + { + this.CommandFilterException(ex.ToInvariantString()); + } + } + + [Event(7, Message = "Command filter threw exception. Command will not be collected. Exception {0}.", Level = EventLevel.Error)] + public void CommandFilterException(string exception) + { + this.WriteEvent(7, exception); + } +} diff --git a/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlEventSourceListener.netfx.cs b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlEventSourceListener.netfx.cs new file mode 100644 index 0000000000..111e4878d3 --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlEventSourceListener.netfx.cs @@ -0,0 +1,191 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if NETFRAMEWORK +using System.Diagnostics; +using System.Diagnostics.Tracing; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Instrumentation.SqlClient.Implementation; + +/// +/// On .NET Framework, neither System.Data.SqlClient nor Microsoft.Data.SqlClient emit DiagnosticSource events. +/// Instead they use EventSource: +/// For System.Data.SqlClient see: reference source. +/// For Microsoft.Data.SqlClient see: SqlClientEventSource. +/// +/// We hook into these event sources and process their BeginExecute/EndExecute events. +/// +/// +/// Note that before version 2.0.0, Microsoft.Data.SqlClient used +/// "Microsoft-AdoNet-SystemData" (same as System.Data.SqlClient), but since +/// 2.0.0 has switched to "Microsoft.Data.SqlClient.EventSource". +/// +internal sealed class SqlEventSourceListener : EventListener +{ + internal const string AdoNetEventSourceName = "Microsoft-AdoNet-SystemData"; + internal const string MdsEventSourceName = "Microsoft.Data.SqlClient.EventSource"; + + internal const int BeginExecuteEventId = 1; + internal const int EndExecuteEventId = 2; + + private readonly SqlClientTraceInstrumentationOptions options; + private EventSource adoNetEventSource; + private EventSource mdsEventSource; + + public SqlEventSourceListener(SqlClientTraceInstrumentationOptions options = null) + { + this.options = options ?? new SqlClientTraceInstrumentationOptions(); + } + + public override void Dispose() + { + if (this.adoNetEventSource != null) + { + this.DisableEvents(this.adoNetEventSource); + } + + if (this.mdsEventSource != null) + { + this.DisableEvents(this.mdsEventSource); + } + + base.Dispose(); + } + + protected override void OnEventSourceCreated(EventSource eventSource) + { + if (eventSource?.Name.StartsWith(AdoNetEventSourceName, StringComparison.Ordinal) == true) + { + this.adoNetEventSource = eventSource; + this.EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All); + } + else if (eventSource?.Name.StartsWith(MdsEventSourceName, StringComparison.Ordinal) == true) + { + this.mdsEventSource = eventSource; + this.EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All); + } + + base.OnEventSourceCreated(eventSource); + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + try + { + if (eventData.EventId == BeginExecuteEventId) + { + this.OnBeginExecute(eventData); + } + else if (eventData.EventId == EndExecuteEventId) + { + this.OnEndExecute(eventData); + } + } + catch (Exception exc) + { + SqlClientInstrumentationEventSource.Log.UnknownErrorProcessingEvent(nameof(SqlEventSourceListener), nameof(this.OnEventWritten), exc); + } + } + + private void OnBeginExecute(EventWrittenEventArgs eventData) + { + /* + Expected payload: + [0] -> ObjectId + [1] -> DataSource + [2] -> Database + [3] -> CommandText + + Note: + - For "Microsoft-AdoNet-SystemData" v1.0: [3] CommandText = CommandType == CommandType.StoredProcedure ? CommandText : string.Empty; (so it is set for only StoredProcedure command types) + (https://github.com/dotnet/SqlClient/blob/v1.0.19239.1/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs#L6369) + - For "Microsoft-AdoNet-SystemData" v1.1: [3] CommandText = sqlCommand.CommandText (so it is set for all command types) + (https://github.com/dotnet/SqlClient/blob/v1.1.0/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs#L7459) + - For "Microsoft.Data.SqlClient.EventSource" v2.0+: [3] CommandText = sqlCommand.CommandText (so it is set for all command types). + (https://github.com/dotnet/SqlClient/blob/f4568ce68da21db3fe88c0e72e1287368aaa1dc8/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs#L6641) + */ + + if ((eventData?.Payload?.Count ?? 0) < 4) + { + SqlClientInstrumentationEventSource.Log.InvalidPayload(nameof(SqlEventSourceListener), nameof(this.OnBeginExecute)); + return; + } + + var activity = SqlActivitySourceHelper.ActivitySource.StartActivity( + SqlActivitySourceHelper.ActivityName, + ActivityKind.Client, + default(ActivityContext), + SqlActivitySourceHelper.CreationTags); + + if (activity == null) + { + // There is no listener or it decided not to sample the current request. + return; + } + + string databaseName = (string)eventData.Payload[2]; + + activity.DisplayName = databaseName; + + if (activity.IsAllDataRequested) + { + activity.SetTag(SemanticConventions.AttributeDbName, databaseName); + + this.options.AddConnectionLevelDetailsToActivity((string)eventData.Payload[1], activity); + + string commandText = (string)eventData.Payload[3]; + if (!string.IsNullOrEmpty(commandText) && this.options.SetDbStatementForText) + { + activity.SetTag(SemanticConventions.AttributeDbStatement, commandText); + } + } + } + + private void OnEndExecute(EventWrittenEventArgs eventData) + { + /* + Expected payload: + [0] -> ObjectId + [1] -> CompositeState bitmask (0b001 -> successFlag, 0b010 -> isSqlExceptionFlag , 0b100 -> synchronousFlag) + [2] -> SqlExceptionNumber + */ + + if ((eventData?.Payload?.Count ?? 0) < 3) + { + SqlClientInstrumentationEventSource.Log.InvalidPayload(nameof(SqlEventSourceListener), nameof(this.OnEndExecute)); + return; + } + + var activity = Activity.Current; + if (activity?.Source != SqlActivitySourceHelper.ActivitySource) + { + return; + } + + try + { + if (activity.IsAllDataRequested) + { + int compositeState = (int)eventData.Payload[1]; + if ((compositeState & 0b001) != 0b001) + { + if ((compositeState & 0b010) == 0b010) + { + var errorText = $"SqlExceptionNumber {eventData.Payload[2]} thrown."; + activity.SetStatus(ActivityStatusCode.Error, errorText); + } + else + { + activity.SetStatus(ActivityStatusCode.Error, "Unknown Sql failure."); + } + } + } + } + finally + { + activity.Stop(); + } + } +} +#endif diff --git a/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/SqlClientInstrumentation.cs b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/SqlClientInstrumentation.cs new file mode 100644 index 0000000000..3ba553e269 --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/SqlClientInstrumentation.cs @@ -0,0 +1,72 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#nullable disable + +#if NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +using OpenTelemetry.Instrumentation.SqlClient.Implementation; +#endif + +namespace OpenTelemetry.Instrumentation.SqlClient; + +/// +/// SqlClient instrumentation. +/// +internal sealed class SqlClientInstrumentation : IDisposable +{ + internal const string SqlClientDiagnosticListenerName = "SqlClientDiagnosticListener"; +#if NET6_0_OR_GREATER + internal const string SqlClientTrimmingUnsupportedMessage = "Trimming is not yet supported with SqlClient instrumentation."; +#endif +#if NETFRAMEWORK + private readonly SqlEventSourceListener sqlEventSourceListener; +#else + private static readonly HashSet DiagnosticSourceEvents = new() + { + "System.Data.SqlClient.WriteCommandBefore", + "Microsoft.Data.SqlClient.WriteCommandBefore", + "System.Data.SqlClient.WriteCommandAfter", + "Microsoft.Data.SqlClient.WriteCommandAfter", + "System.Data.SqlClient.WriteCommandError", + "Microsoft.Data.SqlClient.WriteCommandError", + }; + + private readonly Func isEnabled = (eventName, _, _) + => DiagnosticSourceEvents.Contains(eventName); + + private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber; +#endif + + /// + /// Initializes a new instance of the class. + /// + /// Configuration options for sql instrumentation. +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode(SqlClientTrimmingUnsupportedMessage)] +#endif + public SqlClientInstrumentation( + SqlClientTraceInstrumentationOptions options = null) + { +#if NETFRAMEWORK + this.sqlEventSourceListener = new SqlEventSourceListener(options); +#else + this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber( + name => new SqlClientDiagnosticListener(name, options), + listener => listener.Name == SqlClientDiagnosticListenerName, + this.isEnabled, + SqlClientInstrumentationEventSource.Log.UnknownErrorProcessingEvent); + this.diagnosticSourceSubscriber.Subscribe(); +#endif + } + + /// + public void Dispose() + { +#if NETFRAMEWORK + this.sqlEventSourceListener?.Dispose(); +#else + this.diagnosticSourceSubscriber?.Dispose(); +#endif + } +} diff --git a/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/SqlClientTraceInstrumentationOptions.cs b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/SqlClientTraceInstrumentationOptions.cs new file mode 100644 index 0000000000..831a0bd615 --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/SqlClientTraceInstrumentationOptions.cs @@ -0,0 +1,303 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +#nullable disable + +using System.Collections.Concurrent; +using System.Data; +using System.Diagnostics; +using System.Text.RegularExpressions; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Instrumentation.SqlClient; + +/// +/// Options for . +/// +/// +/// For help and examples see: . +/// +public class SqlClientTraceInstrumentationOptions +{ + /* + * Match... + * protocol[ ]:[ ]serverName + * serverName + * serverName[ ]\[ ]instanceName + * serverName[ ],[ ]port + * serverName[ ]\[ ]instanceName[ ],[ ]port + * + * [ ] can be any number of white-space, SQL allows it for some reason. + * + * Optional "protocol" can be "tcp", "lpc" (shared memory), or "np" (named pipes). See: + * https://docs.microsoft.com/troubleshoot/sql/connect/use-server-name-parameter-connection-string, and + * https://docs.microsoft.com/dotnet/api/system.data.sqlclient.sqlconnection.connectionstring?view=dotnet-plat-ext-5.0 + * + * In case of named pipes the Data Source string can take form of: + * np:serverName\instanceName, or + * np:\\serverName\pipe\pipeName, or + * np:\\serverName\pipe\MSSQL$instanceName\pipeName - in this case a separate regex (see NamedPipeRegex below) + * is used to extract instanceName + */ + private static readonly Regex DataSourceRegex = new("^(.*\\s*:\\s*\\\\{0,2})?(.*?)\\s*(?:[\\\\,]|$)\\s*(.*?)\\s*(?:,|$)\\s*(.*)$", RegexOptions.Compiled); + + /// + /// In a Data Source string like "np:\\serverName\pipe\MSSQL$instanceName\pipeName" match the + /// "pipe\MSSQL$instanceName" segment to extract instanceName if it is available. + /// + /// + /// + /// + private static readonly Regex NamedPipeRegex = new("pipe\\\\MSSQL\\$(.*?)\\\\", RegexOptions.Compiled); + + private static readonly ConcurrentDictionary ConnectionDetailCache = new(StringComparer.OrdinalIgnoreCase); + + /// + /// Gets or sets a value indicating whether or not the should add the names of commands as the tag. Default + /// value: . + /// + /// + /// SetDbStatementForStoredProcedure is only supported on .NET + /// and .NET Core runtimes. + /// + public bool SetDbStatementForStoredProcedure { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not the should add the text of commands as + /// the tag. + /// Default value: . + /// + /// + /// + /// WARNING: SetDbStatementForText will capture the raw + /// CommandText. Make sure your CommandText property never + /// contains any sensitive data. + /// + /// SetDbStatementForText is supported on all runtimes. + /// + /// On .NET and .NET Core SetDbStatementForText only applies to + /// SqlCommands with . + /// On .NET Framework SetDbStatementForText applies to all + /// SqlCommands regardless of . + /// + /// When using System.Data.SqlClient use + /// SetDbStatementForText to capture StoredProcedure command + /// names. + /// When using Microsoft.Data.SqlClient use + /// SetDbStatementForText to capture Text, StoredProcedure, and all + /// other command text. + /// + /// + /// + /// + public bool SetDbStatementForText { get; set; } + + /// + /// Gets or sets a value indicating whether or not the should parse the DataSource on a + /// SqlConnection into server name, instance name, and/or port + /// connection-level attribute tags. Default value: . + /// + /// + /// + /// EnableConnectionLevelAttributes is supported on all runtimes. + /// + /// + /// The default behavior is to set the SqlConnection DataSource as the tag. + /// If enabled, SqlConnection DataSource will be parsed and the server name will be sent as the + /// or tag, + /// the instance name will be sent as the tag, + /// and the port will be sent as the tag if it is not 1433 (the default port). + /// + /// + public bool EnableConnectionLevelAttributes { get; set; } + + /// + /// Gets or sets an action to enrich an with the + /// raw SqlCommand object. + /// + /// + /// Enrich is only executed on .NET and .NET Core + /// runtimes. + /// The parameters passed to the enrich action are: + /// + /// The being enriched. + /// The name of the event. Currently only "OnCustom" is + /// used but more events may be added in the future. + /// The raw SqlCommand object from which additional + /// information can be extracted to enrich the . + /// + /// + public Action Enrich { get; set; } + + /// + /// Gets or sets a filter function that determines whether or not to + /// collect telemetry about a command. + /// + /// + /// Filter is only executed on .NET and .NET Core + /// runtimes. + /// Notes: + /// + /// The first parameter passed to the filter function is the raw + /// SqlCommand object for the command being executed. + /// The return value for the filter function is interpreted as: + /// + /// If filter returns , the command is + /// collected. + /// If filter returns or throws an + /// exception the command is NOT collected. + /// + /// + /// + public Func Filter { get; set; } + + /// + /// Gets or sets a value indicating whether the exception will be + /// recorded as or not. Default value: . + /// + /// + /// RecordException is only supported on .NET and .NET Core + /// runtimes. + /// For specification details see: . + /// + public bool RecordException { get; set; } + + internal static SqlConnectionDetails ParseDataSource(string dataSource) + { + Match match = DataSourceRegex.Match(dataSource); + + string serverHostName = match.Groups[2].Value; + string serverIpAddress = null; + + string instanceName; + + var uriHostNameType = Uri.CheckHostName(serverHostName); + if (uriHostNameType == UriHostNameType.IPv4 || uriHostNameType == UriHostNameType.IPv6) + { + serverIpAddress = serverHostName; + serverHostName = null; + } + + string maybeProtocol = match.Groups[1].Value; + bool isNamedPipe = maybeProtocol.Length > 0 && + maybeProtocol.StartsWith("np", StringComparison.OrdinalIgnoreCase); + + if (isNamedPipe) + { + string pipeName = match.Groups[3].Value; + if (pipeName.Length > 0) + { + var namedInstancePipeMatch = NamedPipeRegex.Match(pipeName); + if (namedInstancePipeMatch.Success) + { + instanceName = namedInstancePipeMatch.Groups[1].Value; + return new SqlConnectionDetails + { + ServerHostName = serverHostName, + ServerIpAddress = serverIpAddress, + InstanceName = instanceName, + Port = null, + }; + } + } + + return new SqlConnectionDetails + { + ServerHostName = serverHostName, + ServerIpAddress = serverIpAddress, + InstanceName = null, + Port = null, + }; + } + + string port; + if (match.Groups[4].Length > 0) + { + instanceName = match.Groups[3].Value; + port = match.Groups[4].Value; + if (port == "1433") + { + port = null; + } + } + else if (int.TryParse(match.Groups[3].Value, out int parsedPort)) + { + port = parsedPort == 1433 ? null : match.Groups[3].Value; + instanceName = null; + } + else + { + instanceName = match.Groups[3].Value; + + if (string.IsNullOrEmpty(instanceName)) + { + instanceName = null; + } + + port = null; + } + + return new SqlConnectionDetails + { + ServerHostName = serverHostName, + ServerIpAddress = serverIpAddress, + InstanceName = instanceName, + Port = port, + }; + } + + internal void AddConnectionLevelDetailsToActivity(string dataSource, Activity sqlActivity) + { + if (!this.EnableConnectionLevelAttributes) + { + sqlActivity.SetTag(SemanticConventions.AttributePeerService, dataSource); + } + else + { + if (!ConnectionDetailCache.TryGetValue(dataSource, out SqlConnectionDetails connectionDetails)) + { + connectionDetails = ParseDataSource(dataSource); + ConnectionDetailCache.TryAdd(dataSource, connectionDetails); + } + + if (!string.IsNullOrEmpty(connectionDetails.InstanceName)) + { + sqlActivity.SetTag(SemanticConventions.AttributeDbMsSqlInstanceName, connectionDetails.InstanceName); + } + + if (!string.IsNullOrEmpty(connectionDetails.ServerHostName)) + { + sqlActivity.SetTag(SemanticConventions.AttributeServerAddress, connectionDetails.ServerHostName); + } + else + { + sqlActivity.SetTag(SemanticConventions.AttributeServerSocketAddress, connectionDetails.ServerIpAddress); + } + + if (!string.IsNullOrEmpty(connectionDetails.Port)) + { + // TODO: Should we continue to emit this if the default port (1433) is being used? + sqlActivity.SetTag(SemanticConventions.AttributeServerPort, connectionDetails.Port); + } + } + } + + internal sealed class SqlConnectionDetails + { + public string ServerHostName { get; set; } + + public string ServerIpAddress { get; set; } + + public string InstanceName { get; set; } + + public string Port { get; set; } + } +} diff --git a/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/TracerProviderBuilderExtensions.cs b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/TracerProviderBuilderExtensions.cs new file mode 100644 index 0000000000..8e3e7eedb0 --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/TracerProviderBuilderExtensions.cs @@ -0,0 +1,82 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +#nullable disable + +#if NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using OpenTelemetry.Instrumentation.SqlClient; +using OpenTelemetry.Instrumentation.SqlClient.Implementation; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Trace; + +/// +/// Extension methods to simplify registering of dependency instrumentation. +/// +public static class TracerProviderBuilderExtensions +{ + /// + /// Enables SqlClient instrumentation. + /// + /// being configured. + /// The instance of to chain the calls. +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode(SqlClientInstrumentation.SqlClientTrimmingUnsupportedMessage)] +#endif + public static TracerProviderBuilder AddSqlClientInstrumentation(this TracerProviderBuilder builder) + => AddSqlClientInstrumentation(builder, name: null, configureSqlClientTraceInstrumentationOptions: null); + + /// + /// Enables SqlClient instrumentation. + /// + /// being configured. + /// Callback action for configuring . + /// The instance of to chain the calls. +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode(SqlClientInstrumentation.SqlClientTrimmingUnsupportedMessage)] +#endif + public static TracerProviderBuilder AddSqlClientInstrumentation( + this TracerProviderBuilder builder, + Action configureSqlClientTraceInstrumentationOptions) + => AddSqlClientInstrumentation(builder, name: null, configureSqlClientTraceInstrumentationOptions); + + /// + /// Enables SqlClient instrumentation. + /// + /// being configured. + /// Name which is used when retrieving options. + /// Callback action for configuring . + /// The instance of to chain the calls. +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode(SqlClientInstrumentation.SqlClientTrimmingUnsupportedMessage)] +#endif + + public static TracerProviderBuilder AddSqlClientInstrumentation( + this TracerProviderBuilder builder, + string name, + Action configureSqlClientTraceInstrumentationOptions) + { + Guard.ThrowIfNull(builder); + + name ??= Options.DefaultName; + + if (configureSqlClientTraceInstrumentationOptions != null) + { + builder.ConfigureServices(services => services.Configure(name, configureSqlClientTraceInstrumentationOptions)); + } + + builder.AddInstrumentation(sp => + { + var sqlOptions = sp.GetRequiredService>().Get(name); + + return new SqlClientInstrumentation(sqlOptions); + }); + + builder.AddSource(SqlActivitySourceHelper.ActivitySourceName); + + return builder; + } +} diff --git a/src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/DiagnosticSourceListener.cs b/src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/DiagnosticSourceListener.cs new file mode 100644 index 0000000000..53d7e990d2 --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/DiagnosticSourceListener.cs @@ -0,0 +1,49 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#nullable disable + +using System.Diagnostics; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Instrumentation; + +internal sealed class DiagnosticSourceListener : IObserver> +{ + private readonly ListenerHandler handler; + + private readonly Action logUnknownException; + + public DiagnosticSourceListener(ListenerHandler handler, Action logUnknownException) + { + Guard.ThrowIfNull(handler); + + this.handler = handler; + this.logUnknownException = logUnknownException; + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(KeyValuePair value) + { + if (!this.handler.SupportsNullActivity && Activity.Current == null) + { + return; + } + + try + { + this.handler.OnEventWritten(value.Key, value.Value); + } + catch (Exception ex) + { + this.logUnknownException?.Invoke(this.handler?.SourceName, value.Key, ex); + } + } +} diff --git a/src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/DiagnosticSourceSubscriber.cs b/src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/DiagnosticSourceSubscriber.cs new file mode 100644 index 0000000000..49528fea47 --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/DiagnosticSourceSubscriber.cs @@ -0,0 +1,105 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#nullable disable + +using System.Diagnostics; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Instrumentation; + +internal sealed class DiagnosticSourceSubscriber : IDisposable, IObserver +{ + private readonly List listenerSubscriptions; + private readonly Func handlerFactory; + private readonly Func diagnosticSourceFilter; + private readonly Func isEnabledFilter; + private readonly Action logUnknownException; + private long disposed; + private IDisposable allSourcesSubscription; + + public DiagnosticSourceSubscriber( + ListenerHandler handler, + Func isEnabledFilter, + Action logUnknownException) + : this(_ => handler, value => handler.SourceName == value.Name, isEnabledFilter, logUnknownException) + { + } + + public DiagnosticSourceSubscriber( + Func handlerFactory, + Func diagnosticSourceFilter, + Func isEnabledFilter, + Action logUnknownException) + { + Guard.ThrowIfNull(handlerFactory); + + this.listenerSubscriptions = new List(); + this.handlerFactory = handlerFactory; + this.diagnosticSourceFilter = diagnosticSourceFilter; + this.isEnabledFilter = isEnabledFilter; + this.logUnknownException = logUnknownException; + } + + public void Subscribe() + { + if (this.allSourcesSubscription == null) + { + this.allSourcesSubscription = DiagnosticListener.AllListeners.Subscribe(this); + } + } + + public void OnNext(DiagnosticListener value) + { + if ((Interlocked.Read(ref this.disposed) == 0) && + this.diagnosticSourceFilter(value)) + { + var handler = this.handlerFactory(value.Name); + var listener = new DiagnosticSourceListener(handler, this.logUnknownException); + var subscription = this.isEnabledFilter == null ? + value.Subscribe(listener) : + value.Subscribe(listener, this.isEnabledFilter); + + lock (this.listenerSubscriptions) + { + this.listenerSubscriptions.Add(subscription); + } + } + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (Interlocked.CompareExchange(ref this.disposed, 1, 0) == 1) + { + return; + } + + lock (this.listenerSubscriptions) + { + foreach (var listenerSubscription in this.listenerSubscriptions) + { + listenerSubscription?.Dispose(); + } + + this.listenerSubscriptions.Clear(); + } + + this.allSourcesSubscription?.Dispose(); + this.allSourcesSubscription = null; + } +} diff --git a/src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/ListenerHandler.cs b/src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/ListenerHandler.cs new file mode 100644 index 0000000000..98c55107a6 --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/ListenerHandler.cs @@ -0,0 +1,40 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Diagnostics; + +namespace OpenTelemetry.Instrumentation; + +/// +/// ListenerHandler base class. +/// +internal abstract class ListenerHandler +{ + /// + /// Initializes a new instance of the class. + /// + /// The name of the . + public ListenerHandler(string sourceName) + { + this.SourceName = sourceName; + } + + /// + /// Gets the name of the . + /// + public string SourceName { get; } + + /// + /// Gets a value indicating whether the supports NULL . + /// + public virtual bool SupportsNullActivity { get; } + + /// + /// Method called for an event which does not have 'Start', 'Stop' or 'Exception' as suffix. + /// + /// Custom name. + /// An object that represent the value being passed as a payload for the event. + public virtual void OnEventWritten(string name, object payload) + { + } +} diff --git a/src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/PropertyFetcher.cs b/src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/PropertyFetcher.cs new file mode 100644 index 0000000000..a2ac797d8a --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Shared/DiagnosticSourceInstrumentation/PropertyFetcher.cs @@ -0,0 +1,218 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#nullable enable + +#if NETSTANDARD2_1_0_OR_GREATER || NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif +using System.Reflection; + +namespace OpenTelemetry.Instrumentation; + +/// +/// PropertyFetcher fetches a property from an object. +/// +/// The type of the property being fetched. +internal sealed class PropertyFetcher +{ +#if NET6_0_OR_GREATER + private const string TrimCompatibilityMessage = "PropertyFetcher is used to access properties on objects dynamically by design and cannot be made trim compatible."; +#endif + private readonly string propertyName; + private PropertyFetch? innerFetcher; + + /// + /// Initializes a new instance of the class. + /// + /// Property name to fetch. + public PropertyFetcher(string propertyName) + { + this.propertyName = propertyName; + } + + public int NumberOfInnerFetchers => this.innerFetcher == null + ? 0 + : 1 + this.innerFetcher.NumberOfInnerFetchers; + + /// + /// Try to fetch the property from the object. + /// + /// Object to be fetched. + /// Fetched value. + /// if the property was fetched. +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode(TrimCompatibilityMessage)] +#endif + public bool TryFetch( +#if NETSTANDARD2_1_0_OR_GREATER || NET6_0_OR_GREATER + [NotNullWhen(true)] +#endif + object? obj, + out T? value) + { + var innerFetcher = this.innerFetcher; + if (innerFetcher is null) + { + return TryFetchRare(obj, this.propertyName, ref this.innerFetcher, out value); + } + + return innerFetcher.TryFetch(obj, out value); + } + +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode(TrimCompatibilityMessage)] +#endif + private static bool TryFetchRare(object? obj, string propertyName, ref PropertyFetch? destination, out T? value) + { + if (obj is null) + { + value = default; + return false; + } + + var fetcher = PropertyFetch.Create(obj.GetType().GetTypeInfo(), propertyName); + + if (fetcher is null) + { + value = default; + return false; + } + + destination = fetcher; + + return fetcher.TryFetch(obj, out value); + } + + // see https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode(TrimCompatibilityMessage)] +#endif + private abstract class PropertyFetch + { + public abstract int NumberOfInnerFetchers { get; } + + public static PropertyFetch? Create(TypeInfo type, string propertyName) + { + var property = type.DeclaredProperties.FirstOrDefault(p => string.Equals(p.Name, propertyName, StringComparison.OrdinalIgnoreCase)) ?? type.GetProperty(propertyName); + return CreateFetcherForProperty(property); + + static PropertyFetch? CreateFetcherForProperty(PropertyInfo? propertyInfo) + { + if (propertyInfo == null || !typeof(T).IsAssignableFrom(propertyInfo.PropertyType)) + { + // returns null and wait for a valid payload to arrive. + return null; + } + + var declaringType = propertyInfo.DeclaringType; + if (declaringType!.IsValueType) + { + throw new NotSupportedException( + $"Type: {declaringType.FullName} is a value type. PropertyFetcher can only operate on reference payload types."); + } + + if (declaringType == typeof(object)) + { + // TODO: REMOVE this if branch when .NET 7 is out of support. + // This branch is never executed and is only needed for .NET 7 AOT-compiler at trimming stage; i.e., + // this is not needed in .NET 8, because the compiler is improved and call into MakeGenericMethod will be AOT-compatible. + // It is used to force the AOT compiler to create an instantiation of the method with a reference type. + // The code for that instantiation can then be reused at runtime to create instantiation over any other reference. + return CreateInstantiated(propertyInfo); + } + else + { + return DynamicInstantiationHelper(declaringType, propertyInfo); + } + + // Separated as a local function to be able to target the suppression to just this call. + // IL3050 was generated here because of the call to MakeGenericType, which is problematic in AOT if one of the type parameters is a value type; + // because the compiler might need to generate code specific to that type. + // If the type parameter is a reference type, there will be no problem; because the generated code can be shared among all reference type instantiations. +#if NET6_0_OR_GREATER + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "The code guarantees that all the generic parameters are reference types.")] +#endif + static PropertyFetch? DynamicInstantiationHelper(Type declaringType, PropertyInfo propertyInfo) + { + return (PropertyFetch?)typeof(PropertyFetch) + .GetMethod(nameof(CreateInstantiated), BindingFlags.NonPublic | BindingFlags.Static)! + .MakeGenericMethod(declaringType) // This is validated in the earlier call chain to be a reference type. + .Invoke(null, new object[] { propertyInfo })!; + } + } + } + + public abstract bool TryFetch( +#if NETSTANDARD2_1_0_OR_GREATER || NET6_0_OR_GREATER + [NotNullWhen(true)] +#endif + object? obj, + out T? value); + + // Goal: make PropertyFetcher AOT-compatible. + // AOT compiler can't guarantee correctness when call into MakeGenericType or MakeGenericMethod + // if one of the generic parameters is a value type (reference types are OK.) + // For PropertyFetcher, the decision was made to only support reference type payloads, i.e.: + // the object from which to get the property value MUST be a reference type. + // Create generics with the declared object type as a generic parameter is OK, but we need the return type + // of the property to be a value type (on top of reference types.) + // Normally, we would have a helper class like `PropertyFetchInstantiated` that takes 2 generic parameters, + // the declared object type, and the type of the property value. + // But that would mean calling MakeGenericType, with value type parameters which AOT won't support. + // + // As a workaround, Generic instantiation was split into: + // 1. The object type comes from the PropertyFetcher generic parameter. + // Compiler supports it even if it is a value type; the type is known statically during compilation + // since PropertyFetcher is used with it. + // 2. Then, the declared object type is passed as a generic parameter to a generic method on PropertyFetcher (or nested type.) + // Therefore, calling into MakeGenericMethod will only require specifying one parameter - the declared object type. + // The declared object type is guaranteed to be a reference type (throw on value type.) Thus, MakeGenericMethod is AOT compatible. + private static PropertyFetch CreateInstantiated(PropertyInfo propertyInfo) + where TDeclaredObject : class + => new PropertyFetchInstantiated(propertyInfo); + +#if NET6_0_OR_GREATER + [RequiresUnreferencedCode(TrimCompatibilityMessage)] +#endif + private sealed class PropertyFetchInstantiated : PropertyFetch + where TDeclaredObject : class + { + private readonly string propertyName; + private readonly Func propertyFetch; + private PropertyFetch? innerFetcher; + + public PropertyFetchInstantiated(PropertyInfo property) + { + this.propertyName = property.Name; + this.propertyFetch = (Func)property.GetMethod!.CreateDelegate(typeof(Func)); + } + + public override int NumberOfInnerFetchers => this.innerFetcher == null + ? 0 + : 1 + this.innerFetcher.NumberOfInnerFetchers; + + public override bool TryFetch( +#if NETSTANDARD2_1_0_OR_GREATER || NET6_0_OR_GREATER + [NotNullWhen(true)] +#endif + object? obj, + out T? value) + { + if (obj is TDeclaredObject o) + { + value = this.propertyFetch(o); + return true; + } + + var innerFetcher = this.innerFetcher; + if (innerFetcher is null) + { + return TryFetchRare(obj, this.propertyName, ref this.innerFetcher, out value); + } + + return innerFetcher.TryFetch(obj, out value); + } + } + } +} diff --git a/src/Vendoring/OpenTelemetry.Shared/ExceptionExtensions.cs b/src/Vendoring/OpenTelemetry.Shared/ExceptionExtensions.cs new file mode 100644 index 0000000000..9070b59c20 --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Shared/ExceptionExtensions.cs @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#nullable enable + +using System.Globalization; + +namespace OpenTelemetry.Internal; + +internal static class ExceptionExtensions +{ + /// + /// Returns a culture-independent string representation of the given object, + /// appropriate for diagnostics tracing. + /// + /// Exception to convert to string. + /// Exception as string with no culture. + public static string ToInvariantString(this Exception exception) + { + var originalUICulture = Thread.CurrentThread.CurrentUICulture; + + try + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; + return exception.ToString(); + } + finally + { + Thread.CurrentThread.CurrentUICulture = originalUICulture; + } + } +} diff --git a/src/Vendoring/OpenTelemetry.Shared/Guard.cs b/src/Vendoring/OpenTelemetry.Shared/Guard.cs new file mode 100644 index 0000000000..e768d67604 --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Shared/Guard.cs @@ -0,0 +1,203 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#nullable enable + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Runtime.CompilerServices; + +#pragma warning disable SA1402 // File may only contain a single type +#pragma warning disable SA1403 // File may only contain a single namespace +#pragma warning disable SA1649 // File name should match first type name + +#if !NET6_0_OR_GREATER +namespace System.Runtime.CompilerServices +{ + /// Allows capturing of the expressions passed to a method. + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] + internal sealed class CallerArgumentExpressionAttribute : Attribute + { + public CallerArgumentExpressionAttribute(string parameterName) + { + this.ParameterName = parameterName; + } + + public string ParameterName { get; } + } +} +#endif + +#if !NET6_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER +namespace System.Diagnostics.CodeAnalysis +{ + /// Specifies that an output is not even if + /// the corresponding type allows it. Specifies that an input argument was + /// not when the call returns. + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class NotNullAttribute : Attribute + { + } +} +#endif + +#pragma warning disable IDE0161 // Convert to file-scoped namespace +namespace OpenTelemetry.Internal +{ + /// + /// Methods for guarding against exception throwing values. + /// + internal static class Guard + { + /// + /// Throw an exception if the value is null. + /// + /// The value to check. + /// The parameter name to use in the thrown exception. + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ThrowIfNull([NotNull] object? value, [CallerArgumentExpression("value")] string? paramName = null) + { + if (value is null) + { + throw new ArgumentNullException(paramName, "Must not be null"); + } + } + + /// + /// Throw an exception if the value is null or empty. + /// + /// The value to check. + /// The parameter name to use in the thrown exception. + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ThrowIfNullOrEmpty([NotNull] string? value, [CallerArgumentExpression("value")] string? paramName = null) +#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException("Must not be null or empty", paramName); + } + } +#pragma warning restore CS8777 // Parameter must have a non-null value when exiting. + + /// + /// Throw an exception if the value is null or whitespace. + /// + /// The value to check. + /// The parameter name to use in the thrown exception. + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ThrowIfNullOrWhitespace([NotNull] string? value, [CallerArgumentExpression("value")] string? paramName = null) +#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("Must not be null or whitespace", paramName); + } + } +#pragma warning restore CS8777 // Parameter must have a non-null value when exiting. + + /// + /// Throw an exception if the value is zero. + /// + /// The value to check. + /// The message to use in the thrown exception. + /// The parameter name to use in the thrown exception. + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ThrowIfZero(int value, string message = "Must not be zero", [CallerArgumentExpression("value")] string? paramName = null) + { + if (value == 0) + { + throw new ArgumentException(message, paramName); + } + } + + /// + /// Throw an exception if the value is not considered a valid timeout. + /// + /// The value to check. + /// The parameter name to use in the thrown exception. + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ThrowIfInvalidTimeout(int value, [CallerArgumentExpression("value")] string? paramName = null) + { + ThrowIfOutOfRange(value, paramName, min: Timeout.Infinite, message: $"Must be non-negative or '{nameof(Timeout)}.{nameof(Timeout.Infinite)}'"); + } + + /// + /// Throw an exception if the value is not within the given range. + /// + /// The value to check. + /// The parameter name to use in the thrown exception. + /// The inclusive lower bound. + /// The inclusive upper bound. + /// The name of the lower bound. + /// The name of the upper bound. + /// An optional custom message to use in the thrown exception. + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ThrowIfOutOfRange(int value, [CallerArgumentExpression("value")] string? paramName = null, int min = int.MinValue, int max = int.MaxValue, string? minName = null, string? maxName = null, string? message = null) + { + Range(value, paramName, min, max, minName, maxName, message); + } + + /// + /// Throw an exception if the value is not within the given range. + /// + /// The value to check. + /// The parameter name to use in the thrown exception. + /// The inclusive lower bound. + /// The inclusive upper bound. + /// The name of the lower bound. + /// The name of the upper bound. + /// An optional custom message to use in the thrown exception. + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ThrowIfOutOfRange(double value, [CallerArgumentExpression("value")] string? paramName = null, double min = double.MinValue, double max = double.MaxValue, string? minName = null, string? maxName = null, string? message = null) + { + Range(value, paramName, min, max, minName, maxName, message); + } + + /// + /// Throw an exception if the value is not of the expected type. + /// + /// The value to check. + /// The parameter name to use in the thrown exception. + /// The type attempted to convert to. + /// The value casted to the specified type. + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T ThrowIfNotOfType([NotNull] object? value, [CallerArgumentExpression("value")] string? paramName = null) + { + if (value is not T result) + { + throw new InvalidCastException($"Cannot cast '{paramName}' from '{value?.GetType().ToString() ?? "null"}' to '{typeof(T)}'"); + } + + return result; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Range(T value, string? paramName, T min, T max, string? minName, string? maxName, string? message) + where T : IComparable + { + if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0) + { + var minMessage = minName != null ? $": {minName}" : string.Empty; + var maxMessage = maxName != null ? $": {maxName}" : string.Empty; + var exMessage = message ?? string.Format( + CultureInfo.InvariantCulture, + "Must be in the range: [{0}{1}, {2}{3}]", + min, + minMessage, + max, + maxMessage); + throw new ArgumentOutOfRangeException(paramName, value, exMessage); + } + } + } +} diff --git a/src/Vendoring/OpenTelemetry.Shared/SemanticConventions.cs b/src/Vendoring/OpenTelemetry.Shared/SemanticConventions.cs new file mode 100644 index 0000000000..8628b79d23 --- /dev/null +++ b/src/Vendoring/OpenTelemetry.Shared/SemanticConventions.cs @@ -0,0 +1,121 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#nullable enable + +namespace OpenTelemetry.Trace; + +/// +/// Constants for semantic attribute names outlined by the OpenTelemetry specifications. +/// and +/// . +/// +internal static class SemanticConventions +{ + // The set of constants matches the specification as of this commit. + // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/general/trace.md + // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/exceptions/exceptions-spans.md + public const string AttributeNetTransport = "net.transport"; + public const string AttributeNetPeerIp = "net.peer.ip"; + public const string AttributeNetPeerPort = "net.peer.port"; + public const string AttributeNetPeerName = "net.peer.name"; + public const string AttributeNetHostIp = "net.host.ip"; + public const string AttributeNetHostPort = "net.host.port"; + public const string AttributeNetHostName = "net.host.name"; + + public const string AttributeEnduserId = "enduser.id"; + public const string AttributeEnduserRole = "enduser.role"; + public const string AttributeEnduserScope = "enduser.scope"; + + public const string AttributePeerService = "peer.service"; + + public const string AttributeHttpMethod = "http.method"; + public const string AttributeHttpUrl = "http.url"; + public const string AttributeHttpTarget = "http.target"; + public const string AttributeHttpHost = "http.host"; + public const string AttributeHttpScheme = "http.scheme"; + public const string AttributeHttpStatusCode = "http.status_code"; + public const string AttributeHttpStatusText = "http.status_text"; + public const string AttributeHttpFlavor = "http.flavor"; + public const string AttributeHttpServerName = "http.server_name"; + public const string AttributeHttpRoute = "http.route"; + public const string AttributeHttpClientIP = "http.client_ip"; + public const string AttributeHttpUserAgent = "http.user_agent"; + public const string AttributeHttpRequestContentLength = "http.request_content_length"; + public const string AttributeHttpRequestContentLengthUncompressed = "http.request_content_length_uncompressed"; + public const string AttributeHttpResponseContentLength = "http.response_content_length"; + public const string AttributeHttpResponseContentLengthUncompressed = "http.response_content_length_uncompressed"; + + public const string AttributeDbSystem = "db.system"; + public const string AttributeDbConnectionString = "db.connection_string"; + public const string AttributeDbUser = "db.user"; + public const string AttributeDbMsSqlInstanceName = "db.mssql.instance_name"; + public const string AttributeDbJdbcDriverClassName = "db.jdbc.driver_classname"; + public const string AttributeDbName = "db.name"; + public const string AttributeDbStatement = "db.statement"; + public const string AttributeDbOperation = "db.operation"; + public const string AttributeDbInstance = "db.instance"; + public const string AttributeDbUrl = "db.url"; + public const string AttributeDbCassandraKeyspace = "db.cassandra.keyspace"; + public const string AttributeDbHBaseNamespace = "db.hbase.namespace"; + public const string AttributeDbRedisDatabaseIndex = "db.redis.database_index"; + public const string AttributeDbMongoDbCollection = "db.mongodb.collection"; + + public const string AttributeRpcSystem = "rpc.system"; + public const string AttributeRpcService = "rpc.service"; + public const string AttributeRpcMethod = "rpc.method"; + public const string AttributeRpcGrpcStatusCode = "rpc.grpc.status_code"; + + public const string AttributeMessageType = "message.type"; + public const string AttributeMessageId = "message.id"; + public const string AttributeMessageCompressedSize = "message.compressed_size"; + public const string AttributeMessageUncompressedSize = "message.uncompressed_size"; + + public const string AttributeFaasTrigger = "faas.trigger"; + public const string AttributeFaasExecution = "faas.execution"; + public const string AttributeFaasDocumentCollection = "faas.document.collection"; + public const string AttributeFaasDocumentOperation = "faas.document.operation"; + public const string AttributeFaasDocumentTime = "faas.document.time"; + public const string AttributeFaasDocumentName = "faas.document.name"; + public const string AttributeFaasTime = "faas.time"; + public const string AttributeFaasCron = "faas.cron"; + + public const string AttributeMessagingSystem = "messaging.system"; + public const string AttributeMessagingDestination = "messaging.destination"; + public const string AttributeMessagingDestinationKind = "messaging.destination_kind"; + public const string AttributeMessagingTempDestination = "messaging.temp_destination"; + public const string AttributeMessagingProtocol = "messaging.protocol"; + public const string AttributeMessagingProtocolVersion = "messaging.protocol_version"; + public const string AttributeMessagingUrl = "messaging.url"; + public const string AttributeMessagingMessageId = "messaging.message_id"; + public const string AttributeMessagingConversationId = "messaging.conversation_id"; + public const string AttributeMessagingPayloadSize = "messaging.message_payload_size_bytes"; + public const string AttributeMessagingPayloadCompressedSize = "messaging.message_payload_compressed_size_bytes"; + public const string AttributeMessagingOperation = "messaging.operation"; + + public const string AttributeExceptionEventName = "exception"; + public const string AttributeExceptionType = "exception.type"; + public const string AttributeExceptionMessage = "exception.message"; + public const string AttributeExceptionStacktrace = "exception.stacktrace"; + public const string AttributeErrorType = "error.type"; + + // v1.21.0 + // https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md + // https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/database/database-spans.md + // https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/rpc/rpc-spans.md + public const string AttributeClientAddress = "client.address"; + public const string AttributeClientPort = "client.port"; + public const string AttributeHttpRequestMethod = "http.request.method"; // replaces: "http.method" (AttributeHttpMethod) + public const string AttributeHttpResponseStatusCode = "http.response.status_code"; // replaces: "http.status_code" (AttributeHttpStatusCode) + public const string AttributeNetworkProtocolVersion = "network.protocol.version"; // replaces: "http.flavor" (AttributeHttpFlavor) + public const string AttributeNetworkProtocolName = "network.protocol.name"; + public const string AttributeServerAddress = "server.address"; // replaces: "net.host.name" (AttributeNetHostName) and "net.peer.name" (AttributeNetPeerName) + public const string AttributeServerPort = "server.port"; // replaces: "net.host.port" (AttributeNetHostPort) and "net.peer.port" (AttributeNetPeerPort) + public const string AttributeServerSocketAddress = "server.socket.address"; // replaces: "net.peer.ip" (AttributeNetPeerIp) + public const string AttributeUrlFull = "url.full"; // replaces: "http.url" (AttributeHttpUrl) + public const string AttributeUrlPath = "url.path"; // replaces: "http.target" (AttributeHttpTarget) + public const string AttributeUrlScheme = "url.scheme"; // replaces: "http.scheme" (AttributeHttpScheme) + public const string AttributeUrlQuery = "url.query"; + public const string AttributeUserAgentOriginal = "user_agent.original"; // replaces: "http.user_agent" (AttributeHttpUserAgent) + public const string AttributeHttpRequestMethodOriginal = "http.request.method_original"; +} diff --git a/src/Vendoring/README.md b/src/Vendoring/README.md new file mode 100644 index 0000000000..b830ab9610 --- /dev/null +++ b/src/Vendoring/README.md @@ -0,0 +1,30 @@ +# OpenTelemetry.Instrumentation.Share + +``` +git clone https://github.com/open-telemetry/opentelemetry-dotnet.git +``` + +## Instructions + +- Copy required files from `src/Shared`: + - `DiagnosticSourceInstrumentation\*.cs` + - `ExceptionExtensions.cs` + - `Guard.cs` + - `SemanticConventions.cs` + +# OpenTelemetry.Instrumentation.SqlClient + +``` +git clone https://github.com/open-telemetry/opentelemetry-dotnet.git +``` + +## Instructions + +- Copy files from `src/OpenTelemetry.Instrumentation.SqlClient`: + - `OpenTelemetry.Instrumentation.SqlClient.csproj` + - `**\*.cs` + +## Customizations + +- Added `#nullable disable` in files that require it. +- Added `GlobalSuppressions.cs` to fix incompatible coding style. From 55fa1afb40afa2dd87e45774e216458a80d8712f Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 20 Mar 2024 17:33:13 -0700 Subject: [PATCH 02/11] Fix markdown --- src/Vendoring/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Vendoring/README.md b/src/Vendoring/README.md index b830ab9610..f0dad06c18 100644 --- a/src/Vendoring/README.md +++ b/src/Vendoring/README.md @@ -1,10 +1,10 @@ -# OpenTelemetry.Instrumentation.Share +## OpenTelemetry.Instrumentation.Share -``` +```console git clone https://github.com/open-telemetry/opentelemetry-dotnet.git ``` -## Instructions +### Instructions - Copy required files from `src/Shared`: - `DiagnosticSourceInstrumentation\*.cs` @@ -12,19 +12,19 @@ git clone https://github.com/open-telemetry/opentelemetry-dotnet.git - `Guard.cs` - `SemanticConventions.cs` -# OpenTelemetry.Instrumentation.SqlClient +## OpenTelemetry.Instrumentation.SqlClient -``` +```console git clone https://github.com/open-telemetry/opentelemetry-dotnet.git ``` -## Instructions +### Instructions - Copy files from `src/OpenTelemetry.Instrumentation.SqlClient`: - `OpenTelemetry.Instrumentation.SqlClient.csproj` - `**\*.cs` -## Customizations +### Customizations - Added `#nullable disable` in files that require it. - Added `GlobalSuppressions.cs` to fix incompatible coding style. From bcd43bf5d6211c8573289acf3c388e9bbe4fdc79 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 20 Mar 2024 17:34:30 -0700 Subject: [PATCH 03/11] Fix markdown --- src/Vendoring/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Vendoring/README.md b/src/Vendoring/README.md index f0dad06c18..dfbe49322e 100644 --- a/src/Vendoring/README.md +++ b/src/Vendoring/README.md @@ -1,3 +1,5 @@ +# Vendoring code sync instructions + ## OpenTelemetry.Instrumentation.Share ```console From f6ce499752fdc7f1a52e84a157d587f2dca6ba6e Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 20 Mar 2024 21:28:57 -0700 Subject: [PATCH 04/11] Update vendoring README [ci-skip] --- src/Vendoring/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Vendoring/README.md b/src/Vendoring/README.md index dfbe49322e..30a2b127d0 100644 --- a/src/Vendoring/README.md +++ b/src/Vendoring/README.md @@ -1,6 +1,6 @@ # Vendoring code sync instructions -## OpenTelemetry.Instrumentation.Share +## OpenTelemetry.Shared ```console git clone https://github.com/open-telemetry/opentelemetry-dotnet.git From 7432141d448ff6124a2438401ed1c22b65b5d457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Thu, 21 Mar 2024 11:50:26 -0700 Subject: [PATCH 05/11] Update THIRD-PARTY-NOTICES.TXT Co-authored-by: Reiley Yang --- THIRD-PARTY-NOTICES.TXT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index 7b5fdf7df4..b346117dc1 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -58,7 +58,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -License notice for OpenTelemetry .NET (https://github.com/open-telemetry/opentelemetry-dotnet) +License notice for OpenTelemetry ---------------------------------------------------------------------------------------------- Copyright 2023 OpenTelemetry Authors From 56fac8069aa10ef74c0f0444efe8a576a2820d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Thu, 21 Mar 2024 11:52:24 -0700 Subject: [PATCH 06/11] Update THIRD-PARTY-NOTICES.TXT Co-authored-by: Reiley Yang --- THIRD-PARTY-NOTICES.TXT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index b346117dc1..671b0fed04 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -61,7 +61,7 @@ THE SOFTWARE. License notice for OpenTelemetry ---------------------------------------------------------------------------------------------- -Copyright 2023 OpenTelemetry Authors +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. From ca09144e7192199dfeff3720f19d4e2d484d847b Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Thu, 21 Mar 2024 12:20:25 -0700 Subject: [PATCH 07/11] Use .editorconfig for vendor files --- src/Vendoring/.editorconfig | 17 +++++++++++++++++ .../GlobalSuppressions.cs | 11 ----------- 2 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 src/Vendoring/.editorconfig delete mode 100644 src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/GlobalSuppressions.cs diff --git a/src/Vendoring/.editorconfig b/src/Vendoring/.editorconfig new file mode 100644 index 0000000000..495740bf14 --- /dev/null +++ b/src/Vendoring/.editorconfig @@ -0,0 +1,17 @@ +[*.{cs,vb}] + +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = silent + +# IDE1006: Required naming style +dotnet_diagnostic.IDE1006.severity = silent + +# IDE0028: Use collection initializers +dotnet_diagnostic.IDE0028.severity = silent + +# CA1852: Seal internal types +dotnet_diagnostic.CA1852.severity = silent + +# IDE0060: Remove unused parameters +dotnet_diagnostic.IDE0060.severity = silent + diff --git a/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/GlobalSuppressions.cs b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/GlobalSuppressions.cs deleted file mode 100644 index 3e28ac51f5..0000000000 --- a/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/GlobalSuppressions.cs +++ /dev/null @@ -1,11 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("Style", "IDE0028:Simplify collection initialization", Justification = "Vendor code", Scope = "namespaceanddescendants", Target = "~N:OpenTelemetry.Instrumentation.SqlClient")] -[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Vendor code", Scope = "namespaceanddescendants", Target = "~N:OpenTelemetry.Instrumentation")] -[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Vendor code", Scope = "namespaceanddescendants", Target = "~N:OpenTelemetry.Instrumentation.SqlClient")] -[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Vendor code", Scope = "namespaceanddescendants", Target = "~N:OpenTelemetry.Instrumentation")] From febe05f1a702c151b8199523f237e0a34d3776a9 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Thu, 21 Mar 2024 12:20:36 -0700 Subject: [PATCH 08/11] Change public classes to internal --- .../SqlClientTraceInstrumentationOptions.cs | 2 +- .../TracerProviderBuilderExtensions.cs | 2 +- src/Vendoring/README.md | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/SqlClientTraceInstrumentationOptions.cs b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/SqlClientTraceInstrumentationOptions.cs index 831a0bd615..7f62149b14 100644 --- a/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/SqlClientTraceInstrumentationOptions.cs +++ b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/SqlClientTraceInstrumentationOptions.cs @@ -16,7 +16,7 @@ namespace OpenTelemetry.Instrumentation.SqlClient; /// /// For help and examples see: . /// -public class SqlClientTraceInstrumentationOptions +internal class SqlClientTraceInstrumentationOptions { /* * Match... diff --git a/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/TracerProviderBuilderExtensions.cs b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/TracerProviderBuilderExtensions.cs index 8e3e7eedb0..c316d65080 100644 --- a/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/TracerProviderBuilderExtensions.cs +++ b/src/Vendoring/OpenTelemetry.Instrumentation.SqlClient/TracerProviderBuilderExtensions.cs @@ -16,7 +16,7 @@ namespace OpenTelemetry.Trace; /// /// Extension methods to simplify registering of dependency instrumentation. /// -public static class TracerProviderBuilderExtensions +internal static class TracerProviderBuilderExtensions { /// /// Enables SqlClient instrumentation. diff --git a/src/Vendoring/README.md b/src/Vendoring/README.md index 30a2b127d0..3fc2e64f90 100644 --- a/src/Vendoring/README.md +++ b/src/Vendoring/README.md @@ -30,3 +30,4 @@ git clone https://github.com/open-telemetry/opentelemetry-dotnet.git - Added `#nullable disable` in files that require it. - Added `GlobalSuppressions.cs` to fix incompatible coding style. +- Mark all imported classes as internal. From 6f0becd128b17c77430392515952fa6141677527 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Thu, 21 Mar 2024 12:45:29 -0700 Subject: [PATCH 09/11] Missing instruction [ci-skip] --- src/Vendoring/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Vendoring/README.md b/src/Vendoring/README.md index 3fc2e64f90..6818037f2b 100644 --- a/src/Vendoring/README.md +++ b/src/Vendoring/README.md @@ -30,4 +30,4 @@ git clone https://github.com/open-telemetry/opentelemetry-dotnet.git - Added `#nullable disable` in files that require it. - Added `GlobalSuppressions.cs` to fix incompatible coding style. -- Mark all imported classes as internal. +- Change all `public` classes to `internal`. From c80468faca2e3d67d29102d630ecc3221141f334 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Thu, 21 Mar 2024 21:32:38 -0700 Subject: [PATCH 10/11] Fix package dependencies --- .../Aspire.Microsoft.Data.SqlClient.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Aspire.Microsoft.Data.SqlClient/Aspire.Microsoft.Data.SqlClient.csproj b/src/Components/Aspire.Microsoft.Data.SqlClient/Aspire.Microsoft.Data.SqlClient.csproj index e7145bc927..725bf65956 100644 --- a/src/Components/Aspire.Microsoft.Data.SqlClient/Aspire.Microsoft.Data.SqlClient.csproj +++ b/src/Components/Aspire.Microsoft.Data.SqlClient/Aspire.Microsoft.Data.SqlClient.csproj @@ -22,10 +22,10 @@ + - From 9b4f0eb14f9802bf97843a3fd2a06c0106fc5224 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 22 Mar 2024 09:57:14 -0700 Subject: [PATCH 11/11] Cleanup --- .../Aspire.Microsoft.Data.SqlClient.csproj | 2 +- ...Aspire.Microsoft.EntityFrameworkCore.SqlServer.csproj | 2 +- src/Vendoring/README.md | 9 ++++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Components/Aspire.Microsoft.Data.SqlClient/Aspire.Microsoft.Data.SqlClient.csproj b/src/Components/Aspire.Microsoft.Data.SqlClient/Aspire.Microsoft.Data.SqlClient.csproj index 725bf65956..b65ab77d88 100644 --- a/src/Components/Aspire.Microsoft.Data.SqlClient/Aspire.Microsoft.Data.SqlClient.csproj +++ b/src/Components/Aspire.Microsoft.Data.SqlClient/Aspire.Microsoft.Data.SqlClient.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/Components/Aspire.Microsoft.EntityFrameworkCore.SqlServer/Aspire.Microsoft.EntityFrameworkCore.SqlServer.csproj b/src/Components/Aspire.Microsoft.EntityFrameworkCore.SqlServer/Aspire.Microsoft.EntityFrameworkCore.SqlServer.csproj index ed6d88bf28..754cfa9da7 100644 --- a/src/Components/Aspire.Microsoft.EntityFrameworkCore.SqlServer/Aspire.Microsoft.EntityFrameworkCore.SqlServer.csproj +++ b/src/Components/Aspire.Microsoft.EntityFrameworkCore.SqlServer/Aspire.Microsoft.EntityFrameworkCore.SqlServer.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Vendoring/README.md b/src/Vendoring/README.md index 6818037f2b..5316257037 100644 --- a/src/Vendoring/README.md +++ b/src/Vendoring/README.md @@ -4,6 +4,8 @@ ```console git clone https://github.com/open-telemetry/opentelemetry-dotnet.git +git fetch --tags +git checkout tags/Instrumentation.SqlClient-1.7.0-beta.1 ``` ### Instructions @@ -18,16 +20,17 @@ git clone https://github.com/open-telemetry/opentelemetry-dotnet.git ```console git clone https://github.com/open-telemetry/opentelemetry-dotnet.git +git fetch --tags +git checkout tags/Instrumentation.SqlClient-1.7.0-beta.1 ``` ### Instructions - Copy files from `src/OpenTelemetry.Instrumentation.SqlClient`: - - `OpenTelemetry.Instrumentation.SqlClient.csproj` - `**\*.cs` ### Customizations -- Added `#nullable disable` in files that require it. -- Added `GlobalSuppressions.cs` to fix incompatible coding style. +- Add `#nullable disable` in files that require it. - Change all `public` classes to `internal`. +- Update `src/Vendoring/.editorconfig` with the required exemptions.