diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 8ea459330..c9bf83d3e 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -25,6 +25,7 @@ import { fromRow } from "./session/info" import { SessionRunner } from "./session/runner/index" import { SessionStore } from "./session/store" import { SessionExecution } from "./session/execution" +import { logFailure } from "./session/logging" import { MessageDecodeError } from "./session/error" import { SessionEvent } from "./session/event" import { SessionInput } from "./session/input" @@ -177,10 +178,7 @@ export const layer = Layer.effect( Effect.tapCause((cause) => Cause.hasInterruptsOnly(cause) ? Effect.void - : Effect.logError("Failed to wake Session").pipe( - Effect.annotateLogs("sessionID", admitted.sessionID), - Effect.annotateLogs("cause", cause), - ), + : logFailure("Failed to wake Session", admitted.sessionID, cause), ), Effect.ignore, Effect.forkIn(scope, { startImmediately: true }), diff --git a/packages/core/src/session/execution/local.ts b/packages/core/src/session/execution/local.ts index f933d43d6..b921ce939 100644 --- a/packages/core/src/session/execution/local.ts +++ b/packages/core/src/session/execution/local.ts @@ -5,6 +5,7 @@ import { SessionRunner } from "../runner" import { SessionSchema } from "../schema" import { SessionStore } from "../store" import { SessionExecution } from "../execution" +import { logFailure } from "../logging" /** Current-process routing for implicit-local Locations. Future remote placement belongs here. */ export const layer = Layer.effect( @@ -20,11 +21,7 @@ export const layer = Layer.effect( Effect.provide(locations.get(session.location)), ) }), - onFailure: (sessionID, cause) => - Effect.logError("Failed to drain Session").pipe( - Effect.annotateLogs("sessionID", sessionID), - Effect.annotateLogs("cause", cause), - ), + onFailure: (sessionID, cause) => logFailure("Failed to drain Session", sessionID, cause), }) return SessionExecution.Service.of({ diff --git a/packages/core/src/session/logging.ts b/packages/core/src/session/logging.ts new file mode 100644 index 000000000..947633da7 --- /dev/null +++ b/packages/core/src/session/logging.ts @@ -0,0 +1,8 @@ +import { Cause, Effect } from "effect" +import { SessionSchema } from "./schema" + +export const logFailure = ( + message: "Failed to drain Session" | "Failed to wake Session", + sessionID: SessionSchema.ID, + cause: Cause.Cause, +) => Effect.logError(message, cause).pipe(Effect.annotateLogs("sessionID", sessionID)) diff --git a/packages/core/src/session/run-coordinator.ts b/packages/core/src/session/run-coordinator.ts index 5068b913a..d52b63e8f 100644 --- a/packages/core/src/session/run-coordinator.ts +++ b/packages/core/src/session/run-coordinator.ts @@ -2,6 +2,7 @@ export * as SessionRunCoordinator from "./run-coordinator" import { Cause, Context, Deferred, Effect, Exit, Fiber, FiberSet, Layer, Scope } from "effect" import { SessionRunner } from "./runner" +import { logFailure } from "./logging" import { SessionSchema } from "./schema" export type Mode = "run" | "wake" @@ -275,11 +276,7 @@ export const layer = Layer.effect( Effect.flatMap((runner) => make({ drain: (sessionID, mode) => runner.run({ sessionID, force: mode === "run" }), - onFailure: (sessionID, cause) => - Effect.logError("Failed to drain Session").pipe( - Effect.annotateLogs("sessionID", sessionID), - Effect.annotateLogs("cause", cause), - ), + onFailure: (sessionID, cause) => logFailure("Failed to drain Session", sessionID, cause), }), ), Effect.map(Service.of), diff --git a/packages/core/test/session-logging.test.ts b/packages/core/test/session-logging.test.ts new file mode 100644 index 000000000..3d6cff2e4 --- /dev/null +++ b/packages/core/test/session-logging.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, test } from "bun:test" +import { Cause, Effect, Logger } from "effect" +import { logFailure } from "@opencode-ai/core/session/logging" +import { SessionSchema } from "@opencode-ai/core/session/schema" + +describe("Session logging", () => { + for (const message of ["Failed to drain Session", "Failed to wake Session"] as const) { + test(`renders the cause for ${message}`, async () => { + const entries: Array> = [] + const logger = Logger.formatStructured.pipe( + Logger.map((entry): void => { + entries.push(entry) + }), + ) + + await logFailure( + message, + SessionSchema.ID.make("session-123"), + Cause.fail({ _tag: "SessionFailure", detail: { code: "nested-code" } }), + ).pipe(Effect.provide(Logger.layer([logger])), Effect.runPromise) + + expect(entries).toHaveLength(1) + expect(entries[0]?.message).toBe(message) + expect(entries[0]?.annotations).toEqual({ sessionID: "session-123" }) + expect(entries[0]?.cause).toContain("SessionFailure") + expect(entries[0]?.cause).toContain("nested-code") + expect(entries[0]?.cause).not.toContain("[Object") + }) + } +})