Skip to content

Commit 9dda555

Browse files
committed
feat: add opentelemetry extension package
Adds OpenTelemetry extension package with support for session level tracing.
1 parent 8fb0053 commit 9dda555

33 files changed

+716
-35
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.8.0" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\..\..\src\Extensions\Cassandra.OpenTelemetry\Cassandra.OpenTelemetry.csproj" />
16+
</ItemGroup>
17+
18+
</Project>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using Cassandra;
2+
using Cassandra.OpenTelemetry;
3+
using OpenTelemetry;
4+
using OpenTelemetry.Resources;
5+
using OpenTelemetry.Trace;
6+
7+
namespace ConsoleExporter
8+
{
9+
internal class Program
10+
{
11+
private const string ContactPoint = "localhost";
12+
private const string SessionName = "otel-example";
13+
14+
private static void Main(string[] args)
15+
{
16+
Program.MainAsync(args).GetAwaiter().GetResult();
17+
}
18+
19+
private static async Task MainAsync(string[] args)
20+
{
21+
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
22+
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(
23+
serviceName: "CassandraDemo",
24+
serviceVersion: "1.0.0"))
25+
.AddSource(CassandraInstrumentation.ActivitySourceName)
26+
.AddConsoleExporter()
27+
.Build();
28+
29+
var cluster = Cluster.Builder()
30+
.AddContactPoint(Program.ContactPoint)
31+
.WithSessionName(Program.SessionName)
32+
.AddOpenTelemetryInstrumentation(options => options.IncludeDatabaseStatement = true)
33+
.Build();
34+
35+
var session = await cluster.ConnectAsync().ConfigureAwait(false);
36+
37+
//// Execute a query every 5 seconds
38+
39+
var cts = new CancellationTokenSource();
40+
var task = Task.Run(async () =>
41+
{
42+
while (!cts.IsCancellationRequested)
43+
{
44+
try
45+
{
46+
var x = await session.ExecuteAsync(new SimpleStatement("SELECT * FROM system.local"));
47+
}
48+
catch (Exception ex)
49+
{
50+
Console.WriteLine($"ERROR: {ex}");
51+
}
52+
53+
Thread.Sleep(5000);
54+
}
55+
});
56+
57+
Console.WriteLine("Press enter to shutdown the session and exit.");
58+
Console.ReadLine();
59+
60+
cts.Cancel();
61+
62+
await task.ConfigureAwait(false);
63+
await cluster.ShutdownAsync().ConfigureAwait(false);
64+
}
65+
}
66+
}

examples/examples.sln

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ColumnEncryption", "ColumnE
3737
EndProject
3838
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ColumnEncryptionExample", "ColumnEncryption\ColumnEncryptionExample\ColumnEncryptionExample.csproj", "{AE159E33-D289-472C-8174-4FA9C55C29DF}"
3939
EndProject
40+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cassandra.OpenTelemetry", "..\src\Extensions\Cassandra.OpenTelemetry\Cassandra.OpenTelemetry.csproj", "{10076771-C9F5-4FF4-82E7-33EC4AF555FD}"
41+
EndProject
42+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OpenTelemetry", "OpenTelemetry", "{DD2D2F9B-513D-4518-8289-E12CB2BC5B12}"
43+
EndProject
44+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleExporter", "OpenTelemetry\ConsoleExporter\ConsoleExporter.csproj", "{616E808D-9BF5-4AB5-866B-6D4F35D1102E}"
45+
EndProject
4046
Global
4147
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4248
Debug|Any CPU = Debug|Any CPU
@@ -79,6 +85,14 @@ Global
7985
{AE159E33-D289-472C-8174-4FA9C55C29DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
8086
{AE159E33-D289-472C-8174-4FA9C55C29DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
8187
{AE159E33-D289-472C-8174-4FA9C55C29DF}.Release|Any CPU.Build.0 = Release|Any CPU
88+
{10076771-C9F5-4FF4-82E7-33EC4AF555FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
89+
{10076771-C9F5-4FF4-82E7-33EC4AF555FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
90+
{10076771-C9F5-4FF4-82E7-33EC4AF555FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
91+
{10076771-C9F5-4FF4-82E7-33EC4AF555FD}.Release|Any CPU.Build.0 = Release|Any CPU
92+
{616E808D-9BF5-4AB5-866B-6D4F35D1102E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
93+
{616E808D-9BF5-4AB5-866B-6D4F35D1102E}.Debug|Any CPU.Build.0 = Debug|Any CPU
94+
{616E808D-9BF5-4AB5-866B-6D4F35D1102E}.Release|Any CPU.ActiveCfg = Release|Any CPU
95+
{616E808D-9BF5-4AB5-866B-6D4F35D1102E}.Release|Any CPU.Build.0 = Release|Any CPU
8296
EndGlobalSection
8397
GlobalSection(SolutionProperties) = preSolution
8498
HideSolutionNode = FALSE
@@ -99,6 +113,9 @@ Global
99113
{1A866971-206C-4A6F-B6B1-8B0408B67618} = {A68222F1-E44C-4B20-8CEC-A10A89FC4982}
100114
{1320BA10-E76B-4247-B1F1-71221349A94E} = {A589A223-6F57-40C7-BE7D-00E87354A69E}
101115
{AE159E33-D289-472C-8174-4FA9C55C29DF} = {1320BA10-E76B-4247-B1F1-71221349A94E}
116+
{10076771-C9F5-4FF4-82E7-33EC4AF555FD} = {EE6E727D-835E-42D1-8E62-081E0B6FAAC2}
117+
{DD2D2F9B-513D-4518-8289-E12CB2BC5B12} = {A589A223-6F57-40C7-BE7D-00E87354A69E}
118+
{616E808D-9BF5-4AB5-866B-6D4F35D1102E} = {DD2D2F9B-513D-4518-8289-E12CB2BC5B12}
102119
EndGlobalSection
103120
GlobalSection(ExtensibilityGlobals) = postSolution
104121
SolutionGuid = {A731352A-6C46-4D13-A44F-07731EE671DA}

src/Cassandra.IntegrationTests/Cassandra.IntegrationTests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292

9393
<ItemGroup>
9494
<PackageReference Include="App.Metrics" Version="3.2.0" />
95+
<PackageReference Include="OpenTelemetry.Exporter.InMemory" Version="1.5.1" />
9596
<PackageReference Include="SSH.NET" Version="2020.0.2" />
9697
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
9798
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
@@ -119,6 +120,9 @@
119120
<PackageReference Include="JetBrains.DotMemoryUnit" Version="3.2.20220510" />
120121
<Reference Include="System" />
121122
</ItemGroup>
123+
<ItemGroup>
124+
<ProjectReference Include="..\Extensions\Cassandra.OpenTelemetry\Cassandra.OpenTelemetry.csproj" />
125+
</ItemGroup>
122126
<ItemGroup>
123127
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
124128
</ItemGroup>
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
using System.Collections.Generic;
2+
using System.Diagnostics;
3+
using System.Linq;
4+
using System.Threading;
5+
using Cassandra.OpenTelemetry;
6+
using Cassandra.Tests;
7+
using NUnit.Framework;
8+
using OpenTelemetry;
9+
using OpenTelemetry.Trace;
10+
11+
namespace Cassandra.IntegrationTests.OpenTelemetry
12+
{
13+
[Category(TestCategory.Short)]
14+
public class OpenTelemetryTests : SharedClusterTest
15+
{
16+
private const string OpenTelemetrySourceName = "Cassandra.OpenTelemetry";
17+
18+
private readonly List<Activity> _exportedActivities = new List<Activity>();
19+
20+
public OpenTelemetryTests()
21+
{
22+
Sdk.CreateTracerProviderBuilder()
23+
.AddSource(OpenTelemetrySourceName)
24+
.AddInMemoryExporter(_exportedActivities)
25+
.Build();
26+
}
27+
28+
[TearDown]
29+
public void Teardown()
30+
{
31+
_exportedActivities.Clear();
32+
}
33+
34+
[Category(TestCategory.RealClusterLong)]
35+
[Test]
36+
public void AddOpenTelemetry_WithKeyspaceAvailable_DbOperationAndDbNameAreIncluded()
37+
{
38+
var keyspace = "system";
39+
var expectedActivityName = $"ExecuteAsync {keyspace}";
40+
var expectedDbNameAttribute = keyspace;
41+
var cluster = GetNewTemporaryCluster(b => b.AddOpenTelemetryInstrumentation());
42+
var session = cluster.Connect();
43+
44+
var statement = new SimpleStatement("SELECT key FROM system.local");
45+
statement.SetKeyspace(keyspace);
46+
session.ExecuteAsync(statement);
47+
48+
var activity = GetActivity();
49+
50+
ValidateCommonAttributes(activity);
51+
52+
Assert.AreEqual(expectedActivityName, activity.DisplayName);
53+
Assert.AreEqual(expectedDbNameAttribute, activity.Tags.First(kvp => kvp.Key == "db.name").Value);
54+
}
55+
56+
[Category(TestCategory.RealClusterLong)]
57+
[Test]
58+
public void AddOpenTelemetry_WithoutKeyspace_DbNameIsNotIncluded()
59+
{
60+
var keyspace = "system";
61+
var expectedActivityName = $"ExecuteAsync";
62+
var expectedDbNameAttribute = keyspace;
63+
var cluster = GetNewTemporaryCluster(b => b.AddOpenTelemetryInstrumentation());
64+
var session = cluster.Connect();
65+
66+
var statement = new SimpleStatement("SELECT key FROM system.local");
67+
session.ExecuteAsync(statement);
68+
69+
var activity = GetActivity();
70+
71+
ValidateCommonAttributes(activity);
72+
73+
Assert.AreEqual(expectedActivityName, activity.DisplayName);
74+
Assert.IsNull(activity.Tags.FirstOrDefault(kvp => kvp.Key == "db.name").Value);
75+
}
76+
77+
[Category(TestCategory.RealClusterLong)]
78+
[Test]
79+
public void AddOpenTelemetry_WithDefaultOptions_DbStatementIsNotIncludedAsAttribute()
80+
{
81+
var cluster = GetNewTemporaryCluster(b => b.AddOpenTelemetryInstrumentation());
82+
var session = cluster.Connect();
83+
84+
var statement = new SimpleStatement("SELECT key FROM system.local");
85+
session.ExecuteAsync(statement);
86+
87+
var activity = GetActivity();
88+
89+
ValidateCommonAttributes(activity);
90+
91+
Assert.IsNull(activity.Tags.FirstOrDefault(kvp => kvp.Key == "db.statement").Value);
92+
}
93+
94+
[Category(TestCategory.RealClusterLong)]
95+
[Test]
96+
public void AddOpenTelemetry_WithIncludeDatabaseStatementOption_DbStatementIsIncludedAsAttribute()
97+
{
98+
var expectedDbStatement = "SELECT key FROM system.local";
99+
100+
var cluster = GetNewTemporaryCluster(b => b.AddOpenTelemetryInstrumentation(options => options.IncludeDatabaseStatement = true));
101+
var session = cluster.Connect();
102+
103+
var statement = new SimpleStatement("SELECT key FROM system.local");
104+
session.ExecuteAsync(statement);
105+
106+
var activity = GetActivity();
107+
108+
ValidateCommonAttributes(activity);
109+
110+
Assert.AreEqual(expectedDbStatement, activity.Tags.First(kvp => kvp.Key == "db.statement").Value);
111+
}
112+
113+
private Activity GetActivity()
114+
{
115+
var count = 0;
116+
117+
while(count < 5)
118+
{
119+
if(_exportedActivities.FirstOrDefault() != null)
120+
{
121+
return _exportedActivities.FirstOrDefault();
122+
}
123+
124+
count++;
125+
Thread.Sleep(1000);
126+
}
127+
128+
return _exportedActivities.FirstOrDefault();
129+
}
130+
131+
private static void ValidateCommonAttributes(Activity activity)
132+
{
133+
var expectedActivityKind = ActivityKind.Client;
134+
var expectedTags = new Dictionary<string, string>()
135+
{
136+
{"db.system", "cassandra" },
137+
{"db.operation", "ExecuteAsync" },
138+
};
139+
140+
Assert.AreEqual(activity.Kind, expectedActivityKind);
141+
142+
var tags = activity.Tags;
143+
144+
foreach (var pair in expectedTags)
145+
{
146+
Assert.AreEqual(tags.FirstOrDefault(x => x.Key == pair.Key).Value, expectedTags[pair.Key]);
147+
}
148+
}
149+
}
150+
}

src/Cassandra.Tests/TestConfigurationBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ internal class TestConfigurationBuilder
7878

7979
public IEndPointResolver EndPointResolver { get; set; }
8080

81-
public IObserverFactoryBuilder ObserverFactoryBuilder { get; set; } = new MetricsObserverFactoryBuilder();
81+
public IObserverFactoryBuilder ObserverFactoryBuilder { get; set; } = new MetricsObserverFactoryBuilder(true);
8282

8383
public DriverMetricsOptions MetricsOptions { get; set; } = new DriverMetricsOptions();
8484

src/Cassandra.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
2121
stylecop.json = stylecop.json
2222
EndProjectSection
2323
EndProject
24+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cassandra.OpenTelemetry", "Extensions\Cassandra.OpenTelemetry\Cassandra.OpenTelemetry.csproj", "{FF5E5BE8-9144-4917-A009-C4FC369084CF}"
25+
EndProject
2426
Global
2527
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2628
Debug|Any CPU = Debug|Any CPU
@@ -43,6 +45,10 @@ Global
4345
{93FA7B24-7B58-42E5-89E6-7F8023B03BA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
4446
{93FA7B24-7B58-42E5-89E6-7F8023B03BA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
4547
{93FA7B24-7B58-42E5-89E6-7F8023B03BA3}.Release|Any CPU.Build.0 = Release|Any CPU
48+
{FF5E5BE8-9144-4917-A009-C4FC369084CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49+
{FF5E5BE8-9144-4917-A009-C4FC369084CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
50+
{FF5E5BE8-9144-4917-A009-C4FC369084CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
51+
{FF5E5BE8-9144-4917-A009-C4FC369084CF}.Release|Any CPU.Build.0 = Release|Any CPU
4652
EndGlobalSection
4753
GlobalSection(SolutionProperties) = preSolution
4854
HideSolutionNode = FALSE
@@ -52,6 +58,7 @@ Global
5258
{A68FC72B-569E-49EA-BE94-E2A594824A2E} = {343488BE-0304-44B0-B19C-5D6FA2B1186E}
5359
{21DDE658-5BCB-4845-BC2F-47B102E3E522} = {343488BE-0304-44B0-B19C-5D6FA2B1186E}
5460
{93FA7B24-7B58-42E5-89E6-7F8023B03BA3} = {33EA4AFB-736A-48FB-B6BB-0E8F1163ABF0}
61+
{FF5E5BE8-9144-4917-A009-C4FC369084CF} = {33EA4AFB-736A-48FB-B6BB-0E8F1163ABF0}
5562
EndGlobalSection
5663
GlobalSection(ExtensibilityGlobals) = postSolution
5764
SolutionGuid = {B8534DFA-1600-4E72-A2FB-AEC7DB33CD01}

src/Cassandra/Builder.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
using Cassandra.ExecutionProfiles;
2929
using Cassandra.Metrics;
3030
using Cassandra.Metrics.Abstractions;
31+
using Cassandra.OpenTelemetry;
3132
using Cassandra.Serialization;
3233

3334
namespace Cassandra
@@ -71,6 +72,7 @@ public class Builder : IInitializer
7172
private MetadataSyncOptions _metadataSyncOptions;
7273
private IEndPointResolver _endPointResolver;
7374
private IDriverMetricsProvider _driverMetricsProvider;
75+
private IRequestTracker _driverTracer;
7476
private DriverMetricsOptions _metricsOptions;
7577
private MonitorReportingOptions _monitorReportingOptions = new MonitorReportingOptions();
7678
private string _sessionName;
@@ -203,7 +205,8 @@ public Configuration GetConfiguration()
203205
_monitorReportingOptions,
204206
typeSerializerDefinitions,
205207
_keepContactPointsUnresolved,
206-
_allowBetaProtocolVersions);
208+
_allowBetaProtocolVersions,
209+
requestTracker: _driverTracer);
207210

208211
return config;
209212
}
@@ -1011,6 +1014,12 @@ public Builder WithMetrics(IDriverMetricsProvider driverMetricsProvider, DriverM
10111014
return this;
10121015
}
10131016

1017+
public Builder WithRequestTracker(IRequestTracker trace)
1018+
{
1019+
_driverTracer = trace;
1020+
return this;
1021+
}
1022+
10141023
/// <summary>
10151024
/// <para>
10161025
/// Adds Execution Profiles to the Cluster instance.

src/Cassandra/Cassandra.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<RepositoryUrl>https://github.com/datastax/csharp-driver</RepositoryUrl>
2626
<PackageProjectUrl>https://github.com/datastax/csharp-driver</PackageProjectUrl>
2727
<LangVersion>7.1</LangVersion>
28+
<NuGetAudit>false</NuGetAudit>
2829
</PropertyGroup>
2930
<PropertyGroup Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(TargetFramework)', '^net4\d'))">
3031
<DefineConstants>$(DefineConstants);NETFRAMEWORK</DefineConstants>
@@ -34,7 +35,7 @@
3435
</PropertyGroup>
3536

3637
<ItemGroup>
37-
<None Include="..\..\LICENSE.md" Pack="true" PackagePath="LICENSE.md"/>
38+
<None Include="..\..\LICENSE.md" Pack="true" PackagePath="LICENSE.md" />
3839
</ItemGroup>
3940

4041
<ItemGroup>

0 commit comments

Comments
 (0)