Skip to content

feat(setup): add OTEL_INSTRUMENT_FETCH env var to toggle fetch tracing - #10

Merged
Ryan Zhu (underthestars-zhy) merged 1 commit into
mainfrom
ryan/fix-auto-instrument
Jun 29, 2026
Merged

feat(setup): add OTEL_INSTRUMENT_FETCH env var to toggle fetch tracing#10
Ryan Zhu (underthestars-zhy) merged 1 commit into
mainfrom
ryan/fix-auto-instrument

Conversation

@underthestars-zhy

@underthestars-zhy Ryan Zhu (underthestars-zhy) commented Jun 29, 2026

Copy link
Copy Markdown
Member

Summary

  • Environment toggle for outbound fetch tracing. A new OTEL_INSTRUMENT_FETCH env var flips fetch instrumentation on or off, completing the package's "env wins over code" config story that already covered endpoints, headers, and log level. true / 1 forces it on (even without a traces endpoint), false / 0 disables it — so operators can silence noisy or expensive fetch spans in production without a code change or redeploy of app logic.
  • Precedence: OTEL_INSTRUMENT_FETCH (env) → instrumentFetch (option) → smart default (on when a traces endpoint is configured). Resolved as want = envWant ?? option ?? hasTraces. The env var only drives the on/off decision; the object form (mode, ignore) still governs how fetch is traced when it's on, so OTEL_INSTRUMENT_FETCH=true alongside instrumentFetch: { mode: "global", ignore } keeps your mode and ignore.
  • Unrecognized values defer to code. A new parseBooleanEnv() helper returns undefined for unset or unrecognized values (mirroring logger.ts's envLevel()), so a typo like OTEL_INSTRUMENT_FETCH=maybe falls through to the instrumentFetch option rather than forcing a default. Accepts true/1/false/0, case- and whitespace-insensitive.
  • Built on the fetch instrumentation from feat(fetch): automatic outbound fetch tracing + end-to-end Collector tests #5 / feat(fetch): native undici instrumentation on Node + per-instance createInstrumentedFetch #8 — this adds the env-level kill switch (and override) on top of the native-undici-on-Node / global-wrap-on-Bun strategy.

Usage

# Silence outbound fetch tracing in production — no code change, no app redeploy
OTEL_INSTRUMENT_FETCH=false

# Or force it on even without a traces endpoint configured
OTEL_INSTRUMENT_FETCH=true
// Env wins over the option: with OTEL_INSTRUMENT_FETCH=false set, this stays OFF
setupOtel({ serviceName: "orders-api", instrumentFetch: true });

Because setupOtel() is idempotent (first call wins), a later setupOtel({ instrumentFetch: false }) can't turn off instrumentation an earlier call already enabled — decide on the first call, or use OTEL_INSTRUMENT_FETCH, which is read whenever setup actually runs.

Changes

File Change
src/setup.ts Add parseBooleanEnv() (accepts true/1/false/0, case- & whitespace-insensitive; undefined for unset/unrecognized — so the code option still decides). startFetchInstrumentation now resolves want = parseBooleanEnv(process.env.OTEL_INSTRUMENT_FETCH) ?? option ?? hasTraces. JSDoc on instrumentFetch and startFetchInstrumentation documents the env override.
tests/setup.test.ts Replace the Bun-only globalThis.fetch-identity check with a fetchInstrumentationActive() helper that detects both strategies — the global-wrap marker (Symbol.for("@photon-ai/otel.fetch.original")) or native undici diagnostics_channel subscribers — so on/off is asserted correctly on Node too (the bare global check was a silent no-op there). +3 tests: env false beats instrumentFetch: true; env true beats instrumentFetch: false; an unrecognized value falls through to the code option.
tests/integration/otel-collector.test.ts New end-to-end case: drive the OTEL_INSTRUMENT_FETCH=false scenario in a child process under the same runtime (process.execPath), then assert the collector received the child's control span but no fetch CLIENT span. readSpans(targetNonce) is now per-run scoped so the child's nonce can be queried separately from the main run.
tests/integration/disabled-fetch.child.mjs New. Child process for the disabled-fetch case — setupOtel is process-global and idempotent, so it can't share the main run's process. Imports the built dist/ bundle so it runs unchanged under both node and bun; makes one fetch and flushes.
.github/workflows/integration.yml Add a bun run build step before the integration suite (the new child imports dist/).
tests/integration/README.md Document the new bun run build prerequisite and the child-process OTEL_INSTRUMENT_FETCH=false scenario.
README.md / docs/configuration.mdx / docs/guides/fetch-instrumentation.mdx Document the OTEL_INSTRUMENT_FETCH env var, its precedence over the option and default, the env-vs-config table row, and the setupOtel()-idempotency interaction.

Test plan

  • bun run test71 unit tests pass (3 new in setup.test.ts; the suite's fetch on/off assertions now detect both the global-wrap and native-undici strategies)
  • bun run build — tsdown ESM (22.91 kB) + DTS (8.51 kB) build succeeds, emitting dist/index.js + dist/index.d.ts
  • bun x ultracite check — clean (only the unrelated broken-symlink warning under .cursor/rules/)
  • bun run test:integration — real OTLP/HTTP round-trip to a live Collector, including the new OTEL_INSTRUMENT_FETCH=false child case on both Bun and Node. Requires Docker, so not run on this machine; covered in CI by .github/workflows/integration.yml.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features

    • Added support for an environment variable toggle to enable or disable fetch tracing.
    • Expanded integration coverage for fetch tracing being turned off in a separate process.
  • Bug Fixes

    • Improved fetch instrumentation precedence so the environment variable overrides the configuration option and default behavior.
    • Ensured tests verify both supported instrumentation paths consistently.
  • Documentation

    • Updated setup, configuration, and fetch instrumentation guides with the new toggle and precedence rules.
    • Clarified integration test setup requirements.

Introduces `OTEL_INSTRUMENT_FETCH` (`true`/`1` | `false`/`0`) that
overrides both the `instrumentFetch` option and the smart default,
matching the rest of the package's env-wins config story.

- `parseBooleanEnv` helper mirrors `envLevel()` — defers on unrecognized
  values so the code option still applies
- Integration test covers the disabled case via a child process
  (`disabled-fetch.child.mjs`) because `setupOtel` is process-global and
  idempotent; CI workflow now runs `bun run build` first since the child
  imports the built bundle
- Unit tests use a `fetchInstrumentationActive()` helper that checks
  both
  the global-wrap marker and the undici diagnostics_channel subscriber,
  catching the native Node path that never reassigns `globalThis.fetch`
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds OTEL_INSTRUMENT_FETCH environment variable support to setupOtel(), giving it precedence over the instrumentFetch option and smart default. A parseBooleanEnv helper parses true/1/false/0 values. Unit tests, a new integration child-process script (disabled-fetch.child.mjs), and an extended collector integration test validate the behavior end-to-end. CI gains a build step, and README/docs are updated.

Changes

OTEL_INSTRUMENT_FETCH toggle

Layer / File(s) Summary
parseBooleanEnv helper and startFetchInstrumentation precedence
src/setup.ts
Adds parseBooleanEnv() to parse true/1/false/0 env strings and changes startFetchInstrumentation enablement to envWant ?? option ?? hasTraces, with updated JSDoc.
Unit tests for env precedence and fetchInstrumentationActive helper
tests/setup.test.ts
Introduces fetchInstrumentationActive() covering both global-wrap and undici-channel strategies; updates existing fetch-on/off tests and adds new tests for OTEL_INSTRUMENT_FETCH=false, true, and unrecognized values.
Integration child-process script
tests/integration/disabled-fetch.child.mjs
Adds a standalone child script that boots OTel with OTEL_INSTRUMENT_FETCH=false, executes a fetch inside a control span, then shuts down and flushes telemetry.
Integration test: spawn child, poll collector, assert no CLIENT span
tests/integration/otel-collector.test.ts
Generalizes readSpans to accept a target nonce, spawns the disabled-fetch child via runDisabledFetchChild(), polls the collector, and asserts no fetch CLIENT span is emitted and the parent span has zero children.
CI build step and documentation
.github/workflows/integration.yml, tests/integration/README.md, README.md, docs/configuration.mdx, docs/guides/fetch-instrumentation.mdx
Adds bun run build step before integration tests; updates README and docs with OTEL_INSTRUMENT_FETCH descriptions, precedence table, and idempotency clarifications.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • photon-hq/otel#5: Modifies setupOtel()/instrumentFetch wiring and integration collector tests — the base functionality this PR extends with env-var precedence.
  • photon-hq/otel#8: Changes startFetchInstrumentation() Node/Bun strategy and mode routing — directly overlaps with the same decision point where envWant is now inserted.

Poem

🐇 Hoppy news from the warren today,
An env var can now have its say!
OTEL_INSTRUMENT_FETCH=false — shh, be still,
No fetch spans shall leak against your will.
The rabbit checked the collector and smiled with glee:
Zero CLIENT spans — instrumentation-free! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding OTEL_INSTRUMENT_FETCH to control fetch tracing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ryan/fix-auto-instrument

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/setup.test.ts`:
- Around line 24-27: The fetchInstrumentationActive helper is using a
process-global Undici channel check, which can be polluted by unrelated
subscribers and make the setup test flaky. Update the tests around
fetchInstrumentationActive, setupOtel, and the UNDICI_CHANNEL assertion to
capture the baseline subscriber state before each test run and verify only the
change introduced by setupOtel() rather than the absolute
dc.hasSubscribers(UNDICI_CHANNEL) value. Keep the existing FETCH_PATCH_MARKER
check, but scope the undici-channel assertion to the current test’s delta so
other tests or helpers do not affect the result.
- Around line 149-204: The setupOtel tests are leaving a live OTEL handle behind
if an assertion fails before shutdown, which can contaminate later cases because
setupOtel() reuses the active handle. Update the fetch-instrumentation tests to
always call handle.shutdown() in a finally block (or centralized afterEach
cleanup) around the setupOtel() usage, using the existing setupOtel and
fetchInstrumentationActive helpers so cleanup runs even when expectations throw.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6eb3ad2-e867-4a6c-a2ab-2a360fc1a373

📥 Commits

Reviewing files that changed from the base of the PR and between 191a15b and fc0df5d.

📒 Files selected for processing (9)
  • .github/workflows/integration.yml
  • README.md
  • docs/configuration.mdx
  • docs/guides/fetch-instrumentation.mdx
  • src/setup.ts
  • tests/integration/README.md
  • tests/integration/disabled-fetch.child.mjs
  • tests/integration/otel-collector.test.ts
  • tests/setup.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

Prefer interface for defining object shapes in TypeScript rather than type aliases

**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Prefer unknown over any when the type is genuinely unknown
Use as const const assertions for immutable values and literal types
Leverage TypeScript type narrowing instead of type assertions
Use meaningful variable names instead of magic numbers; extract descriptive constants
Use arrow functions for callbacks and short functions
Prefer for...of loops over .forEach() and indexed for loops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Use const by default, let only when reassignment is needed, and never use var
Always await promises in async functions and use the return value
Use async/await syntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors

Files:

  • tests/setup.test.ts
  • src/setup.ts
  • tests/integration/otel-collector.test.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

**/*.{js,jsx,ts,tsx}: Use camelCase for variable and function names in JavaScript/TypeScript
Use PascalCase for class and component names in JavaScript/TypeScript
Always use async/await for promise handling instead of .then() chains
Include JSDoc comments for exported functions and classes
Use meaningful variable names that clearly describe their purpose
Avoid deeply nested conditionals; use early returns or guard clauses instead
Use const by default, let when reassignment is needed, avoid var

**/*.{js,jsx,ts,tsx}: Remove console.log, debugger, and alert statements from production code
Throw Error objects with descriptive messages, not strings or other values
Use try-catch blocks meaningfully; don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Keep functions focused and under reasonable cognitive complexity limits
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Group related code together and separate concerns
Add rel="noopener" when using target="_blank" on links
Avoid dangerouslySetInnerHTML unless absolutely necessary
Don't use eval() or assign directly to document.cookie
Validate and sanitize user input
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Avoid barrel files (index files that re-export everything)
Use proper image components (for example, Next.js <Image>) over <img> tags
Use next/head or the App Router metadata API for head elements
Use Server Components for async data fetching instead of async Client Components

Files:

  • tests/setup.test.ts
  • src/setup.ts
  • tests/integration/otel-collector.test.ts
**/*.test.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

Write unit tests for all public functions and components

Files:

  • tests/setup.test.ts
  • tests/integration/otel-collector.test.ts
**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{test,spec}.{js,jsx,ts,tsx}: Write assertions inside it() or test() blocks
Avoid done callbacks in async tests; use async/await instead
Don't use .only or .skip in committed code
Keep test suites reasonably flat; avoid excessive describe nesting

Files:

  • tests/setup.test.ts
  • tests/integration/otel-collector.test.ts
🪛 ast-grep (0.44.0)
tests/integration/otel-collector.test.ts

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🔇 Additional comments (12)
.github/workflows/integration.yml (1)

34-38: LGTM!

tests/integration/README.md (2)

24-28: LGTM!


54-58: LGTM!

README.md (2)

116-116: LGTM!


140-141: LGTM!

docs/configuration.mdx (2)

186-193: LGTM!


239-239: LGTM!

docs/guides/fetch-instrumentation.mdx (2)

22-37: LGTM!


144-145: LGTM!

src/setup.ts (1)

51-53: LGTM!

Also applies to: 98-114, 167-185

tests/integration/disabled-fetch.child.mjs (1)

1-56: LGTM!

tests/integration/otel-collector.test.ts (1)

1-1: LGTM!

Also applies to: 36-39, 48-51, 166-195, 255-300, 350-375, 462-480

Comment thread tests/setup.test.ts
Comment on lines +24 to +27
function fetchInstrumentationActive(): boolean {
const fetchFn = globalThis.fetch as unknown as Record<symbol, unknown>;
const globalWrapped = Boolean(fetchFn[FETCH_PATCH_MARKER]);
return globalWrapped || dc.hasSubscribers(UNDICI_CHANNEL);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Scope the undici-channel check to this test run.

dc.hasSubscribers(UNDICI_CHANNEL) is process-global, so any unrelated subscriber makes fetchInstrumentationActive() return true even when setupOtel() did not enable fetch instrumentation. That makes these assertions flaky as soon as another test or helper touches the same channel. Snapshot the baseline before each test and assert on the delta instead of the absolute subscriber state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/setup.test.ts` around lines 24 - 27, The fetchInstrumentationActive
helper is using a process-global Undici channel check, which can be polluted by
unrelated subscribers and make the setup test flaky. Update the tests around
fetchInstrumentationActive, setupOtel, and the UNDICI_CHANNEL assertion to
capture the baseline subscriber state before each test run and verify only the
change introduced by setupOtel() rather than the absolute
dc.hasSubscribers(UNDICI_CHANNEL) value. Keep the existing FETCH_PATCH_MARKER
check, but scope the undici-channel assertion to the current test’s delta so
other tests or helpers do not affect the result.

Comment thread tests/setup.test.ts
Comment on lines +149 to +204
it("OTEL_INSTRUMENT_FETCH=false disables fetch even with instrumentFetch: true", async () => {
process.env.OTEL_INSTRUMENT_FETCH = "false";
const original = globalThis.fetch;
try {
const handle = setupOtel({
serviceName: "env-fetch-off",
endpoint: "https://otel.example.com",
instrumentFetch: true,
});
// Env wins over the code option: neither strategy may activate.
expect(fetchInstrumentationActive()).toBe(false);
expect(globalThis.fetch).toBe(original);
await handle.shutdown();
} finally {
globalThis.fetch = original;
}
});

it("OTEL_INSTRUMENT_FETCH=true enables fetch even with instrumentFetch: false", async () => {
process.env.OTEL_INSTRUMENT_FETCH = "true";
const original = globalThis.fetch;
try {
const handle = setupOtel({
serviceName: "env-fetch-on",
endpoint: "https://otel.example.com",
instrumentFetch: false,
});
// Env wins over the code option: instrumentation is active despite the
// explicit opt-out.
expect(fetchInstrumentationActive()).toBe(true);
await handle.shutdown();
expect(fetchInstrumentationActive()).toBe(false);
expect(globalThis.fetch).toBe(original);
} finally {
globalThis.fetch = original;
}
});

it("ignores an unrecognized OTEL_INSTRUMENT_FETCH and honors the code option", async () => {
process.env.OTEL_INSTRUMENT_FETCH = "maybe";
const original = globalThis.fetch;
try {
const handle = setupOtel({
serviceName: "env-fetch-bogus",
endpoint: "https://otel.example.com",
instrumentFetch: false,
});
// Unrecognized value -> parseBooleanEnv returns undefined -> the code
// option (false) decides, so fetch stays disabled.
expect(fetchInstrumentationActive()).toBe(false);
expect(globalThis.fetch).toBe(original);
await handle.shutdown();
} finally {
globalThis.fetch = original;
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Always shut down setupOtel() from cleanup.

If an expectation throws before await handle.shutdown(), activeHandle stays set and later tests will reuse the stale global setup because setupOtel() short-circuits while a handle is active. Move shutdown into finally/afterEach so failures do not contaminate the rest of the suite.

Suggested pattern
   it("OTEL_INSTRUMENT_FETCH=false disables fetch even with instrumentFetch: true", async () => {
     process.env.OTEL_INSTRUMENT_FETCH = "false";
     const original = globalThis.fetch;
+    let handle: ReturnType<typeof setupOtel> | undefined;
     try {
-      const handle = setupOtel({
+      handle = setupOtel({
         serviceName: "env-fetch-off",
         endpoint: "https://otel.example.com",
         instrumentFetch: true,
       });
       // Env wins over the code option: neither strategy may activate.
       expect(fetchInstrumentationActive()).toBe(false);
       expect(globalThis.fetch).toBe(original);
-      await handle.shutdown();
     } finally {
+      await handle?.shutdown();
       globalThis.fetch = original;
     }
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("OTEL_INSTRUMENT_FETCH=false disables fetch even with instrumentFetch: true", async () => {
process.env.OTEL_INSTRUMENT_FETCH = "false";
const original = globalThis.fetch;
try {
const handle = setupOtel({
serviceName: "env-fetch-off",
endpoint: "https://otel.example.com",
instrumentFetch: true,
});
// Env wins over the code option: neither strategy may activate.
expect(fetchInstrumentationActive()).toBe(false);
expect(globalThis.fetch).toBe(original);
await handle.shutdown();
} finally {
globalThis.fetch = original;
}
});
it("OTEL_INSTRUMENT_FETCH=true enables fetch even with instrumentFetch: false", async () => {
process.env.OTEL_INSTRUMENT_FETCH = "true";
const original = globalThis.fetch;
try {
const handle = setupOtel({
serviceName: "env-fetch-on",
endpoint: "https://otel.example.com",
instrumentFetch: false,
});
// Env wins over the code option: instrumentation is active despite the
// explicit opt-out.
expect(fetchInstrumentationActive()).toBe(true);
await handle.shutdown();
expect(fetchInstrumentationActive()).toBe(false);
expect(globalThis.fetch).toBe(original);
} finally {
globalThis.fetch = original;
}
});
it("ignores an unrecognized OTEL_INSTRUMENT_FETCH and honors the code option", async () => {
process.env.OTEL_INSTRUMENT_FETCH = "maybe";
const original = globalThis.fetch;
try {
const handle = setupOtel({
serviceName: "env-fetch-bogus",
endpoint: "https://otel.example.com",
instrumentFetch: false,
});
// Unrecognized value -> parseBooleanEnv returns undefined -> the code
// option (false) decides, so fetch stays disabled.
expect(fetchInstrumentationActive()).toBe(false);
expect(globalThis.fetch).toBe(original);
await handle.shutdown();
} finally {
globalThis.fetch = original;
}
});
it("OTEL_INSTRUMENT_FETCH=false disables fetch even with instrumentFetch: true", async () => {
process.env.OTEL_INSTRUMENT_FETCH = "false";
const original = globalThis.fetch;
let handle: ReturnType<typeof setupOtel> | undefined;
try {
handle = setupOtel({
serviceName: "env-fetch-off",
endpoint: "https://otel.example.com",
instrumentFetch: true,
});
// Env wins over the code option: neither strategy may activate.
expect(fetchInstrumentationActive()).toBe(false);
expect(globalThis.fetch).toBe(original);
} finally {
await handle?.shutdown();
globalThis.fetch = original;
}
});
it("OTEL_INSTRUMENT_FETCH=true enables fetch even with instrumentFetch: false", async () => {
process.env.OTEL_INSTRUMENT_FETCH = "true";
const original = globalThis.fetch;
try {
const handle = setupOtel({
serviceName: "env-fetch-on",
endpoint: "https://otel.example.com",
instrumentFetch: false,
});
// Env wins over the code option: instrumentation is active despite the
// explicit opt-out.
expect(fetchInstrumentationActive()).toBe(true);
await handle.shutdown();
expect(fetchInstrumentationActive()).toBe(false);
expect(globalThis.fetch).toBe(original);
} finally {
globalThis.fetch = original;
}
});
it("ignores an unrecognized OTEL_INSTRUMENT_FETCH and honors the code option", async () => {
process.env.OTEL_INSTRUMENT_FETCH = "maybe";
const original = globalThis.fetch;
try {
const handle = setupOtel({
serviceName: "env-fetch-bogus",
endpoint: "https://otel.example.com",
instrumentFetch: false,
});
// Unrecognized value -> parseBooleanEnv returns undefined -> the code
// option (false) decides, so fetch stays disabled.
expect(fetchInstrumentationActive()).toBe(false);
expect(globalThis.fetch).toBe(original);
await handle.shutdown();
} finally {
globalThis.fetch = original;
}
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/setup.test.ts` around lines 149 - 204, The setupOtel tests are leaving
a live OTEL handle behind if an assertion fails before shutdown, which can
contaminate later cases because setupOtel() reuses the active handle. Update the
fetch-instrumentation tests to always call handle.shutdown() in a finally block
(or centralized afterEach cleanup) around the setupOtel() usage, using the
existing setupOtel and fetchInstrumentationActive helpers so cleanup runs even
when expectations throw.

@underthestars-zhy
Ryan Zhu (underthestars-zhy) merged commit 1b93ef5 into main Jun 29, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release Fight on!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant