OpenTelemetry integration for distributed tracing, custom metrics, and OTLP exporters. ASP.NET Core, EF Core, and HttpClient instrumentation. Trigger: OpenTelemetry, tracing, metrics, OTLP, distributed tracing, observability.
From dotnet-ai-kitnpx claudepluginhub faysilalshareef/dotnet-ai-kit --plugin dotnet-ai-kitThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Delivers DB-free sandbox API regression tests for Next.js/Vitest to catch AI blind spots in self-reviewed code changes like API routes and backend logic.
ActivitySource for application-level tracingIMeterFactory for DI-friendly custom metrics// Program.cs
var serviceName = "{Domain}Service";
var serviceVersion = typeof(Program).Assembly
.GetName().Version?.ToString() ?? "1.0.0";
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource
.AddService(serviceName, serviceVersion: serviceVersion))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddSource(serviceName) // custom ActivitySource
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri(
builder.Configuration["Otlp:Endpoint"]
?? "http://localhost:4317");
}))
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddMeter(serviceName) // custom Meter
.AddOtlpExporter());
public sealed class OrderService(
IOrderRepository repository,
ILogger<OrderService> logger)
{
// Static ActivitySource per service
private static readonly ActivitySource ActivitySource =
new("{Domain}Service");
public async Task<Order> CreateOrderAsync(
CreateOrderCommand command, CancellationToken ct)
{
// Start a new activity (span)
using var activity = ActivitySource.StartActivity(
"CreateOrder", ActivityKind.Internal);
// Add tags (attributes) for searchability
activity?.SetTag("order.customer", command.CustomerName);
activity?.SetTag("order.item_count", command.Items.Count);
var order = Order.Create(command.CustomerName);
foreach (var item in command.Items)
{
using var itemActivity = ActivitySource.StartActivity(
"AddOrderItem");
itemActivity?.SetTag("product.id",
item.ProductId.ToString());
order.AddItem(item.ProductId, item.Quantity);
}
repository.Add(order);
activity?.SetTag("order.id", order.Id.ToString());
activity?.SetTag("order.total", order.Total);
activity?.SetStatus(ActivityStatusCode.Ok);
logger.LogInformation(
"Created order {OrderId}", order.Id);
return order;
}
}
public sealed class OrderMetrics
{
private readonly Counter<long> _ordersCreated;
private readonly Counter<long> _ordersFailed;
private readonly Histogram<double> _processingDuration;
private readonly UpDownCounter<long> _activeOrders;
public OrderMetrics(IMeterFactory meterFactory)
{
var meter = meterFactory.Create("{Domain}Service");
_ordersCreated = meter.CreateCounter<long>(
"orders.created",
unit: "orders",
description: "Number of orders created");
_ordersFailed = meter.CreateCounter<long>(
"orders.failed",
unit: "orders",
description: "Number of failed order operations");
_processingDuration = meter.CreateHistogram<double>(
"orders.processing.duration",
unit: "ms",
description: "Order processing duration");
_activeOrders = meter.CreateUpDownCounter<long>(
"orders.active",
unit: "orders",
description: "Currently active orders");
}
public void OrderCreated(string status) =>
_ordersCreated.Add(1,
new KeyValuePair<string, object?>("status", status));
public void OrderFailed(string reason) =>
_ordersFailed.Add(1,
new KeyValuePair<string, object?>("reason", reason));
public void RecordProcessingDuration(double durationMs) =>
_processingDuration.Record(durationMs);
public void OrderActivated() => _activeOrders.Add(1);
public void OrderCompleted() => _activeOrders.Add(-1);
}
// Register as singleton
builder.Services.AddSingleton<OrderMetrics>();
internal sealed class CreateOrderHandler(
IOrderRepository repository,
IUnitOfWork unitOfWork,
OrderMetrics metrics)
: IRequestHandler<CreateOrderCommand, Result<Guid>>
{
public async Task<Result<Guid>> Handle(
CreateOrderCommand request, CancellationToken ct)
{
var sw = Stopwatch.StartNew();
try
{
var order = Order.Create(request.CustomerName);
repository.Add(order);
await unitOfWork.SaveChangesAsync(ct);
metrics.OrderCreated("success");
metrics.OrderActivated();
return Result<Guid>.Success(order.Id);
}
catch (Exception ex)
{
metrics.OrderFailed(ex.GetType().Name);
throw;
}
finally
{
metrics.RecordProcessingDuration(
sw.Elapsed.TotalMilliseconds);
}
}
}
// Set baggage for cross-service context propagation
Activity.Current?.SetBaggage("tenant.id", tenantId);
Activity.Current?.SetBaggage("user.id", userId);
// Read baggage in downstream service
var tenantId = Activity.Current?.GetBaggageItem("tenant.id");
// appsettings.Development.json
{
"Otlp": {
"Endpoint": "http://localhost:4317"
}
}
// Run Aspire Dashboard locally
// docker run --rm -p 18888:18888 -p 4317:18889 \
// mcr.microsoft.com/dotnet/aspire-dashboard:latest
// OTLP (default — works with Jaeger, Grafana Tempo, Aspire Dashboard)
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("http://localhost:4317");
options.Protocol = OtlpExportProtocol.Grpc;
})
// Console (development debugging)
.AddConsoleExporter()
// Prometheus (metrics only)
.WithMetrics(metrics => metrics
.AddPrometheusExporter());
app.MapPrometheusScrapingEndpoint();
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
<PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<!-- Development -->
<PackageReference Include="OpenTelemetry.Exporter.Console" />
SetStatus(Error))OpenTelemetry packages in .csprojAddOpenTelemetry() in Program.csActivitySource usage in service classesIMeterFactory or Meter usageAddOtlpExporter configurationAddOpenTelemetry() with tracing and metricsActivitySource for application-level tracingIMeterFactory for business metrics| Signal | Use |
|---|---|
| Traces | Request flow across services, latency analysis |
| Metrics | Counters (orders/sec), histograms (latency p99) |
| Logs | Detailed event context (already via Serilog) |
| Baggage | Cross-service context (tenant ID, user ID) |