Skip to content

Commit b3e89e4

Browse files
loispostulaolblak
andauthored
docs: add OpenTelemetry telemetry documentation and blog post (#2809)
Signed-off-by: Loïs Postula <lois@postu.la> Co-authored-by: Olivier Vernin <olivier@vernin.me>
1 parent c0d51bb commit b3e89e4

File tree

3 files changed

+340
-0
lines changed

3 files changed

+340
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
title: "OpenTelemetry Tracing in Updatecli"
3+
date: 2026-04-01T00:00:00+00:00
4+
draft: false
5+
weight: 50
6+
images: [""]
7+
contributors: ["lpostula"]
8+
---
9+
10+
Updatecli now supports [**OpenTelemetry**](https://opentelemetry.io/) tracing. Set one environment variable and you get a full trace of every pipeline run — sources, conditions, targets, and HTTP calls to external APIs.
11+
12+
![Updatecli trace in Grafana Tempo](otel-trace-tempo.png)
13+
14+
The trace above shows a `pipeline diff` run. You can see the prepare phase, a single pipeline with its resources, and the individual GitHub GraphQL calls. Most of the 12 seconds is network time.
15+
16+
## How to use it
17+
18+
Point `OTEL_EXPORTER_OTLP_ENDPOINT` at any OTLP-compatible backend:
19+
20+
```bash
21+
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
22+
updatecli pipeline diff --config manifest.yaml
23+
```
24+
25+
That's it. No flags, no config file changes. If the variable is not set, tracing is disabled and there is no overhead.
26+
27+
Works with Jaeger, Grafana Tempo, Datadog, Honeycomb, or any other OTLP backend. For a quick local setup:
28+
29+
```bash
30+
docker run --rm -p 4317:4317 -p 16686:16686 \
31+
jaegertracing/all-in-one:latest
32+
```
33+
34+
Then open `http://localhost:16686`.
35+
36+
## What you get
37+
38+
Each span carries attributes like pipeline name, resource kind, result status, and changed files. HTTP requests to GitHub, Docker registries, and Helm repos show up automatically with method, URL, and status code.
39+
40+
Errors on spans are sanitized — URL-embedded credentials get stripped before reaching the backend.
41+
42+
Full reference in the [**Telemetry documentation**](/docs/core/telemetry/).
43+
44+
## Links
45+
46+
- [**OpenTelemetry**](https://opentelemetry.io/)
47+
- [**Telemetry documentation**](/docs/core/telemetry/)
48+
- [**Jaeger**](https://www.jaegertracing.io/)
49+
- [**Grafana Tempo**](https://grafana.com/oss/tempo/)
224 KB
Loading
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
---
2+
title: "Telemetry"
3+
description: "Observe Updatecli pipeline execution with OpenTelemetry tracing"
4+
lead: "Observe Updatecli pipeline execution with OpenTelemetry tracing"
5+
date: 2026-04-01T00:00:00+00:00
6+
lastmod: 2026-04-01T00:00:00+00:00
7+
draft: false
8+
images: []
9+
menu:
10+
docs:
11+
parent: "core"
12+
weight: 190
13+
toc: true
14+
---
15+
// <!-- Required for asciidoctor -->
16+
:toc:
17+
// Set toclevels to be at least your hugo [markup.tableOfContents.endLevel] config key
18+
:toclevels: 4
19+
20+
== Description
21+
22+
Updatecli supports distributed tracing via OpenTelemetry. When enabled, every pipeline run emits spans that show exactly where time is spent and which resources succeeded or failed.
23+
24+
Tracing is opt-in and disabled by default. When tracing initialization fails, Updatecli continues normally — tracing never blocks execution.
25+
26+
== Enabling Tracing
27+
28+
Configuration is done entirely via standard OpenTelemetry environment variables. No Updatecli-specific flags are required.
29+
30+
[cols="1,3",options="header"]
31+
|===
32+
| Variable | Description
33+
34+
| `OTEL_TRACES_EXPORTER`
35+
| Selects the exporter. Supported values: `otlp` (gRPC), `otlphttp` (HTTP/protobuf), `console` or `stdout` (prints to stdout).
36+
37+
| `OTEL_EXPORTER_OTLP_ENDPOINT`
38+
| Collector endpoint for all signals (e.g. `http://localhost:4317` for gRPC, `http://localhost:4318` for HTTP).
39+
40+
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`
41+
| Override the endpoint specifically for traces.
42+
|===
43+
44+
When neither `OTEL_TRACES_EXPORTER` nor any endpoint variable is set, tracing is fully disabled (noop). When only an endpoint is set with no exporter name, Updatecli infers `otlp` (gRPC).
45+
46+
NOTE: The `console` and `stdout` exporter values are aliases — both print spans to stdout. This is useful for verifying that tracing works, but the output is very verbose (one JSON block per span). For day-to-day use, prefer sending traces to a backend like Jaeger or Grafana Tempo.
47+
48+
== Span Hierarchy
49+
50+
Each Updatecli run produces a trace with the following span structure:
51+
52+
----
53+
updatecli (root)
54+
├── updatecli.prepare
55+
│ ├── updatecli.load_configurations
56+
│ ├── updatecli.init_scm
57+
│ └── updatecli.autodiscovery
58+
└── updatecli.run
59+
├── updatecli.pipeline (one per pipeline)
60+
│ └── updatecli.resource (one per source, condition, or target)
61+
├── updatecli.push_commits (apply mode only)
62+
├── updatecli.run_actions
63+
└── updatecli.prune_scm_branches (apply mode with branch cleanup enabled)
64+
----
65+
66+
NOTE: `updatecli.push_commits` and `updatecli.prune_scm_branches` are only emitted in apply mode when push is enabled. They will not appear in `diff` or `prepare` traces.
67+
68+
== Span Attributes
69+
70+
=== Root Span
71+
72+
[cols="1,3",options="header"]
73+
|===
74+
| Attribute | Description
75+
76+
| `updatecli.command`
77+
| The CLI subcommand that was run (e.g. `pipeline/apply`, `pipeline/diff`).
78+
79+
| `updatecli.version`
80+
| The Updatecli version string.
81+
|===
82+
83+
=== Run Span
84+
85+
[cols="1,3",options="header"]
86+
|===
87+
| Attribute | Description
88+
89+
| `updatecli.pipeline_count`
90+
| Total number of pipelines executed in this run.
91+
92+
| `updatecli.dry_run`
93+
| Whether the run was in dry-run mode.
94+
|===
95+
96+
=== Pipeline Span
97+
98+
[cols="1,3",options="header"]
99+
|===
100+
| Attribute | Description
101+
102+
| `updatecli.pipeline.name`
103+
| Human-readable pipeline name.
104+
105+
| `updatecli.pipeline.id`
106+
| Unique pipeline identifier.
107+
108+
| `updatecli.pipeline.sources_count`
109+
| Number of sources in the pipeline.
110+
111+
| `updatecli.pipeline.conditions_count`
112+
| Number of conditions in the pipeline.
113+
114+
| `updatecli.pipeline.targets_count`
115+
| Number of targets in the pipeline.
116+
117+
| `updatecli.pipeline.dry_run`
118+
| Present (and set to `true`) only when dry-run mode is active.
119+
120+
| `updatecli.pipeline.result`
121+
| Final result of the pipeline execution.
122+
123+
| `updatecli.pipeline.crawler_kind`
124+
| Crawler that generated this pipeline (autodiscovered pipelines only).
125+
|===
126+
127+
=== Resource Span
128+
129+
[cols="1,3",options="header"]
130+
|===
131+
| Attribute | Description
132+
133+
| `updatecli.resource.id`
134+
| Resource identifier within the pipeline.
135+
136+
| `updatecli.resource.category`
137+
| Resource category: `source`, `condition`, or `target`.
138+
139+
| `updatecli.resource.name`
140+
| Resource name.
141+
142+
| `updatecli.resource.kind`
143+
| Plugin kind (e.g. `github/release`, `file`).
144+
145+
| `updatecli.resource.result`
146+
| Execution result of this resource.
147+
148+
| `updatecli.resource.description`
149+
| Human-readable description of what the resource did.
150+
|===
151+
152+
==== Condition-Specific Attributes
153+
154+
[cols="1,3",options="header"]
155+
|===
156+
| Attribute | Description
157+
158+
| `updatecli.condition.pass`
159+
| Whether the condition passed.
160+
161+
| `updatecli.condition.source_id`
162+
| ID of the source this condition depends on, if any.
163+
|===
164+
165+
==== Target-Specific Attributes
166+
167+
[cols="1,3",options="header"]
168+
|===
169+
| Attribute | Description
170+
171+
| `updatecli.target.changed`
172+
| Whether the target made a change.
173+
174+
| `updatecli.target.dry_run`
175+
| Whether this target ran in dry-run mode.
176+
177+
| `updatecli.target.source_id`
178+
| ID of the source this target applies, if any.
179+
180+
| `updatecli.target.files`
181+
| Files modified by this target.
182+
|===
183+
184+
==== Span Events
185+
186+
[cols="1,3",options="header"]
187+
|===
188+
| Event | Span | Description
189+
190+
| `target.changed`
191+
| `updatecli.resource`
192+
| Emitted on target resource spans that modified files. The changed files are available in the `updatecli.target.files` span attribute.
193+
194+
| `pipeline.failed`
195+
| `updatecli.run`
196+
| Emitted on the run span when a pipeline fails. Includes `pipeline.name` and sanitized `error` attributes.
197+
|===
198+
199+
=== Prepare Span Attributes
200+
201+
[cols="1,2,3",options="header"]
202+
|===
203+
| Span | Attribute | Description
204+
205+
| `updatecli.load_configurations`
206+
| `updatecli.pipelines_loaded`
207+
| Number of pipelines loaded from manifests.
208+
209+
| `updatecli.autodiscovery`
210+
| `updatecli.autodiscovery.default_crawlers_enabled`
211+
| Whether default crawlers were enabled (true when no manifests are found).
212+
|===
213+
214+
=== Result Values
215+
216+
Pipeline and resource results use the following symbols:
217+
218+
[cols="1,3",options="header"]
219+
|===
220+
| Symbol | Meaning
221+
222+
| `✔`
223+
| Success
224+
225+
| `⚠`
226+
| Attention — a change was detected (or would be applied in dry-run mode)
227+
228+
| `✗`
229+
| Failure
230+
231+
| `-`
232+
| Skipped
233+
|===
234+
235+
== HTTP Instrumentation
236+
237+
Outgoing HTTP requests — to registries, GitHub, and other external APIs — are automatically instrumented via an `otelhttp` transport wrapper. HTTP spans appear as children of the pipeline span, giving full visibility into external API latency without any additional configuration.
238+
239+
== Credential Safety
240+
241+
Error messages recorded on spans are automatically sanitized to strip URL-embedded credentials before they are sent to the trace backend. For example, `https://user:token@github.com` is recorded as `https://****:****@github.com`. This prevents accidental token leaks regardless of which backend you use.
242+
243+
== Examples
244+
245+
=== Local Debugging with Stdout
246+
247+
Print spans to stdout without running a collector:
248+
249+
[source,bash]
250+
----
251+
OTEL_TRACES_EXPORTER=console updatecli pipeline diff --config manifest.yaml
252+
----
253+
254+
IMPORTANT: The `console` exporter is very verbose — it outputs a full JSON block for every span, including HTTP requests. It is best suited for one-off verification, not regular use. For a better experience, use a trace backend like Jaeger (see below).
255+
256+
=== Jaeger
257+
258+
Start Jaeger all-in-one locally:
259+
260+
[source,bash]
261+
----
262+
docker run --rm -p 4317:4317 -p 16686:16686 \
263+
jaegertracing/all-in-one:latest
264+
----
265+
266+
Then run Updatecli with the gRPC endpoint. Because only the endpoint is set, Updatecli infers `otlp` (gRPC) automatically:
267+
268+
[source,bash]
269+
----
270+
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 updatecli pipeline apply --config manifest.yaml
271+
----
272+
273+
Open `http://localhost:16686` to explore the traces.
274+
275+
=== Grafana Tempo or Any OTLP-Compatible Backend
276+
277+
[source,bash]
278+
----
279+
OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4317 updatecli pipeline apply --config manifest.yaml
280+
----
281+
282+
=== HTTP Exporter
283+
284+
For backends that only accept HTTP/protobuf instead of gRPC:
285+
286+
[source,bash]
287+
----
288+
OTEL_TRACES_EXPORTER=otlphttp \
289+
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
290+
updatecli pipeline apply --config manifest.yaml
291+
----

0 commit comments

Comments
 (0)