@vzn/vx-otel 0.0.0 → 0.0.484

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vx contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # @vzn/vx-otel
2
+
3
+ The OpenTelemetry exporter plugin for [`@vzn/vx`](https://github.com/vznjs/vx).
4
+ Maps each `vx run` to **OTLP traces, metrics and logs** over HTTP/JSON — no
5
+ OpenTelemetry SDK dependency (it speaks the OTLP wire protocol directly, so the
6
+ package stays zero-dependency and version-drift-free).
7
+
8
+ ## Usage
9
+
10
+ The options type is `OtelPluginOptions`.
11
+
12
+ ```sh
13
+ npm install -D @vzn/vx @vzn/vx-otel # or: pnpm add -D -w · yarn add -D (-W on Yarn 1) · bun add -d
14
+ ```
15
+
16
+ ```ts
17
+ // vx.workspace.ts
18
+ import { defineWorkspace } from '@vzn/vx/config'
19
+ import { otel } from '@vzn/vx-otel'
20
+
21
+ export default defineWorkspace({
22
+ plugins: [otel()],
23
+ })
24
+ ```
25
+
26
+ `otel()` is **zero-config** via the standard OTel environment variables and
27
+ **declines safely** (exports nothing) when no endpoint is set — so it is safe
28
+ to declare in every environment:
29
+
30
+ | Variable | Purpose |
31
+ | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
32
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | base collector URL (e.g. `http://localhost:4318`) |
33
+ | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | full traces URL override |
34
+ | `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | full metrics URL override |
35
+ | `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | full logs URL override |
36
+ | `OTEL_LOGS_EXPORTER=none` | export traces + metrics only |
37
+ | `OTEL_METRICS_EXPORTER=none` | no metrics |
38
+ | `OTEL_TRACES_EXPORTER=none` | no traces |
39
+ | `OTEL_SDK_DISABLED=true` | export nothing (the plugin declines) |
40
+ | `OTEL_SERVICE_NAME` | service name (default `vx`) |
41
+ | `OTEL_EXPORTER_OTLP_HEADERS` | `k=v,k=v` headers (e.g. auth), percent-encoded |
42
+ | `OTEL_EXPORTER_OTLP_<SIGNAL>_HEADERS` | one signal's headers, over the shared ones |
43
+ | `OTEL_RESOURCE_ATTRIBUTES` | `k=v,k=v` resource attributes, percent-encoded (a malformed one is dropped whole and warned); its `service.name` names the service when `OTEL_SERVICE_NAME` is unset |
44
+ | `OTEL_EXPORTER_OTLP_TIMEOUT` | per-request timeout in ms (default 15000, at most 2³¹−1); `OTEL_EXPORTER_OTLP_<SIGNAL>_TIMEOUT` for one signal. The `timeoutMs` option must be a positive number, or it is refused |
45
+ | `OTEL_EXPORTER_OTLP_COMPRESSION` | `gzip` or `none` (default); `OTEL_EXPORTER_OTLP_<SIGNAL>_COMPRESSION` for one signal, the `compression` option over both |
46
+ | `OTEL_EXPORTER_OTLP_CERTIFICATE` | a PEM file of the CA that signed the collector's certificate (`_<SIGNAL>_` for one signal) |
47
+ | `OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE` / `_CLIENT_KEY` | PEM files of a client certificate and key, for a collector that asks for mutual TLS |
48
+ | `OTEL_EXPORTER_OTLP_PROTOCOL` | vx sends OTLP/HTTP JSON only; under `grpc` a failed export says so (`OTEL_EXPORTER_OTLP_<SIGNAL>_PROTOCOL` per signal) |
49
+
50
+ Each signal ships only to its own URL: the base endpoint's `/v1/<signal>`
51
+ (appended to its path, before any query), or its override. A signal's own endpoint alone is enough: with only a metrics or logs
52
+ endpoint set, that signal exports and the others stay off. Spans and log records go at most 1 000 and 4 MiB to a
53
+ request, so a large run stays under a collector's body limit. Header names are case-insensitive (a signal's own
54
+ `Authorization` replaces the shared `authorization`); a name no header can
55
+ carry (`Authorization: Basic …` written curl-style, with a colon) is not
56
+ sent, and the warning prints neither its name nor its value. A `headers`
57
+ option value that is not a string is not sent either, with a warning
58
+ naming the header.
59
+
60
+ With only a traces URL set, metrics and logs are not
61
+ exported (they used to be POSTed to the traces URL, which a collector
62
+ refuses), and `metrics: true` or `logs: true` without a URL says so once.
63
+ Both are booleans: a string (`'false'` is truthy) is refused.
64
+
65
+ The package exports `otel`, its options type `OtelPluginOptions`, and the
66
+ types of the `post` option (a `PostFn`: the transport, fetch unless given,
67
+ handed each request's URL, body, headers, abort signal and `OtlpTls`). The
68
+ OTLP builders are internal (the wire they send is the contract,
69
+ `tests/contract/otlp.txt`).
70
+
71
+ Options override env:
72
+
73
+ ```ts
74
+ otel({
75
+ endpoint: 'https://collector.example.com',
76
+ serviceName: 'my-monorepo',
77
+ headers: { authorization: 'Bearer …' },
78
+ metrics: true, // default
79
+ logs: true, // default: each executed task's output tail
80
+ timeoutMs: 15_000, // default: per request
81
+ compression: 'none', // default; 'gzip' for every signal
82
+ tracesEndpoint: 'https://traces.example.com/v1/traces', // one signal's full URL; also metricsEndpoint, logsEndpoint
83
+ })
84
+ ```
85
+
86
+ ## What it exports
87
+
88
+ **A trace per run** (OTel CI/CD + VCS semantic conventions):
89
+
90
+ - a root `vx.run` span — `cicd.pipeline.run.id`, `vcs.ref.head.revision`,
91
+ `vcs.ref.head.name`, `vx.command` (the command line; what follows `--`
92
+ is counted, `-- <N arguments>`, never quoted, and a secret value
93
+ before it is `***`), CI provider,
94
+ host/os/arch, vx version, `--tag k=v` →
95
+ `vx.tag.<k>`, and `cicd.pipeline.result` (`success`, `failure`, or
96
+ `cancellation` for a run stopped with nothing failed); a red run sets
97
+ span status `ERROR`;
98
+ - a child `vx.task` span per task — `cicd.pipeline.task.name`,
99
+ `cicd.pipeline.task.run.result` (the convention's enum: `success` for a
100
+ run or a hit, `failure`, `timeout`, `skip`, `cancellation`), vx's own
101
+ status as `vx.task.status`, `vx.cache.source` (miss/local/remote, or none for a task that never ran),
102
+ `vx.task.hash`, duration, CPU ms, peak RSS, and on a skipped task its root
103
+ blocker (`vx.task.blocked_by`), on a timed-out one `vx.task.timed_out`, on
104
+ a sandboxed one its violation count (`vx.task.sandbox_violations`), on a
105
+ persistent one that never became ready why (`vx.task.not_ready`). A
106
+ failed task sets span status
107
+ `ERROR`.
108
+
109
+ **Metrics per run**: `vx.tasks.total`, `vx.tasks.failed`,
110
+ `vx.tasks.cache_hits{source=local|remote}`, and the `vx.run.duration_ms` gauge.
111
+ The counts are DELTA sums over the run's own interval (start to end), so a
112
+ backend adds runs rather than reading each as the series' new total.
113
+
114
+ **Logs per run** (on by default): the captured output tail of each executed
115
+ task, as one log record linked to its task span (unlinked when traces are
116
+ off: the span is never exported). Build output can hold
117
+ secrets, and this sends it to the collector; `logs: false` or
118
+ `OTEL_LOGS_EXPORTER=none` turns it off.
119
+
120
+ ## Behavior note
121
+
122
+ This replaces core's previous hardcoded OTel emit, which fired automatically
123
+ whenever `OTEL_EXPORTER_OTLP_ENDPOINT` was set. OTel is now a **plugin**: the
124
+ env var alone no longer auto-exports — you must declare `otel()` in
125
+ `vx.workspace.ts`. Telemetry is observe-only and can never change, slow, or
126
+ fail a run (every export is buffered, time-bounded, and swallows errors).
127
+
128
+ A collector shedding load (`429`, `502`, `503`, `504`, or a dropped
129
+ connection) is retried twice, 200 then 800 ms apart or after the
130
+ `Retry-After` it names (at most 2 s), inside the end-of-run deadline; a
131
+ deadline that passes mid-wait ends the retries and warns the last answer.
132
+
133
+ Swallowed, but not silent: an export that does not land warns once per
134
+ signal URL, naming what happened — a collector that cannot be reached, one
135
+ that refuses the request (`HTTP 401`, `404`, `500`, with the collector's own
136
+ message), or one that accepts it and reports part of the data dropped
137
+ (OTLP's `partialSuccess`). A collector that answers with a redirect
138
+ is refused too, naming where it points: like the OTel SDK exporters,
139
+ vx-otel follows none, since a header such as `x-honeycomb-team` would
140
+ reach the new origin with it. The URL in that line is printed with any
141
+ userinfo and query string replaced by `***`, so an endpoint that carries
142
+ its credential in the URL does not leak it into a CI log. The run stays
143
+ green either way; a collector that
144
+ takes too long is cut off by core's end-of-run deadline
145
+ (`VX_TEARDOWN_TIMEOUT_MS`, 3 s by default) with a line saying the buffered
146
+ records were lost.
package/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ // Root entry shim: Bun 1.4.0's `--compile` binaries resolve an on-disk
2
+ // package by `<pkg>/index.ts` and ignore package.json `exports` / `main`
3
+ // (see packages/vx/index.ts). Same module as the exports map names.
4
+ export * from './src/index.js'
package/package.json CHANGED
@@ -1,13 +1,46 @@
1
1
  {
2
2
  "name": "@vzn/vx-otel",
3
- "version": "0.0.0",
4
- "description": "Placeholder; real releases are published by CI.",
3
+ "version": "0.0.484",
5
4
  "license": "MIT",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+https://github.com/vznjs/vx.git"
5
+ "description": "OpenTelemetry exporter for vx — a telemetry plugin mapping runs to OTLP traces + metrics (CI/CD + VCS semantic conventions).",
6
+ "keywords": [
7
+ "vx",
8
+ "vx-plugin",
9
+ "monorepo",
10
+ "opentelemetry",
11
+ "otlp",
12
+ "tracing",
13
+ "telemetry"
14
+ ],
15
+ "type": "module",
16
+ "main": "./src/index.ts",
17
+ "types": "./src/index.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./src/index.ts",
21
+ "import": "./src/index.ts"
22
+ }
23
+ },
24
+ "files": [
25
+ "index.ts",
26
+ "src",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "engines": {
31
+ "bun": ">=1.4"
9
32
  },
10
33
  "publishConfig": {
11
34
  "access": "public"
12
- }
35
+ },
36
+ "peerDependencies": {
37
+ "@vzn/vx": "^0.0.484"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/vznjs/vx.git",
42
+ "directory": "packages/vx-otel"
43
+ },
44
+ "homepage": "https://github.com/vznjs/vx/tree/main/packages/vx-otel#readme",
45
+ "bugs": "https://github.com/vznjs/vx/issues"
13
46
  }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ // Public API for @vzn/vx-otel — the OpenTelemetry exporter plugin for vx.
2
+ //
3
+ // Usage in vx.workspace.ts:
4
+ // import { defineWorkspace } from '@vzn/vx'
5
+ // import { otel } from '@vzn/vx-otel'
6
+ // export default defineWorkspace({ plugins: [otel()] })
7
+ //
8
+ // Zero-config via the standard OTel env vars (OTEL_EXPORTER_OTLP_ENDPOINT,
9
+ // OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_HEADERS). Maps a run to OTLP traces +
10
+ // metrics + per-task logs over HTTP/JSON — no OpenTelemetry SDK dependency.
11
+
12
+ export { otel, type OtelPluginOptions } from './plugin.js'
13
+ // The transport the `post` option takes, and the TLS material it is handed.
14
+ export { type OtlpTls, type PostFn } from './sink.js'