Meziantou.Framework.OpenTelemetryCollector 3.0.6

Prefix Reserved
dotnet add package Meziantou.Framework.OpenTelemetryCollector --version 3.0.6
                    
NuGet\Install-Package Meziantou.Framework.OpenTelemetryCollector -Version 3.0.6
                    
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="Meziantou.Framework.OpenTelemetryCollector" Version="3.0.6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Meziantou.Framework.OpenTelemetryCollector" Version="3.0.6" />
                    
Directory.Packages.props
<PackageReference Include="Meziantou.Framework.OpenTelemetryCollector" />
                    
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 Meziantou.Framework.OpenTelemetryCollector --version 3.0.6
                    
#r "nuget: Meziantou.Framework.OpenTelemetryCollector, 3.0.6"
                    
#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 Meziantou.Framework.OpenTelemetryCollector@3.0.6
                    
#: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=Meziantou.Framework.OpenTelemetryCollector&version=3.0.6
                    
Install as a Cake Addin
#tool nuget:?package=Meziantou.Framework.OpenTelemetryCollector&version=3.0.6
                    
Install as a Cake Tool

Meziantou.Framework.OpenTelemetryCollector

This package provides OpenTelemetry OTLP receiver endpoints for:

  • HTTP (/v1/logs, /v1/traces, /v1/metrics), using either application/x-protobuf or application/json (OTLP/JSON)
  • gRPC (LogsService, TraceService, MetricsService)

Responses use the same encoding as the request.

Compressed requests are handled by the ASP.NET Core request decompression middleware, so any encoding it supports (gzip, br, deflate, or a custom provider) works:

builder.Services.AddRequestDecompression();

var app = builder.Build();
app.UseRequestDecompression();
app.MapOpenTelemetryReceiverEndpoints();

The receiver API is abstract, so you can implement custom handling logic and register one or multiple receivers. A receiver derives from OpenTelemetryHandler (Meziantou.Framework.OpenTelemetryCollector namespace) and overrides HandleLogsAsync, HandleTracesAsync and HandleMetricsAsync.

For an in-memory implementation, use the Meziantou.Framework.OpenTelemetryCollector.InMemory package.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetryReceiver<MyReceiver>();

var app = builder.Build();
app.MapOpenTelemetryReceiverEndpoints();

app.Run();

Securing the endpoints

MapOpenTelemetryReceiverEndpoints returns an IEndpointConventionBuilder, so the usual endpoint conventions apply to every mapped endpoint, HTTP and gRPC alike. An OTLP receiver is an unauthenticated write endpoint, so restrict it unless it is only reachable from a trusted network:

app.MapOpenTelemetryReceiverEndpoints()
   .RequireAuthorization()
   .RequireRateLimiting("otlp");

Request sampling

Use request samplers to drop logs, traces, or metrics before they are dispatched to handlers:

public sealed class KeepOnlyNonEmptyRequestsSampler : OpenTelemetrySampler
{
    public override ValueTask<bool> ShouldSampleLogsAsync(OpenTelemetryHandlerContext context, ExportLogsServiceRequest request, CancellationToken cancellationToken)
        => ValueTask.FromResult(request.ResourceLogs.Count > 0);

    public override ValueTask<bool> ShouldSampleTracesAsync(OpenTelemetryHandlerContext context, ExportTraceServiceRequest request, CancellationToken cancellationToken)
        => ValueTask.FromResult(request.ResourceSpans.Count > 0);

    public override ValueTask<bool> ShouldSampleMetricsAsync(OpenTelemetryHandlerContext context, ExportMetricsServiceRequest request, CancellationToken cancellationToken)
        => ValueTask.FromResult(request.ResourceMetrics.Count > 0);
}

Then add the sampler to the options of the receiver:

builder.Services.AddOpenTelemetryReceiver<MyReceiver>(options =>
{
    options.Samplers.Add(new KeepOnlyNonEmptyRequestsSampler());
});

Trace tail filtering

Use tail sampling for traces when child spans can arrive before the root span. The collector buffers spans per trace id and evaluates the sampler when:

  • the root span is observed
  • or MaxTraceDuration is reached
builder.Services.AddOpenTelemetryReceiver<MyReceiver>(options =>
{
    options.Samplers.Add(new OpenTelemetryTailSampler
    {
        MaxTraceDuration = TimeSpan.FromSeconds(30),
        MaxBufferedSpansPerTrace = 5000,
        MaxBufferedSpans = 100_000,
        OverflowPolicy = OpenTelemetryTailBufferOverflowPolicy.DropWholeTrace,
        ShouldSample = static (context, _) =>
            ValueTask.FromResult(context.RootSpan?.Name?.Contains("critical", StringComparison.OrdinalIgnoreCase) is true),
    });
});

Traces that never receive their root span are evaluated by a background sweep, so they are released even when no other trace is received. The sweep runs every SweepInterval, which defaults to a quarter of MaxTraceDuration.

Once spans left the buffer they are dispatched to the handlers without a cancellation token: the originating request has already been answered, so aborting the dispatch would silently discard buffered data belonging to other requests.

OpenTelemetryTailBufferOverflowPolicy configures what happens to a trace that grows beyond MaxBufferedSpansPerTrace:

  • DropWholeTrace: drop the whole buffered trace
  • DropOldestSpans: keep newest spans
  • DropNewestSpans: keep oldest spans

MaxBufferedSpans is a separate limit, over all buffered traces. Reaching it evicts whole traces, largest first, so a trace that is within MaxBufferedSpansPerTrace is never truncated because of unrelated traffic, and new traces keep being accepted once the buffer is full.

A trace dropped as a whole — because it exceeded MaxBufferedSpansPerTrace under DropWholeTrace, or because it was evicted to free global capacity — is remembered for MaxTraceDuration, so the spans of the same trace received later are dropped too instead of being emitted as a fragment of the original trace.

Spans dropped because a trace exceeded its own limit are reported to the client that sent them (see below). Spans lost to a global eviction are only logged: they belong to traces buffered by earlier requests, so reporting them in the response of whichever request happened to trigger the eviction would describe records that request never sent.

A handler that throws while a buffered trace is dispatched is logged and skipped. Buffered traces are dispatched outside of the request that produced them, so the failure never fails the request that happened to trigger the flush, and never stops the remaining traces from being dispatched.

Reporting rejected records

Samplers and handlers can report the records they could not accept. They are sent back to the client in the OTLP partial_success response field:

public sealed class DropOversizedLogsHandler : OpenTelemetryHandler
{
    private const int MaxLogRecordsPerRequest = 100;

    public override ValueTask HandleLogsAsync(OpenTelemetryHandlerContext context, ExportLogsServiceRequest request, CancellationToken cancellationToken)
    {
        var rejected = Store(request);
        if (rejected > 0)
        {
            context.PartialSuccess.Reject(rejected, "The storage quota is exceeded");
        }

        return ValueTask.CompletedTask;
    }

    public override ValueTask HandleTracesAsync(OpenTelemetryHandlerContext context, ExportTraceServiceRequest request, CancellationToken cancellationToken)
        => ValueTask.CompletedTask;

    public override ValueTask HandleMetricsAsync(OpenTelemetryHandlerContext context, ExportMetricsServiceRequest request, CancellationToken cancellationToken)
        => ValueTask.CompletedTask;

    // Stores the log records that fit in the quota, and returns the number of the other ones
    private static int Store(ExportLogsServiceRequest request)
    {
        var count = request.ResourceLogs.Sum(resourceLogs => resourceLogs.ScopeLogs.Sum(scopeLogs => scopeLogs.LogRecords.Count));
        return Math.Max(0, count - MaxLogRecordsPerRequest);
    }
}

Spans dropped by OpenTelemetryTailSampler because a buffer limit is reached are reported the same way.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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.  net11.0 is compatible. 
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 Meziantou.Framework.OpenTelemetryCollector:

Package Downloads
Meziantou.Framework.OpenTelemetryCollector.InMemory

In-memory OpenTelemetry collector receiver for Meziantou.Framework.OpenTelemetryCollector

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.6 77 10/21/2026
3.0.5 100 10/14/2026
3.0.4 100 10/5/2026
3.0.3 106 9/30/2026
3.0.2 118 9/11/2026
3.0.1 127 9/6/2026
3.0.0 116 9/4/2026
2.1.0 120 8/21/2026
2.0.1 140 8/9/2026
2.0.0 139 7/5/2026
1.0.8 153 6/28/2026
1.0.7 150 6/22/2026
1.0.6 152 6/13/2026
1.0.5 145 5/23/2026
1.0.4 155 5/14/2026
1.0.3 147 5/6/2026
1.0.2 145 4/25/2026
1.0.1 157 3/29/2026
1.0.0 483 3/7/2026