Skip to content

Commit 5a9c9a8

Browse files
authored
[Instrumentation.EntityFrameworkCore] Update semantic conventions for stable release (#2130)
Co-authored-by: joegoldman2 <[email protected]>
1 parent b8fe2cb commit 5a9c9a8

File tree

10 files changed

+339
-7
lines changed

10 files changed

+339
-7
lines changed

opentelemetry-dotnet-contrib.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1FCC8E
239239
ProjectSection(SolutionItems) = preProject
240240
src\Shared\ActivityInstrumentationHelper.cs = src\Shared\ActivityInstrumentationHelper.cs
241241
src\Shared\AssemblyVersionExtensions.cs = src\Shared\AssemblyVersionExtensions.cs
242+
src\Shared\DatabaseSemanticConventionHelper.cs = src\Shared\DatabaseSemanticConventionHelper.cs
242243
src\Shared\DiagnosticSourceListener.cs = src\Shared\DiagnosticSourceListener.cs
243244
src\Shared\DiagnosticSourceSubscriber.cs = src\Shared\DiagnosticSourceSubscriber.cs
244245
src\Shared\ExceptionExtensions.cs = src\Shared\ExceptionExtensions.cs

src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,28 @@
22

33
## Unreleased
44

5+
* The new database semantic conventions can be opted in to by setting
6+
the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable. This allows for a
7+
transition period for users to experiment with the new semantic conventions
8+
and adapt as necessary. The environment variable supports the following
9+
values:
10+
* `database` - emit the new, frozen (proposed for stable) database
11+
attributes, and stop emitting the old experimental database
12+
attributes that the instrumentation emitted previously.
13+
* `database/dup` - emit both the old and the frozen (proposed for stable) database
14+
attributes, allowing for a more seamless transition.
15+
* The default behavior (in the absence of one of these values) is to continue
16+
emitting the same database semantic conventions that were emitted in
17+
the previous version.
18+
* Note: this option will be be removed after the new database
19+
semantic conventions is marked stable. At which time this
20+
instrumentation can receive a stable release, and the old database
21+
semantic conventions will no longer be supported. Refer to the
22+
specification for more information regarding the new database
23+
semantic conventions for
24+
[spans](https://github.com/open-telemetry/semantic-conventions/blob/v1.28.0/docs/database/database-spans.md).
25+
([#2130](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2130))
26+
527
## 1.0.0-beta.12
628

729
Released 2024-Jun-18

src/OpenTelemetry.Instrumentation.EntityFrameworkCore/EntityFrameworkInstrumentationOptions.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System.Data;
55
using System.Diagnostics;
6+
using Microsoft.Extensions.Configuration;
7+
using static OpenTelemetry.Internal.DatabaseSemanticConventionHelper;
68

79
namespace OpenTelemetry.Instrumentation.EntityFrameworkCore;
810

@@ -11,6 +13,21 @@ namespace OpenTelemetry.Instrumentation.EntityFrameworkCore;
1113
/// </summary>
1214
public class EntityFrameworkInstrumentationOptions
1315
{
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="EntityFrameworkInstrumentationOptions"/> class.
18+
/// </summary>
19+
public EntityFrameworkInstrumentationOptions()
20+
: this(new ConfigurationBuilder().AddEnvironmentVariables().Build())
21+
{
22+
}
23+
24+
internal EntityFrameworkInstrumentationOptions(IConfiguration configuration)
25+
{
26+
var databaseSemanticConvention = GetSemanticConventionOptIn(configuration);
27+
this.EmitOldAttributes = databaseSemanticConvention.HasFlag(DatabaseSemanticConvention.Old);
28+
this.EmitNewAttributes = databaseSemanticConvention.HasFlag(DatabaseSemanticConvention.New);
29+
}
30+
1431
/// <summary>
1532
/// Gets or sets a value indicating whether or not the <see cref="EntityFrameworkInstrumentation"/> should add the names of <see cref="CommandType.StoredProcedure"/> commands as the <see cref="Implementation.EntityFrameworkDiagnosticListener.AttributeDbStatement"/> tag. Default value: True.
1633
/// </summary>
@@ -50,4 +67,14 @@ public class EntityFrameworkInstrumentationOptions
5067
/// </list>
5168
/// </remarks>
5269
public Func<string?, IDbCommand, bool>? Filter { get; set; }
70+
71+
/// <summary>
72+
/// Gets or sets a value indicating whether the old database attributes should be emitted.
73+
/// </summary>
74+
internal bool EmitOldAttributes { get; set; }
75+
76+
/// <summary>
77+
/// Gets or sets a value indicating whether the new database attributes should be emitted.
78+
/// </summary>
79+
internal bool EmitNewAttributes { get; set; }
5380
}

src/OpenTelemetry.Instrumentation.EntityFrameworkCore/Implementation/EntityFrameworkDiagnosticListener.cs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ internal sealed class EntityFrameworkDiagnosticListener : ListenerHandler
1919
internal const string EntityFrameworkCoreCommandError = "Microsoft.EntityFrameworkCore.Database.Command.CommandError";
2020

2121
internal const string AttributePeerService = "peer.service";
22+
internal const string AttributeServerAddress = "server.address";
2223
internal const string AttributeDbSystem = "db.system";
2324
internal const string AttributeDbName = "db.name";
25+
internal const string AttributeDbNamespace = "db.namespace";
2426
internal const string AttributeDbStatement = "db.statement";
27+
internal const string AttributeDbQueryText = "db.query.text";
2528

2629
internal static readonly Assembly Assembly = typeof(EntityFrameworkDiagnosticListener).Assembly;
2730
internal static readonly string ActivitySourceName = Assembly.GetName().Name;
@@ -139,10 +142,19 @@ public override void OnEventWritten(string name, object? payload)
139142
}
140143

141144
var dataSource = (string)this.dataSourceFetcher.Fetch(connection);
142-
activity.AddTag(AttributeDbName, database);
143145
if (!string.IsNullOrEmpty(dataSource))
144146
{
145-
activity.AddTag(AttributePeerService, dataSource);
147+
activity.AddTag(AttributeServerAddress, dataSource);
148+
}
149+
150+
if (this.options.EmitOldAttributes)
151+
{
152+
activity.AddTag(AttributeDbName, database);
153+
}
154+
155+
if (this.options.EmitNewAttributes)
156+
{
157+
activity.AddTag(AttributeDbNamespace, database);
146158
}
147159
}
148160
}
@@ -196,15 +208,31 @@ public override void OnEventWritten(string name, object? payload)
196208
case CommandType.StoredProcedure:
197209
if (this.options.SetDbStatementForStoredProcedure)
198210
{
199-
activity.AddTag(AttributeDbStatement, commandText);
211+
if (this.options.EmitOldAttributes)
212+
{
213+
activity.AddTag(AttributeDbStatement, commandText);
214+
}
215+
216+
if (this.options.EmitNewAttributes)
217+
{
218+
activity.AddTag(AttributeDbQueryText, commandText);
219+
}
200220
}
201221

202222
break;
203223

204224
case CommandType.Text:
205225
if (this.options.SetDbStatementForText)
206226
{
207-
activity.AddTag(AttributeDbStatement, commandText);
227+
if (this.options.EmitOldAttributes)
228+
{
229+
activity.AddTag(AttributeDbStatement, commandText);
230+
}
231+
232+
if (this.options.EmitNewAttributes)
233+
{
234+
activity.AddTag(AttributeDbQueryText, commandText);
235+
}
208236
}
209237

210238
break;

src/OpenTelemetry.Instrumentation.EntityFrameworkCore/OpenTelemetry.Instrumentation.EntityFrameworkCore.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,28 @@
77
<MinVerTagPrefix>Instrumentation.EntityFrameworkCore-</MinVerTagPrefix>
88
</PropertyGroup>
99

10-
<!-- Do not run Package Baseline Validation as this package has never released a stable version.
10+
<!-- Do not run Package Baseline Validation as this package has never released a stable version.
1111
Remove this property once we have released a stable version and add PackageValidationBaselineVersion property. -->
1212
<PropertyGroup>
1313
<DisablePackageBaselineValidation>true</DisablePackageBaselineValidation>
1414
</PropertyGroup>
1515

1616
<ItemGroup>
17+
<PackageReference Include="Microsoft.Extensions.Configuration" Version="$(MicrosoftExtensionsConfigurationPkgVer)"/>
1718
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPkgVer)" />
1819
<PackageReference Include="OpenTelemetry.Api.ProviderBuilderExtensions" Version="$(OpenTelemetryCoreLatestVersion)" />
1920
</ItemGroup>
2021

2122
<ItemGroup>
2223
<Compile Include="$(RepoRoot)\src\Shared\AssemblyVersionExtensions.cs" Link="Includes\AssemblyVersionExtensions.cs" />
24+
<Compile Include="$(RepoRoot)\src\Shared\DatabaseSemanticConventionHelper.cs" Link="Includes\DatabaseSemanticConventionHelper.cs" />
2325
<Compile Include="$(RepoRoot)\src\Shared\DiagnosticSourceListener.cs" Link="Includes\DiagnosticSourceListener.cs" />
2426
<Compile Include="$(RepoRoot)\src\Shared\DiagnosticSourceSubscriber.cs" Link="Includes\DiagnosticSourceSubscriber.cs" />
27+
<Compile Include="$(RepoRoot)\src\Shared\EnvironmentVariables\*.cs" Link="Includes\EnvironmentVariables\%(Filename).cs" />
2528
<Compile Include="$(RepoRoot)\src\Shared\ExceptionExtensions.cs" Link="Includes\ExceptionExtensions.cs" />
2629
<Compile Include="$(RepoRoot)\src\Shared\Guard.cs" Link="Includes\Guard.cs" />
2730
<Compile Include="$(RepoRoot)\src\Shared\ListenerHandler.cs" Link="Includes\ListenerHandler.cs" />
31+
<Compile Include="$(RepoRoot)\src\Shared\NullableAttributes.cs" Link="Includes\NullableAttributes.cs" />
2832
<Compile Include="$(RepoRoot)\src\Shared\PropertyFetcher.cs" Link="Includes\PropertyFetcher.cs" />
2933
</ItemGroup>
3034

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#nullable enable
5+
6+
using System.Diagnostics.CodeAnalysis;
7+
using Microsoft.Extensions.Configuration;
8+
9+
namespace OpenTelemetry.Internal;
10+
11+
/// <summary>
12+
/// Helper class for Database Semantic Conventions.
13+
/// </summary>
14+
/// <remarks>
15+
/// Due to a breaking change in the semantic convention, affected instrumentation libraries
16+
/// must inspect an environment variable to determine which attributes to emit.
17+
/// This is expected to be removed when the instrumentation libraries reach Stable.
18+
/// <see href="https://github.com/open-telemetry/semantic-conventions/blob/v1.28.0/docs/database/database-spans.md"/>.
19+
/// </remarks>
20+
internal static class DatabaseSemanticConventionHelper
21+
{
22+
internal const string SemanticConventionOptInKeyName = "OTEL_SEMCONV_STABILITY_OPT_IN";
23+
internal static readonly char[] Separator = new[] { ',', ' ' };
24+
25+
[Flags]
26+
public enum DatabaseSemanticConvention
27+
{
28+
/// <summary>
29+
/// Instructs an instrumentation library to emit the old experimental Database attributes.
30+
/// </summary>
31+
Old = 0x1,
32+
33+
/// <summary>
34+
/// Instructs an instrumentation library to emit the new, v1.28.0 Database attributes.
35+
/// </summary>
36+
New = 0x2,
37+
38+
/// <summary>
39+
/// Instructs an instrumentation library to emit both the old and new attributes.
40+
/// </summary>
41+
Dupe = Old | New,
42+
}
43+
44+
public static DatabaseSemanticConvention GetSemanticConventionOptIn(IConfiguration configuration)
45+
{
46+
if (TryGetConfiguredValues(configuration, out var values))
47+
{
48+
if (values.Contains("database/dup"))
49+
{
50+
return DatabaseSemanticConvention.Dupe;
51+
}
52+
else if (values.Contains("database"))
53+
{
54+
return DatabaseSemanticConvention.New;
55+
}
56+
}
57+
58+
return DatabaseSemanticConvention.Old;
59+
}
60+
61+
private static bool TryGetConfiguredValues(IConfiguration configuration, [NotNullWhen(true)] out HashSet<string>? values)
62+
{
63+
try
64+
{
65+
var stringValue = configuration[SemanticConventionOptInKeyName];
66+
67+
if (string.IsNullOrWhiteSpace(stringValue))
68+
{
69+
values = null;
70+
return false;
71+
}
72+
73+
var stringValues = stringValue!.Split(separator: Separator, options: StringSplitOptions.RemoveEmptyEntries);
74+
values = new HashSet<string>(stringValues, StringComparer.OrdinalIgnoreCase);
75+
return true;
76+
}
77+
catch
78+
{
79+
values = null;
80+
return false;
81+
}
82+
}
83+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using Microsoft.Extensions.Configuration;
5+
using Xunit;
6+
using static OpenTelemetry.Internal.DatabaseSemanticConventionHelper;
7+
8+
namespace OpenTelemetry.Internal.Tests;
9+
10+
public class DatabaseSemanticConventionHelperTests
11+
{
12+
public static IEnumerable<object[]> TestCases => new List<object[]>
13+
{
14+
new object[] { null!, DatabaseSemanticConvention.Old },
15+
new object[] { string.Empty, DatabaseSemanticConvention.Old },
16+
new object[] { " ", DatabaseSemanticConvention.Old },
17+
new object[] { "junk", DatabaseSemanticConvention.Old },
18+
new object[] { "none", DatabaseSemanticConvention.Old },
19+
new object[] { "NONE", DatabaseSemanticConvention.Old },
20+
new object[] { "database", DatabaseSemanticConvention.New },
21+
new object[] { "DATABASE", DatabaseSemanticConvention.New },
22+
new object[] { "database/dup", DatabaseSemanticConvention.Dupe },
23+
new object[] { "DATABASE/DUP", DatabaseSemanticConvention.Dupe },
24+
new object[] { "junk,,junk", DatabaseSemanticConvention.Old },
25+
new object[] { "junk,JUNK", DatabaseSemanticConvention.Old },
26+
new object[] { "junk1,junk2", DatabaseSemanticConvention.Old },
27+
new object[] { "junk,database", DatabaseSemanticConvention.New },
28+
new object[] { "junk,database , database ,junk", DatabaseSemanticConvention.New },
29+
new object[] { "junk,database/dup", DatabaseSemanticConvention.Dupe },
30+
new object[] { "junk, database/dup ", DatabaseSemanticConvention.Dupe },
31+
new object[] { "database/dup,database", DatabaseSemanticConvention.Dupe },
32+
new object[] { "database,database/dup", DatabaseSemanticConvention.Dupe },
33+
};
34+
35+
[Fact]
36+
public void VerifyFlags()
37+
{
38+
var testValue = DatabaseSemanticConvention.Dupe;
39+
Assert.True(testValue.HasFlag(DatabaseSemanticConvention.Old));
40+
Assert.True(testValue.HasFlag(DatabaseSemanticConvention.New));
41+
42+
testValue = DatabaseSemanticConvention.Old;
43+
Assert.True(testValue.HasFlag(DatabaseSemanticConvention.Old));
44+
Assert.False(testValue.HasFlag(DatabaseSemanticConvention.New));
45+
46+
testValue = DatabaseSemanticConvention.New;
47+
Assert.False(testValue.HasFlag(DatabaseSemanticConvention.Old));
48+
Assert.True(testValue.HasFlag(DatabaseSemanticConvention.New));
49+
}
50+
51+
[Theory]
52+
[MemberData(nameof(TestCases))]
53+
public void VerifyGetSemanticConventionOptIn_UsingEnvironmentVariable(string input, string expectedValue)
54+
{
55+
try
56+
{
57+
Environment.SetEnvironmentVariable(SemanticConventionOptInKeyName, input);
58+
59+
var expected = Enum.Parse(typeof(DatabaseSemanticConvention), expectedValue);
60+
Assert.Equal(expected, GetSemanticConventionOptIn(new ConfigurationBuilder().AddEnvironmentVariables().Build()));
61+
}
62+
finally
63+
{
64+
Environment.SetEnvironmentVariable(SemanticConventionOptInKeyName, null);
65+
}
66+
}
67+
68+
[Theory]
69+
[MemberData(nameof(TestCases))]
70+
public void VerifyGetSemanticConventionOptIn_UsingIConfiguration(string input, string expectedValue)
71+
{
72+
var configuration = new ConfigurationBuilder()
73+
.AddInMemoryCollection(new Dictionary<string, string?> { [SemanticConventionOptInKeyName] = input })
74+
.Build();
75+
76+
var expected = Enum.Parse(typeof(DatabaseSemanticConvention), expectedValue);
77+
Assert.Equal(expected, GetSemanticConventionOptIn(configuration));
78+
}
79+
}

test/OpenTelemetry.Contrib.Shared.Tests/OpenTelemetry.Contrib.Shared.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10+
<PackageReference Include="Microsoft.Extensions.Configuration" Version="$(MicrosoftExtensionsConfigurationPkgVer)"/>
1011
<PackageReference Include="OpenTelemetry.Api" Version="$(OpenTelemetryCoreLatestVersion)" />
1112
</ItemGroup>
1213

1314
<ItemGroup>
1415
<Compile Include="$(RepoRoot)\src\Shared\ActivityHelperExtensions.cs" Link="Includes\ActivityHelperExtensions.cs" />
1516
<Compile Include="$(RepoRoot)\src\Shared\ActivityInstrumentationHelper.cs" Link="Includes\ActivityInstrumentationHelper.cs" />
17+
<Compile Include="$(RepoRoot)\src\Shared\DatabaseSemanticConventionHelper.cs" Link="Includes\DatabaseSemanticConventionHelper.cs" />
18+
<Compile Include="$(RepoRoot)\src\Shared\EnvironmentVariables\*.cs" Link="Includes\EnvironmentVariables\%(Filename).cs" />
1619
<Compile Include="$(RepoRoot)\src\Shared\GrpcTagHelper.cs" Link="Includes\GrpcTagHelper.cs" />
1720
<Compile Include="$(RepoRoot)\src\Shared\GrpcStatusCanonicalCode.cs" Link="Includes\GrpcStatusCanonicalCode.cs" />
21+
<Compile Include="$(RepoRoot)\src\Shared\NullableAttributes.cs" Link="Includes\NullableAttributes.cs" />
1822
<Compile Include="$(RepoRoot)\src\Shared\PropertyFetcher.AOT.cs" Link="Includes\PropertyFetcher.AOT.cs" />
1923
<Compile Include="$(RepoRoot)\src\Shared\RedactionHelper.cs" Link="Includes\RedactionHelper.cs" />
2024
<Compile Include="$(RepoRoot)\src\Shared\RequestDataHelper.cs" Link="Includes\RequestDataHelper.cs" />

0 commit comments

Comments
 (0)