From a6e60da779a0c9752631af87d0964883f39c36ad Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sat, 19 Sep 2026 14:40:50 -0400 Subject: [PATCH 1/3] fix(observability): OTEL_SDK_DISABLED stops only the route it belongs to An environment variable should not be able to countermand an endpoint the user configured in Settings or named with T3CODE_OTLP_*, which is the rule every other OTEL_* name already follows here. The kill switch a shared machine needs is now T3CODE_OTEL_SDK_DISABLED, a name T3 Code owns. Signed-off-by: Yordis Prieto --- .../desktop/src/app/DesktopOtlpExport.test.ts | 20 +++++- apps/desktop/src/app/DesktopOtlpExport.ts | 9 ++- apps/server/src/cli/config.test.ts | 16 ++++- apps/server/src/cli/config.ts | 6 +- .../src/observability/Layers/Observability.ts | 6 -- ...the-standard-otel-variables-are-honored.md | 19 +++-- ...22-the-desktop-app-reports-its-own-work.md | 2 +- docs/operations/observability.md | 17 +++-- packages/shared/src/otelEnvironment.test.ts | 41 +++++++++++ packages/shared/src/otelEnvironment.ts | 72 +++++++++++++++++-- 10 files changed, 175 insertions(+), 33 deletions(-) diff --git a/apps/desktop/src/app/DesktopOtlpExport.test.ts b/apps/desktop/src/app/DesktopOtlpExport.test.ts index 6483652d75d5..2e365dbd946d 100644 --- a/apps/desktop/src/app/DesktopOtlpExport.test.ts +++ b/apps/desktop/src/app/DesktopOtlpExport.test.ts @@ -109,7 +109,7 @@ describe("resolveDesktopOtlpExport", () => { }), ); - it.effect("stops every export when the OpenTelemetry SDK is disabled", () => + it.effect("keeps the named endpoint when the OpenTelemetry SDK is disabled", () => Effect.gen(function* () { const resolved = yield* resolve( { @@ -118,13 +118,29 @@ describe("resolveDesktopOtlpExport", () => { }, { named: { traces: "http://127.0.0.1:4318/v1/traces" } }, ); - assert.strictEqual(resolved.traces.url, undefined); + assert.strictEqual(resolved.traces.url, "http://127.0.0.1:4318/v1/traces"); assert.strictEqual(resolved.metrics.url, undefined); assert.strictEqual(resolved.logs.url, undefined); assert.include(resolved.warnings.join("\n"), "OTEL_SDK_DISABLED"); }), ); + it.effect("stops every export once T3 Code's own switch is set", () => + Effect.gen(function* () { + const resolved = yield* resolve( + { + T3CODE_OTEL_SDK_DISABLED: "true", + OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com", + }, + { named: { traces: "http://127.0.0.1:4318/v1/traces" } }, + ); + assert.strictEqual(resolved.traces.url, undefined); + assert.strictEqual(resolved.metrics.url, undefined); + assert.strictEqual(resolved.logs.url, undefined); + assert.include(resolved.warnings.join("\n"), "T3CODE_OTEL_SDK_DISABLED"); + }), + ); + it.effect("declines only the signal that asked for a protocol T3 Code cannot speak", () => Effect.gen(function* () { const resolved = yield* resolve({ diff --git a/apps/desktop/src/app/DesktopOtlpExport.ts b/apps/desktop/src/app/DesktopOtlpExport.ts index 9b5b78359fc7..6bc868638230 100644 --- a/apps/desktop/src/app/DesktopOtlpExport.ts +++ b/apps/desktop/src/app/DesktopOtlpExport.ts @@ -120,16 +120,15 @@ export const resolveDesktopOtlpExport = (input: DesktopOtlpExportInput): Desktop attributes: { ...otel.resource.attributes, ...input.runtimeAttributes }, }; - if (otel.disabled) { + // `OTEL_SDK_DISABLED` is already scoped to the OpenTelemetry variables by + // the reader, so only T3 Code's own switch reaches an endpoint named here. + if (otel.forceDisabled) { return { traces: offSignal, metrics: offSignal, logs: offSignal, resource, - warnings: [ - ...otel.warnings, - "OTEL_SDK_DISABLED is set, so the desktop app exports no telemetry; this overrides T3CODE_OTLP_* and Settings too", - ], + warnings: [...otel.warnings], }; } diff --git a/apps/server/src/cli/config.test.ts b/apps/server/src/cli/config.test.ts index 8a3d940c6f3c..d2875a00c832 100644 --- a/apps/server/src/cli/config.test.ts +++ b/apps/server/src/cli/config.test.ts @@ -777,7 +777,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { }), ); - it.effect("exports nothing at all once the SDK is switched off", () => + it.effect("switches off the OpenTelemetry variables without touching the named endpoint", () => Effect.gen(function* () { const resolved = yield* resolveWithEnv({ OTEL_SDK_DISABLED: "true", @@ -785,6 +785,20 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { T3CODE_OTLP_TRACES_URL: "http://localhost:4318/v1/traces", }); + expect(resolved.otlpTracesUrl).toBe("http://localhost:4318/v1/traces"); + expect(resolved.otlpMetricsUrl).toBeUndefined(); + expect(resolved.otlpLogsUrl).toBeUndefined(); + }), + ); + + it.effect("exports nothing at all once T3 Code's own switch is set", () => + Effect.gen(function* () { + const resolved = yield* resolveWithEnv({ + T3CODE_OTEL_SDK_DISABLED: "true", + OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com", + T3CODE_OTLP_TRACES_URL: "http://localhost:4318/v1/traces", + }); + expect(resolved.otlpTracesUrl).toBeUndefined(); expect(resolved.otlpMetricsUrl).toBeUndefined(); expect(resolved.otlpLogsUrl).toBeUndefined(); diff --git a/apps/server/src/cli/config.ts b/apps/server/src/cli/config.ts index 84ac0a8c61da..103450848bbe 100644 --- a/apps/server/src/cli/config.ts +++ b/apps/server/src/cli/config.ts @@ -430,13 +430,13 @@ export const resolveServerConfig = ( traceBatchWindowMs: env.traceBatchWindowMs, traceMaxBytes: env.traceMaxBytes, traceMaxFiles: env.traceMaxFiles, - otlpTracesUrl: otelEnvironment.disabled + otlpTracesUrl: otelEnvironment.forceDisabled ? undefined : (namedTracesUrl ?? otelEnvironment.traces.settings?.url), - otlpMetricsUrl: otelEnvironment.disabled + otlpMetricsUrl: otelEnvironment.forceDisabled ? undefined : (namedMetricsUrl ?? otelEnvironment.metrics.settings?.url), - otlpLogsUrl: otelEnvironment.disabled + otlpLogsUrl: otelEnvironment.forceDisabled ? undefined : (namedLogsUrl ?? otelEnvironment.logs.settings?.url), // T3 Code has one interval variable and it deliberately covers every diff --git a/apps/server/src/observability/Layers/Observability.ts b/apps/server/src/observability/Layers/Observability.ts index 14e7d240afd4..e8c11eb0671a 100644 --- a/apps/server/src/observability/Layers/Observability.ts +++ b/apps/server/src/observability/Layers/Observability.ts @@ -27,12 +27,6 @@ export const ObservabilityLive = Layer.unwrap( yield* Effect.logWarning(warning); } - if (otel.disabled) { - yield* Effect.logWarning( - "OTEL_SDK_DISABLED is set, so no telemetry is exported; this overrides T3CODE_OTLP_* and Settings too", - ); - } - // One variable can decline every signal, and saying so three times reads // like three separate problems. const declined = new Set( diff --git a/docs/fork/0018-the-standard-otel-variables-are-honored.md b/docs/fork/0018-the-standard-otel-variables-are-honored.md index 74bb36d698df..e7479ad9120d 100644 --- a/docs/fork/0018-the-standard-otel-variables-are-honored.md +++ b/docs/fork/0018-the-standard-otel-variables-are-honored.md @@ -24,9 +24,11 @@ `OTEL_BLRP_MAX_EXPORT_BATCH_SIZE` are the batching knobs the specification defines for logs, so a delay meant for spans does not decide how promptly a log record arrives. -- Turn export off from the environment. `OTEL_SDK_DISABLED=true` stops every - export, including one configured in Settings, which is the one switch a - shared machine needs. +- Turn export off from the environment. `T3CODE_OTEL_SDK_DISABLED=true` stops + every export, including one configured in Settings, which is the one switch a + shared machine needs. `OTEL_SDK_DISABLED=true` is scoped the way every other + standard name here is: it switches off what these variables configured and + leaves an endpoint you named yourself alone. - Keep whatever you have. The `T3CODE_OTLP_*` names, the desktop bootstrap envelope, and Settings all still win over the environment, and a setup that never mentioned OpenTelemetry keeps the wire format it always used. @@ -54,8 +56,15 @@ people can actually reach. Auto-enabling from an ambient endpoint is the deliberate part. Every other OpenTelemetry SDK behaves this way, and a telemetry variable that some processes -honor and others quietly ignore is worse than either answer, so `OTEL_SDK_DISABLED` -is the way out rather than a requirement to opt in. +honor and others quietly ignore is worse than either answer, so +`OTEL_SDK_DISABLED` is the way out rather than a requirement to opt in. + +That switch stops the route it belongs to and no more, which is the same rule +the rest of these variables follow. A name the environment supplied should not +be able to countermand a choice someone made in Settings, so the switch that +can is `T3CODE_OTEL_SDK_DISABLED`, a name T3 Code owns. Two switches is one more +than the specification describes, and it is the only honest way to have both an +opt-out for the ambient case and a kill switch for the machine. ## Upstream considerations diff --git a/docs/fork/0022-the-desktop-app-reports-its-own-work.md b/docs/fork/0022-the-desktop-app-reports-its-own-work.md index 09856b82f10d..b630106f692c 100644 --- a/docs/fork/0022-the-desktop-app-reports-its-own-work.md +++ b/docs/fork/0022-the-desktop-app-reports-its-own-work.md @@ -18,7 +18,7 @@ Metrics stay off while the main process records none, so a configured metrics endpoint hears from the server and nobody else rather than receiving an empty payload every interval. -- Turn it off the same way. `OTEL_SDK_DISABLED=true` stops both processes. +- Turn it off the same way. `T3CODE_OTEL_SDK_DISABLED=true` stops both processes. - Tell the two apart without trusting the environment. The main process reports as `t3-desktop`, joining `t3-server` and `t3-web`, and `service.runtime` on it is always `desktop`, so an ambient diff --git a/docs/operations/observability.md b/docs/operations/observability.md index c3ffc646f100..c63290db8d20 100644 --- a/docs/operations/observability.md +++ b/docs/operations/observability.md @@ -208,7 +208,9 @@ The base endpoint is a base, not a full URL: traces go to `/v1/traces` Set `OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_ENDPOINT` when a signal needs a full URL of its own. Ambient `OTEL_*` variables turn export on by themselves. A work collector in your shell profile means -T3 Code exports to it, so use `OTEL_SDK_DISABLED=true` if that is not what you want. +T3 Code exports to it, so use `OTEL_SDK_DISABLED=true` if that is not what you want. It switches off +the variables in this section and nothing else: an endpoint you named with `T3CODE_OTLP_*` or set in +Settings keeps exporting, and `T3CODE_OTEL_SDK_DISABLED=true` is the switch that stops those too. #### Which Processes Export @@ -263,14 +265,16 @@ goes. The three signals are resolved separately, so traces can come from one source and metrics or logs from another. -`OTEL_SDK_DISABLED=true` outranks all four and stops every export, including one configured through -Settings. +Each switch stops the sources it belongs to. `OTEL_SDK_DISABLED=true` stops source 4, the way every +other `OTEL_*` variable reaches source 4 alone, so a signal configured above it keeps exporting. +`T3CODE_OTEL_SDK_DISABLED=true` outranks all four and stops every export, including one configured +through Settings, which is the one switch a shared machine needs. #### What Is Read | Variable | Effect | | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- | -| `OTEL_SDK_DISABLED` | Stops all export | +| `OTEL_SDK_DISABLED` | Stops export configured by the variables below | | `OTEL_EXPORTER_OTLP_ENDPOINT` | Base URL for every signal | | `OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_ENDPOINT` | Full URL for one signal | | `OTEL_EXPORTER_OTLP_HEADERS`, `OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_HEADERS` | Export headers, per signal overriding the shared ones | @@ -341,7 +345,9 @@ An empty value means the same thing as an unset one, so `OTEL_SERVICE_VERSION=` variable were not there at all. An empty `OTEL_SERVICE_NAME` is not an attempt to rename anything, so it is not warned about either. `OTEL_SDK_DISABLED` follows the specification's one rule for booleans: the case-insensitive string `true` is the only value that switches export off, and -anything else, including `yes` and `1`, leaves it on. +anything else, including `yes` and `1`, leaves it on. `T3CODE_OTEL_SDK_DISABLED` is T3 Code's own +name, so it takes `true`, `1`, `yes`, and `on`, and a value it cannot read is reported and ignored +rather than treated as either answer. A `OTEL_EXPORTER_OTLP_HEADERS` or `OTEL_RESOURCE_ATTRIBUTES` value that fails to decode is discarded whole rather than partly. A half-parsed credential reaches the collector as the same authentication @@ -725,6 +731,7 @@ OTLP export: - `T3CODE_OTLP_HEADERS`: extra headers for all three exporters, same format as `OTEL_EXPORTER_OTLP_HEADERS`: comma-separated `key=value` pairs with percent-encoded values. - `T3CODE_OTLP_PROTOCOL`: `http/json` (default) or `http/protobuf` +- `T3CODE_OTEL_SDK_DISABLED`: stops every export, whatever configured it, including Settings If the OTLP URLs are unset, local tracing still works, metrics stay in-process only, and logs stay on stdout only. diff --git a/packages/shared/src/otelEnvironment.test.ts b/packages/shared/src/otelEnvironment.test.ts index dda0afb7f6dd..d5c593ac7bde 100644 --- a/packages/shared/src/otelEnvironment.test.ts +++ b/packages/shared/src/otelEnvironment.test.ts @@ -107,6 +107,47 @@ describe("OtelEnvironment", () => { assert.strictEqual(resolved.disabled, true); assert.strictEqual(resolved.traces.settings, undefined); assert.strictEqual(resolved.metrics.settings, undefined); + // Scoped to these variables, so the caller's own endpoints survive, and + // the warning names the switch that does reach them. + assert.strictEqual(resolved.forceDisabled, false); + assert.include(resolved.warnings.join("\n"), "T3CODE_OTEL_SDK_DISABLED=true"); + }), + ); + + it.effect("stops every route when T3 Code's own switch is set", () => + Effect.gen(function* () { + const resolved = yield* OtelEnvironment.load.pipe( + withEnv({ + T3CODE_OTEL_SDK_DISABLED: "true", + OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com", + }), + ); + assert.strictEqual(resolved.forceDisabled, true); + assert.strictEqual(resolved.traces.settings, undefined); + assert.strictEqual(resolved.metrics.settings, undefined); + assert.strictEqual(resolved.logs.settings, undefined); + assert.deepStrictEqual(resolved.warnings, [ + "T3CODE_OTEL_SDK_DISABLED is set, so no telemetry is exported, whatever named the endpoint", + ]); + }), + ); + + it.effect("reads T3 Code's own switch the way T3 Code reads a boolean", () => + Effect.gen(function* () { + // Ours to define, so it takes the affirmatives people type. The + // specification's single-value rule stays with the OTEL_* name. + const numeric = yield* OtelEnvironment.load.pipe(withEnv({ T3CODE_OTEL_SDK_DISABLED: "1" })); + assert.isTrue(numeric.forceDisabled); + + const nonsense = yield* OtelEnvironment.load.pipe( + withEnv({ + T3CODE_OTEL_SDK_DISABLED: "maybe", + OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com", + }), + ); + assert.isFalse(nonsense.forceDisabled); + assert.isDefined(nonsense.traces.settings); + assert.include(nonsense.warnings.join("\n"), "T3CODE_OTEL_SDK_DISABLED=maybe"); }), ); diff --git a/packages/shared/src/otelEnvironment.ts b/packages/shared/src/otelEnvironment.ts index aef5beb22449..485df0a360fc 100644 --- a/packages/shared/src/otelEnvironment.ts +++ b/packages/shared/src/otelEnvironment.ts @@ -9,7 +9,9 @@ * told twice. * * Read by every T3 Code process that exports telemetry, so the server and the - * desktop app cannot disagree about what a variable means. + * desktop app cannot disagree about what a variable means. That is also why + * the one switch that outranks every route, `T3CODE_OTEL_SDK_DISABLED`, is + * read here rather than per process. * * Only the variables T3 Code can act on are read. The exporter speaks * OTLP over HTTP, so `grpc` is declined loudly rather than answered with a @@ -85,7 +87,18 @@ export interface OtlpResourceSettings { } export interface OtelEnvironment { - /** `OTEL_SDK_DISABLED`. When set, nothing is exported by any route. */ + /** + * `T3CODE_OTEL_SDK_DISABLED`. T3 Code's own switch, and the only one that + * outranks every route: nothing is exported, whoever named the endpoint. + * It is a `T3CODE_*` name because a switch that overrides a choice made in + * Settings has to be one the app owns. + */ + readonly forceDisabled: boolean; + /** + * `OTEL_SDK_DISABLED`. Scoped to the variables read here, like every other + * name in this module: it stops these variables from exporting and leaves an + * endpoint named by `T3CODE_OTLP_*` or set in Settings alone. + */ readonly disabled: boolean; /** * Settings that were named but could not be used, each already phrased for a @@ -125,6 +138,29 @@ const optionalString = (name: string) => const specBoolean = (name: string) => optionalString(name).pipe(Effect.map((raw) => raw?.toLowerCase() === "true")); +/** + * A `T3CODE_*` name is ours, so it answers to the affirmatives people actually + * type rather than the single value the specification allows. Anything else is + * named and ignored: one typo should neither stop every export nor take the + * rest of the environment down with it. + */ +const forkBoolean = (name: string) => + optionalString(name).pipe( + Effect.map((raw): { readonly value: boolean; readonly warnings: ReadonlyArray } => { + if (raw === undefined) { + return { value: false, warnings: [] }; + } + const value = raw.toLowerCase(); + if (["true", "1", "yes", "on"].includes(value)) { + return { value: true, warnings: [] }; + } + if (["false", "0", "no", "off"].includes(value)) { + return { value: false, warnings: [] }; + } + return { value: false, warnings: [`${name}=${raw} is not a yes or a no and was ignored`] }; + }), + ); + /** * A number that is not a number is warned about and dropped, which is what the * specification asks for anywhere a value is unrecognized. Letting the read @@ -446,6 +482,21 @@ const resolveResource = Effect.gen(function* () { const UNREADABLE = "the OpenTelemetry environment could not be read"; +/** + * `OTEL_SDK_DISABLED` switches off the route these variables configure and + * nothing else, the same way every other name here is a fallback rather than + * an override. An endpoint that a `T3CODE_OTLP_*` name or Settings already + * answered is not this variable's to turn off, and saying so is the whole + * point of the message: someone who set it expecting silence needs to know + * which half of the configuration it reached. + */ +const SDK_DISABLED = + "OTEL_SDK_DISABLED is set, so the OpenTelemetry environment variables export nothing; a T3CODE_OTLP_* endpoint or one set in Settings still exports, and T3CODE_OTEL_SDK_DISABLED=true stops that too"; + +/** The one switch a shared machine needs, and the reason it is a `T3CODE_*` name. */ +const FORCE_DISABLED = + "T3CODE_OTEL_SDK_DISABLED is set, so no telemetry is exported, whatever named the endpoint"; + /** * Read the environment. Never fails: a variable T3 Code cannot honor * leaves the corresponding setting unset and is reported through the signal's @@ -453,25 +504,34 @@ const UNREADABLE = "the OpenTelemetry environment could not be read"; * to start. */ export const load: Effect.Effect = Effect.gen(function* () { + const force = yield* forkBoolean("T3CODE_OTEL_SDK_DISABLED"); const disabled = yield* specBoolean("OTEL_SDK_DISABLED"); + // Either switch silences this route. Only the `T3CODE_*` one reaches the + // endpoints these variables did not supply, and that is the caller's to act + // on through `forceDisabled`. + const off = force.value || disabled; const protocolDecision = yield* resolveProtocol; const resource = yield* resolveResource; const temporality = yield* resolveMetricsTemporality; - const traces = disabled + const traces = off ? { value: undefined, warnings: [] } : yield* signalSettings("TRACES", protocolDecision.traces.protocol, undefined); - const metrics = disabled + const metrics = off ? { value: undefined, warnings: [] } : yield* signalSettings("METRICS", protocolDecision.metrics.protocol, temporality.value); - const logs = disabled + const logs = off ? { value: undefined, warnings: [] } : yield* signalSettings("LOGS", protocolDecision.logs.protocol, undefined); return { + forceDisabled: force.value, disabled, // Every signal reads the generic `OTEL_EXPORTER_OTLP_*` variables, so one // bad value arrives here once per signal and would be logged that often. warnings: [ ...new Set([ + ...force.warnings, + ...(force.value ? [FORCE_DISABLED] : []), + ...(disabled && !force.value ? [SDK_DISABLED] : []), ...protocolDecision.warnings, ...resource.warnings, ...temporality.warnings, @@ -503,6 +563,7 @@ export const load: Effect.Effect = Effect.gen(function* () { Effect.catchCause((cause) => Effect.logWarning("Could not read the OpenTelemetry environment", cause).pipe( Effect.as({ + forceDisabled: false, disabled: false, warnings: [], traces: { settings: undefined, declined: UNREADABLE }, @@ -519,6 +580,7 @@ export const noSignal: OtlpSignal = { settings: undefined, declined: undefined } /** An environment that asked for nothing, for tests and for the pairing CLI. */ export const none: OtelEnvironment = { + forceDisabled: false, disabled: false, warnings: [], traces: noSignal, From 8d761605447559e6da43e6e64c27b71fc0e287bc Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sat, 19 Sep 2026 15:03:01 -0400 Subject: [PATCH 2/3] fix(observability): one disable setting, T3 Code's name asked first Scoping OTEL_SDK_DISABLED to its own route was the wrong reading of the precedence rule. Precedence is about which name answers a setting, and turning export off is one setting: T3CODE_OTEL_SDK_DISABLED answers it, OTEL_SDK_DISABLED answers it when ours is unset. Asking ours first is what a machine needs to keep T3 Code exporting while its profile disables every other SDK. Signed-off-by: Yordis Prieto --- .../desktop/src/app/DesktopOtlpExport.test.ts | 16 ++- apps/desktop/src/app/DesktopOtlpExport.ts | 4 +- apps/server/src/cli/config.test.ts | 14 +-- apps/server/src/cli/config.ts | 6 +- ...the-standard-otel-variables-are-honored.md | 22 ++-- ...22-the-desktop-app-reports-its-own-work.md | 3 +- docs/operations/observability.md | 26 +++-- packages/shared/src/otelEnvironment.test.ts | 42 ++++--- packages/shared/src/otelEnvironment.ts | 104 +++++++++--------- 9 files changed, 120 insertions(+), 117 deletions(-) diff --git a/apps/desktop/src/app/DesktopOtlpExport.test.ts b/apps/desktop/src/app/DesktopOtlpExport.test.ts index 2e365dbd946d..f495354e310f 100644 --- a/apps/desktop/src/app/DesktopOtlpExport.test.ts +++ b/apps/desktop/src/app/DesktopOtlpExport.test.ts @@ -109,7 +109,7 @@ describe("resolveDesktopOtlpExport", () => { }), ); - it.effect("keeps the named endpoint when the OpenTelemetry SDK is disabled", () => + it.effect("stops every export when the OpenTelemetry SDK is disabled", () => Effect.gen(function* () { const resolved = yield* resolve( { @@ -118,26 +118,24 @@ describe("resolveDesktopOtlpExport", () => { }, { named: { traces: "http://127.0.0.1:4318/v1/traces" } }, ); - assert.strictEqual(resolved.traces.url, "http://127.0.0.1:4318/v1/traces"); + assert.strictEqual(resolved.traces.url, undefined); assert.strictEqual(resolved.metrics.url, undefined); assert.strictEqual(resolved.logs.url, undefined); assert.include(resolved.warnings.join("\n"), "OTEL_SDK_DISABLED"); }), ); - it.effect("stops every export once T3 Code's own switch is set", () => + it.effect("keeps exporting when T3 Code's own name says to", () => Effect.gen(function* () { const resolved = yield* resolve( { - T3CODE_OTEL_SDK_DISABLED: "true", - OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com", + T3CODE_OTEL_SDK_DISABLED: "false", + OTEL_SDK_DISABLED: "true", }, { named: { traces: "http://127.0.0.1:4318/v1/traces" } }, ); - assert.strictEqual(resolved.traces.url, undefined); - assert.strictEqual(resolved.metrics.url, undefined); - assert.strictEqual(resolved.logs.url, undefined); - assert.include(resolved.warnings.join("\n"), "T3CODE_OTEL_SDK_DISABLED"); + assert.strictEqual(resolved.traces.url, "http://127.0.0.1:4318/v1/traces"); + assert.deepStrictEqual(resolved.warnings, []); }), ); diff --git a/apps/desktop/src/app/DesktopOtlpExport.ts b/apps/desktop/src/app/DesktopOtlpExport.ts index 6bc868638230..5f6b8d1214b3 100644 --- a/apps/desktop/src/app/DesktopOtlpExport.ts +++ b/apps/desktop/src/app/DesktopOtlpExport.ts @@ -120,9 +120,7 @@ export const resolveDesktopOtlpExport = (input: DesktopOtlpExportInput): Desktop attributes: { ...otel.resource.attributes, ...input.runtimeAttributes }, }; - // `OTEL_SDK_DISABLED` is already scoped to the OpenTelemetry variables by - // the reader, so only T3 Code's own switch reaches an endpoint named here. - if (otel.forceDisabled) { + if (otel.disabled) { return { traces: offSignal, metrics: offSignal, diff --git a/apps/server/src/cli/config.test.ts b/apps/server/src/cli/config.test.ts index d2875a00c832..bda5fbdbd2d9 100644 --- a/apps/server/src/cli/config.test.ts +++ b/apps/server/src/cli/config.test.ts @@ -777,7 +777,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { }), ); - it.effect("switches off the OpenTelemetry variables without touching the named endpoint", () => + it.effect("exports nothing at all once the SDK is switched off", () => Effect.gen(function* () { const resolved = yield* resolveWithEnv({ OTEL_SDK_DISABLED: "true", @@ -785,23 +785,21 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { T3CODE_OTLP_TRACES_URL: "http://localhost:4318/v1/traces", }); - expect(resolved.otlpTracesUrl).toBe("http://localhost:4318/v1/traces"); + expect(resolved.otlpTracesUrl).toBeUndefined(); expect(resolved.otlpMetricsUrl).toBeUndefined(); expect(resolved.otlpLogsUrl).toBeUndefined(); }), ); - it.effect("exports nothing at all once T3 Code's own switch is set", () => + it.effect("keeps exporting when T3 Code's own name says to, whatever the standard one says", () => Effect.gen(function* () { const resolved = yield* resolveWithEnv({ - T3CODE_OTEL_SDK_DISABLED: "true", - OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com", + T3CODE_OTEL_SDK_DISABLED: "false", + OTEL_SDK_DISABLED: "true", T3CODE_OTLP_TRACES_URL: "http://localhost:4318/v1/traces", }); - expect(resolved.otlpTracesUrl).toBeUndefined(); - expect(resolved.otlpMetricsUrl).toBeUndefined(); - expect(resolved.otlpLogsUrl).toBeUndefined(); + expect(resolved.otlpTracesUrl).toBe("http://localhost:4318/v1/traces"); }), ); diff --git a/apps/server/src/cli/config.ts b/apps/server/src/cli/config.ts index 103450848bbe..84ac0a8c61da 100644 --- a/apps/server/src/cli/config.ts +++ b/apps/server/src/cli/config.ts @@ -430,13 +430,13 @@ export const resolveServerConfig = ( traceBatchWindowMs: env.traceBatchWindowMs, traceMaxBytes: env.traceMaxBytes, traceMaxFiles: env.traceMaxFiles, - otlpTracesUrl: otelEnvironment.forceDisabled + otlpTracesUrl: otelEnvironment.disabled ? undefined : (namedTracesUrl ?? otelEnvironment.traces.settings?.url), - otlpMetricsUrl: otelEnvironment.forceDisabled + otlpMetricsUrl: otelEnvironment.disabled ? undefined : (namedMetricsUrl ?? otelEnvironment.metrics.settings?.url), - otlpLogsUrl: otelEnvironment.forceDisabled + otlpLogsUrl: otelEnvironment.disabled ? undefined : (namedLogsUrl ?? otelEnvironment.logs.settings?.url), // T3 Code has one interval variable and it deliberately covers every diff --git a/docs/fork/0018-the-standard-otel-variables-are-honored.md b/docs/fork/0018-the-standard-otel-variables-are-honored.md index e7479ad9120d..a0b7aa26a4b2 100644 --- a/docs/fork/0018-the-standard-otel-variables-are-honored.md +++ b/docs/fork/0018-the-standard-otel-variables-are-honored.md @@ -24,11 +24,11 @@ `OTEL_BLRP_MAX_EXPORT_BATCH_SIZE` are the batching knobs the specification defines for logs, so a delay meant for spans does not decide how promptly a log record arrives. -- Turn export off from the environment. `T3CODE_OTEL_SDK_DISABLED=true` stops - every export, including one configured in Settings, which is the one switch a - shared machine needs. `OTEL_SDK_DISABLED=true` is scoped the way every other - standard name here is: it switches off what these variables configured and - leaves an endpoint you named yourself alone. +- Turn export off from the environment. `OTEL_SDK_DISABLED=true` stops every + export, including one configured in Settings, which is the one switch a + shared machine needs. `T3CODE_OTEL_SDK_DISABLED` is the same setting asked of + T3 Code's own name first, so `false` there keeps T3 Code exporting on a + machine whose profile disables every other SDK. - Keep whatever you have. The `T3CODE_OTLP_*` names, the desktop bootstrap envelope, and Settings all still win over the environment, and a setup that never mentioned OpenTelemetry keeps the wire format it always used. @@ -59,12 +59,12 @@ OpenTelemetry SDK behaves this way, and a telemetry variable that some processes honor and others quietly ignore is worse than either answer, so `OTEL_SDK_DISABLED` is the way out rather than a requirement to opt in. -That switch stops the route it belongs to and no more, which is the same rule -the rest of these variables follow. A name the environment supplied should not -be able to countermand a choice someone made in Settings, so the switch that -can is `T3CODE_OTEL_SDK_DISABLED`, a name T3 Code owns. Two switches is one more -than the specification describes, and it is the only honest way to have both an -opt-out for the ambient case and a kill switch for the machine. +Turning export off is one setting with two names, not two switches, and it is +read in the same order as everything else here: ours, then the standard one. +The ordering is the whole point. Inheriting `OTEL_SDK_DISABLED` from a shell +profile is common, and without a name of our own the only way to get T3 Code's +telemetry back would be to unset a variable the rest of the machine depends +on. ## Upstream considerations diff --git a/docs/fork/0022-the-desktop-app-reports-its-own-work.md b/docs/fork/0022-the-desktop-app-reports-its-own-work.md index b630106f692c..cf6ff0d4d499 100644 --- a/docs/fork/0022-the-desktop-app-reports-its-own-work.md +++ b/docs/fork/0022-the-desktop-app-reports-its-own-work.md @@ -18,7 +18,8 @@ Metrics stay off while the main process records none, so a configured metrics endpoint hears from the server and nobody else rather than receiving an empty payload every interval. -- Turn it off the same way. `T3CODE_OTEL_SDK_DISABLED=true` stops both processes. +- Turn it off the same way. `OTEL_SDK_DISABLED=true` stops both processes, and + so does `T3CODE_OTEL_SDK_DISABLED=true`, which is read first. - Tell the two apart without trusting the environment. The main process reports as `t3-desktop`, joining `t3-server` and `t3-web`, and `service.runtime` on it is always `desktop`, so an ambient diff --git a/docs/operations/observability.md b/docs/operations/observability.md index c63290db8d20..0e09de740486 100644 --- a/docs/operations/observability.md +++ b/docs/operations/observability.md @@ -208,9 +208,9 @@ The base endpoint is a base, not a full URL: traces go to `/v1/traces` Set `OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_ENDPOINT` when a signal needs a full URL of its own. Ambient `OTEL_*` variables turn export on by themselves. A work collector in your shell profile means -T3 Code exports to it, so use `OTEL_SDK_DISABLED=true` if that is not what you want. It switches off -the variables in this section and nothing else: an endpoint you named with `T3CODE_OTLP_*` or set in -Settings keeps exporting, and `T3CODE_OTEL_SDK_DISABLED=true` is the switch that stops those too. +T3 Code exports to it, so use `OTEL_SDK_DISABLED=true` if that is not what you want. If the reverse is +your problem, a profile that disables every other SDK on the machine, `T3CODE_OTEL_SDK_DISABLED=false` +keeps T3 Code exporting. #### Which Processes Export @@ -265,16 +265,18 @@ goes. The three signals are resolved separately, so traces can come from one source and metrics or logs from another. -Each switch stops the sources it belongs to. `OTEL_SDK_DISABLED=true` stops source 4, the way every -other `OTEL_*` variable reaches source 4 alone, so a signal configured above it keeps exporting. -`T3CODE_OTEL_SDK_DISABLED=true` outranks all four and stops every export, including one configured -through Settings, which is the one switch a shared machine needs. +Whether anything is exported at all is one setting, read in that same order: `T3CODE_OTEL_SDK_DISABLED` +answers it, and `OTEL_SDK_DISABLED` answers it only when T3 Code's own name is unset. Either way the +answer stops every export, including one configured through Settings, which is the one switch a shared +machine needs. Reading ours first is what lets `T3CODE_OTEL_SDK_DISABLED=false` override an ambient +`OTEL_SDK_DISABLED=true`, so a machine can disable every other SDK and still ask for T3 Code's +telemetry. #### What Is Read | Variable | Effect | | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- | -| `OTEL_SDK_DISABLED` | Stops export configured by the variables below | +| `OTEL_SDK_DISABLED` | Stops all export, unless `T3CODE_OTEL_SDK_DISABLED` answered first | | `OTEL_EXPORTER_OTLP_ENDPOINT` | Base URL for every signal | | `OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_ENDPOINT` | Full URL for one signal | | `OTEL_EXPORTER_OTLP_HEADERS`, `OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_HEADERS` | Export headers, per signal overriding the shared ones | @@ -346,8 +348,8 @@ variable were not there at all. An empty `OTEL_SERVICE_NAME` is not an attempt t so it is not warned about either. `OTEL_SDK_DISABLED` follows the specification's one rule for booleans: the case-insensitive string `true` is the only value that switches export off, and anything else, including `yes` and `1`, leaves it on. `T3CODE_OTEL_SDK_DISABLED` is T3 Code's own -name, so it takes `true`, `1`, `yes`, and `on`, and a value it cannot read is reported and ignored -rather than treated as either answer. +name, so it takes `true`, `1`, `yes`, `on` and their negatives, and a value it cannot read is reported +and then left to `OTEL_SDK_DISABLED` to answer rather than treated as either answer itself. A `OTEL_EXPORTER_OTLP_HEADERS` or `OTEL_RESOURCE_ATTRIBUTES` value that fails to decode is discarded whole rather than partly. A half-parsed credential reaches the collector as the same authentication @@ -731,7 +733,9 @@ OTLP export: - `T3CODE_OTLP_HEADERS`: extra headers for all three exporters, same format as `OTEL_EXPORTER_OTLP_HEADERS`: comma-separated `key=value` pairs with percent-encoded values. - `T3CODE_OTLP_PROTOCOL`: `http/json` (default) or `http/protobuf` -- `T3CODE_OTEL_SDK_DISABLED`: stops every export, whatever configured it, including Settings +- `T3CODE_OTEL_SDK_DISABLED`: stops every export, whatever configured it, including Settings. Read + before `OTEL_SDK_DISABLED`, so `false` here keeps T3 Code exporting on a machine that sets the + standard name. If the OTLP URLs are unset, local tracing still works, metrics stay in-process only, and logs stay on stdout only. diff --git a/packages/shared/src/otelEnvironment.test.ts b/packages/shared/src/otelEnvironment.test.ts index d5c593ac7bde..166801f6ead4 100644 --- a/packages/shared/src/otelEnvironment.test.ts +++ b/packages/shared/src/otelEnvironment.test.ts @@ -107,46 +107,56 @@ describe("OtelEnvironment", () => { assert.strictEqual(resolved.disabled, true); assert.strictEqual(resolved.traces.settings, undefined); assert.strictEqual(resolved.metrics.settings, undefined); - // Scoped to these variables, so the caller's own endpoints survive, and - // the warning names the switch that does reach them. - assert.strictEqual(resolved.forceDisabled, false); - assert.include(resolved.warnings.join("\n"), "T3CODE_OTEL_SDK_DISABLED=true"); + // Someone who inherited this from a shell profile has somewhere to go. + assert.include(resolved.warnings.join("\n"), "T3CODE_OTEL_SDK_DISABLED=false"); }), ); - it.effect("stops every route when T3 Code's own switch is set", () => + it.effect("lets T3 Code's own name answer before the standard one", () => Effect.gen(function* () { - const resolved = yield* OtelEnvironment.load.pipe( + const off = yield* OtelEnvironment.load.pipe( withEnv({ T3CODE_OTEL_SDK_DISABLED: "true", OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com", }), ); - assert.strictEqual(resolved.forceDisabled, true); - assert.strictEqual(resolved.traces.settings, undefined); - assert.strictEqual(resolved.metrics.settings, undefined); - assert.strictEqual(resolved.logs.settings, undefined); - assert.deepStrictEqual(resolved.warnings, [ - "T3CODE_OTEL_SDK_DISABLED is set, so no telemetry is exported, whatever named the endpoint", + assert.isTrue(off.disabled); + assert.strictEqual(off.traces.settings, undefined); + assert.deepStrictEqual(off.warnings, [ + "T3CODE_OTEL_SDK_DISABLED is set, so no telemetry is exported, whatever configured it", ]); + + // The point of reading ours first: a machine that disables every other + // SDK in its shell profile can still ask for T3 Code's telemetry. + const on = yield* OtelEnvironment.load.pipe( + withEnv({ + T3CODE_OTEL_SDK_DISABLED: "false", + OTEL_SDK_DISABLED: "true", + OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com", + }), + ); + assert.isFalse(on.disabled); + assert.isDefined(on.traces.settings); + assert.deepStrictEqual(on.warnings, []); }), ); - it.effect("reads T3 Code's own switch the way T3 Code reads a boolean", () => + it.effect("reads T3 Code's own name the way T3 Code reads a boolean", () => Effect.gen(function* () { // Ours to define, so it takes the affirmatives people type. The // specification's single-value rule stays with the OTEL_* name. const numeric = yield* OtelEnvironment.load.pipe(withEnv({ T3CODE_OTEL_SDK_DISABLED: "1" })); - assert.isTrue(numeric.forceDisabled); + assert.isTrue(numeric.disabled); + // A value that answers nothing leaves the source under it to answer. const nonsense = yield* OtelEnvironment.load.pipe( withEnv({ T3CODE_OTEL_SDK_DISABLED: "maybe", + OTEL_SDK_DISABLED: "true", OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com", }), ); - assert.isFalse(nonsense.forceDisabled); - assert.isDefined(nonsense.traces.settings); + assert.isTrue(nonsense.disabled); assert.include(nonsense.warnings.join("\n"), "T3CODE_OTEL_SDK_DISABLED=maybe"); }), ); diff --git a/packages/shared/src/otelEnvironment.ts b/packages/shared/src/otelEnvironment.ts index 485df0a360fc..789b19ed719c 100644 --- a/packages/shared/src/otelEnvironment.ts +++ b/packages/shared/src/otelEnvironment.ts @@ -10,8 +10,8 @@ * * Read by every T3 Code process that exports telemetry, so the server and the * desktop app cannot disagree about what a variable means. That is also why - * the one switch that outranks every route, `T3CODE_OTEL_SDK_DISABLED`, is - * read here rather than per process. + * `T3CODE_OTEL_SDK_DISABLED` is read here: it is the same setting as + * `OTEL_SDK_DISABLED`, asked of T3 Code's own name first. * * Only the variables T3 Code can act on are read. The exporter speaks * OTLP over HTTP, so `grpc` is declined loudly rather than answered with a @@ -88,16 +88,11 @@ export interface OtlpResourceSettings { export interface OtelEnvironment { /** - * `T3CODE_OTEL_SDK_DISABLED`. T3 Code's own switch, and the only one that - * outranks every route: nothing is exported, whoever named the endpoint. - * It is a `T3CODE_*` name because a switch that overrides a choice made in - * Settings has to be one the app owns. - */ - readonly forceDisabled: boolean; - /** - * `OTEL_SDK_DISABLED`. Scoped to the variables read here, like every other - * name in this module: it stops these variables from exporting and leaves an - * endpoint named by `T3CODE_OTLP_*` or set in Settings alone. + * Whether anything is exported at all, by any route. `T3CODE_OTEL_SDK_DISABLED` + * answers it, and `OTEL_SDK_DISABLED` answers it only when T3 Code's own name + * is unset, which is the source order every other setting here follows. So + * `T3CODE_OTEL_SDK_DISABLED=false` is how a machine that exports + * `OTEL_SDK_DISABLED` for everything else keeps T3 Code exporting. */ readonly disabled: boolean; /** @@ -140,25 +135,31 @@ const specBoolean = (name: string) => /** * A `T3CODE_*` name is ours, so it answers to the affirmatives people actually - * type rather than the single value the specification allows. Anything else is - * named and ignored: one typo should neither stop every export nor take the - * rest of the environment down with it. + * type rather than the single value the specification allows. `undefined` means + * the name did not answer, either because it is unset or because its value was + * unreadable, and the source under it decides instead. A typo therefore costs + * that variable and nothing else, the same as everywhere else here. */ const forkBoolean = (name: string) => optionalString(name).pipe( - Effect.map((raw): { readonly value: boolean; readonly warnings: ReadonlyArray } => { - if (raw === undefined) { - return { value: false, warnings: [] }; - } - const value = raw.toLowerCase(); - if (["true", "1", "yes", "on"].includes(value)) { - return { value: true, warnings: [] }; - } - if (["false", "0", "no", "off"].includes(value)) { - return { value: false, warnings: [] }; - } - return { value: false, warnings: [`${name}=${raw} is not a yes or a no and was ignored`] }; - }), + Effect.map( + (raw): { readonly value: boolean | undefined; readonly warnings: ReadonlyArray } => { + if (raw === undefined) { + return { value: undefined, warnings: [] }; + } + const value = raw.toLowerCase(); + if (["true", "1", "yes", "on"].includes(value)) { + return { value: true, warnings: [] }; + } + if (["false", "0", "no", "off"].includes(value)) { + return { value: false, warnings: [] }; + } + return { + value: undefined, + warnings: [`${name}=${raw} is not a yes or a no and was ignored`], + }; + }, + ), ); /** @@ -483,19 +484,15 @@ const resolveResource = Effect.gen(function* () { const UNREADABLE = "the OpenTelemetry environment could not be read"; /** - * `OTEL_SDK_DISABLED` switches off the route these variables configure and - * nothing else, the same way every other name here is a fallback rather than - * an override. An endpoint that a `T3CODE_OTLP_*` name or Settings already - * answered is not this variable's to turn off, and saying so is the whole - * point of the message: someone who set it expecting silence needs to know - * which half of the configuration it reached. + * Whichever name switched export off is the one worth naming, because it is + * the one the reader has to go and unset. An ambient `OTEL_SDK_DISABLED` is + * the case where that is not obvious and where the answer is not to unset + * anything, so the message carries the override with it. */ -const SDK_DISABLED = - "OTEL_SDK_DISABLED is set, so the OpenTelemetry environment variables export nothing; a T3CODE_OTLP_* endpoint or one set in Settings still exports, and T3CODE_OTEL_SDK_DISABLED=true stops that too"; - -/** The one switch a shared machine needs, and the reason it is a `T3CODE_*` name. */ -const FORCE_DISABLED = - "T3CODE_OTEL_SDK_DISABLED is set, so no telemetry is exported, whatever named the endpoint"; +const disabledBy = (name: string) => + name === "OTEL_SDK_DISABLED" + ? "OTEL_SDK_DISABLED is set, so no telemetry is exported, whatever configured it; set T3CODE_OTEL_SDK_DISABLED=false to export anyway" + : `${name} is set, so no telemetry is exported, whatever configured it`; /** * Read the environment. Never fails: a variable T3 Code cannot honor @@ -504,34 +501,33 @@ const FORCE_DISABLED = * to start. */ export const load: Effect.Effect = Effect.gen(function* () { - const force = yield* forkBoolean("T3CODE_OTEL_SDK_DISABLED"); - const disabled = yield* specBoolean("OTEL_SDK_DISABLED"); - // Either switch silences this route. Only the `T3CODE_*` one reaches the - // endpoints these variables did not supply, and that is the caller's to act - // on through `forceDisabled`. - const off = force.value || disabled; + const fork = yield* forkBoolean("T3CODE_OTEL_SDK_DISABLED"); + const spec = yield* specBoolean("OTEL_SDK_DISABLED"); + // One setting, read the way every other setting here is read: T3 Code's own + // name answers it, and the standard name answers it only when ours is unset. + const disabled = fork.value ?? spec; const protocolDecision = yield* resolveProtocol; const resource = yield* resolveResource; const temporality = yield* resolveMetricsTemporality; - const traces = off + const traces = disabled ? { value: undefined, warnings: [] } : yield* signalSettings("TRACES", protocolDecision.traces.protocol, undefined); - const metrics = off + const metrics = disabled ? { value: undefined, warnings: [] } : yield* signalSettings("METRICS", protocolDecision.metrics.protocol, temporality.value); - const logs = off + const logs = disabled ? { value: undefined, warnings: [] } : yield* signalSettings("LOGS", protocolDecision.logs.protocol, undefined); return { - forceDisabled: force.value, disabled, // Every signal reads the generic `OTEL_EXPORTER_OTLP_*` variables, so one // bad value arrives here once per signal and would be logged that often. warnings: [ ...new Set([ - ...force.warnings, - ...(force.value ? [FORCE_DISABLED] : []), - ...(disabled && !force.value ? [SDK_DISABLED] : []), + ...fork.warnings, + ...(disabled + ? [disabledBy(fork.value === true ? "T3CODE_OTEL_SDK_DISABLED" : "OTEL_SDK_DISABLED")] + : []), ...protocolDecision.warnings, ...resource.warnings, ...temporality.warnings, @@ -563,7 +559,6 @@ export const load: Effect.Effect = Effect.gen(function* () { Effect.catchCause((cause) => Effect.logWarning("Could not read the OpenTelemetry environment", cause).pipe( Effect.as({ - forceDisabled: false, disabled: false, warnings: [], traces: { settings: undefined, declined: UNREADABLE }, @@ -580,7 +575,6 @@ export const noSignal: OtlpSignal = { settings: undefined, declined: undefined } /** An environment that asked for nothing, for tests and for the pairing CLI. */ export const none: OtelEnvironment = { - forceDisabled: false, disabled: false, warnings: [], traces: noSignal, From 8c9ae36d8a9b43d24dd749a5471491b800d23c2d Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sat, 19 Sep 2026 15:09:29 -0400 Subject: [PATCH 3/3] refactor(observability): name the boolean reader after what defines it Signed-off-by: Yordis Prieto --- packages/shared/src/otelEnvironment.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/shared/src/otelEnvironment.ts b/packages/shared/src/otelEnvironment.ts index 789b19ed719c..0d623b154089 100644 --- a/packages/shared/src/otelEnvironment.ts +++ b/packages/shared/src/otelEnvironment.ts @@ -140,7 +140,7 @@ const specBoolean = (name: string) => * unreadable, and the source under it decides instead. A typo therefore costs * that variable and nothing else, the same as everywhere else here. */ -const forkBoolean = (name: string) => +const t3Boolean = (name: string) => optionalString(name).pipe( Effect.map( (raw): { readonly value: boolean | undefined; readonly warnings: ReadonlyArray } => { @@ -501,11 +501,11 @@ const disabledBy = (name: string) => * to start. */ export const load: Effect.Effect = Effect.gen(function* () { - const fork = yield* forkBoolean("T3CODE_OTEL_SDK_DISABLED"); + const t3 = yield* t3Boolean("T3CODE_OTEL_SDK_DISABLED"); const spec = yield* specBoolean("OTEL_SDK_DISABLED"); // One setting, read the way every other setting here is read: T3 Code's own // name answers it, and the standard name answers it only when ours is unset. - const disabled = fork.value ?? spec; + const disabled = t3.value ?? spec; const protocolDecision = yield* resolveProtocol; const resource = yield* resolveResource; const temporality = yield* resolveMetricsTemporality; @@ -524,9 +524,9 @@ export const load: Effect.Effect = Effect.gen(function* () { // bad value arrives here once per signal and would be logged that often. warnings: [ ...new Set([ - ...fork.warnings, + ...t3.warnings, ...(disabled - ? [disabledBy(fork.value === true ? "T3CODE_OTEL_SDK_DISABLED" : "OTEL_SDK_DISABLED")] + ? [disabledBy(t3.value === true ? "T3CODE_OTEL_SDK_DISABLED" : "OTEL_SDK_DISABLED")] : []), ...protocolDecision.warnings, ...resource.warnings,