From a9ccb14c934edb5b404b40507340c495cc682199 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:28:31 +0000 Subject: [PATCH 1/9] [Instrumentation.EntityFrameworkCore] Update few attributes to align with latest Semantic Conventions --- .../EntityFrameworkInstrumentationOptions.cs | 4 ++-- .../EntityFrameworkDiagnosticListener.cs | 21 ++++++++++--------- .../EntityFrameworkDiagnosticListenerTests.cs | 6 +++--- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/EntityFrameworkInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/EntityFrameworkInstrumentationOptions.cs index 04b9cabf4a..0f92a2a95d 100644 --- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/EntityFrameworkInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/EntityFrameworkInstrumentationOptions.cs @@ -12,12 +12,12 @@ namespace OpenTelemetry.Instrumentation.EntityFrameworkCore; public class EntityFrameworkInstrumentationOptions { /// - /// Gets or sets a value indicating whether or not the should add the names of commands as the tag. Default value: True. + /// Gets or sets a value indicating whether or not the should add the names of commands as the tag. Default value: True. /// 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: False. + /// Gets or sets a value indicating whether or not the should add the text of commands as the tag. Default value: False. /// public bool SetDbStatementForText { get; set; } diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/Implementation/EntityFrameworkDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/Implementation/EntityFrameworkDiagnosticListener.cs index 6f04b69f86..ebb26c585d 100644 --- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/Implementation/EntityFrameworkDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/Implementation/EntityFrameworkDiagnosticListener.cs @@ -18,10 +18,10 @@ internal sealed class EntityFrameworkDiagnosticListener : ListenerHandler internal const string EntityFrameworkCoreCommandExecuted = "Microsoft.EntityFrameworkCore.Database.Command.CommandExecuted"; internal const string EntityFrameworkCoreCommandError = "Microsoft.EntityFrameworkCore.Database.Command.CommandError"; - internal const string AttributePeerService = "peer.service"; + internal const string AttributeServerAddress = "server.address"; internal const string AttributeDbSystem = "db.system"; - internal const string AttributeDbName = "db.name"; - internal const string AttributeDbStatement = "db.statement"; + internal const string AttributeDbNamespace = "db.namespace"; + internal const string AttributeDbQueryText = "db.query.text"; internal static readonly Assembly Assembly = typeof(EntityFrameworkDiagnosticListener).Assembly; internal static readonly string ActivitySourceName = Assembly.GetName().Name; @@ -33,7 +33,7 @@ internal sealed class EntityFrameworkDiagnosticListener : ListenerHandler private readonly PropertyFetcher dbContextFetcher = new("Context"); private readonly PropertyFetcher dbContextDatabaseFetcher = new("Database"); private readonly PropertyFetcher providerNameFetcher = new("ProviderName"); - private readonly PropertyFetcher dataSourceFetcher = new("DataSource"); + private readonly PropertyFetcher hostFetcher = new("Host"); private readonly PropertyFetcher databaseFetcher = new("Database"); private readonly PropertyFetcher commandTypeFetcher = new("CommandType"); private readonly PropertyFetcher commandTextFetcher = new("CommandText"); @@ -138,11 +138,12 @@ public override void OnEventWritten(string name, object? payload) break; } - var dataSource = (string)this.dataSourceFetcher.Fetch(connection); - activity.AddTag(AttributeDbName, database); - if (!string.IsNullOrEmpty(dataSource)) + activity.AddTag(AttributeDbNamespace, database); + + var host = (string)this.hostFetcher.Fetch(connection); + if (!string.IsNullOrEmpty(host)) { - activity.AddTag(AttributePeerService, dataSource); + activity.AddTag(AttributeServerAddress, host); } } } @@ -196,7 +197,7 @@ public override void OnEventWritten(string name, object? payload) case CommandType.StoredProcedure: if (this.options.SetDbStatementForStoredProcedure) { - activity.AddTag(AttributeDbStatement, commandText); + activity.AddTag(AttributeDbQueryText, commandText); } break; @@ -204,7 +205,7 @@ public override void OnEventWritten(string name, object? payload) case CommandType.Text: if (this.options.SetDbStatementForText) { - activity.AddTag(AttributeDbStatement, commandText); + activity.AddTag(AttributeDbQueryText, commandText); } break; diff --git a/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/EntityFrameworkDiagnosticListenerTests.cs b/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/EntityFrameworkDiagnosticListenerTests.cs index 30accde1dd..46cf9c06fc 100644 --- a/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/EntityFrameworkDiagnosticListenerTests.cs +++ b/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/EntityFrameworkDiagnosticListenerTests.cs @@ -66,7 +66,7 @@ public void EntityFrameworkEnrichDisplayNameWithEnrichWithIDbCommand() { var stateDisplayName = $"{command.CommandType} main"; activity1.DisplayName = stateDisplayName; - activity1.SetTag("db.name", stateDisplayName); + activity1.SetTag("db.namespace", stateDisplayName); }; }).Build(); @@ -235,9 +235,9 @@ private static void VerifyActivityData(Activity activity, bool isError = false, Assert.Equal("sqlite", activity.Tags.FirstOrDefault(t => t.Key == EntityFrameworkDiagnosticListener.AttributeDbSystem).Value); // TBD: SqlLite not setting the DataSource so it doesn't get set. - Assert.DoesNotContain(activity.Tags, t => t.Key == EntityFrameworkDiagnosticListener.AttributePeerService); + Assert.DoesNotContain(activity.Tags, t => t.Key == EntityFrameworkDiagnosticListener.AttributeServerAddress); - Assert.Equal(altDisplayName ?? "main", activity.Tags.FirstOrDefault(t => t.Key == EntityFrameworkDiagnosticListener.AttributeDbName).Value); + Assert.Equal(altDisplayName ?? "main", activity.Tags.FirstOrDefault(t => t.Key == EntityFrameworkDiagnosticListener.AttributeDbNamespace).Value); if (!isError) { From 7a838c6b0f7fdf96657186ddd74bdbfdf6733031 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:34:31 +0000 Subject: [PATCH 2/9] Add changelog entry --- .../CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md index e1c2828603..edf7180e85 100644 --- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +* Some attributes have been updated to align with + the latest version of Semantic Conventions: + - Rename `db.name` to `db.namespace` + - Rename `db.statement` to `db.query.text` + - Rename `peer.service` to `server.address` + ([#2130](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2130)) + ## 1.0.0-beta.12 Released 2024-Jun-18 From 858946eb2d36eb2a0bea949639dad4c5b709e12a Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:58:35 +0000 Subject: [PATCH 3/9] Update changelog --- .../CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md index edf7180e85..6b5f5492c5 100644 --- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md @@ -4,9 +4,9 @@ * Some attributes have been updated to align with the latest version of Semantic Conventions: - - Rename `db.name` to `db.namespace` - - Rename `db.statement` to `db.query.text` - - Rename `peer.service` to `server.address` + - Rename `db.name` to `db.namespace` + - Rename `db.statement` to `db.query.text` + - Rename `peer.service` to `server.address` ([#2130](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2130)) ## 1.0.0-beta.12 From bcc0bfa9db61950636e623dac9327225cf30ec9b Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:34:43 +0000 Subject: [PATCH 4/9] Update changelog --- .../CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md index 6b5f5492c5..915422fa5b 100644 --- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md @@ -4,16 +4,16 @@ * Some attributes have been updated to align with the latest version of Semantic Conventions: - - Rename `db.name` to `db.namespace` - - Rename `db.statement` to `db.query.text` - - Rename `peer.service` to `server.address` + * Rename `db.name` to `db.namespace` + * Rename `db.statement` to `db.query.text` + * Rename `peer.service` to `server.address` ([#2130](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2130)) ## 1.0.0-beta.12 Released 2024-Jun-18 -* Update `Microsoft.Extensions.Options` to `8.0.0`. +* Update `Microsoft.Extensio²ns.Options` to `8.0.0`. ([#1830](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1830)) * Updated OpenTelemetry core component version(s) to `1.9.0`. From 5540065c72df08c02913c0ceb7776533d243bc2c Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:38:48 +0000 Subject: [PATCH 5/9] Update changelog --- .../CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md index 915422fa5b..18478eecf5 100644 --- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md @@ -13,7 +13,7 @@ Released 2024-Jun-18 -* Update `Microsoft.Extensio²ns.Options` to `8.0.0`. +* Update `Microsoft.Extensions.Options` to `8.0.0`. ([#1830](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1830)) * Updated OpenTelemetry core component version(s) to `1.9.0`. From fa9a1e673daa9345b54bdc5c7d515c0a9148f309 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:40:39 +0000 Subject: [PATCH 6/9] Update changelog --- .../CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md index 18478eecf5..51eebe548e 100644 --- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md @@ -4,9 +4,9 @@ * Some attributes have been updated to align with the latest version of Semantic Conventions: - * Rename `db.name` to `db.namespace` - * Rename `db.statement` to `db.query.text` - * Rename `peer.service` to `server.address` + * Rename `db.name` to `db.namespace` + * Rename `db.statement` to `db.query.text` + * Rename `peer.service` to `server.address` ([#2130](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2130)) ## 1.0.0-beta.12 From 646c90934b2539ad1f3dbdb623fd2e95cca12ea8 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Mon, 14 Oct 2024 10:15:17 +0000 Subject: [PATCH 7/9] Allow users to opt-in for new attributes --- opentelemetry-dotnet-contrib.sln | 1 + .../CHANGELOG.md | 25 ++++-- .../EntityFrameworkInstrumentationOptions.cs | 31 ++++++- .../EntityFrameworkDiagnosticListener.cs | 41 +++++++-- ...Instrumentation.EntityFrameworkCore.csproj | 6 +- .../DatabaseSemanticConventionHelper.cs | 83 +++++++++++++++++++ .../DatabaseSemanticConventionHelperTests.cs | 79 ++++++++++++++++++ .../EntityFrameworkDiagnosticListenerTests.cs | 50 ++++++++++- ...EntityFrameworkInstrumentationOptionsTests | 42 ++++++++++ 9 files changed, 339 insertions(+), 19 deletions(-) create mode 100644 src/Shared/DatabaseSemanticConventionHelper.cs create mode 100644 test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/DatabaseSemanticConventionHelperTests.cs create mode 100644 test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/EntityFrameworkInstrumentationOptionsTests diff --git a/opentelemetry-dotnet-contrib.sln b/opentelemetry-dotnet-contrib.sln index b4f5fc258c..49bcf2e393 100644 --- a/opentelemetry-dotnet-contrib.sln +++ b/opentelemetry-dotnet-contrib.sln @@ -239,6 +239,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1FCC8E ProjectSection(SolutionItems) = preProject src\Shared\ActivityInstrumentationHelper.cs = src\Shared\ActivityInstrumentationHelper.cs src\Shared\AssemblyVersionExtensions.cs = src\Shared\AssemblyVersionExtensions.cs + src\Shared\DatabaseSemanticConventionHelper.cs = src\Shared\DatabaseSemanticConventionHelper.cs src\Shared\DiagnosticSourceListener.cs = src\Shared\DiagnosticSourceListener.cs src\Shared\DiagnosticSourceSubscriber.cs = src\Shared\DiagnosticSourceSubscriber.cs src\Shared\ExceptionExtensions.cs = src\Shared\ExceptionExtensions.cs diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md index 51eebe548e..2c8fc2c1de 100644 --- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md @@ -2,11 +2,26 @@ ## Unreleased -* Some attributes have been updated to align with - the latest version of Semantic Conventions: - * Rename `db.name` to `db.namespace` - * Rename `db.statement` to `db.query.text` - * Rename `peer.service` to `server.address` +* The new database semantic conventions can be opted in to by setting + the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable. This allows for a + transition period for users to experiment with the new semantic conventions + and adapt as necessary. The environment variable supports the following + values: + * `database` - emit the new, frozen (proposed for stable) database + attributes, and stop emitting the old experimental database + attributes that the instrumentation emitted previously. + * `database/dup` - emit both the old and the frozen (proposed for stable) database + attributes, allowing for a more seamless transition. + * The default behavior (in the absence of one of these values) is to continue + emitting the same database semantic conventions that were emitted in + the previous version. + * Note: this option will be be removed after the new database + semantic conventions is marked stable. At which time this + instrumentation can receive a stable release, and the old database + semantic conventions will no longer be supported. Refer to the + specification for more information regarding the new database + semantic conventions for + [spans](https://github.com/open-telemetry/semantic-conventions/blob/v1.28.0/docs/database/database-spans.md). ([#2130](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2130)) ## 1.0.0-beta.12 diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/EntityFrameworkInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/EntityFrameworkInstrumentationOptions.cs index 0f92a2a95d..60bf2b527d 100644 --- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/EntityFrameworkInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/EntityFrameworkInstrumentationOptions.cs @@ -3,6 +3,8 @@ using System.Data; using System.Diagnostics; +using Microsoft.Extensions.Configuration; +using static OpenTelemetry.Internal.DatabaseSemanticConventionHelper; namespace OpenTelemetry.Instrumentation.EntityFrameworkCore; @@ -12,12 +14,27 @@ namespace OpenTelemetry.Instrumentation.EntityFrameworkCore; public class EntityFrameworkInstrumentationOptions { /// - /// Gets or sets a value indicating whether or not the should add the names of commands as the tag. Default value: True. + /// Initializes a new instance of the class. + /// + public EntityFrameworkInstrumentationOptions() + : this(new ConfigurationBuilder().AddEnvironmentVariables().Build()) + { + } + + internal EntityFrameworkInstrumentationOptions(IConfiguration configuration) + { + var databaseSemanticConvention = GetSemanticConventionOptIn(configuration); + this.EmitOldAttributes = databaseSemanticConvention.HasFlag(DatabaseSemanticConvention.Old); + this.EmitNewAttributes = databaseSemanticConvention.HasFlag(DatabaseSemanticConvention.New); + } + + /// + /// Gets or sets a value indicating whether or not the should add the names of commands as the tag. Default value: True. /// 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: False. + /// Gets or sets a value indicating whether or not the should add the text of commands as the tag. Default value: False. /// public bool SetDbStatementForText { get; set; } @@ -50,4 +67,14 @@ public class EntityFrameworkInstrumentationOptions /// /// public Func? Filter { get; set; } + + /// + /// Gets or sets a value indicating whether the old database attributes should be emitted. + /// + internal bool EmitOldAttributes { get; set; } + + /// + /// Gets or sets a value indicating whether the new database attributes should be emitted. + /// + internal bool EmitNewAttributes { get; set; } } diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/Implementation/EntityFrameworkDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/Implementation/EntityFrameworkDiagnosticListener.cs index ebb26c585d..322c35c587 100644 --- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/Implementation/EntityFrameworkDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/Implementation/EntityFrameworkDiagnosticListener.cs @@ -18,9 +18,12 @@ internal sealed class EntityFrameworkDiagnosticListener : ListenerHandler internal const string EntityFrameworkCoreCommandExecuted = "Microsoft.EntityFrameworkCore.Database.Command.CommandExecuted"; internal const string EntityFrameworkCoreCommandError = "Microsoft.EntityFrameworkCore.Database.Command.CommandError"; + internal const string AttributePeerService = "peer.service"; internal const string AttributeServerAddress = "server.address"; internal const string AttributeDbSystem = "db.system"; + internal const string AttributeDbName = "db.name"; internal const string AttributeDbNamespace = "db.namespace"; + internal const string AttributeDbStatement = "db.statement"; internal const string AttributeDbQueryText = "db.query.text"; internal static readonly Assembly Assembly = typeof(EntityFrameworkDiagnosticListener).Assembly; @@ -33,7 +36,7 @@ internal sealed class EntityFrameworkDiagnosticListener : ListenerHandler private readonly PropertyFetcher dbContextFetcher = new("Context"); private readonly PropertyFetcher dbContextDatabaseFetcher = new("Database"); private readonly PropertyFetcher providerNameFetcher = new("ProviderName"); - private readonly PropertyFetcher hostFetcher = new("Host"); + private readonly PropertyFetcher dataSourceFetcher = new("DataSource"); private readonly PropertyFetcher databaseFetcher = new("Database"); private readonly PropertyFetcher commandTypeFetcher = new("CommandType"); private readonly PropertyFetcher commandTextFetcher = new("CommandText"); @@ -138,12 +141,20 @@ public override void OnEventWritten(string name, object? payload) break; } - activity.AddTag(AttributeDbNamespace, database); + var dataSource = (string)this.dataSourceFetcher.Fetch(connection); + if (!string.IsNullOrEmpty(dataSource)) + { + activity.AddTag(AttributeServerAddress, dataSource); + } + + if (this.options.EmitOldAttributes) + { + activity.AddTag(AttributeDbName, database); + } - var host = (string)this.hostFetcher.Fetch(connection); - if (!string.IsNullOrEmpty(host)) + if (this.options.EmitNewAttributes) { - activity.AddTag(AttributeServerAddress, host); + activity.AddTag(AttributeDbNamespace, database); } } } @@ -197,7 +208,15 @@ public override void OnEventWritten(string name, object? payload) case CommandType.StoredProcedure: if (this.options.SetDbStatementForStoredProcedure) { - activity.AddTag(AttributeDbQueryText, commandText); + if (this.options.EmitOldAttributes) + { + activity.AddTag(AttributeDbStatement, commandText); + } + + if (this.options.EmitNewAttributes) + { + activity.AddTag(AttributeDbQueryText, commandText); + } } break; @@ -205,7 +224,15 @@ public override void OnEventWritten(string name, object? payload) case CommandType.Text: if (this.options.SetDbStatementForText) { - activity.AddTag(AttributeDbQueryText, commandText); + if (this.options.EmitOldAttributes) + { + activity.AddTag(AttributeDbStatement, commandText); + } + + if (this.options.EmitNewAttributes) + { + activity.AddTag(AttributeDbQueryText, commandText); + } } break; diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/OpenTelemetry.Instrumentation.EntityFrameworkCore.csproj b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/OpenTelemetry.Instrumentation.EntityFrameworkCore.csproj index 4af147744b..361404b0af 100644 --- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/OpenTelemetry.Instrumentation.EntityFrameworkCore.csproj +++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/OpenTelemetry.Instrumentation.EntityFrameworkCore.csproj @@ -7,24 +7,28 @@ Instrumentation.EntityFrameworkCore- - true + + + + diff --git a/src/Shared/DatabaseSemanticConventionHelper.cs b/src/Shared/DatabaseSemanticConventionHelper.cs new file mode 100644 index 0000000000..ef1c85cbc1 --- /dev/null +++ b/src/Shared/DatabaseSemanticConventionHelper.cs @@ -0,0 +1,83 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#nullable enable + +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Configuration; + +namespace OpenTelemetry.Internal; + +/// +/// Helper class for Database Semantic Conventions. +/// +/// +/// Due to a breaking change in the semantic convention, affected instrumentation libraries +/// must inspect an environment variable to determine which attributes to emit. +/// This is expected to be removed when the instrumentation libraries reach Stable. +/// . +/// +internal static class DatabaseSemanticConventionHelper +{ + internal const string SemanticConventionOptInKeyName = "OTEL_SEMCONV_STABILITY_OPT_IN"; + internal static readonly char[] Separator = new[] { ',', ' ' }; + + [Flags] + public enum DatabaseSemanticConvention + { + /// + /// Instructs an instrumentation library to emit the old experimental Database attributes. + /// + Old = 0x1, + + /// + /// Instructs an instrumentation library to emit the new, v1.28.0 Database attributes. + /// + New = 0x2, + + /// + /// Instructs an instrumentation library to emit both the old and new attributes. + /// + Dupe = Old | New, + } + + public static DatabaseSemanticConvention GetSemanticConventionOptIn(IConfiguration configuration) + { + if (TryGetConfiguredValues(configuration, out var values)) + { + if (values.Contains("database/dup")) + { + return DatabaseSemanticConvention.Dupe; + } + else if (values.Contains("database")) + { + return DatabaseSemanticConvention.New; + } + } + + return DatabaseSemanticConvention.Old; + } + + private static bool TryGetConfiguredValues(IConfiguration configuration, [NotNullWhen(true)] out HashSet? values) + { + try + { + var stringValue = configuration[SemanticConventionOptInKeyName]; + + if (string.IsNullOrWhiteSpace(stringValue)) + { + values = null; + return false; + } + + var stringValues = stringValue!.Split(separator: Separator, options: StringSplitOptions.RemoveEmptyEntries); + values = new HashSet(stringValues, StringComparer.OrdinalIgnoreCase); + return true; + } + catch + { + values = null; + return false; + } + } +} diff --git a/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/DatabaseSemanticConventionHelperTests.cs b/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/DatabaseSemanticConventionHelperTests.cs new file mode 100644 index 0000000000..748ac1ea90 --- /dev/null +++ b/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/DatabaseSemanticConventionHelperTests.cs @@ -0,0 +1,79 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Configuration; +using Xunit; +using static OpenTelemetry.Internal.DatabaseSemanticConventionHelper; + +namespace OpenTelemetry.Internal.Tests; + +public class DatabaseSemanticConventionHelperTests +{ + public static IEnumerable TestCases => new List + { + new object[] { null!, DatabaseSemanticConvention.Old }, + new object[] { string.Empty, DatabaseSemanticConvention.Old }, + new object[] { " ", DatabaseSemanticConvention.Old }, + new object[] { "junk", DatabaseSemanticConvention.Old }, + new object[] { "none", DatabaseSemanticConvention.Old }, + new object[] { "NONE", DatabaseSemanticConvention.Old }, + new object[] { "database", DatabaseSemanticConvention.New }, + new object[] { "DATABASE", DatabaseSemanticConvention.New }, + new object[] { "database/dup", DatabaseSemanticConvention.Dupe }, + new object[] { "DATABASE/DUP", DatabaseSemanticConvention.Dupe }, + new object[] { "junk,,junk", DatabaseSemanticConvention.Old }, + new object[] { "junk,JUNK", DatabaseSemanticConvention.Old }, + new object[] { "junk1,junk2", DatabaseSemanticConvention.Old }, + new object[] { "junk,database", DatabaseSemanticConvention.New }, + new object[] { "junk,database , database ,junk", DatabaseSemanticConvention.New }, + new object[] { "junk,database/dup", DatabaseSemanticConvention.Dupe }, + new object[] { "junk, database/dup ", DatabaseSemanticConvention.Dupe }, + new object[] { "database/dup,database", DatabaseSemanticConvention.Dupe }, + new object[] { "database,database/dup", DatabaseSemanticConvention.Dupe }, + }; + + [Fact] + public void VerifyFlags() + { + var testValue = DatabaseSemanticConvention.Dupe; + Assert.True(testValue.HasFlag(DatabaseSemanticConvention.Old)); + Assert.True(testValue.HasFlag(DatabaseSemanticConvention.New)); + + testValue = DatabaseSemanticConvention.Old; + Assert.True(testValue.HasFlag(DatabaseSemanticConvention.Old)); + Assert.False(testValue.HasFlag(DatabaseSemanticConvention.New)); + + testValue = DatabaseSemanticConvention.New; + Assert.False(testValue.HasFlag(DatabaseSemanticConvention.Old)); + Assert.True(testValue.HasFlag(DatabaseSemanticConvention.New)); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void VerifyGetSemanticConventionOptIn_UsingEnvironmentVariable(string input, string expectedValue) + { + try + { + Environment.SetEnvironmentVariable(SemanticConventionOptInKeyName, input); + + var expected = Enum.Parse(typeof(DatabaseSemanticConvention), expectedValue); + Assert.Equal(expected, GetSemanticConventionOptIn(new ConfigurationBuilder().AddEnvironmentVariables().Build())); + } + finally + { + Environment.SetEnvironmentVariable(SemanticConventionOptInKeyName, null); + } + } + + [Theory] + [MemberData(nameof(TestCases))] + public void VerifyGetSemanticConventionOptIn_UsingIConfiguration(string input, string expectedValue) + { + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { [SemanticConventionOptInKeyName] = input }) + .Build(); + + var expected = Enum.Parse(typeof(DatabaseSemanticConvention), expectedValue); + Assert.Equal(expected, GetSemanticConventionOptIn(configuration)); + } +} diff --git a/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/EntityFrameworkDiagnosticListenerTests.cs b/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/EntityFrameworkDiagnosticListenerTests.cs index 46cf9c06fc..6610d02ffd 100644 --- a/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/EntityFrameworkDiagnosticListenerTests.cs +++ b/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/EntityFrameworkDiagnosticListenerTests.cs @@ -66,7 +66,7 @@ public void EntityFrameworkEnrichDisplayNameWithEnrichWithIDbCommand() { var stateDisplayName = $"{command.CommandType} main"; activity1.DisplayName = stateDisplayName; - activity1.SetTag("db.namespace", stateDisplayName); + activity1.SetTag("db.name", stateDisplayName); }; }).Build(); @@ -86,6 +86,40 @@ public void EntityFrameworkEnrichDisplayNameWithEnrichWithIDbCommand() VerifyActivityData(activity, altDisplayName: $"{expectedDisplayName}"); } + [Fact] + public void EntityFrameworkEnrichDisplayNameWithEnrichWithIDbCommand_New() + { + var exportedItems = new List(); + var expectedDisplayName = "Text main"; + using var shutdownSignal = Sdk.CreateTracerProviderBuilder() + .AddInMemoryExporter(exportedItems) + .AddEntityFrameworkCoreInstrumentation(options => + { + options.EnrichWithIDbCommand = (activity1, command) => + { + var stateDisplayName = $"{command.CommandType} main"; + activity1.DisplayName = stateDisplayName; + activity1.SetTag("db.namespace", stateDisplayName); + }; + options.EmitNewAttributes = true; + }).Build(); + + using (var context = new ItemsContext(this.contextOptions)) + { + var items = context.Set().OrderBy(e => e.Name).ToList(); + + Assert.Equal(3, items.Count); + Assert.Equal("ItemOne", items[0].Name); + Assert.Equal("ItemThree", items[1].Name); + Assert.Equal("ItemTwo", items[2].Name); + } + + Assert.Single(exportedItems); + var activity = exportedItems[0]; + + VerifyActivityData(activity, altDisplayName: $"{expectedDisplayName}", emitNewAttributes: true); + } + [Fact] public void EntityFrameworkContextExceptionEventsInstrumentedTest() { @@ -227,7 +261,7 @@ private static SqliteConnection CreateInMemoryDatabase() return connection; } - private static void VerifyActivityData(Activity activity, bool isError = false, string? altDisplayName = null) + private static void VerifyActivityData(Activity activity, bool isError = false, string? altDisplayName = null, bool emitNewAttributes = false) { Assert.Equal(altDisplayName ?? "main", activity.DisplayName); @@ -235,9 +269,17 @@ private static void VerifyActivityData(Activity activity, bool isError = false, Assert.Equal("sqlite", activity.Tags.FirstOrDefault(t => t.Key == EntityFrameworkDiagnosticListener.AttributeDbSystem).Value); // TBD: SqlLite not setting the DataSource so it doesn't get set. - Assert.DoesNotContain(activity.Tags, t => t.Key == EntityFrameworkDiagnosticListener.AttributeServerAddress); + Assert.DoesNotContain(activity.Tags, t => t.Key == EntityFrameworkDiagnosticListener.AttributePeerService); - Assert.Equal(altDisplayName ?? "main", activity.Tags.FirstOrDefault(t => t.Key == EntityFrameworkDiagnosticListener.AttributeDbNamespace).Value); + if (!emitNewAttributes) + { + Assert.Equal(altDisplayName ?? "main", activity.Tags.FirstOrDefault(t => t.Key == EntityFrameworkDiagnosticListener.AttributeDbName).Value); + } + + if (emitNewAttributes) + { + Assert.Equal(altDisplayName ?? "main", activity.Tags.FirstOrDefault(t => t.Key == EntityFrameworkDiagnosticListener.AttributeDbNamespace).Value); + } if (!isError) { diff --git a/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/EntityFrameworkInstrumentationOptionsTests b/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/EntityFrameworkInstrumentationOptionsTests new file mode 100644 index 0000000000..6f8d54b892 --- /dev/null +++ b/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/EntityFrameworkInstrumentationOptionsTests @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Configuration; +using OpenTelemetry.Internal; +using Xunit; + +namespace OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests; + +public class EntityFrameworkInstrumentationOptionsTests +{ + [Fact] + public void ShouldEmitOldAttributesWhenStabilityOptInIsDatabaseDup() + { + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { [DatabaseSemanticConventionHelper.SemanticConventionOptInKeyName] = "database/dup" }) + .Build(); + var options = new EntityFrameworkInstrumentationOptions(configuration); + Assert.True(options.EmitOldAttributes); + Assert.True(options.EmitNewAttributes); + } + + [Fact] + public void ShouldEmitOldAttributesWhenStabilityOptInIsNotSpecified() + { + var configuration = new ConfigurationBuilder().Build(); + var options = new EntityFrameworkInstrumentationOptions(configuration); + Assert.True(options.EmitOldAttributes); + Assert.False(options.EmitNewAttributes); + } + + [Fact] + public void ShouldEmitNewAttributesWhenStabilityOptInIsDatabase() + { + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { [DatabaseSemanticConventionHelper.SemanticConventionOptInKeyName] = "database" }) + .Build(); + var options = new EntityFrameworkInstrumentationOptions(configuration); + Assert.False(options.EmitOldAttributes); + Assert.True(options.EmitNewAttributes); + } +} From c503d8a588114b51e3d61711af0f22702502498f Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Thu, 17 Oct 2024 05:43:37 +0000 Subject: [PATCH 8/9] Move DatabaseSemanticConventionHelperTests --- .../DatabaseSemanticConventionHelperTests.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests => OpenTelemetry.Contrib.Shared.Tests}/DatabaseSemanticConventionHelperTests.cs (100%) diff --git a/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/DatabaseSemanticConventionHelperTests.cs b/test/OpenTelemetry.Contrib.Shared.Tests/DatabaseSemanticConventionHelperTests.cs similarity index 100% rename from test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/DatabaseSemanticConventionHelperTests.cs rename to test/OpenTelemetry.Contrib.Shared.Tests/DatabaseSemanticConventionHelperTests.cs From 46f1e987e2b0448a4ee70aa2f29cf9f9d0bd69c2 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Thu, 17 Oct 2024 05:58:05 +0000 Subject: [PATCH 9/9] Add missing references --- .../OpenTelemetry.Contrib.Shared.Tests.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/OpenTelemetry.Contrib.Shared.Tests/OpenTelemetry.Contrib.Shared.Tests.csproj b/test/OpenTelemetry.Contrib.Shared.Tests/OpenTelemetry.Contrib.Shared.Tests.csproj index 53e2d966e5..f6b04bb9c4 100644 --- a/test/OpenTelemetry.Contrib.Shared.Tests/OpenTelemetry.Contrib.Shared.Tests.csproj +++ b/test/OpenTelemetry.Contrib.Shared.Tests/OpenTelemetry.Contrib.Shared.Tests.csproj @@ -7,14 +7,18 @@ + + + +