@r_masseater/vitest-otlp 0.0.0-stage → 0.0.2

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.
Files changed (3) hide show
  1. package/LICENSE +87 -0
  2. package/README.md +170 -2
  3. package/package.json +75 -3
package/LICENSE ADDED
@@ -0,0 +1,87 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 masseater
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.
22
+
23
+ ---
24
+
25
+ @tanstack/eslint-plugin-start
26
+
27
+ MIT License
28
+
29
+ Copyright (c) 2021-present Tanner Linsley
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining a copy
32
+ of this software and associated documentation files (the "Software"), to deal
33
+ in the Software without restriction, including without limitation the rights
34
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
35
+ copies of the Software, and to permit persons to whom the Software is
36
+ furnished to do so, subject to the following conditions:
37
+
38
+ The above copyright notice and this permission notice shall be included in all
39
+ copies or substantial portions of the Software.
40
+
41
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
44
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
46
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
47
+ SOFTWARE.
48
+
49
+ ---
50
+
51
+ @typescript-eslint/utils
52
+
53
+ MIT License
54
+
55
+ Copyright (c) 2019 typescript-eslint and other contributors
56
+
57
+ Permission is hereby granted, free of charge, to any person obtaining a copy
58
+ of this software and associated documentation files (the "Software"), to deal
59
+ in the Software without restriction, including without limitation the rights
60
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
61
+ copies of the Software, and to permit persons to whom the Software is
62
+ furnished to do so, subject to the following conditions:
63
+
64
+ The above copyright notice and this permission notice shall be included in all
65
+ copies or substantial portions of the Software.
66
+
67
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
68
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
69
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
70
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
71
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
72
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
73
+ SOFTWARE.
74
+
75
+ ---
76
+
77
+ globals
78
+
79
+ MIT License
80
+
81
+ Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
82
+
83
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
84
+
85
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
86
+
87
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,171 @@
1
- # Temporary Holding Version
1
+ # @r_masseater/vitest-otlp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A Vitest plugin that receives OTLP during tests. A test can then assert the
4
+ spans, metrics and logs that the code under test really exported. It shows that
5
+ auto-instrumentation and `OTEL_*` settings work. It also shows that telemetry
6
+ actually leaves the process.
7
+
8
+ It requires [Vite+](https://viteplus.dev/) and Node.js 26 or later.
9
+
10
+ ```sh
11
+ vp add -D @r_masseater/vitest-otlp
12
+ ```
13
+
14
+ ```ts
15
+ import { otlp } from "@r_masseater/vitest-otlp";
16
+ import { defineConfig } from "vite-plus";
17
+
18
+ export default defineConfig({
19
+ plugins: [otlp()],
20
+ });
21
+ ```
22
+
23
+ ```ts
24
+ import { NodeSDK } from "@opentelemetry/sdk-node";
25
+ import { waitForSpans } from "@r_masseater/vitest-otlp";
26
+ import { Effect } from "effect";
27
+ import { expect, test } from "vite-plus/test";
28
+
29
+ test("checkout is traced", () =>
30
+ Effect.gen(function* () {
31
+ const sdk = new NodeSDK();
32
+ sdk.start();
33
+ yield* checkout;
34
+ yield* Effect.promise(() => sdk.shutdown());
35
+
36
+ const spans = yield* waitForSpans({
37
+ where: (span) => span.name === "checkout",
38
+ });
39
+ expect(spans).toMatchObject([{ attributes: { "order.id": "o-1" } }]);
40
+ }).pipe(Effect.runPromise));
41
+ ```
42
+
43
+ ## How it works
44
+
45
+ Each test file gets its own OTLP receiver on free loopback ports. It serves
46
+ OTLP/HTTP with protobuf or JSON, and OTLP/gRPC. Both accept gzip. Messages are
47
+ decoded with code that [protobuf-es](https://github.com/bufbuild/protobuf-es)
48
+ generated from
49
+ [opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)
50
+ v1.11.1.
51
+
52
+ The test learns the receiver through the standard environment variables. An SDK
53
+ that reads its configuration from the environment sends to it without code
54
+ changes. Child processes of the test inherit the variables. They send to the
55
+ same receiver.
56
+
57
+ An in-memory exporter skips the real export path. This plugin instead sends
58
+ over OTLP and reads the environment, just as production does. A Collector or
59
+ grafana/otel-lgtm in a container needs Docker. This plugin needs none, and the
60
+ test reads what arrived directly.
61
+
62
+ ## API
63
+
64
+ The read API is written with Effect.
65
+
66
+ `waitForSpans`, `waitForLogs` and `waitForMetrics` wait for items that match
67
+ `where`. They reread every `interval` until `count` items arrive, and return all
68
+ matches. The defaults are 20 ms and 1. After `timeout` they fail with
69
+ `TelemetryNotReceived`, which carries how many arrived. The default timeout is
70
+ 5 seconds.
71
+
72
+ `receivedSpans`, `receivedLogs` and `receivedMetrics` return everything that has
73
+ arrived in the test file so far. `clearTelemetry` discards what has arrived.
74
+ `receiverAddress` returns the `httpEndpoint` and `grpcEndpoint` of the receiver.
75
+
76
+ Attributes become plain objects such as `{ "order.id": "o-1" }`. IDs become hex
77
+ strings. Spans and logs carry `resource` and `scope`. They hold the resource
78
+ attributes and the instrumentation scope.
79
+
80
+ ## Environment
81
+
82
+ `OTEL_EXPORTER_OTLP_ENDPOINT` is the HTTP URL of the receiver. When
83
+ `OTEL_EXPORTER_OTLP_PROTOCOL` is `grpc`, it is the gRPC URL instead.
84
+ `OTEL_TRACES_EXPORTER`, `OTEL_METRICS_EXPORTER` and `OTEL_LOGS_EXPORTER` are
85
+ `otlp`.
86
+
87
+ Export delays are shortened, but only when they are unset.
88
+
89
+ | Variable | Value |
90
+ | ----------------------------- | ----- |
91
+ | `OTEL_BSP_SCHEDULE_DELAY` | 50 |
92
+ | `OTEL_BLRP_SCHEDULE_DELAY` | 50 |
93
+ | `OTEL_METRIC_EXPORT_INTERVAL` | 100 |
94
+ | `OTEL_METRIC_EXPORT_TIMEOUT` | 100 |
95
+
96
+ The per-signal variables `OTEL_EXPORTER_OTLP_<SIGNAL>_ENDPOINT` are emptied.
97
+ Test telemetry therefore never leaks to an outside Collector.
98
+
99
+ The receiver closes when the test file ends. With `isolate: false`, files may
100
+ share one SDK. Then telemetry from the second file on is lost.
101
+
102
+ ## Semantic conventions
103
+
104
+ `otlp({ semconv: true })` also checks every export against the OpenTelemetry
105
+ semantic conventions. It starts the live-check of
106
+ [OpenTelemetry Weaver](https://github.com/open-telemetry/weaver) once for the
107
+ run. Each receiver forwards what it gets to Weaver before storing it. When the
108
+ run ends, the plugin reads the report of Weaver. The run then fails and lists
109
+ every finding at or above `failOn`.
110
+
111
+ ```ts
112
+ otlp({
113
+ semconv: {
114
+ registry: "https://github.com/open-telemetry/semantic-conventions.git@v1.45.0[model]",
115
+ weaver: "weaver",
116
+ failOn: "violation",
117
+ },
118
+ });
119
+ ```
120
+
121
+ The values above are the defaults. `failOn` is `violation`, `improvement` or
122
+ `information`. A custom attribute is a violation unless `registry` points to a
123
+ registry that defines it. The `weaver` binary must be on `PATH`. In GitHub
124
+ Actions, the
125
+ [setup-weaver](https://github.com/open-telemetry/weaver/tree/main/.github/actions/setup-weaver)
126
+ action installs it.
127
+
128
+ ## Cloudflare Workers
129
+
130
+ Workers trace KV, D1, R2 and `fetch` calls without code changes, and hand the
131
+ spans and logs to a
132
+ [streaming tail worker](https://developers.cloudflare.com/workers/observability/logs/tail-workers/).
133
+ `tailWorker()` from `@r_masseater/vitest-otlp/workers` builds a tail worker for
134
+ [Miniflare](https://developers.cloudflare.com/workers/testing/miniflare/). It
135
+ turns each invocation into OTLP and sends it to the receiver of the test file.
136
+
137
+ ```ts
138
+ import { waitForSpans } from "@r_masseater/vitest-otlp";
139
+ import { tailWorker } from "@r_masseater/vitest-otlp/workers";
140
+ import { Effect } from "effect";
141
+ import { Miniflare } from "miniflare";
142
+
143
+ test("caching writes to KV", () =>
144
+ Effect.gen(function* () {
145
+ const tail = yield* tailWorker();
146
+ const miniflare = new Miniflare({
147
+ workers: [
148
+ {
149
+ name: "app",
150
+ scriptPath: "worker.js",
151
+ kvNamespaces: ["CACHE"],
152
+ streamingTails: [tail.name],
153
+ },
154
+ tail,
155
+ ],
156
+ });
157
+ yield* Effect.promise(() => miniflare.dispatchFetch("http://localhost/"));
158
+ yield* waitForSpans({ where: (span) => span.name === "kv_put" });
159
+ yield* Effect.promise(() => miniflare.dispose());
160
+ }).pipe(Effect.runPromise));
161
+ ```
162
+
163
+ The invocation becomes a server span named after its trigger, such as `fetch`.
164
+ Each traced call becomes a client span under it, and `console` calls become log
165
+ records in the same trace. The resource `service.name` is the script name of
166
+ the Worker. Miniflare reports no script name, so pass `serviceName` to
167
+ `tailWorker()` instead.
168
+
169
+ ## License
170
+
171
+ MIT
package/package.json CHANGED
@@ -1,6 +1,78 @@
1
1
  {
2
2
  "name": "@r_masseater/vitest-otlp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.2",
4
+ "description": "A Vitest plugin that receives OTLP traces, metrics and logs during tests so tests can assert on the telemetry the code under test actually exported.",
5
+ "keywords": [
6
+ "opentelemetry",
7
+ "otel",
8
+ "otlp",
9
+ "testing",
10
+ "vitest",
11
+ "vitest-plugin"
12
+ ],
13
+ "homepage": "https://github.com/masseater/measure-everything/tree/main/libs/vitest-otlp#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/masseater/measure-everything/issues"
16
+ },
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/masseater/measure-everything.git",
21
+ "directory": "libs/vitest-otlp"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "LICENSE"
26
+ ],
27
+ "type": "module",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.mts",
31
+ "default": "./dist/index.mjs"
32
+ },
33
+ "./setup": {
34
+ "types": "./dist/setup.d.mts",
35
+ "default": "./dist/setup.mjs"
36
+ },
37
+ "./semconv": {
38
+ "types": "./dist/semconv.d.mts",
39
+ "default": "./dist/semconv.mjs"
40
+ },
41
+ "./workers": {
42
+ "types": "./dist/workers.d.mts",
43
+ "default": "./dist/workers.mjs"
44
+ },
45
+ "./tail-worker": "./dist/tail.worker.js",
46
+ "./package.json": "./package.json"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public",
50
+ "provenance": true
51
+ },
52
+ "dependencies": {
53
+ "@bufbuild/protobuf": "2.16.0",
54
+ "@effect/platform-node": "4.0.7",
55
+ "effect": "4.0.7"
56
+ },
57
+ "devDependencies": {
58
+ "@bufbuild/buf": "1.75.0",
59
+ "@bufbuild/protoc-gen-es": "2.16.0",
60
+ "@cloudflare/workers-types": "5.20261101.1",
61
+ "@opentelemetry/api": "1.10.0",
62
+ "@opentelemetry/sdk-node": "0.300.0",
63
+ "@types/node": "26.6.6",
64
+ "miniflare": "4.20260730.0",
65
+ "vite-plus": "1.4.0"
66
+ },
67
+ "peerDependencies": {
68
+ "vite-plus": "1.4.0"
69
+ },
70
+ "engines": {
71
+ "node": ">=26"
72
+ },
73
+ "scripts": {
74
+ "build": "vp pack",
75
+ "generate": "buf generate",
76
+ "test": "vp test run"
77
+ }
6
78
  }