Telemetry Tutorial#
scafctl emits all three OpenTelemetry signals — logs, traces, and metrics. By default, telemetry is silent (noop providers). When you point scafctl at an OTLP endpoint every signal is exported for backend analysis.
Overview#
| Signal | Default output | With --otel-endpoint |
|---|---|---|
| Logs | slog text/JSON → stderr (via --log-level) | Also batched via OTLP gRPC |
| Traces | Disabled (noop) | Exported via OTLP gRPC |
| Metrics | Prometheus /metrics (MCP server) | Also pushed via OTLP gRPC |
Configuration#
Flags (per-invocation)#
# Point at a local OTel Collector
scafctl run solution -f solution.yaml \
--otel-endpoint localhost:4317 \
--otel-insecure # disable TLS (development only)
# Override with environment variable instead
OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317 \
scafctl run solution -f solution.yaml# Point at a local OTel Collector
scafctl run solution -f solution.yaml `
--otel-endpoint localhost:4317 `
--otel-insecure # disable TLS (development only)
# Override with environment variable instead
$env:OTEL_EXPORTER_OTLP_ENDPOINT = 'localhost:4317'
scafctl run solution -f solution.yaml| Flag | Env override | Default | Description |
|---|---|---|---|
--otel-endpoint | OTEL_EXPORTER_OTLP_ENDPOINT | (none) | OTLP gRPC endpoint. When unset, tracing is disabled (noop). |
--otel-insecure | (none) | false | Skip TLS verification. Use in local dev only. |
Signals in Detail#
Logs#
Logs use the go-logr/logr interface throughout the codebase. The underlying
sink is a multiSink that fans out to:
- slog handler → stderr (text or JSON via
--log-format) - otellogr bridge → OTel
LoggerProvider→ OTLP when--otel-endpointis set
When no OTLP endpoint is configured, only the slog handler is active — no OTel
log records are emitted to stderr. When an active span is in scope, the OTLP log
record carries the trace_id and span_id automatically (correlation without
any extra code).
Control verbosity:
scafctl run solution -f solution.yaml \
--log-level debug \
--log-format json \
--otel-endpoint localhost:4317 \
--otel-insecurescafctl run solution -f solution.yaml `
--log-level debug `
--log-format json `
--otel-endpoint localhost:4317 `
--otel-insecureSee the Logging Tutorial for full flag reference.
Traces#
Spans are created at every major execution boundary:
| Subsystem | Span name | Key attributes |
|---|---|---|
| HTTP client | http.client.request (otelhttp) | http.method, http.url, http.status_code |
| Provider executor | provider.Execute | provider.name |
| Resolver executor | resolver.Execute | resolver.count |
| Resolver (single) | resolver.executeResolver | resolver.name, resolver.phase, resolver.sensitive |
| Solution loader | solution.Get | solution.path |
| Solution loader (bundle) | solution.GetWithBundle | solution.path |
| Solution (local FS) | solution.FromLocalFileSystem | solution.path |
| Solution (URL) | solution.FromURL | solution.url |
| Action workflow | action.Execute | action.count |
| Action (single) | action.executeAction | action.name |
| MCP tool call | mcp.tool | mcp.tool.name |
All spans propagate W3C traceparent / tracestate headers on outbound HTTP
requests via otelhttp.NewTransport, enabling distributed tracing when calling
instrumented backends.
Local trace debugging#
Without --otel-endpoint, tracing is disabled (noop). To inspect traces locally,
run a local collector such as otel-desktop-viewer
or Jaeger and point scafctl at it:
# Start local Jaeger (see examples/telemetry/ for Docker Compose)
OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317 \
scafctl run solution -f solution.yaml --otel-insecure# Start local Jaeger (see examples/telemetry/ for Docker Compose)
$env:OTEL_EXPORTER_OTLP_ENDPOINT = 'localhost:4317'
scafctl run solution -f solution.yaml --otel-insecureMetrics#
Metrics are exported via the Prometheus bridge exporter. The MCP server exposes
a /metrics endpoint (Prometheus scrape format). When --otel-endpoint is set,
the same metrics are also pushed via OTLP gRPC at the default push interval.
Key metrics:
| Metric | Type | Labels |
|---|---|---|
scafctl_provider_execution_duration_seconds | Histogram | provider_name, status |
scafctl_provider_execution_total | Counter | provider_name, status |
scafctl_http_client_duration_seconds | Histogram | status_code, url, method |
scafctl_http_client_requests_total | Counter | status_code, url, method |
scafctl_resolver_execution_duration_seconds | Histogram | resolver_name, status |
scafctl_resolver_executions_total | Counter | resolver_name, status |
scafctl_get_solution_time_histogram | Histogram | path |
Running Locally with Jaeger#
The examples/telemetry/ directory contains a ready-to-use Docker Compose stack
with Jaeger (all-in-one) and an OTel Collector.
Prerequisites#
- Docker and Docker Compose
Start the stack#
cd examples/telemetry
docker compose up -dServices started:
| Service | URL |
|---|---|
| Jaeger UI | http://localhost:16686 |
| OTel Collector (OTLP gRPC) | localhost:4317 |
| OTel Collector (OTLP HTTP) | localhost:4318 |
| Prometheus metrics (collector) | http://localhost:8888/metrics |
Run a traced command#
OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317 \
scafctl run solution -f examples/actions/hello-world.yaml \
--otel-insecure \
--log-level debug$env:OTEL_EXPORTER_OTLP_ENDPOINT = 'localhost:4317'
scafctl run solution -f examples/actions/hello-world.yaml `
--otel-insecure `
--log-level debugView traces in Jaeger#
- Open http://localhost:16686
- Select service scafctl from the dropdown
- Click Find Traces
- Click any trace to see the waterfall of spans
Tear down#
cd examples/telemetry
docker compose downRunning with a Production Collector#
Point scafctl at your organisation’s OTLP endpoint (no --otel-insecure flag
for TLS-enabled collectors):
scafctl run solution -f solution.yaml \
--otel-endpoint otel-collector.example.com:4317 \
--log-level infoscafctl run solution -f solution.yaml `
--otel-endpoint otel-collector.example.com:4317 `
--log-level infoUse the environment variable in CI/CD pipelines instead of flags:
# GitHub Actions
env:
OTEL_EXPORTER_OTLP_ENDPOINT: otel-collector.example.com:4317
steps:
- run: scafctl run solution -f solution.yaml --log-level infoResource Attributes#
Every span, metric, and log record produced by scafctl includes:
| Attribute | Value |
|---|---|
service.name | scafctl |
service.version | Build version (e.g. 1.2.3) |
commit | Git commit SHA |
build_time | Binary build timestamp |
os.type | Host OS (e.g. darwin, linux) |
host.name | Hostname |
Next Steps#
- Logging Tutorial — Log levels, formats, and file output
- MCP Server Tutorial
— Host the MCP server and scrape
/metrics - examples/telemetry/README.md — Local Jaeger + Collector setup