Arinti.Langfuse.Tracing.ContextPropagation 1.0.0-alpha.1

This is a prerelease version of Arinti.Langfuse.Tracing.ContextPropagation.
dotnet add package Arinti.Langfuse.Tracing.ContextPropagation --version 1.0.0-alpha.1
                    
NuGet\Install-Package Arinti.Langfuse.Tracing.ContextPropagation -Version 1.0.0-alpha.1
                    
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="Arinti.Langfuse.Tracing.ContextPropagation" Version="1.0.0-alpha.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Arinti.Langfuse.Tracing.ContextPropagation" Version="1.0.0-alpha.1" />
                    
Directory.Packages.props
<PackageReference Include="Arinti.Langfuse.Tracing.ContextPropagation" />
                    
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 Arinti.Langfuse.Tracing.ContextPropagation --version 1.0.0-alpha.1
                    
#r "nuget: Arinti.Langfuse.Tracing.ContextPropagation, 1.0.0-alpha.1"
                    
#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 Arinti.Langfuse.Tracing.ContextPropagation@1.0.0-alpha.1
                    
#: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=Arinti.Langfuse.Tracing.ContextPropagation&version=1.0.0-alpha.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Arinti.Langfuse.Tracing.ContextPropagation&version=1.0.0-alpha.1&prerelease
                    
Install as a Cake Tool

Arinti.Langfuse.Tracing.ContextPropagation

A small library that lets a single .NET process emit two coherent OpenTelemetry trace trees at the same time - one technical (Jaeger/Tempo/…), one semantic (Langfuse) - across async flow and across HTTP boundaries.

The mechanism lives in LangfuseTracing.cs and LangfuseAwarePropagator.cs. See the repository README for the project-level rationale; this document focuses on how a trace flows through the code at runtime.


Core idea in one sentence

Langfuse-relevant spans are created from a dedicated ActivitySource ("Langfuse"), and LangfuseTracing.StartActivity rewires their parent so each Langfuse span's parent is the nearest Langfuse ancestor - never a technical span - both in-process (via AsyncLocal) and across HTTP (via a langfuse entry on the W3C tracestate header).


How a trace flows

sequenceDiagram
	autonumber
	participant App as App code
	participant LT as LangfuseTracing
	participant ALocal as AsyncLocal<br/>(LangfuseCurrent)
	participant Act as Activity.Current
	participant Prop as LangfuseAwarePropagator
	participant HC as HttpClient<br/>(technical span)
	participant Srv as Server (next process)
	Note over App, Act: 1. Inside a technical span (e.g. "Jaeger Workflow")
	App ->> Act: AppSource.StartActivity("Jaeger Workflow")
	Note over App, Srv: 2. Start a Langfuse span
	App ->> LT: StartActivity("Langfuse Workflow")
	LT ->> Act: Read Activity.Current → nonLangfuseParent
	LT ->> ALocal: Read LangfuseCurrent → langfuseParent
	LT ->> LT: ResolveParentContext()<br/>1) local Langfuse ancestor?<br/>2) tracestate from upstream?<br/>3) fall back to non-Langfuse ancestor
	LT ->> Act: ActivitySource("Langfuse").StartActivity(parent = resolved)
	LT ->> ALocal: LangfuseCurrent = newSpan
	LT ->> Act: Activity.Current = nonLangfuseParent<br/>(hide Langfuse span from technical children<br/>and preserve the invariant that Current<br/>is always non-Langfuse when user code runs)
	Note over App, Srv: 3. Outbound HTTP - technical span only
	App ->> HC: HttpClient.GetAsync(...)
	HC ->> Act: starts HTTP span under nonLangfuseParent<br/>(not under Langfuse span - it's hidden)
	HC ->> Prop: Inject(activity, carrier, setter)
	Prop ->> ALocal: Read LangfuseCurrent.SpanId
	Prop ->> HC: traceparent: <technical parent><br/>tracestate: langfuse=p:<spanid>,...
	HC ->> Srv: HTTP request
	Note over Srv: 4. Server side
	Srv ->> Srv: AspNetCore parses tracestate header<br/>onto root ASP.NET Activity.TraceStateString
	Srv ->> LT: StartActivity("Langfuse Agent")
	LT ->> LT: No local Langfuse ancestor →<br/>rule 2: read tracestate via Activity.TraceStateString<br/>(walks up the parent chain),<br/>parent = remote span id from `langfuse` entry,<br/>trace id = current non-Langfuse ancestor
	Note over App, Srv: 5. Langfuse span ends
	LT -->> ALocal: ActivityStopped → pop LangfuseCurrent<br/>(via Langfuse.Parent custom property)

Parent resolution rules

flowchart TD
	Start([StartActivity called]) --> Q1{Local Langfuse<br/>ancestor in<br/>LangfuseCurrent?}
	Q1 -- yes --> R1[Parent = langfuseParent.Context<br/><i>in-process chain stays linked</i>]
	Q1 -- no --> Q2{Activity.Current's<br/>TraceStateString has<br/>a `langfuse` entry?}
	Q2 -- yes --> R2[Parent = remote span id from tracestate<br/>TraceId = non-Langfuse ancestor's<br/><i>cross-process chain stays linked</i>]
	Q2 -- no --> Q3{Any non-Langfuse<br/>ancestor at all?}
	Q3 -- yes --> R3[Parent = non-Langfuse ancestor's Context<br/><i>trace id stays continuous, span is a Langfuse root</i>]
	Q3 -- no --> R4[default ActivityContext<br/><i>brand-new trace</i>]

Usage

Minimal: a single Langfuse pipeline

// AddLangfuseTracing() adds the "Langfuse" source AND runs LangfuseTracing.Install() to
// register the activity listener and wrap DistributedContextPropagator.Current - idempotent.
TracerProvider langfuse = Sdk.CreateTracerProviderBuilder()
    .AddLangfuseTracing()
    .AddOtlpExporter(o => { /* Langfuse endpoint + auth */ })
    .Build();

// Anywhere in app code - same API surface as ActivitySource.StartActivity:
using Activity? span = LangfuseTracing.StartActivity("Langfuse Agent", ActivityKind.Internal);
span?.SetTag("langfuse.observation.type", "agent");
// ... work ...

Typical: two pipelines side-by-side (Langfuse + technical)

The library is designed for this layout - one process, two TracerProviders, each subscribed only to its own sources, so every span lands in exactly one backend.

ActivitySource appSource = new("MyApp");

// Technical pipeline: framework + app sources → Jaeger / Tempo / your APM
TracerProvider technical = Sdk.CreateTracerProviderBuilder()
    .ConfigureResource(r => r.AddService("my-app"))
    .AddHttpClientInstrumentation()
    .AddAspNetCoreInstrumentation()
    .AddSource(appSource.Name)
    .AddOtlpExporter(o => o.Endpoint = new Uri("http://localhost:4317"))
    .Build();

// Langfuse pipeline: only the "Langfuse" source → Langfuse OTLP endpoint
TracerProvider langfuse = Sdk.CreateTracerProviderBuilder()
    .ConfigureResource(r => r.AddService("my-app"))
    .AddLangfuseTracing()
    .AddOtlpExporter(o =>
    {
        o.Endpoint = new Uri("https://cloud.langfuse.com/api/public/otel/v1/traces");
        o.Protocol = OtlpExportProtocol.HttpProtobuf;
        o.Headers  = "Authorization=Basic <base64(pk:sk)>";
    })
    .Build();

// Now both trees record concurrently from the same call chain:
using (Activity? workflow = appSource.StartActivity("Workflow")) // technical
using (Activity? agent = LangfuseTracing.StartActivity("Agent")) // langfuse
using (Activity? internal_ = appSource.StartActivity("Internal step")) // technical, parented to Workflow
using (Activity? tool = LangfuseTracing.StartActivity("Tool")) // langfuse, parented to Agent
{
    // ... work - outbound HTTP calls carry both traceparent and `langfuse=p:<id>` tracestate.
}

Wiring up without a TracerProviderBuilder

If you are configuring tracing outside the OpenTelemetry SDK (for example in low-level tests that work directly with DistributedContextPropagator), call LangfuseTracing.Install() directly - it is idempotent and is exactly what AddLangfuseTracing() invokes under the hood.

LangfuseTracing.Install();

Technical spans keep using their own ActivitySources as normal. The two pipelines remain fully independent - they just happen to share a trace_id.


What this library is not

  • It does not configure exporters, samplers, or instrumentation - that is the host app's job.
  • It does not filter, drop, or rewrite spans on export. Routing is done by AddSource.
  • It is not a generalized multi-pipeline framework. It implements one specific pattern: a semantic subset tree alongside a technical tree, kept connected across async and HTTP.
Product Compatible and additional computed target framework versions.
.NET 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 (2)

Showing the top 2 NuGet packages that depend on Arinti.Langfuse.Tracing.ContextPropagation:

Package Downloads
Arinti.Langfuse.Tracing.MEAI

An IChatClient middleware that records a Langfuse `generation` observation per provider request, shaped per OTel GenAI semantic conventions (chat span). Plug into a ChatClientBuilder pipeline; works with any Microsoft.Extensions.AI adapter (OpenAI, Azure OpenAI, Mistral, Gemini, …).

Arinti.Langfuse.Tracing.SemanticKernel

OpenTelemetry tracing primitives for Microsoft.SemanticKernel — filters, hooks, and helpers that shape SK pipeline activity into the OTel GenAI span conventions Langfuse ingests natively.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-alpha.1 452 7/10/2026
1.0.0-alpha.0 185 5/21/2026