Skip to content

Commit 91643f1

Browse files
authored
OpenTelemetry follow up PR (#613)
- Add Distributed Tracing example - Add `db.query.text` support for `BoundStatement` and `BatchStatement` - Rename builder extension method - Add support for speculative executions (complete pending executions when parent request is completed)
1 parent 21ce497 commit 91643f1

39 files changed

+1134
-249
lines changed

doc/features/opentelemetry/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ The driver provides support for session and node level [traces](https://opentele
44

55
## Including OpenTelemetry instrumentation in your code
66

7-
Add the package `Cassandra.OpenTelemetry` to the project and add the extension method `AddOpenTelemetryInstrumentation()` when building your cluster:
7+
Add the package `Cassandra.OpenTelemetry` to the project and add the extension method `WithOpenTelemetryInstrumentation()` when building your cluster:
88

99
```csharp
1010
var cluster = Cluster.Builder()
1111
.AddContactPoint(Program.ContactPoint)
1212
.WithSessionName(Program.SessionName)
13-
.AddOpenTelemetryInstrumentation()
13+
.WithOpenTelemetryInstrumentation()
1414
.Build();
1515
```
1616

doc/features/request-tracker/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Request Tracker
22

33
The driver provides the `IRequestTracker` interface that tracks the requests at Session and Node levels. It contains *start*, *finish*, and *error* events that can be subscribed by implementing the interface, and should be used by passing the implementation as an argument of the method `WithRequestTracker` that is available in the `Builder`.\
4-
An example of an `IRequestTracker` implementation is the extension package `Cassandra.OpenTelemetry` that can be checked in the [documentation](/doc/features/opentelemetry/README.md).
4+
An example of an `IRequestTracker` implementation is the extension package `Cassandra.OpenTelemetry` that can be checked in the [documentation](../opentelemetry/README.md).
55

66
## Available events
77

@@ -13,3 +13,4 @@ The full API doc is available [here](https://docs.datastax.com/en/drivers/csharp
1313
- **OnNodeStartAsync** - that is triggered when the node request starts
1414
- **OnNodeSuccessAsync** - that is triggered when the node level request finishes successfully.
1515
- **OnNodeErrorAsync** - that is triggered when the node request finishes unsuccessfully.
16+
- **OnNodeAbortedAsync** - that is triggered when the node request is aborted (e.g. pending speculative execution).
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.7.0" />
11+
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
12+
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.7.0" />
13+
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
14+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<ProjectReference Include="..\..\..\..\src\Cassandra\Cassandra.csproj" />
19+
<ProjectReference Include="..\..\..\..\src\Extensions\Cassandra.OpenTelemetry\Cassandra.OpenTelemetry.csproj" />
20+
</ItemGroup>
21+
22+
</Project>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@Api_HostAddress = http://localhost:5284
2+
3+
GET {{Api_HostAddress}}/weatherforecast/
4+
Accept: application/json
5+
6+
###
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Cassandra.Mapping;
2+
using Microsoft.AspNetCore.Mvc;
3+
4+
namespace Api.Controllers
5+
{
6+
[ApiController]
7+
[Route("[controller]")]
8+
public class WeatherForecastController : ControllerBase
9+
{
10+
private readonly ILogger<WeatherForecastController> _logger;
11+
private readonly IMapper _mapper;
12+
13+
public WeatherForecastController(ILogger<WeatherForecastController> logger, IMapper mapper)
14+
{
15+
_logger = logger;
16+
_mapper = mapper;
17+
}
18+
19+
[HttpGet(Name = "GetWeatherForecast")]
20+
public async Task<IEnumerable<WeatherForecast>> GetAsync()
21+
{
22+
var results = await _mapper.FetchAsync<WeatherForecast>().ConfigureAwait(false);
23+
return results.ToArray();
24+
}
25+
}
26+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
2+
using Cassandra;
3+
using Cassandra.Mapping;
4+
using Cassandra.OpenTelemetry;
5+
using OpenTelemetry.Resources;
6+
using OpenTelemetry.Trace;
7+
using ISession = Cassandra.ISession;
8+
9+
namespace Api
10+
{
11+
// This API creates a keyspace "weather" and table "weather_forecast" on startup (if they don't exist).
12+
public class Program
13+
{
14+
private const string CassandraContactPoint = "127.0.0.1";
15+
private const int CassandraPort = 9042;
16+
17+
public static async Task Main(string[] args)
18+
{
19+
var builder = WebApplication.CreateBuilder(args);
20+
21+
// Add services to the container.
22+
23+
builder.Services.AddOpenTelemetry()
24+
.ConfigureResource(resource => resource.AddService("Weather Forecast API"))
25+
.WithTracing(tracing => tracing
26+
.AddAspNetCoreInstrumentation()
27+
.AddSource(CassandraActivitySourceHelper.ActivitySourceName)
28+
//.AddOtlpExporter(opt => opt.Endpoint = new Uri("http://localhost:4317")) // uncomment if you want to use an OTPL exporter like Jaeger
29+
.AddConsoleExporter());
30+
builder.Services.AddSingleton<ICluster>(_ =>
31+
{
32+
var cassandraBuilder = Cluster.Builder()
33+
.AddContactPoint(CassandraContactPoint)
34+
.WithPort(CassandraPort)
35+
.WithOpenTelemetryInstrumentation(opts => opts.IncludeDatabaseStatement = true);
36+
return cassandraBuilder.Build();
37+
});
38+
builder.Services.AddSingleton<ISession>(provider =>
39+
{
40+
var cluster = provider.GetService<ICluster>();
41+
if (cluster == null)
42+
{
43+
throw new ArgumentNullException(nameof(cluster));
44+
}
45+
return cluster.Connect();
46+
});
47+
builder.Services.AddSingleton<IMapper>(provider =>
48+
{
49+
var session = provider.GetService<ISession>();
50+
if (session == null)
51+
{
52+
throw new ArgumentNullException(nameof(session));
53+
}
54+
55+
return new Mapper(session);
56+
});
57+
builder.Services.AddControllers();
58+
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
59+
builder.Services.AddEndpointsApiExplorer();
60+
builder.Services.AddSwaggerGen();
61+
62+
var app = builder.Build();
63+
64+
// force initialization of C* Session because if it fails then it can not be reused and the app should restart
65+
// (or the Session should be created before registering it on the service collection with some kind of retries if needed)
66+
var session = app.Services.GetService<ISession>();
67+
if (session == null)
68+
{
69+
throw new ArgumentNullException(nameof(session));
70+
}
71+
var mapper = app.Services.GetService<IMapper>();
72+
if (mapper == null)
73+
{
74+
throw new ArgumentNullException(nameof(mapper));
75+
}
76+
77+
await SetupWeatherForecastDb(session, mapper).ConfigureAwait(false);
78+
79+
// Configure the HTTP request pipeline.
80+
if (app.Environment.IsDevelopment())
81+
{
82+
app.UseSwagger();
83+
app.UseSwaggerUI();
84+
}
85+
86+
app.UseAuthorization();
87+
88+
89+
app.MapControllers();
90+
91+
await app.RunAsync().ConfigureAwait(false);
92+
}
93+
94+
private static async Task SetupWeatherForecastDb(ISession session, IMapper mapper)
95+
{
96+
await session.ExecuteAsync(
97+
new SimpleStatement(
98+
"CREATE KEYSPACE IF NOT EXISTS weather WITH REPLICATION = { 'class': 'SimpleStrategy', 'replication_factor': 1 }"))
99+
.ConfigureAwait(false);
100+
101+
await session.ExecuteAsync(
102+
new SimpleStatement(
103+
"CREATE TABLE IF NOT EXISTS weather.weather_forecast ( id uuid PRIMARY KEY, date timestamp, summary text, temp_c int )"))
104+
.ConfigureAwait(false);
105+
106+
var weatherForecasts = new WeatherForecast[]
107+
{
108+
new()
109+
{
110+
Date = new DateTime(2024, 9, 18),
111+
Id = Guid.Parse("9c9fdc2c-cf59-4ebe-93ac-c26e2fd1a56a"),
112+
Summary = "Generally clear. Areas of smoke and haze are possible, reducing visibility at times. High 30\u00b0C. Winds NE at 10 to 15 km/h.",
113+
TemperatureC = 30
114+
},
115+
new()
116+
{
117+
Date = new DateTime(2024, 9, 19),
118+
Id = Guid.Parse("b38b338f-56a8-4f56-a8f1-640d037ed8f6"),
119+
Summary = "Generally clear. Areas of smoke and haze are possible, reducing visibility at times. High 28\u00b0C. Winds SSW at 10 to 15 km/h.",
120+
TemperatureC = 28
121+
},
122+
new()
123+
{
124+
Date = new DateTime(2024, 9, 20),
125+
Id = Guid.Parse("04b8e06a-7f59-4921-888f-1a71a52ff7bb"),
126+
Summary = "Partly cloudy. High 24\u00b0C. Winds SW at 10 to 15 km/h.",
127+
TemperatureC = 24
128+
},
129+
new()
130+
{
131+
Date = new DateTime(2024, 9, 21),
132+
Id = Guid.Parse("036c25a6-e354-4613-8c27-1822ffb9e184"),
133+
Summary = "Rain. High 23\u00b0C. Winds SSW and variable. Chance of rain 70%.",
134+
TemperatureC = 23
135+
},
136+
new()
137+
{
138+
Date = new DateTime(2024, 9, 22),
139+
Id = Guid.Parse("ebd16ca8-ee00-42c1-9763-bb19dbf9a8e9"),
140+
Summary = "Morning showers. High 22\u00b0C. Winds SW and variable. Chance of rain 50%.",
141+
TemperatureC = 22
142+
},
143+
};
144+
145+
var tasks = weatherForecasts.Select(w => mapper.InsertAsync(w));
146+
await Task.WhenAll(tasks).ConfigureAwait(false);
147+
}
148+
}
149+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"$schema": "http://json.schemastore.org/launchsettings.json",
3+
"profiles": {
4+
"Api": {
5+
"commandName": "Project",
6+
"dotnetRunMessages": true,
7+
"launchBrowser": true,
8+
"launchUrl": "swagger",
9+
"applicationUrl": "http://localhost:5284",
10+
"environmentVariables": {
11+
"ASPNETCORE_ENVIRONMENT": "Development"
12+
}
13+
}
14+
}
15+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Cassandra.Mapping.Attributes;
2+
3+
namespace Api
4+
{
5+
[Table(Keyspace = "weather", Name = "weather_forecast")]
6+
public class WeatherForecast
7+
{
8+
[PartitionKey]
9+
[Column("id")]
10+
public Guid Id { get; set; }
11+
12+
[Column("date")]
13+
public DateTime? Date { get; set; }
14+
15+
[Column("temp_c")]
16+
public int TemperatureC { get; set; }
17+
18+
[Ignore]
19+
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
20+
21+
[Column("summary")]
22+
public string? Summary { get; set; }
23+
}
24+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.AspNetCore": "Warning"
6+
}
7+
}
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.AspNetCore": "Warning"
6+
}
7+
},
8+
"AllowedHosts": "*"
9+
}

0 commit comments

Comments
 (0)