Skip to content
Merged
4 changes: 2 additions & 2 deletions doc/features/opentelemetry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ The driver provides support for session and node level [traces](https://opentele

## Including OpenTelemetry instrumentation in your code

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

```csharp
var cluster = Cluster.Builder()
.AddContactPoint(Program.ContactPoint)
.WithSessionName(Program.SessionName)
.AddOpenTelemetryInstrumentation()
.WithOpenTelemetryInstrumentation()
.Build();
```

Expand Down
2 changes: 1 addition & 1 deletion doc/features/request-tracker/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Request Tracker

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`.\
An example of an `IRequestTracker` implementation is the extension package `Cassandra.OpenTelemetry` that can be checked in the [documentation](/doc/features/opentelemetry/README.md).
An example of an `IRequestTracker` implementation is the extension package `Cassandra.OpenTelemetry` that can be checked in the [documentation](../opentelemetry/README.md).

## Available events

Expand Down
22 changes: 22 additions & 0 deletions examples/OpenTelemetry/DistributedTracing/Api/Api.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Cassandra\Cassandra.csproj" />
<ProjectReference Include="..\..\..\..\src\Extensions\Cassandra.OpenTelemetry\Cassandra.OpenTelemetry.csproj" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions examples/OpenTelemetry/DistributedTracing/Api/Api.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@Api_HostAddress = http://localhost:5284

GET {{Api_HostAddress}}/weatherforecast/
Accept: application/json

###
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Cassandra.Mapping;
using Microsoft.AspNetCore.Mvc;

namespace Api.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
private readonly IMapper _mapper;

public WeatherForecastController(ILogger<WeatherForecastController> logger, IMapper mapper)
{
_logger = logger;
_mapper = mapper;
}

[HttpGet(Name = "GetWeatherForecast")]
public async Task<IEnumerable<WeatherForecast>> GetAsync()
{
var results = await _mapper.FetchAsync<WeatherForecast>().ConfigureAwait(false);
return results.ToArray();
}
}
}
149 changes: 149 additions & 0 deletions examples/OpenTelemetry/DistributedTracing/Api/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@

using Cassandra;
using Cassandra.Mapping;
using Cassandra.OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using ISession = Cassandra.ISession;

namespace Api
{
// This API creates a keyspace "weather" and table "weather_forecast" on startup (if they don't exist).
public class Program
{
private const string CassandraContactPoint = "127.0.0.1";
private const int CassandraPort = 9042;

public static async Task Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService("Weather Forecast API"))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddSource(CassandraActivitySourceHelper.ActivitySourceName)
//.AddOtlpExporter(opt => opt.Endpoint = new Uri("http://localhost:4317")) // uncomment if you want to use an OTPL exporter like Jaeger
.AddConsoleExporter());
builder.Services.AddSingleton<ICluster>(_ =>
{
var cassandraBuilder = Cluster.Builder()
.AddContactPoint(CassandraContactPoint)
.WithPort(CassandraPort)
.WithOpenTelemetryInstrumentation(opts => opts.IncludeDatabaseStatement = true);
return cassandraBuilder.Build();
});
builder.Services.AddSingleton<ISession>(provider =>
{
var cluster = provider.GetService<ICluster>();
if (cluster == null)
{
throw new ArgumentNullException(nameof(cluster));
}
return cluster.Connect();
});
builder.Services.AddSingleton<IMapper>(provider =>
{
var session = provider.GetService<ISession>();
if (session == null)
{
throw new ArgumentNullException(nameof(session));
}

return new Mapper(session);
});
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// force initialization of C* Session because if it fails then it can not be reused and the app should restart
// (or the Session should be created before registering it on the service collection with some kind of retries if needed)
var session = app.Services.GetService<ISession>();
if (session == null)
{
throw new ArgumentNullException(nameof(session));
}
var mapper = app.Services.GetService<IMapper>();
if (mapper == null)
{
throw new ArgumentNullException(nameof(mapper));
}

await SetupWeatherForecastDb(session, mapper).ConfigureAwait(false);

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseAuthorization();


app.MapControllers();

await app.RunAsync().ConfigureAwait(false);
}

private static async Task SetupWeatherForecastDb(ISession session, IMapper mapper)
{
await session.ExecuteAsync(
new SimpleStatement(
"CREATE KEYSPACE IF NOT EXISTS weather WITH REPLICATION = { 'class': 'SimpleStrategy', 'replication_factor': 1 }"))
.ConfigureAwait(false);

await session.ExecuteAsync(
new SimpleStatement(
"CREATE TABLE IF NOT EXISTS weather.weather_forecast ( id uuid PRIMARY KEY, date timestamp, summary text, temp_c int )"))
.ConfigureAwait(false);

var weatherForecasts = new WeatherForecast[]
{
new()
{
Date = new DateTime(2024, 9, 18),
Id = Guid.Parse("9c9fdc2c-cf59-4ebe-93ac-c26e2fd1a56a"),
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.",
TemperatureC = 30
},
new()
{
Date = new DateTime(2024, 9, 19),
Id = Guid.Parse("b38b338f-56a8-4f56-a8f1-640d037ed8f6"),
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.",
TemperatureC = 28
},
new()
{
Date = new DateTime(2024, 9, 20),
Id = Guid.Parse("04b8e06a-7f59-4921-888f-1a71a52ff7bb"),
Summary = "Partly cloudy. High 24\u00b0C. Winds SW at 10 to 15 km/h.",
TemperatureC = 24
},
new()
{
Date = new DateTime(2024, 9, 21),
Id = Guid.Parse("036c25a6-e354-4613-8c27-1822ffb9e184"),
Summary = "Rain. High 23\u00b0C. Winds SSW and variable. Chance of rain 70%.",
TemperatureC = 23
},
new()
{
Date = new DateTime(2024, 9, 22),
Id = Guid.Parse("ebd16ca8-ee00-42c1-9763-bb19dbf9a8e9"),
Summary = "Morning showers. High 22\u00b0C. Winds SW and variable. Chance of rain 50%.",
TemperatureC = 22
},
};

var tasks = weatherForecasts.Select(w => mapper.InsertAsync(w));
await Task.WhenAll(tasks).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"Api": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5284",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
24 changes: 24 additions & 0 deletions examples/OpenTelemetry/DistributedTracing/Api/WeatherForecast.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Cassandra.Mapping.Attributes;

namespace Api
{
[Table(Keyspace = "weather", Name = "weather_forecast")]
public class WeatherForecast
{
[PartitionKey]
[Column("id")]
public Guid Id { get; set; }

[Column("date")]
public DateTime? Date { get; set; }

[Column("temp_c")]
public int TemperatureC { get; set; }

[Ignore]
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

[Column("summary")]
public string? Summary { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.8.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\src\Extensions\Cassandra.OpenTelemetry\Cassandra.OpenTelemetry.csproj" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
</ItemGroup>

</Project>
61 changes: 61 additions & 0 deletions examples/OpenTelemetry/DistributedTracing/Client/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

namespace Client
{
internal class Program
{
private const string WeatherApiUri = "http://localhost:5284";

private const string WeatherForecastEndpointUri = WeatherApiUri + "/" + "WeatherForecast";

private static readonly ActivitySource ClientActivity = new ActivitySource("Weather Forecast Client Request");

static async Task Main(string[] args)
{
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(
serviceName: "Weather Forecast Client",
serviceVersion: "1.0.0"))
.AddSource(ClientActivity.Name)
//.AddOtlpExporter(opt => opt.Endpoint = new Uri("http://localhost:4317")) // uncomment if you want to use an OTPL exporter like Jaeger
.Build();
var cts = new CancellationTokenSource();
var task = Task.Run(async () =>
{
await Task.Delay(1000, cts.Token).ConfigureAwait(false);
using var httpClient = new HttpClient();
while (!cts.IsCancellationRequested)
{
try
{
using (var _ = ClientActivity.StartActivity(ActivityKind.Client))
{
await Console.Out.WriteLineAsync("TraceId: " + Activity.Current?.TraceId + Environment.NewLine + "Sending request.").ConfigureAwait(false);
var forecastResponse = await httpClient.GetAsync(WeatherForecastEndpointUri, cts.Token).ConfigureAwait(false);

if (forecastResponse.IsSuccessStatusCode)
{
var content = await forecastResponse.Content.ReadAsStringAsync(cts.Token).ConfigureAwait(false);
//var forecast = JsonSerializer.DeserializeAsync<WeatherForecast>(content).ConfigureAwait(false);
await Console.Out.WriteLineAsync("TraceId: " + Activity.Current?.TraceId + Environment.NewLine + content + Environment.NewLine).ConfigureAwait(false);
}
}

await Task.Delay(5000, cts.Token).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
}
}
});

await Console.Out.WriteLineAsync("Press enter to shut down.").ConfigureAwait(false);
await Console.In.ReadLineAsync().ConfigureAwait(false);
await cts.CancelAsync().ConfigureAwait(false);
await task.ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Client
{
public class WeatherForecast
{
public DateOnly Date { get; set; }

public int TemperatureC { get; set; }

public int TemperatureF { get; set; }

public string? Summary { get; set; }
}
}
Loading