gRPC client library for PulseURL traffic event logging service
$ dotnet add package PulseURL.Client.NET client library and ASP.NET Core middleware for PulseURL - a lightweight, purpose-built traffic event logging service for Kubernetes.
Channel<T>System.Diagnostics.Metrics# Install the client library
dotnet add package PulseURL.Client
# For ASP.NET Core middleware
dotnet add package PulseURL.AspNetCore
using PulseURL.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
// Add PulseURL client
builder.Services.AddPulseURL(options =>
{
options.ServiceUrl = "pulseurl-service:9090";
options.EnableMetrics = true;
});
var app = builder.Build();
// Add PulseURL middleware (logs all HTTP requests automatically)
app.UsePulseURL(options =>
{
options.SkipPaths = new HashSet<string> { "/health", "/metrics" };
options.CustomLabels = new Dictionary<string, string>
{
["app"] = "my-api",
["environment"] = "production"
};
});
app.MapGet("/", () => "Hello World!");
app.Run();
That's it! All HTTP requests are now logged to PulseURL with automatic retry, circuit breaker, and batch streaming.
using PulseURL.Client;
// Create client
var options = new PulseURLOptions
{
ServiceUrl = "localhost:9090",
BatchSize = 100,
EnableMetrics = true
};
using var client = new PulseURLClient(options);
// Log events (async, non-blocking)
var eventBuilder = new TrafficEventBuilder()
.WithService("user-api")
.WithUrl("/api/users/123")
.WithRoute("/api/users/{id}")
.WithHttpMethod("GET")
.WithStatusCode(200)
.WithDuration(TimeSpan.FromMilliseconds(45))
.AddLabel("region", "us-east-1");
client.LogEvent(eventBuilder.Build());
// Or log synchronously
await client.LogEventAsync(eventBuilder.Build());
// Batch logging for high throughput
var events = new List<Pulseurl.TrafficEvent> { /* ... */ };
await client.LogEventBatchAsync(events);
var options = new PulseURLOptions
{
// Required
ServiceUrl = "pulseurl-service:9090",
// Performance
BufferSize = 1000, // Event queue size
FlushInterval = TimeSpan.FromSeconds(1),
Timeout = TimeSpan.FromSeconds(2),
// Batching (for high throughput)
BatchSize = 100, // Events per batch (0 = disable)
BatchTimeout = TimeSpan.FromMilliseconds(500),
// Resilience
MaxRetries = 2,
CircuitBreakerThreshold = 5, // Failures before opening
CircuitBreakerDuration = TimeSpan.FromSeconds(30),
// Sampling
SampleRate = 1.0, // 0.0 to 1.0 (1.0 = 100%)
// Observability
EnableMetrics = true
};
app.UsePulseURL(options =>
{
// Paths to skip logging
options.SkipPaths = new HashSet<string>
{
"/health",
"/metrics",
"/swagger"
};
// Custom labels for all events
options.CustomLabels = new Dictionary<string, string>
{
["app"] = "my-api",
["version"] = "v1.2.0",
["environment"] = "production"
};
// Service identification
options.ServiceName = "my-api"; // Default: SERVICE_NAME env var or "unknown-service"
options.Namespace = "production"; // Default: POD_NAMESPACE env var or "default"
// What to capture
options.CaptureClientIp = true;
options.CaptureUserAgent = true;
// Error handling
options.LogErrors = false; // Fire-and-forget by default
});
{
"PulseURL": {
"ServiceUrl": "pulseurl-service:9090",
"BufferSize": 1000,
"BatchSize": 100,
"SampleRate": 1.0,
"EnableMetrics": true,
"CircuitBreakerThreshold": 5,
"CircuitBreakerDuration": "00:00:30"
}
}
Then register with:
builder.Services.AddPulseURL(); // Reads from "PulseURL" section
┌─────────────────────────────────────────────────────┐
│ Your ASP.NET Core App │
│ │
│ ┌──────────────┐ ┌───────────────────┐ │
│ │ Middleware │────────▶│ PulseURL Client │ │
│ └──────────────┘ └─────────┬─────────┘ │
│ │ │
│ ┌──────────▼─────────┐ │
│ │ Channel<Event> │ │
│ │ (Async Buffer) │ │
│ └──────────┬─────────┘ │
│ │ │
│ ┌──────────▼─────────┐ │
│ │ Background Worker │ │
│ │ - Batching │ │
│ │ - Retry │ │
│ │ - Circuit Breaker │ │
│ └──────────┬─────────┘ │
└───────────────────────────────────────┼─────────────┘
│ gRPC
▼
┌───────────────────┐
│ PulseURL Service │
│ :9090 │
└───────────────────┘
System.Diagnostics.Metrics (works with OpenTelemetry)The client automatically logs comprehensive stats every 30 seconds at Information level:
PulseURL stats: created=5000, queued=5000, sent=4739, dropped=261, in_queue=0
Metrics Explained:
created - Total events created (before sampling)queued - Events accepted into the queue (TryWrite succeeded)sent - Events successfully delivered to PulseURLdropped - Events dropped due to buffer overflowin_queue - Real-time count of events waiting in queueDrop Warnings: When events are dropped due to buffer overflow, warnings are logged:
Channel dropped event (buffer_size=1000, url=/api/users/123)
This provides production-ready visibility into:
The client exposes standardized metrics via System.Diagnostics.Metrics (introduced in .NET 6) for integration with observability platforms like Prometheus, Grafana, Azure Monitor, Datadog, and more.
Why System.Diagnostics.Metrics?
Available Metrics:
| Metric Name | Type | Unit | Description | Tags |
|---|---|---|---|---|
pulseurl.events.queued | Counter | {event} | Total events queued for sending | - |
pulseurl.events.sent | Counter | {event} | Total events successfully sent | - |
pulseurl.events.dropped | Counter | {event} | Events dropped (buffer full) | - |
pulseurl.errors.total | Counter | {error} | Total send errors | exception.type |
pulseurl.circuit_breaker.state_changes | Counter | {change} | Circuit breaker transitions | state (open/closed) |
pulseurl.send.duration | Histogram | ms | Send operation latency | success (true/false) |
pulseurl.queue.size | ObservableGauge | {event} | Current queue depth | - |
Consuming Metrics:
using OpenTelemetry.Metrics;
var builder = WebApplication.CreateBuilder(args);
// Add OpenTelemetry with PulseURL metrics
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddMeter("PulseURL.Client"); // Enable PulseURL metrics
metrics.AddPrometheusExporter(); // Export to Prometheus
// or
metrics.AddOtlpExporter(); // Export via OTLP (Grafana, etc.)
});
// Add Prometheus endpoint
app.MapPrometheusScrapingEndpoint();
// PulseURL metrics automatically available at /metrics endpoint:
// pulseurl_events_queued_total 1234
// pulseurl_events_sent_total 1230
// pulseurl_events_dropped_total 4
// pulseurl_send_duration_ms_bucket{success="true",le="50"} 1150
using System.Diagnostics.Metrics;
using var listener = new MeterListener();
listener.InstrumentPublished = (instrument, meterListener) =>
{
if (instrument.Meter.Name == "PulseURL.Client")
{
meterListener.EnableMeasurementEvents(instrument);
}
};
listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state) =>
{
Console.WriteLine($"{instrument.Name}: {measurement}");
});
listener.Start();
Meter Name: PulseURL.Client
Version: 0.1.0
See examples/BasicWebApi for a complete example with:
See examples/FSharpWebApi (coming soon)
BatchSize to 100-500BufferSize if you see dropsSampleRate < 1.0 to sample trafficCheck stats logs - Look for periodic stats in your application logs:
PulseURL stats: created=100, queued=100, sent=0, dropped=0, in_queue=100
sent=0, check PulseURL service healthdropped > 0, increase BufferSizein_queue is growing, check service latencyCheck PulseURL service is running: curl http://pulseurl-service:9090/health
Check circuit breaker state: client.IsCircuitBreakerOpen
Check metrics: pulseurl.events.dropped and pulseurl.errors.total
Enable error logging: options.LogErrors = true
BufferSize (default: 1000)SampleRate (e.g., 0.5 for 50%)CircuitBreakerThreshold or CircuitBreakerDurationdotnet build
# Unit tests
dotnet test
# Integration tests (requires Kubernetes cluster)
cd tests/integration
./test-middleware.sh # Run all tests
./test-middleware.sh baseline # Run specific test
./test-middleware.sh buffer-overflow # Test drop handling
# See docs/TESTING.md for detailed testing guide
cd examples/BasicWebApi
dotnet run
# In another terminal
curl http://localhost:5000/weatherforecast
pulseurl-dotnet/
├── src/
│ ├── PulseURL.Client/ # Core gRPC client
│ └── PulseURL.AspNetCore/ # ASP.NET Core middleware
├── examples/
│ ├── BasicWebApi/ # C# example
│ └── FSharpWebApi/ # F# example (coming soon)
├── tests/
│ ├── PulseURL.Client.Tests/
│ └── PulseURL.AspNetCore.Tests/ (coming soon)
├── proto/
│ └── traffic.proto # Protocol buffer definitions
└── docs/
└── PLANNING.md # Development roadmap
Contributions welcome! This is a learning project following the patterns from pulseurl-go.
MIT License - see LICENSE for details