Skip to content

Commit 0707bd7

Browse files
mabry1985Automakerclaude
authored
feat(telemetry): default OTLP to otel.proto-labs.ai over HTTP, bearer auth, strict opt-in (Phase 4 of homelab-iac#34) (#171)
* feat(telemetry): default OTLP to otel.proto-labs.ai over HTTP, bearer auth, strict opt-in Phase 4 of homelab-iac#34 — wires protoCLI to the public LGTM ingress that just landed on the ava node. Also tightens the opt-in posture so no telemetry leaves the host unless the user explicitly enables it. Defaults - DEFAULT_OTLP_ENDPOINT: 'http://localhost:4317' → 'https://otel.proto-labs.ai' (Cloudflare-fronted, TLS-terminated, hosts the Tempo / Loki / Mimir stack chosen in homelab-iac#34) - getTelemetryOtlpProtocol() default: 'grpc' → 'http' to match the ingress shape; gRPC override still works for users who run their own local OTel collector. Auth - OTEL_INGRESS_TOKEN env var, plumbed as `Authorization: Bearer <token>` into all three HTTP exporters (trace/log/metric). Header is omitted entirely when the env var is unset, preserving exact-match shape for existing tests against arbitrary collectors. - gRPC path picks up the same token via grpc-js Metadata for users on the gRPC override. - Token convention matches Infisical (homelab-media/prod), composes with the existing settings.json `env` block alongside Langfuse keys. Strict opt-in - initializeTelemetry now requires telemetry.enabled === true for ANY outbound exporter to activate. Previously, Langfuse env vars alone could spin up the Langfuse exporter without an explicit opt-in — closes that hole. Privacy is the default; users opt in via `"telemetry": { "enabled": true }` in settings.json (or --telemetry). - Debug log surfaces when Langfuse env vars are detected but telemetry is disabled, so the new behavior is discoverable rather than silent. Tests - New: bearer header attached to HTTP exporters when OTEL_INGRESS_TOKEN is set; omitted when unset. - Updated: existing "Langfuse auto-activates with disabled telemetry" test inverted to assert the opt-in semantics. - Updated: protocol-default tests on both core and cli sides expect 'http'. - Updated: endpoint-default tests on cli side expect the public ingress. Verified locally: 5311 core / 3774 cli tests pass, typecheck + lint clean. End-to-end smoke against the live ingress is gated on the maintainer adding OTEL_INGRESS_TOKEN to settings.json `env`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(telemetry): document strict opt-in policy and OTel ingress defaults Updates user-facing docs to match the Phase 4 behavior change: - README Observability section: shows the opt-in setup with both OTEL_INGRESS_TOKEN and Langfuse keys, removes the now-incorrect 'Langfuse activates independently' subsection (it doesn't anymore), notes the new https://otel.proto-labs.ai default endpoint and bearer auth, surfaces gen_ai.response.thinking and thinking_tokens in the trace table. - README upstream-comparison row reflects the LGTM + Langfuse fan-out. - docs/contributing/telemetry.md: rewritten around the opt-in default, documents OTEL_INGRESS_TOKEN, expands the configuration reference table to include otlpProtocol and the new endpoint default, updates the privacy section to lead with 'silence unless you say otherwise'. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: trigger checks (no-op) --------- Co-authored-by: Automaker <automaker@localhost> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c4dafcf commit 0707bd7

8 files changed

Lines changed: 194 additions & 70 deletions

File tree

README.md

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ At-a-glance overview vs. upstream Qwen Code. For the full architectural breakdow
2727
| Ignore files | `.qwenignore` | `.protoignore` + inherits `.claudeignore` patterns |
2828
| ACP / Zed integration | Stock | Cron-in-Session, concurrent Agent calls, SSE/HTTP MCP, internal-part filtering |
2929
| Extra built-in tools | Standard set | + browser automation, repo-map (PageRank), task tools, mailbox, LSP, voice/STT |
30-
| Observability | Console | Langfuse OTLP traces with harness-intervention spans (SFT-ready) |
30+
| Observability | Console | OTLP/HTTP to LGTM stack + Langfuse, opt-in, with `gen_ai.response.thinking` and harness-intervention spans (SFT-ready) |
3131
| Release pipeline | Manual | Conventional-commit auto-release (`feat:` → minor, `fix:` → patch) |
3232
| VS Code companion | Included | Removed (focus on TUI + ACP/Zed) |
3333

@@ -206,53 +206,56 @@ Both no-op outside a TTY, in screen-reader mode, or under tmux/SSH.
206206

207207
## Observability
208208

209-
proto supports [Langfuse](https://langfuse.com) tracing out of the box. Set three environment variables and every session is fully tracedLLM calls (all providers), tool executions, subagent lifecycles, and turn hierarchy.
209+
proto ships OpenTelemetry-native, with both a Tempo/LGTM-style ops backend and Langfuse for prompt-grade trace UI. Both are **opt-in**nothing is sent anywhere until `telemetry.enabled` is `true`.
210210

211211
### Setup
212212

213-
Add to the `env` block in `~/.proto/settings.json`:
213+
Add to `~/.proto/settings.json`:
214214

215215
```json
216216
{
217+
"telemetry": { "enabled": true },
217218
"env": {
219+
"OTEL_INGRESS_TOKEN": "<bearer token from your Infisical or vault>",
218220
"LANGFUSE_PUBLIC_KEY": "pk-lf-...",
219221
"LANGFUSE_SECRET_KEY": "sk-lf-...",
220-
"LANGFUSE_BASE_URL": "https://cloud.langfuse.com"
222+
"LANGFUSE_BASE_URL": "https://your-langfuse-instance.example.com"
221223
}
222224
}
223225
```
224226

225-
`LANGFUSE_BASE_URL` is optional and defaults to `https://cloud.langfuse.com`. For a self-hosted instance, set it to your deployment URL.
227+
With `telemetry.enabled = true`:
226228

227-
> **Why `settings.json` and not `.env`?** proto walks up from your CWD loading `.env` files, so a project-level `.env` with Langfuse keys would bleed into proto's tracing and mix your traces into the wrong dataset. The `env` block in `settings.json` is proto-namespaced and completely isolated from your projects.
229+
- **OTLP traces** ship to `https://otel.proto-labs.ai` over HTTP, bearer-auth via `OTEL_INGRESS_TOKEN`. Override `telemetry.otlpEndpoint` / `telemetry.otlpProtocol` to point at a local OTel collector or a different vendor.
230+
- **Langfuse traces** ship to `LANGFUSE_BASE_URL` (defaults to `https://cloud.langfuse.com`) when both Langfuse keys are present.
231+
232+
Without `telemetry.enabled = true`, neither exporter activates regardless of env vars.
233+
234+
> **Why `settings.json` and not `.env`?** proto walks up from your CWD loading `.env` files, so a project-level `.env` with telemetry keys would bleed into proto's tracing and mix your traces into the wrong dataset. The `env` block in `settings.json` is proto-namespaced and completely isolated from your projects.
228235
229236
### What gets traced
230237

231-
| Span | Attributes |
232-
| --------------------- | ---------------------------------------------------------------------------------------------------- |
233-
| `turn` | `session.id`, `turn.id` — root span per user prompt |
234-
| `gen_ai chat {model}` | `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, `gen_ai.request.model` — one per LLM call |
235-
| `tool/{name}` | `tool.name`, `tool.type`, `tool.duration_ms` — one per tool execution |
236-
| `agent/{name}` | `agent.name`, `agent.status`, `agent.duration_ms` — one per subagent |
238+
| Span | Attributes |
239+
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
240+
| `turn` | `session.id`, `turn.id` — root span per user prompt |
241+
| `gen_ai chat {model}` | `gen_ai.usage.{input,output,thinking}_tokens`, `gen_ai.request.model`, `gen_ai.response.thinking` (when present) — one per LLM call |
242+
| `tool/{name}` | `tool.name`, `tool.type`, `tool.duration_ms` — one per tool execution |
243+
| `agent/{name}` | `agent.name`, `agent.status`, `agent.duration_ms` — one per subagent |
237244

238245
All three provider backends are covered: OpenAI-compatible, Anthropic, and Gemini.
239246

240247
### Prompt content logging
241248

242-
Full prompt messages and response text are included in traces by default. To disable:
249+
Full prompt messages, response text, and reasoning text are included in traces by default. To disable:
243250

244251
```json
245252
// ~/.proto/settings.json
246253
{
247-
"telemetry": { "logPrompts": false }
254+
"telemetry": { "enabled": true, "logPrompts": false }
248255
}
249256
```
250257

251-
> **Privacy note:** `logPrompts` is enabled by default. When enabled, full prompt and response content is sent to your Langfuse instance. Set to `false` if you want traces without message content.
252-
253-
### Langfuse activates independently
254-
255-
Langfuse tracing activates from env vars alone — it does not require `telemetry.enabled: true` in settings. The general telemetry pipeline (OTLP/GCP) and Langfuse are independent.
258+
> **Privacy note:** Telemetry is off by default. When you opt in, `logPrompts` defaults to `true` — full prompt, response, and reasoning content are attached to spans (truncated at 10K chars each). Set `logPrompts: false` if you want token counts and timings without message content.
256259
257260
## Task Management
258261

docs/contributing/telemetry.md

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,65 +2,111 @@
22

33
proto is built on [OpenTelemetry](https://opentelemetry.io/) — the vendor-neutral observability standard. All traces, spans, and metrics use OTLP format and can be exported to any compatible backend.
44

5-
## Langfuse (built-in, recommended)
5+
## Opt-in by default
66

7-
proto ships with a Langfuse exporter. Set these environment variables to activate it — no other configuration needed:
7+
**No telemetry leaves the host until you explicitly enable it.** This is true for every exporter — OTLP, Langfuse, file output. Set:
88

9-
```bash
10-
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
11-
export LANGFUSE_SECRET_KEY="sk-lf-..."
12-
export LANGFUSE_BASE_URL="https://your-langfuse-instance.example.com" # optional, defaults to cloud
9+
```json
10+
{ "telemetry": { "enabled": true } }
1311
```
1412

15-
What is traced:
13+
in `settings.json`, or pass `--telemetry` on the CLI. Without that flag, nothing is sent anywhere — even if Langfuse env vars are present.
14+
15+
## Default ingress: `otel.proto-labs.ai`
16+
17+
When opted in, traces ship to the homelab LGTM stack at `https://otel.proto-labs.ai` over OTLP/HTTP. The endpoint is bearer-token authenticated:
18+
19+
```json
20+
{
21+
"telemetry": { "enabled": true },
22+
"env": {
23+
"OTEL_INGRESS_TOKEN": "<token from Infisical homelab-media/prod>"
24+
}
25+
}
26+
```
27+
28+
Without `OTEL_INGRESS_TOKEN`, the ingress returns 401 (debug-logged in `~/.proto/debug/latest`).
29+
30+
## Langfuse (LLM-grade trace UI)
31+
32+
Langfuse is wired in addition to the LGTM stack — keeps prompt-grade trace debugging available alongside Tempo's APM views. Activate by setting all of these in `settings.json` `env`:
33+
34+
```json
35+
{
36+
"telemetry": { "enabled": true },
37+
"env": {
38+
"LANGFUSE_PUBLIC_KEY": "pk-lf-...",
39+
"LANGFUSE_SECRET_KEY": "sk-lf-...",
40+
"LANGFUSE_BASE_URL": "https://your-langfuse-instance.example.com"
41+
}
42+
}
43+
```
44+
45+
`LANGFUSE_BASE_URL` is optional; defaults to `https://cloud.langfuse.com`.
46+
47+
What's traced:
1648

1749
- Every session turn
1850
- All LLM calls (all providers) with token counts
1951
- Tool calls with input/output
2052
- Sub-agent spawns and completions
53+
- Model reasoning content as `gen_ai.response.thinking` span attribute (when surfaced by the model or gateway)
2154

22-
## OpenTelemetry configuration
23-
24-
Configure via `settings.json` or environment variables:
55+
## Configuration reference
2556

26-
| Setting | Env var | CLI flag | Values | Default |
27-
| -------------------- | -------------------------- | ---------------------------------- | --------------- | ------- |
28-
| `telemetry.enabled` | `PROTO_TELEMETRY_ENABLED` | `--telemetry` / `--no-telemetry` | `true`/`false` | `false` |
29-
| `telemetry.target` | `PROTO_TELEMETRY_TARGET` | `--telemetry-target <local\|otel>` | `local`, `otel` | `local` |
30-
| `telemetry.endpoint` | `PROTO_TELEMETRY_ENDPOINT` | `--telemetry-endpoint <url>` | OTLP URL ||
57+
| Setting | Env var | CLI flag | Values | Default |
58+
| ------------------------ | -------------------------- | ----------------------------------- | --------------- | --------------------------------- |
59+
| `telemetry.enabled` | `PROTO_TELEMETRY_ENABLED` | `--telemetry` / `--no-telemetry` | `true`/`false` | `false` _(opt-in)_ |
60+
| `telemetry.target` | `PROTO_TELEMETRY_TARGET` | `--telemetry-target <local\|otel>` | `local`, `otel` | `local` |
61+
| `telemetry.otlpEndpoint` | `PROTO_TELEMETRY_ENDPOINT` | `--telemetry-otlp-endpoint <url>` | OTLP URL | `https://otel.proto-labs.ai` |
62+
| `telemetry.otlpProtocol` || `--telemetry-otlp-protocol <proto>` | `http`, `grpc` | `http` |
63+
| `telemetry.logPrompts` || `--telemetry-log-prompts` | `true`/`false` | `true` |
64+
| `OTEL_INGRESS_TOKEN` ||| bearer token | — (required for default endpoint) |
3165

3266
### File-based local output
3367

68+
For local-only debugging without shipping anywhere:
69+
3470
```json
3571
{
3672
"telemetry": {
3773
"enabled": true,
3874
"target": "local",
39-
"logFile": "~/.proto/telemetry/traces.jsonl"
75+
"outfile": "~/.proto/telemetry/traces.jsonl"
4076
}
4177
}
4278
```
4379

44-
### Export to any OTLP backend (Jaeger, Datadog, etc.)
80+
### Override to a different OTLP backend
81+
82+
If you run a local OTel Collector or want to point at a different vendor:
4583

4684
```json
4785
{
4886
"telemetry": {
4987
"enabled": true,
50-
"target": "otel",
51-
"endpoint": "http://localhost:4318/v1/traces"
88+
"otlpEndpoint": "http://localhost:4318",
89+
"otlpProtocol": "http"
5290
}
5391
}
5492
```
5593

94+
For a local gRPC collector, set `otlpProtocol: "grpc"` and use port `4317`.
95+
5696
## What is instrumented
5797

5898
- **Session turns** — user prompt, model response, duration
59-
- **LLM calls** — provider, model, input/output tokens, latency
99+
- **LLM calls** — provider, model, input/output/thinking tokens, latency, reasoning content
60100
- **Tool calls** — name, arguments, result, duration
61101
- **Sub-agent lifecycle** — spawn, completion, token usage
62102
- **Harness interventions** — doom loop detection, multi-sample retries
103+
- **Hook executions** — which hook fired, success, duration, exit code, captured stdout/stderr
63104

64105
## Privacy
65106

66-
Traces include prompt content and tool outputs by default. For production environments, configure sampling or filtering at your OTLP collector level to avoid capturing sensitive data.
107+
The opt-in default means privacy is the baseline — silence unless you say otherwise. When opted in:
108+
109+
- `telemetry.logPrompts` controls whether prompt content + reasoning text are attached to spans (default `true`).
110+
- Reasoning text and completion content are truncated at 10K chars on each span.
111+
- For production deployments shipping to shared infra, configure sampling/filtering at the OTel Collector layer.
112+
- Local sandboxed runs default to a per-process session ID; no stable user identifier is exported.

packages/cli/src/config/config.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,9 @@ describe('loadCliConfig telemetry', () => {
889889
const argv = await parseArguments();
890890
const settings: Settings = { telemetry: { enabled: true } };
891891
const config = await loadCliConfig(settings, argv);
892-
expect(config.getTelemetryOtlpEndpoint()).toBe('http://localhost:4317');
892+
expect(config.getTelemetryOtlpEndpoint()).toBe(
893+
'https://otel.proto-labs.ai',
894+
);
893895
});
894896

895897
it('should use telemetry target from settings if CLI flag is not present', async () => {
@@ -981,7 +983,7 @@ describe('loadCliConfig telemetry', () => {
981983
const argv = await parseArguments();
982984
const settings: Settings = { telemetry: { enabled: true } };
983985
const config = await loadCliConfig(settings, argv);
984-
expect(config.getTelemetryOtlpProtocol()).toBe('grpc');
986+
expect(config.getTelemetryOtlpProtocol()).toBe('http');
985987
});
986988

987989
it('should reject invalid --telemetry-otlp-protocol values', async () => {

packages/core/src/config/config.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -710,20 +710,20 @@ describe('Server Config (config.ts)', () => {
710710
expect(config.getTelemetryOtlpProtocol()).toBe('http');
711711
});
712712

713-
it('should return default OTLP protocol if not provided', () => {
713+
it('should return default OTLP protocol of "http" if not provided (matches public ingress at otel.proto-labs.ai)', () => {
714714
const params: ConfigParameters = {
715715
...baseParams,
716716
telemetry: { enabled: true },
717717
};
718718
const config = new Config(params);
719-
expect(config.getTelemetryOtlpProtocol()).toBe('grpc');
719+
expect(config.getTelemetryOtlpProtocol()).toBe('http');
720720
});
721721

722-
it('should return default OTLP protocol if telemetry object is not provided', () => {
722+
it('should return default OTLP protocol of "http" if telemetry object is not provided', () => {
723723
const paramsWithoutTelemetry: ConfigParameters = { ...baseParams };
724724
delete paramsWithoutTelemetry.telemetry;
725725
const config = new Config(paramsWithoutTelemetry);
726-
expect(config.getTelemetryOtlpProtocol()).toBe('grpc');
726+
expect(config.getTelemetryOtlpProtocol()).toBe('http');
727727
});
728728
});
729729

packages/core/src/config/config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1788,7 +1788,10 @@ export class Config {
17881788
}
17891789

17901790
getTelemetryOtlpProtocol(): 'grpc' | 'http' {
1791-
return this.telemetrySettings.otlpProtocol ?? 'grpc';
1791+
// Default 'http' aligns with the public OTLP ingress at
1792+
// otel.proto-labs.ai (Cloudflare-fronted, HTTPS only). Set
1793+
// telemetry.otlpProtocol = 'grpc' for a local OTel collector.
1794+
return this.telemetrySettings.otlpProtocol ?? 'http';
17921795
}
17931796

17941797
getTelemetryTarget(): TelemetryTarget {

packages/core/src/telemetry/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ export enum TelemetryTarget {
1111
}
1212

1313
const DEFAULT_TELEMETRY_TARGET = TelemetryTarget.LOCAL;
14-
const DEFAULT_OTLP_ENDPOINT = 'http://localhost:4317';
14+
// Public OTLP/HTTP ingress fronting the homelab LGTM stack (Cloudflare-fronted,
15+
// TLS-terminated). Authenticated via OTEL_INGRESS_TOKEN bearer token. See
16+
// homelab-iac#34. Override per-host via telemetry.otlpEndpoint in settings.
17+
const DEFAULT_OTLP_ENDPOINT = 'https://otel.proto-labs.ai';
1518

1619
export { DEFAULT_TELEMETRY_TARGET, DEFAULT_OTLP_ENDPOINT };
1720
export {

packages/core/src/telemetry/sdk.test.ts

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,44 @@ describe('Telemetry SDK', () => {
108108
expect(NodeSDK.prototype.start).toHaveBeenCalled();
109109
});
110110

111+
it('attaches Authorization: Bearer header to HTTP exporters when OTEL_INGRESS_TOKEN is set', () => {
112+
process.env['OTEL_INGRESS_TOKEN'] = 'test-bearer-token';
113+
vi.spyOn(mockConfig, 'getTelemetryOtlpProtocol').mockReturnValue('http');
114+
vi.spyOn(mockConfig, 'getTelemetryOtlpEndpoint').mockReturnValue(
115+
'https://otel.proto-labs.ai',
116+
);
117+
118+
initializeTelemetry(mockConfig);
119+
120+
const expectedHeaders = { Authorization: 'Bearer test-bearer-token' };
121+
expect(OTLPTraceExporterHttp).toHaveBeenCalledWith(
122+
expect.objectContaining({ headers: expectedHeaders }),
123+
);
124+
expect(OTLPLogExporterHttp).toHaveBeenCalledWith(
125+
expect.objectContaining({ headers: expectedHeaders }),
126+
);
127+
expect(OTLPMetricExporterHttp).toHaveBeenCalledWith(
128+
expect.objectContaining({ headers: expectedHeaders }),
129+
);
130+
131+
delete process.env['OTEL_INGRESS_TOKEN'];
132+
});
133+
134+
it('omits the headers field on HTTP exporters when OTEL_INGRESS_TOKEN is unset', () => {
135+
delete process.env['OTEL_INGRESS_TOKEN'];
136+
vi.spyOn(mockConfig, 'getTelemetryOtlpProtocol').mockReturnValue('http');
137+
vi.spyOn(mockConfig, 'getTelemetryOtlpEndpoint').mockReturnValue(
138+
'http://localhost:4318',
139+
);
140+
141+
initializeTelemetry(mockConfig);
142+
143+
// Exact match — no `headers` key on the call args.
144+
expect(OTLPTraceExporterHttp).toHaveBeenCalledWith({
145+
url: 'http://localhost:4318/',
146+
});
147+
});
148+
111149
it('should parse gRPC endpoint correctly', () => {
112150
vi.spyOn(mockConfig, 'getTelemetryOtlpEndpoint').mockReturnValue(
113151
'https://my-collector.com',
@@ -272,33 +310,20 @@ describe('Telemetry SDK', () => {
272310
});
273311
});
274312

275-
it('still initializes telemetry when only Langfuse is configured and primary telemetry is disabled', () => {
276-
process.env['LANGFUSE_PUBLIC_KEY'] = 'pk-lf-test';
277-
process.env['LANGFUSE_SECRET_KEY'] = 'sk-lf-test';
278-
vi.spyOn(mockConfig, 'getTelemetryEnabled').mockReturnValue(false);
279-
280-
initializeTelemetry(mockConfig);
281-
282-
// Should still start the SDK because Langfuse processor is non-null
283-
expect(NodeSDK.prototype.start).toHaveBeenCalled();
284-
});
285-
286-
it('does not create the default gRPC OTLP exporter when only Langfuse is configured', () => {
313+
it('does NOT initialize telemetry when only Langfuse env vars are set and telemetry.enabled is false', () => {
314+
// Opt-in policy: privacy is the default. Even if Langfuse env vars are
315+
// present, telemetry stays off until the user explicitly opts in via
316+
// telemetry.enabled = true.
287317
process.env['LANGFUSE_PUBLIC_KEY'] = 'pk-lf-test';
288318
process.env['LANGFUSE_SECRET_KEY'] = 'sk-lf-test';
289319
vi.spyOn(mockConfig, 'getTelemetryEnabled').mockReturnValue(false);
290320

291321
initializeTelemetry(mockConfig);
292322

293-
// The default endpoint is localhost:4317 but with telemetry disabled,
294-
// no gRPC exporter should be instantiated (only the Langfuse HTTP one).
323+
expect(NodeSDK.prototype.start).not.toHaveBeenCalled();
295324
expect(OTLPTraceExporter).not.toHaveBeenCalled();
296325
expect(OTLPLogExporter).not.toHaveBeenCalled();
297326
expect(OTLPMetricExporter).not.toHaveBeenCalled();
298-
299-
const sdkCalls = vi.mocked(NodeSDK).mock.calls;
300-
const spanProcessors = sdkCalls[0][0]?.spanProcessors;
301-
expect(spanProcessors).toHaveLength(1);
302327
});
303328
});
304329
});

0 commit comments

Comments
 (0)