PulseURL.Client 0.2.0

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package PulseURL.Client --version 0.2.0
                    
NuGet\Install-Package PulseURL.Client -Version 0.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PulseURL.Client" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PulseURL.Client" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="PulseURL.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PulseURL.Client --version 0.2.0
                    
#r "nuget: PulseURL.Client, 0.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PulseURL.Client@0.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PulseURL.Client&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=PulseURL.Client&version=0.2.0
                    
Install as a Cake Tool

PulseURL .NET Client

.NET License

.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
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

  1. Fire-and-Forget - Logging never blocks your application
  2. Circuit Breaker - Automatically stops trying when service is down
  3. Batching - Reduces gRPC overhead by 10x+ in high-throughput scenarios
  4. Metrics - Uses System.Diagnostics.Metrics (works with OpenTelemetry)
  5. 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 PulseURL
  • dropped - Events dropped due to buffer overflow
  • in_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:

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

  1. Enable Batching: Set BatchSize to 100-500
  2. Adjust Buffer: Increase BufferSize if you see drops
  3. Sampling: Use SampleRate < 1.0 to sample traffic
  4. Circuit Breaker: Protects your app when PulseURL is down

Troubleshooting

Events Not Appearing

  1. 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, increase BufferSize
    • If in_queue is growing, check service latency
  2. Check PulseURL service is running: curl http://pulseurl-service:9090/health

  3. Check circuit breaker state: client.IsCircuitBreakerOpen

  4. Check metrics: pulseurl.events.dropped and pulseurl.errors.total

  5. Enable 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 CircuitBreakerThreshold or CircuitBreakerDuration

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

Support

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on PulseURL.Client:

Package Downloads
PulseURL.AspNetCore

ASP.NET Core middleware for PulseURL traffic event logging

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0 121 2/1/2026
0.2.0 120 1/25/2026
0.2.0-beta 113 1/25/2026
0.1.1-beta 115 1/25/2026