> ## Documentation Index
> Fetch the complete documentation index at: https://docs.westyx.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# .NET SDK - OpenTelemetry

> One ActivitySource and one Meter named Westyx.Nexus - three spans, three instruments, inert until something subscribes.

The SDK exposes one `ActivitySource` and one `Meter`, both named `Westyx.Nexus`. The name is a constant, so nothing has to be spelled out twice:

```csharp theme={null}
builder.Services.AddOpenTelemetry()
    .WithTracing(t => t.AddSource(NexusDiagnostics.Name))
    .WithMetrics(m => m.AddMeter(NexusDiagnostics.Name));
```

That is the whole integration. `NexusDiagnostics` lives in `Westyx.Nexus` and ships in the core package, so no extra reference is needed.

## Spans

| Span                   | Raised for                                                                        |
| ---------------------- | --------------------------------------------------------------------------------- |
| `nexus.sync`           | Every sync attempt, tagged with the outcome                                       |
| `nexus.token_exchange` | A Workload Identity Federation session exchange or refresh, tagged `wif.provider` |
| `nexus.evaluate_ab`    | A targeted flag evaluation through the AB Testing add-on                          |

The three cover every network call the SDK makes on its own. A read from the cache raises nothing - it does no I/O.

## Instruments

| Instrument                       | Unit        | Tags                                           |
| -------------------------------- | ----------- | ---------------------------------------------- |
| `westyx.nexus.syncs`             | `{sync}`    | `outcome` = `ok` \| `not_modified` \| `failed` |
| `westyx.nexus.sync.duration`     | `s`         | the same `outcome`                             |
| `westyx.nexus.session.refreshes` | `{refresh}` | `wif.provider`, `outcome` = `ok` \| `failed`   |

`not_modified` is a `304`: the data was unchanged and the TTL was reset without a download. It is a success, and separating it from `ok` is what makes the ratio of the two readable as how often your configuration actually changes.

## Why they exist, and what they cost

Both are inert until something subscribes. An `ActivitySource` with no listener returns null from `StartActivity`, and an instrument nobody observes does no work - so an application that wants neither pays for neither, and there is no configuration flag to turn them off.

They exist because the SDK makes network calls on a background loop, which is the shape an operator cannot diagnose from outside. A sync that has been failing for an hour is invisible while the cache keeps serving: the application answers every request correctly, from data that is an hour old. `westyx.nexus.syncs{outcome=failed}` is the signal that says so, and it is the one worth alerting on.

## There is no cache-age gauge

An observable gauge belongs to the meter rather than to a client, so one gauge per client instance would register a duplicate instrument for every client an application creates. The age is:

```csharp theme={null}
var age = DateTimeOffset.UtcNow - client.SyncedAt;
```

Publish it under your own name, with the lifetime you actually have. An application with one client - the normal case - can register it once at startup.

## Correlating with your own traces

The spans are ordinary activities, so they nest under whatever ambient activity is current. On the dependency-injection path the initial sync runs during host startup, outside any request, and every later sync runs on the background loop - so those appear as roots. A sync you trigger yourself with `await client.SyncAsync()` nests under the request that called it, which is usually what you want when you are chasing a specific read.
