PulseURL.AspNetCore
0.3.0
Prefix Reserved
dotnet add package PulseURL.AspNetCore --version 0.3.0
NuGet\Install-Package PulseURL.AspNetCore -Version 0.3.0
<PackageReference Include="PulseURL.AspNetCore" Version="0.3.0" />
<PackageVersion Include="PulseURL.AspNetCore" Version="0.3.0" />
<PackageReference Include="PulseURL.AspNetCore" />
paket add PulseURL.AspNetCore --version 0.3.0
#r "nuget: PulseURL.AspNetCore, 0.3.0"
#:package PulseURL.AspNetCore@0.3.0
#addin nuget:?package=PulseURL.AspNetCore&version=0.3.0
#tool nuget:?package=PulseURL.AspNetCore&version=0.3.0
PulseURL .NET Client
.NET client library and ASP.NET Core middleware for PulseURL - a lightweight, purpose-built traffic event logging service for Kubernetes.
Features
β¨ v0.1.0 Features
- π Async Buffering - Non-blocking event queue using
Channel<T> - π Retry Logic - Exponential backoff with Polly
- β‘ Circuit Breaker - Prevents cascading failures when service is unavailable
- π¦ Batch Streaming - Automatic batching for 10x+ throughput
- π Observability - Automatic stats logging +
System.Diagnostics.Metrics - π― Sampling - Configurable sample rate (0.0 to 1.0)
- π¨ Multi-Targeting - Supports .NET 6.0, 8.0, and 9.0
- π ASP.NET Core Middleware - One-line integration
- π·οΈ Custom Labels - Add metadata to all events
- π« Path Skipping - Exclude health checks, metrics endpoints, etc.
- π Client IP Detection - Supports X-Forwarded-For headers
Quick Start
Installation
# Install the client library
dotnet add package PulseURL.Client
# For ASP.NET Core middleware
dotnet add package PulseURL.AspNetCore
ASP.NET Core Integration (Recommended)
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.
Manual Client Usage
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);
Configuration
Client Options
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
};
Middleware Options
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
});
appsettings.json Configuration
{
"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
Architecture
Client Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Your ASP.NET Core App β
β β
β ββββββββββββββββ βββββββββββββββββββββ β
β β Middleware ββββββββββΆβ PulseURL Client β β
β ββββββββββββββββ βββββββββββ¬ββββββββββ β
β β β
β ββββββββββββΌββββββββββ β
β β Channel<Event> β β
β β (Async Buffer) β β
β ββββββββββββ¬ββββββββββ β
β β β
β ββββββββββββΌββββββββββ β
β β Background Worker β β
β β - Batching β β
β β - Retry β β
β β - Circuit Breaker β β
β ββββββββββββ¬ββββββββββ β
βββββββββββββββββββββββββββββββββββββββββΌββββββββββββββ
β gRPC
βΌ
βββββββββββββββββββββ
β PulseURL Service β
β :9090 β
βββββββββββββββββββββ
Key Design Decisions
- Fire-and-Forget - Logging never blocks your application
- Circuit Breaker - Automatically stops trying when service is down
- Batching - Reduces gRPC overhead by 10x+ in high-throughput scenarios
- Metrics - Uses
System.Diagnostics.Metrics(works with OpenTelemetry) - Multi-Targeting - Single NuGet package works on .NET 6, 8, and 9
Observability
Built-in Stats Logging
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 queue
Drop 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:
- β Event sampling effectiveness
- β Buffer overflow detection
- β Queue backpressure
- β Processing throughput
System.Diagnostics.Metrics
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?
- β Zero dependencies - Built into .NET 6+ runtime
- β OpenTelemetry compatible - Standard OTEL metric types
- β Efficient - Minimal overhead, designed for high-throughput scenarios
- β Vendor neutral - Works with any metrics backend
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:
With OpenTelemetry (Recommended)
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.)
});
With Prometheus
// 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
Direct Listening (For Testing)
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
Examples
C# Minimal API
See examples/BasicWebApi for a complete example with:
- Health endpoints (skipped)
- REST API endpoints (logged)
- Route parameters
- Custom labels
- Error handling
F# Web API
See examples/FSharpWebApi (coming soon)
Performance
Benchmarks
- Middleware Overhead: < 1ms per request
- Throughput (single event): 10,000 req/sec
- Throughput (batch mode): 50,000+ events/sec
- Memory: Stable under load (~50MB for 10k events buffered)
Tips for High-Throughput Scenarios
- Enable Batching: Set
BatchSizeto 100-500 - Adjust Buffer: Increase
BufferSizeif you see drops - Sampling: Use
SampleRate < 1.0to sample traffic - Circuit Breaker: Protects your app when PulseURL is down
Troubleshooting
Events Not Appearing
Check stats logs - Look for periodic stats in your application logs:
PulseURL stats: created=100, queued=100, sent=0, dropped=0, in_queue=100- If
sent=0, check PulseURL service health - If
dropped > 0, increaseBufferSize - If
in_queueis growing, check service latency
- If
Check PulseURL service is running:
curl http://pulseurl-service:9090/healthCheck circuit breaker state:
client.IsCircuitBreakerOpenCheck metrics:
pulseurl.events.droppedandpulseurl.errors.totalEnable error logging:
options.LogErrors = true
High Memory Usage
- Reduce
BufferSize(default: 1000) - Lower
SampleRate(e.g., 0.5 for 50%) - Ensure PulseURL service is healthy
Circuit Breaker Keeps Opening
- PulseURL service may be down or slow
- Check service logs
- Increase
CircuitBreakerThresholdorCircuitBreakerDuration
Development
Building
dotnet build
Testing
# 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
Running the Example
cd examples/BasicWebApi
dotnet run
# In another terminal
curl http://localhost:5000/weatherforecast
Project Structure
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
Roadmap
v0.1.0 (Current) β
- Core client with async buffering
- Retry logic + circuit breaker
- Batch streaming
- Built-in stats logging and observability
- Metrics (System.Diagnostics.Metrics)
- ASP.NET Core middleware
- Multi-targeting (.NET 6, 8, 9)
- C# example
- Integration tests with Kubernetes
- F# example
v0.2.0 (Next)
- F# example
- Unit tests with full coverage
- Performance benchmarks
- NuGet package publishing
v2.0 (Future)
- YARP middleware
- SignalR hub filter
- OpenTelemetry integration
- Source generators for zero-allocation
Contributing
Contributions welcome! This is a learning project following the patterns from pulseurl-go.
License
MIT License - see LICENSE for details
Related Projects
- pulseurl - Main service (Go)
- pulseurl-go - Go client and Gin middleware
Support
- GitHub Issues: https://github.com/pulseurl/pulseurl-dotnet/issues
- Documentation: See docs/PLANNING.md
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net6.0
- Microsoft.Extensions.Configuration.Binder (>= 8.0.0)
- PulseURL.Client (>= 0.3.0)
-
net8.0
- Microsoft.Extensions.Configuration.Binder (>= 8.0.0)
- PulseURL.Client (>= 0.3.0)
-
net9.0
- Microsoft.Extensions.Configuration.Binder (>= 8.0.0)
- PulseURL.Client (>= 0.3.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.3.0 | 95 | 2/1/2026 |
| 0.2.0 | 103 | 1/25/2026 |
| 0.2.0-beta | 100 | 1/25/2026 |
| 0.1.1-beta | 96 | 1/25/2026 |