/** * ProviderEventLoggers — single observability service that owns the shared * provider event log store and exposes its two runtime views: * * - `native` — provider-protocol events as the SDK emits them, written * from inside each `Adapter` factory. * - `canonical` — runtime events after `ProviderService` has normalized * them onto `ProviderRuntimeEvent`. * * Why a service tag and not constructor options? * * - Adapters are now constructed *inside* drivers (`Driver.create()`), * not at the boot Layer. There is no longer a single `makeAdapterLive(options)` * call site where we can hand an `EventNdjsonLogger` in by hand. * - Multiple driver instances per kind (`codex_personal`, `codex_work`) * must share one underlying log store — opening N writers against the * same rotating file would race the rotation logic. Owning the loggers on * a single tag keeps that invariant intact. * - Tests can swap one (or both) loggers with in-memory recorders by * `Layer.succeed(ProviderEventLoggers, { native, canonical })` instead of * juggling per-Layer option threading. * * Both fields are optional because observability must not prevent startup. * * @module provider/Layers/ProviderEventLoggers */ import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import { ServerConfig } from "../../config.ts"; import * as ResourceAttribution from "../../resourceTelemetry/ResourceAttribution.ts"; import * as EventNdjsonLogger from "./EventNdjsonLogger.ts"; /** * Shared logger pair for native + canonical provider event streams. * * Service value is intentionally a struct of two optional loggers rather * than two parallel tags. Construction site is one place * (`layer`); consumers (drivers, `ProviderService`) read one tag and pluck the * field they need. */ export class ProviderEventLoggers extends Context.Service< ProviderEventLoggers, { readonly native: EventNdjsonLogger.EventNdjsonLogger | undefined; readonly canonical: EventNdjsonLogger.EventNdjsonLogger | undefined; } >()("t3/provider/Layers/ProviderEventLoggers") {} /** * Constant value used by tests / boot layers that want to opt out of native * + canonical logging entirely. Keeps the tag non-optional in the type * system while letting the runtime treat absence as a no-op. */ export const NoOpProviderEventLoggers: ProviderEventLoggers["Service"] = { native: undefined, canonical: undefined, }; /** * Builds both stream views over one shared store. Setup failures are logged * and downgraded to the no-op service so diagnostics never block startup. * * @public Service construction is part of the canonical Effect module API. */ export const make = Effect.gen(function* () { const { providerEventLogPath } = yield* ServerConfig; const attribution = yield* ResourceAttribution.ResourceAttribution; const store = yield* EventNdjsonLogger.makeEventNdjsonLogStore(providerEventLogPath, { attribution, }).pipe( Effect.catch((error) => Effect.logWarning(error.message, { error }).pipe( Effect.annotateLogs({ scope: "provider-observability" }), Effect.as(undefined), ), ), ); if (!store) { return ProviderEventLoggers.of(NoOpProviderEventLoggers); } yield* Effect.addFinalizer(() => store.close()); return ProviderEventLoggers.of({ native: store.logger("native"), canonical: store.logger("canonical"), }); }); export const layer = Layer.effect(ProviderEventLoggers, make);