OpenTelemetry emit-side bridge for Tamp builds. One call in
Mainand every build emits structured traces + metrics to any OTLP-speaking backend — Honeycomb, Grafana Tempo, Jaeger, Datadog (via OTLP gateway), self-hosted tamp-beacon, or whatever OTLP receiver you point it at.
| Package | Status |
|---|---|
Tamp.Telemetry |
0.1.0 (initial) |
Tamp.Core already emits Activity spans (Tamp.Build for the build-level span, Tamp.Targets for per-target spans) + a Tamp.Build Meter with build-count / build-duration / peak-memory instruments — see ADR 0018 for the emission contract. What Tamp.Core does NOT do is decide where those signals go. By design — telemetry destination is an adopter choice (vendor lock-in is a real cost), and bundling an OTLP exporter into Core would force every adopter to take an OpenTelemetry transitive dependency whether they want telemetry or not.
Tamp.Telemetry is the bridge package that wires Tamp's existing diagnostic sources into an OpenTelemetry SDK + OTLP exporter. Add a PackageReference to it from a build that wants telemetry; leave it off for builds that don't.
using Tamp;
using Tamp.Telemetry;
class Build : TampBuild
{
public static int Main(string[] args)
{
using var telemetry = TampTelemetry.FromEnvironment();
// ↑ reads OTEL_EXPORTER_OTLP_ENDPOINT + OTEL_SERVICE_NAME et al.
// If no endpoint env is set, the SDK is configured WITHOUT an
// exporter — same code path local + CI, telemetry only emits
// when the env is wired.
return Execute<Build>(args);
}
// ... your build script ...
}That's it. Set OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.example.com/v1/traces in CI and every build sends spans + metrics. Local dev defaults to no-emit unless an adopter sets the env explicitly.
dotnet add package Tamp.TelemetryMulti-targets net8.0 / net9.0 / net10.0. Requires Tamp.Core >= 1.7.0.
Reads standard OpenTelemetry environment variables:
| Env var | Effect |
|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
Base endpoint URI (e.g. https://otel.example.com/v1/traces). Without this, the SDK is configured but no exporter attaches. |
OTEL_EXPORTER_OTLP_HEADERS |
Comma-separated k=v header pairs — typically auth: Authorization=Bearer abc123 |
OTEL_EXPORTER_OTLP_PROTOCOL |
http/protobuf (default) or grpc |
OTEL_SERVICE_NAME |
service.name resource attribute |
OTEL_SERVICE_VERSION |
service.version resource attribute |
This is the recommended shape for CI — your CI vendor supplies the env vars (actions/setup-otel style secret-injection), the build code stays vendor-neutral.
using var telemetry = TampTelemetry.Configure(t => t
.SetOtlpEndpoint("https://otel.example.com/v1/traces")
.SetOtlpHeaders($"Authorization=Bearer {ApiToken.Reveal()}")
.SetServiceName("dasbook")
.SetServiceVersion(Version)
.SetProtocol(OtlpProtocol.HttpProtobuf)
.AddResourceAttribute("team", "platform")
.AddResourceAttribute("ci.runner.pool", Environment.GetEnvironmentVariable("RUNNER_POOL") ?? "default"));Use this when configuration comes from [Parameter] / [Secret] build-script state rather than process env. Both modes are equivalent; the env-driven shape is just less typing for the common case.
- Build span (
Tamp.Build) — root span pertamp <target>invocation. Carries:build.targets,build.cli_version,host.os,host.os_version,host.arch,host.cpu_count,host.total_memory_bytes,dotnet.runtime,ci.vendor,ci.is_ci,build.project.name,build.project.area,build.project.name.source. Terminal tags on exit:build.targets_skipped,build.targets_not_run,build.commands_total,build.exit_code,outcome. - Target spans (
Tamp.Targets) — child span per target invocation. Carries:target.name,target.phase,target.depends_on,target.failure_mode,target.is_assured_after_failure,target.start_working_set_bytes,target.attempt,target.actions_count. Terminal tags on exit:target.duration_ms,target.peak_memory_bytes,target.cpu_time_ms,target.allocated_bytes,target.gen0_collections,target.commands_dispatched,outcome,outcome.reason.
tamp.builds.total— counter, tagged withoutcome(success/failure)tamp.build.duration_ms— histogram, tagged withoutcometamp.build.peak_memory_bytes— histogram, tagged withoutcome
Every emitted span and metric carries these — visible across all of Tamp.Telemetry's traffic:
service.name/service.version/service.instance.id(from your config)host.name—Environment.MachineNamehost.arch—x64/arm64/ etc.os.type—windows/linux/darwinci.vendor—github-actions/azure-devops/gitlab-ci/buildkite/teamcity/circleci/appveyor/travis/jenkins/ci-unknown/local
TampTelemetry.FromEnvironment() and TampTelemetry.Configure(...) return an IDisposable. Wrap in using so pending spans + metrics flush before the process exits — without disposal the OTLP exporter's batched queue can drop the build's terminal events.
using var telemetry = TampTelemetry.FromEnvironment();
return Execute<Build>(args);
// ↑ telemetry disposes here, flushing the build.end spanDisposing twice is safe — the underlying TracerProvider.Dispose / MeterProvider.Dispose are idempotent.
Anything that speaks OTLP/HTTP or OTLP/gRPC. Confirmed shapes:
- tamp-beacon — the self-hosted Tamp telemetry receiver (point at
https://<beacon>/v1/traces). - Honeycomb —
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io+OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=<api-key>. - Grafana Tempo / Mimir — point at your Tempo OTLP endpoint.
- Datadog — point at your local OTLP gateway (Datadog Agent listens on OTLP since DD agent 7.34+).
- Jaeger — Jaeger collector accepts OTLP since 1.35+ (
https://<jaeger>:4318/v1/traces).
- Secret-aware Activity tag redaction. ADR 0018's contract is that no Secret values should land in Activity tags; this satellite trusts that contract. A defense-in-depth
TraceProcessorthat scans tag values against a registered-secret list lands in 0.2.0. build_idcorrelation with--reporter=json. The NDJSONbuild.startevent fromTamp.Core 1.9.0's JsonBuildReporter has its ownbuild_idGUID; the OTLP build span has its ownTraceId. Cross-correlating them needs a smallTamp.Corechange — 0.2.0.- Auto-installer for an OTLP receiver. Out of scope: telemetry destination is the adopter's choice.
Releases follow the Tamp dogfood pattern: bump <Version> in Directory.Build.props, tag v<X.Y.Z>, GitHub Actions runs dotnet tamp Ci then dotnet tamp Push.
MIT. See LICENSE.