From baacb2e7769cb849790ee435e048672e7e771448 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 5 Jul 2026 16:14:02 -0400 Subject: [PATCH] fix(core): tolerate invalid OTLP configuration --- packages/core/src/observability.ts | 8 ++++- .../core/test/effect/observability.test.ts | 36 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/core/src/observability.ts b/packages/core/src/observability.ts index 22285974d8..e99d8919d2 100644 --- a/packages/core/src/observability.ts +++ b/packages/core/src/observability.ts @@ -8,6 +8,12 @@ import { OtlpSerialization } from "effect/unstable/observability" import { Logging } from "./observability/logging" import { Otlp } from "./observability/otlp" +const local = Logger.layer(Logging.loggers(), { mergeWithExisting: false }).pipe( + Layer.provide(NodeFileSystem.layer), + Layer.orDie, + Layer.merge(Layer.succeed(References.MinimumLogLevel, Logging.minimumLogLevel())), +) + export const layer = Layer.unwrap( Effect.gen(function* () { const logs = Logger.layer([...Logging.loggers(), ...Otlp.loggers()], { mergeWithExisting: false }).pipe( @@ -19,6 +25,6 @@ export const layer = Layer.unwrap( ) return Layer.merge(logs, yield* Effect.promise(Otlp.tracingLayer)) }), -) +).pipe(Layer.catchCause(() => local)) export const node = LayerNode.make({ name: "observability", layer, deps: [] }) diff --git a/packages/core/test/effect/observability.test.ts b/packages/core/test/effect/observability.test.ts index 4758563f28..c075986364 100644 --- a/packages/core/test/effect/observability.test.ts +++ b/packages/core/test/effect/observability.test.ts @@ -52,6 +52,42 @@ describe("resource", () => { }) }) +test("falls back to local logging when OTLP initialization fails", async () => { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-observability-test-")) + await using _ = { + async [Symbol.asyncDispose]() { + await fs.rm(dir, { recursive: true, force: true }) + }, + } + const child = Bun.spawn( + [ + process.execPath, + "--eval", + ` + import { Effect } from "effect" + import { Observability } from "./src/observability.ts" + await Effect.void.pipe(Effect.provide(Observability.layer), Effect.scoped, Effect.runPromise) + `, + ], + { + cwd: path.join(import.meta.dir, "../.."), + env: { + ...process.env, + OTEL_EXPORTER_OTLP_ENDPOINT: "://invalid", + XDG_CACHE_HOME: path.join(dir, "cache"), + XDG_CONFIG_HOME: path.join(dir, "config"), + XDG_DATA_HOME: path.join(dir, "data"), + XDG_STATE_HOME: path.join(dir, "state"), + }, + stdout: "ignore", + stderr: "pipe", + }, + ) + const [exitCode, stderr] = await Promise.all([child.exited, new Response(child.stderr).text()]) + + expect({ exitCode, stderr }).toEqual({ exitCode: 0, stderr: "" }) +}) + test("file logger appends concurrent runs with a run on every line", async () => { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-log-test-")) await using _ = {