diff --git a/packages/console/core/src/actor.ts b/packages/console/core/src/actor.ts index 48f4a636634..b78963dedb7 100644 --- a/packages/console/core/src/actor.ts +++ b/packages/console/core/src/actor.ts @@ -1,6 +1,5 @@ import { Context } from "./context" import { UserRole } from "./schema/user.sql" -import { Log } from "./util/log" export namespace Actor { interface Account { @@ -38,8 +37,6 @@ export namespace Actor { const ctx = Context.create() export const use = ctx.use - const log = Log.create().tag("namespace", "actor") - export function provide( type: T, properties: Extract["properties"], @@ -50,12 +47,7 @@ export namespace Actor { type, properties, } as any, - () => { - return Log.provide({ ...properties }, () => { - log.info("provided") - return cb() - }) - }, + cb, ) } diff --git a/packages/console/core/src/util/log.ts b/packages/console/core/src/util/log.ts deleted file mode 100644 index ef3ad85c6b6..00000000000 --- a/packages/console/core/src/util/log.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Context } from "../context" - -export namespace Log { - const ctx = Context.create<{ - tags: Record - }>() - - export function create(tags?: Record) { - tags = tags || {} - - const result = { - info(message?: any, extra?: Record) { - const prefix = Object.entries({ - ...use().tags, - ...tags, - ...extra, - }) - .map(([key, value]) => `${key}=${value}`) - .join(" ") - console.log(prefix, message) - return result - }, - tag(key: string, value: string) { - if (tags) tags[key] = value - return result - }, - clone() { - return Log.create({ ...tags }) - }, - } - - return result - } - - export function provide(tags: Record, cb: () => R) { - const existing = use() - return ctx.provide( - { - tags: { - ...existing.tags, - ...tags, - }, - }, - cb, - ) - } - - function use() { - try { - return ctx.use() - } catch { - return { tags: {} } - } - } -} diff --git a/packages/core/src/effect/logger.ts b/packages/core/src/effect/logger.ts index 95c2754c387..b263c2f1314 100644 --- a/packages/core/src/effect/logger.ts +++ b/packages/core/src/effect/logger.ts @@ -6,7 +6,6 @@ import * as Global from "../global" import { ensureProcessMetadata } from "../util/opencode-process" type Fields = Record -type FieldInput = object export const Level = Schema.Literals(["DEBUG", "INFO", "WARN", "ERROR"]).annotate({ identifier: "LogLevel", @@ -23,30 +22,13 @@ const levelPriority: Record = { const normalizeKey = (key: string) => (key === "sessionID" ? "session.id" : key) -export interface Handle { - readonly debug: (msg?: unknown, extra?: FieldInput) => Effect.Effect - readonly info: (msg?: unknown, extra?: FieldInput) => Effect.Effect - readonly warn: (msg?: unknown, extra?: FieldInput) => Effect.Effect - readonly error: (msg?: unknown, extra?: FieldInput) => Effect.Effect - readonly tag: (key: string, value: string) => Handle - readonly with: (extra: FieldInput) => Handle - readonly clone: () => Handle - readonly time: (message: string, extra?: Fields) => { stop(): void; [Symbol.dispose](): void } -} - -const clean = (input?: FieldInput): Fields => +const clean = (input?: object): Fields => Object.fromEntries( Object.entries(input ?? {}) .filter((entry) => entry[1] !== undefined && entry[1] !== null) .map(([key, value]) => [normalizeKey(key), value]), ) -const call = (run: (msg: string) => Effect.Effect, base: FieldInput, msg?: unknown, extra?: FieldInput) => { - const ann = clean({ ...base, ...extra }) - const fx = run(stringifyMessage(msg)) - return Object.keys(ann).length ? Effect.annotateLogs(fx, ann) : fx -} - export function file() { if (disabled(process.env.OPENCODE_LOG_FILE)) return "" return process.env.OPENCODE_LOG_FILE || path.join(Global.Path.log, "log.jsonl") @@ -91,7 +73,7 @@ function build(inputLevel: Level, ts: Date, message: unknown, fields: Fields): { } } -export const logger = Logger.make((opts) => { +const logger = Logger.make((opts) => { const extra = clean(opts.fiber.getRef(References.CurrentLogAnnotations)) const now = opts.date.getTime() for (const [key, start] of opts.fiber.getRef(References.CurrentLogSpans)) { @@ -118,28 +100,22 @@ export const logger = Logger.make((opts) => { } }) -export const layer = Logger.layer([logger], { mergeWithExisting: false }).pipe( - Layer.tap(() => - Effect.promise(async () => { - const target = file() - if (target) await fs.mkdir(path.dirname(target), { recursive: true }) - }), - ), -) +type LoggerInput = Logger.Logger | Effect.Effect, unknown, unknown> -export const create = (base: FieldInput = {}): Handle => ({ - debug: (msg, extra) => call((item) => Effect.logDebug(item), base, msg, extra), - info: (msg, extra) => call((item) => Effect.logInfo(item), base, msg, extra), - warn: (msg, extra) => call((item) => Effect.logWarning(item), base, msg, extra), - error: (msg, extra) => call((item) => Effect.logError(item), base, msg, extra), - tag: (key, value) => create({ ...base, [key]: value }), - with: (extra) => create({ ...base, ...extra }), - clone: () => create({ ...base }), - time: () => ({ - stop() {}, - [Symbol.dispose]() {}, - }), -}) +const makeLayer = >(loggers: Loggers) => + Logger.layer(loggers, { mergeWithExisting: false }).pipe( + Layer.tap(() => + Effect.promise(async () => { + const target = file() + if (target) await fs.mkdir(path.dirname(target), { recursive: true }) + }), + ), + ) + +export const layer = makeLayer([logger]) + +export const layerWith = >(loggers: Loggers) => + makeLayer([logger, ...loggers] as const) function truthy(value: string | undefined) { return value?.toLowerCase() === "1" || value?.toLowerCase() === "true" diff --git a/packages/core/src/effect/observability.ts b/packages/core/src/effect/observability.ts index 0203079abe1..3ba5bc72bcb 100644 --- a/packages/core/src/effect/observability.ts +++ b/packages/core/src/effect/observability.ts @@ -1,4 +1,4 @@ -import { Effect, Layer, Logger } from "effect" +import { Effect, Layer } from "effect" import { FetchHttpClient } from "effect/unstable/http" import { OtlpLogger, OtlpSerialization } from "effect/unstable/observability" import * as EffectLogger from "./logger" @@ -54,17 +54,13 @@ export function resource(): { serviceName: string; serviceVersion: string; attri } function logs() { - return Logger.layer( - [ - EffectLogger.logger, - OtlpLogger.make({ - url: `${base}/v1/logs`, - resource: resource(), - headers, - }), - ], - { mergeWithExisting: false }, - ).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer)) + return EffectLogger.layerWith([ + OtlpLogger.make({ + url: `${base}/v1/logs`, + resource: resource(), + headers, + }), + ]).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer)) } const traces = async () => { diff --git a/packages/opencode/src/acp-next/event.ts b/packages/opencode/src/acp-next/event.ts index ab42276dbd2..aba104bc7ba 100644 --- a/packages/opencode/src/acp-next/event.ts +++ b/packages/opencode/src/acp-next/event.ts @@ -1,5 +1,4 @@ import type { AgentSideConnection } from "@agentclientprotocol/sdk" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import type { Event, EventMessagePartDelta, @@ -19,9 +18,6 @@ import { shellOutputSnapshot, completedToolUpdate, } from "./tool" - -const log = EffectLogger.create({ service: "acp-next-event" }) - type Connection = Pick type GlobalEventEnvelope = { payload?: Event @@ -55,7 +51,6 @@ export class Subscription { this.started = true this.run().catch((error: unknown) => { if (this.abort.signal.aborted) return - log.error("event subscription failed", { error }) }) } @@ -92,9 +87,7 @@ export class Subscription { for await (const event of events.stream) { if (this.abort.signal.aborted) return if (!event.payload) continue - await this.handle(event.payload).catch((error: unknown) => { - log.error("failed to handle event", { error, type: event.payload?.type }) - }) + await this.handle(event.payload).catch((error: unknown) => {}) } if (!this.abort.signal.aborted) await new Promise((resolve) => setTimeout(resolve, 1000)) } @@ -182,7 +175,6 @@ export class Subscription { ) .then((response) => response.data) .catch((error: unknown) => { - log.error("unexpected error when fetching message for delta metadata", { error, messageId, partId }) return undefined }) if (!message) return diff --git a/packages/opencode/src/acp-next/service.ts b/packages/opencode/src/acp-next/service.ts index e31700c2267..2749f1350e8 100644 --- a/packages/opencode/src/acp-next/service.ts +++ b/packages/opencode/src/acp-next/service.ts @@ -30,7 +30,6 @@ import { type SetSessionModeResponse, } from "@agentclientprotocol/sdk" import { InstallationVersion } from "@opencode-ai/core/installation/version" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import type { Message, OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2" import { Context, Effect, Layer, ManagedRuntime } from "effect" import * as ACPNextError from "./error" @@ -43,8 +42,6 @@ import { Provider } from "@/provider/provider" import type { Command } from "@/command" export const AuthMethodID = "opencode-login" -const log = EffectLogger.create({ service: "acp-next-service" }) - export type Error = ACPNextError.Error export type Interface = { @@ -296,13 +293,7 @@ export function make(input: { yield* request( () => input.sdk.session.abort({ directory: removed.cwd, sessionID: params.sessionId }, { throwOnError: true }), "session", - ).pipe( - Effect.catch((error) => - Effect.sync(() => { - log.error("failed to abort session while closing ACP session", { error, sessionID: params.sessionId }) - }), - ), - ) + ).pipe(Effect.catch((error) => Effect.sync(() => {}))) return {} }) @@ -477,9 +468,7 @@ function replayMessages(subscription: ACPNextEvent.Subscription | undefined, mes if (!subscription) return Effect.void return Effect.promise(async () => { for (const message of messages) { - await subscription.replayMessage(message).catch((error: unknown) => { - log.error("failed to replay ACP message", { error, messageID: message.info.id }) - }) + await subscription.replayMessage(message).catch((error: unknown) => {}) } }) } diff --git a/packages/opencode/src/acp-next/usage.ts b/packages/opencode/src/acp-next/usage.ts index 4a3fc5eb3ea..708644abee3 100644 --- a/packages/opencode/src/acp-next/usage.ts +++ b/packages/opencode/src/acp-next/usage.ts @@ -1,14 +1,10 @@ import type { AgentSideConnection, Usage } from "@agentclientprotocol/sdk" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import type { AssistantMessage as OpenCodeAssistantMessage, Message } from "@opencode-ai/sdk/v2" import { InstanceRef } from "@/effect/instance-ref" import { InstanceStore } from "@/project/instance-store" import { ModelID, ProviderID } from "@/provider/schema" import { Provider } from "@/provider/provider" import { Context, Effect, Layer, SynchronizedRef } from "effect" - -const log = EffectLogger.create({ service: "acp-next-usage" }) - export type AssistantTokenCost = Pick export type AssistantMessage = AssistantTokenCost & @@ -157,7 +153,6 @@ export const layer = Layer.effect( Effect.map((providers) => findContextLimit(providers, input.providerID, input.modelID)), Effect.catch((error) => Effect.sync(() => { - log.error("failed to get providers for usage context limit", { error }) return undefined }), ), @@ -184,7 +179,6 @@ export const layer = Layer.effect( const messages = yield* messageLoader.messages({ sessionID: input.sessionID, directory: input.directory }).pipe( Effect.catch((error) => Effect.sync(() => { - log.error("failed to fetch messages for usage update", { error }) return undefined }), ), @@ -213,9 +207,7 @@ export const layer = Layer.effect( cost: { amount: totalSessionCost(messages), currency: "USD" }, }, }) - .catch((error) => { - log.error("failed to send usage update", { error }) - }), + .catch((error) => {}), ) }) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index b8290981c98..1b5e4e189b7 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -33,7 +33,6 @@ import { type Usage, } from "@agentclientprotocol/sdk" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { pathToFileURL } from "url" import { Filesystem } from "@/util/filesystem" import { Hash } from "@opencode-ai/core/util/hash" @@ -57,9 +56,6 @@ type ModelOption = { modelId: string; name: string } const decodeTodos = Schema.decodeUnknownResult(Schema.fromJsonString(Schema.Array(Todo.Info))) const DEFAULT_VARIANT_VALUE = "default" - -const log = EffectLogger.create({ service: "acp-agent" }) - async function getContextLimit( sdk: OpencodeClient, providerID: ProviderID, @@ -70,7 +66,6 @@ async function getContextLimit( .providers({ directory }) .then((x) => x.data?.providers ?? []) .catch((error) => { - log.error("failed to get providers for context limit", { error }) return [] }) @@ -89,7 +84,6 @@ async function sendUsageUpdate( .messages({ sessionID, directory }, { throwOnError: true }) .then((x) => x.data) .catch((error) => { - log.error("failed to fetch messages for usage update", { error }) return undefined }) @@ -124,9 +118,7 @@ async function sendUsageUpdate( cost: { amount: totalCost, currency: "USD" }, }, }) - .catch((error) => { - log.error("failed to send usage update", { error }) - }) + .catch((error) => {}) } export function init({ sdk: _sdk }: { sdk: OpencodeClient }) { @@ -166,7 +158,6 @@ export class Agent implements ACPAgent { this.eventStarted = true this.runEventSubscription().catch((error) => { if (this.eventAbort.signal.aborted) return - log.error("event subscription failed", { error }) }) } @@ -180,9 +171,7 @@ export class Agent implements ACPAgent { if (this.eventAbort.signal.aborted) return const payload = event?.payload if (!payload) continue - await this.handleEvent(payload as Event).catch((error) => { - log.error("failed to handle event", { error, type: payload.type }) - }) + await this.handleEvent(payload as Event).catch((error) => {}) } } } @@ -213,11 +202,6 @@ export class Agent implements ACPAgent { options: this.permissionOptions, }) .catch(async (error) => { - log.error("failed to request permission from ACP", { - error, - permissionID: permission.id, - sessionID: permission.sessionID, - }) await this.sdk.permission.reply({ requestID: permission.id, reply: "reject", @@ -258,9 +242,7 @@ export class Agent implements ACPAgent { directory, }) }) - .catch((error) => { - log.error("failed to handle permission", { error, permissionID: permission.id }) - }) + .catch((error) => {}) .finally(() => { if (this.permissionQueues.get(permission.sessionID) === next) { this.permissionQueues.delete(permission.sessionID) @@ -271,7 +253,6 @@ export class Agent implements ACPAgent { } case "message.part.updated": { - log.info("message part updated", { event: event.properties }) const props = event.properties const part = props.part const session = this.sessionManager.tryGet(part.sessionID) @@ -306,9 +287,7 @@ export class Agent implements ACPAgent { rawInput: part.state.input, }, }) - .catch((error) => { - log.error("failed to send tool in_progress to ACP", { error }) - }) + .catch((error) => {}) return } this.shellSnapshots.set(part.callID, hash) @@ -335,9 +314,7 @@ export class Agent implements ACPAgent { ...(content.length > 0 && { content }), }, }) - .catch((error) => { - log.error("failed to send tool in_progress to ACP", { error }) - }) + .catch((error) => {}) return case "completed": { @@ -365,11 +342,8 @@ export class Agent implements ACPAgent { }), }, }) - .catch((error) => { - log.error("failed to send session update for todo", { error }) - }) + .catch((error) => {}) } else { - log.error("failed to parse todo output", { error: parsedTodos.failure }) } } @@ -387,9 +361,7 @@ export class Agent implements ACPAgent { rawOutput: completedToolRawOutput(part), }, }) - .catch((error) => { - log.error("failed to send tool completed to ACP", { error }) - }) + .catch((error) => {}) return } case "error": @@ -420,9 +392,7 @@ export class Agent implements ACPAgent { }, }, }) - .catch((error) => { - log.error("failed to send tool error to ACP", { error }) - }) + .catch((error) => {}) return } } @@ -452,7 +422,6 @@ export class Agent implements ACPAgent { ) .then((x) => x.data) .catch((error) => { - log.error("unexpected error when fetching message", { error }) return undefined }) @@ -474,9 +443,7 @@ export class Agent implements ACPAgent { }, }, }) - .catch((error) => { - log.error("failed to send text delta to ACP", { error }) - }) + .catch((error) => {}) return } @@ -493,9 +460,7 @@ export class Agent implements ACPAgent { }, }, }) - .catch((error) => { - log.error("failed to send reasoning delta to ACP", { error }) - }) + .catch((error) => {}) } return } @@ -503,8 +468,6 @@ export class Agent implements ACPAgent { } async initialize(params: InitializeRequest): Promise { - log.info("initialize", { protocolVersion: params.protocolVersion }) - const authMethod: AuthMethod = { description: "Run `opencode auth login` in the terminal", name: "Login with opencode", @@ -561,9 +524,6 @@ export class Agent implements ACPAgent { // Store ACP session state const state = await this.sessionManager.create(params.cwd, params.mcpServers, model) const sessionId = state.id - - log.info("creating_session", { sessionId, mcpServers: params.mcpServers.length }) - const load = await this.loadSessionMode({ cwd: directory, mcpServers: params.mcpServers, @@ -600,9 +560,6 @@ export class Agent implements ACPAgent { const messages = await this.loadSessionMessages(directory, sessionId) this.restoreSessionStateFromMessages(sessionId, messages) - - log.info("load_session", { sessionId, mcpServers: params.mcpServers.length }) - const result = await this.loadSessionMode({ cwd: directory, mcpServers: params.mcpServers, @@ -610,7 +567,6 @@ export class Agent implements ACPAgent { }) for (const msg of messages ?? []) { - log.debug("replay message", msg) await this.processMessage(msg) } @@ -699,9 +655,6 @@ export class Agent implements ACPAgent { const messages = await this.loadSessionMessages(directory, sessionId) this.restoreSessionStateFromMessages(sessionId, messages) - - log.info("fork_session", { sessionId, mcpServers: mcpServers.length }) - const mode = await this.loadSessionMode({ cwd: directory, mcpServers, @@ -709,7 +662,6 @@ export class Agent implements ACPAgent { }) for (const msg of messages ?? []) { - log.debug("replay message", msg) await this.processMessage(msg) } @@ -738,9 +690,6 @@ export class Agent implements ACPAgent { const messages = await this.loadSessionMessages(directory, sessionId, 20) this.restoreSessionStateFromMessages(sessionId, messages) - - log.info("resume_session", { sessionId, mcpServers: mcpServers.length }) - const result = await this.loadSessionMode({ cwd: directory, mcpServers, @@ -773,17 +722,13 @@ export class Agent implements ACPAgent { }, { throwOnError: true }, ) - .catch((error) => { - log.error("failed to abort session while closing ACP session", { error, sessionID: params.sessionId }) - }) + .catch((error) => {}) this.permissionQueues.delete(params.sessionId) - log.info("close_session", { sessionId: params.sessionId }) return {} } private async processMessage(message: SessionMessageResponse) { - log.debug("process message", message) if (message.info.role !== "assistant" && message.info.role !== "user") return const sessionId = message.info.sessionID @@ -820,9 +765,7 @@ export class Agent implements ACPAgent { ...(runningContent.length > 0 && { content: runningContent }), }, }) - .catch((err) => { - log.error("failed to send tool in_progress to ACP", { error: err }) - }) + .catch((err) => {}) break case "completed": this.toolStarts.delete(part.callID) @@ -849,11 +792,8 @@ export class Agent implements ACPAgent { }), }, }) - .catch((err) => { - log.error("failed to send session update for todo", { error: err }) - }) + .catch((err) => {}) } else { - log.error("failed to parse todo output", { error: parsedTodos.failure }) } } @@ -871,9 +811,7 @@ export class Agent implements ACPAgent { rawOutput: completedToolRawOutput(part), }, }) - .catch((err) => { - log.error("failed to send tool completed to ACP", { error: err }) - }) + .catch((err) => {}) break case "error": this.toolStarts.delete(part.callID) @@ -903,9 +841,7 @@ export class Agent implements ACPAgent { }, }, }) - .catch((err) => { - log.error("failed to send tool error to ACP", { error: err }) - }) + .catch((err) => {}) break } } else if (part.type === "text") { @@ -924,9 +860,7 @@ export class Agent implements ACPAgent { }, }, }) - .catch((err) => { - log.error("failed to send text to ACP", { error: err }) - }) + .catch((err) => {}) } } else if (part.type === "file") { // Replay file attachments as appropriate ACP content blocks. @@ -952,9 +886,7 @@ export class Agent implements ACPAgent { content: { type: "resource_link", uri: url, name: filename, mimeType: mime }, }, }) - .catch((err) => { - log.error("failed to send resource_link to ACP", { error: err }) - }) + .catch((err) => {}) } else if (url.startsWith("data:")) { // Embedded content - parse data URL and send as appropriate block type const base64Match = url.match(/^data:([^;]+);base64,(.*)$/) @@ -979,9 +911,7 @@ export class Agent implements ACPAgent { }, }, }) - .catch((err) => { - log.error("failed to send image to ACP", { error: err }) - }) + .catch((err) => {}) } else { // Non-image: text types get decoded, binary types stay as blob const isText = effectiveMime.startsWith("text/") || effectiveMime === "application/json" @@ -1003,9 +933,7 @@ export class Agent implements ACPAgent { content: { type: "resource", resource }, }, }) - .catch((err) => { - log.error("failed to send resource to ACP", { error: err }) - }) + .catch((err) => {}) } } // URLs that don't match file:// or data: are skipped (unsupported) @@ -1023,9 +951,7 @@ export class Agent implements ACPAgent { }, }, }) - .catch((err) => { - log.error("failed to send reasoning to ACP", { error: err }) - }) + .catch((err) => {}) } } } @@ -1055,9 +981,7 @@ export class Agent implements ACPAgent { rawInput: {}, }, }) - .catch((error) => { - log.error("failed to send tool pending to ACP", { error }) - }) + .catch((error) => {}) } private async loadAvailableModes(directory: string): Promise { @@ -1176,9 +1100,7 @@ export class Agent implements ACPAgent { }, { throwOnError: true }, ) - .catch((error) => { - log.error("failed to add mcp server", { name: key, error }) - }) + .catch((error) => {}) }), ) @@ -1400,9 +1322,6 @@ export class Agent implements ACPAgent { break } } - - log.info("parts", { parts }) - const cmd = (() => { const text = parts .filter((p): p is { type: "text"; text: string } => p.type === "text") @@ -1521,7 +1440,6 @@ export class Agent implements ACPAgent { ) .then((x) => x.data) .catch((error) => { - log.error("unexpected error when fetching message", { error }) return undefined }) } @@ -1673,7 +1591,6 @@ async function defaultModel(config: ACPConfig, cwd?: string): Promise<{ provider return Provider.parseModel(cfg.model) }) .catch((error) => { - log.error("failed to load user config for default model", { error }) return undefined }) @@ -1681,7 +1598,6 @@ async function defaultModel(config: ACPConfig, cwd?: string): Promise<{ provider .providers({ directory }, { throwOnError: true }) .then((x) => x.data?.providers ?? []) .catch((error) => { - log.error("failed to list providers for default model", { error }) return [] }) @@ -1728,7 +1644,6 @@ async function lastUsedModel( .list({ directory, roots: true, limit: 1 }, { throwOnError: true }) .then((x) => x.data?.[0]) .catch((error) => { - log.error("failed to list sessions for default model", { error }) return undefined }) if (!session) return @@ -1737,7 +1652,6 @@ async function lastUsedModel( .messages({ sessionID: session.id, directory, limit: 20 }, { throwOnError: true }) .then((x) => x.data?.findLast((message) => message.info.role === "user")?.info) .catch((error) => { - log.error("failed to load session messages for default model", { error, sessionID: session.id }) return undefined }) if (lastUser?.role !== "user") return @@ -1792,7 +1706,6 @@ function parseUri( function getNewContent(fileOriginal: string, unifiedDiff: string): string | undefined { const result = applyPatch(fileOriginal, unifiedDiff) if (result === false) { - log.error("Failed to apply unified diff (context mismatch)") return undefined } return result diff --git a/packages/opencode/src/acp/session.ts b/packages/opencode/src/acp/session.ts index 60df81fcdf0..f79faef54b1 100644 --- a/packages/opencode/src/acp/session.ts +++ b/packages/opencode/src/acp/session.ts @@ -1,10 +1,6 @@ import { RequestError, type McpServer } from "@agentclientprotocol/sdk" import type { ACPSessionState } from "./types" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import type { OpencodeClient } from "@opencode-ai/sdk/v2" - -const log = EffectLogger.create({ service: "acp-session-manager" }) - export class ACPSessionManager { private sessions = new Map() private sdk: OpencodeClient @@ -37,8 +33,6 @@ export class ACPSessionManager { createdAt: new Date(), model: resolvedModel, } - log.info("creating_session", { state }) - this.sessions.set(sessionId, state) return state } @@ -68,8 +62,6 @@ export class ACPSessionManager { createdAt: new Date(session.time.created), model: resolvedModel, } - log.info("loading_session", { state }) - this.sessions.set(sessionId, state) return state } @@ -77,7 +69,6 @@ export class ACPSessionManager { get(sessionId: string): ACPSessionState { const session = this.sessions.get(sessionId) if (!session) { - log.error("session not found", { sessionId }) throw RequestError.invalidParams(JSON.stringify({ error: `Session not found: ${sessionId}` })) } return session diff --git a/packages/opencode/src/bus/index.ts b/packages/opencode/src/bus/index.ts index caf2e268766..81cdbab1394 100644 --- a/packages/opencode/src/bus/index.ts +++ b/packages/opencode/src/bus/index.ts @@ -1,6 +1,5 @@ import { Effect, Exit, Layer, PubSub, Scope, Context, Stream, Schema } from "effect" import { EffectBridge } from "@/effect/bridge" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { BusEvent } from "./bus-event" import { GlobalBus } from "./global" import { InstanceState } from "@/effect/instance-state" @@ -9,9 +8,6 @@ import { serviceUse } from "@opencode-ai/core/effect/service-use" import { Identifier } from "@/id/id" import type { InstanceContext } from "@/project/instance-context" import { InstanceRef } from "@/effect/instance-ref" - -const log = EffectLogger.create({ service: "bus" }) - type BusProperties> = Schema.Schema.Type export const InstanceDisposed = BusEvent.define( @@ -101,7 +97,7 @@ export const layer = Layer.effect( return Effect.gen(function* () { const s = yield* InstanceState.get(state) const payload: Payload = { id: options?.id ?? createID(), type: def.type, properties } - log.info("publishing", { type: def.type }) + yield* Effect.logInfo("publishing").pipe(Effect.annotateLogs({ service: "bus", ...{ type: def.type } })) const ps = s.typed.get(def.type) if (ps) yield* PubSub.publish(ps, payload) @@ -124,26 +120,34 @@ export const layer = Layer.effect( def: D, ): Effect.Effect>, never, Scope.Scope> => Effect.gen(function* () { - log.info("subscribing", { type: def.type }) + yield* Effect.logInfo("subscribing").pipe(Effect.annotateLogs({ service: "bus", ...{ type: def.type } })) const s = yield* InstanceState.get(state) const ps = yield* getOrCreate(s, def) const subscription = yield* PubSub.subscribe(ps) - yield* Effect.addFinalizer(() => Effect.sync(() => log.info("unsubscribing", { type: def.type }))) + yield* Effect.addFinalizer(() => + Effect.sync(() => + Effect.logInfo("unsubscribing").pipe(Effect.annotateLogs({ service: "bus", ...{ type: def.type } })), + ), + ) return Stream.fromSubscription(subscription) }) const subscribeAll = (): Effect.Effect, never, Scope.Scope> => Effect.gen(function* () { - log.info("subscribing", { type: "*" }) + yield* Effect.logInfo("subscribing").pipe(Effect.annotateLogs({ service: "bus", ...{ type: "*" } })) const s = yield* InstanceState.get(state) const subscription = yield* PubSub.subscribe(s.wildcard) - yield* Effect.addFinalizer(() => Effect.sync(() => log.info("unsubscribing", { type: "*" }))) + yield* Effect.addFinalizer(() => + Effect.sync(() => + Effect.logInfo("unsubscribing").pipe(Effect.annotateLogs({ service: "bus", ...{ type: "*" } })), + ), + ) return Stream.fromSubscription(subscription) }) function on(pubsub: PubSub.PubSub, type: string, callback: (event: T) => unknown) { return Effect.gen(function* () { - log.info("subscribing", { type }) + yield* Effect.logInfo("subscribing").pipe(Effect.annotateLogs({ service: "bus", ...{ type } })) const bridge = yield* EffectBridge.make() const scope = yield* Scope.make() const subscription = yield* Scope.provide(scope)(PubSub.subscribe(pubsub)) @@ -153,9 +157,7 @@ export const layer = Layer.effect( Stream.runForEach((msg) => Effect.tryPromise({ try: () => Promise.resolve().then(() => callback(msg)), - catch: (cause) => { - log.error("subscriber failed", { type, cause }) - }, + catch: (cause) => {}, }).pipe(Effect.ignore), ), Effect.forkScoped, @@ -163,7 +165,6 @@ export const layer = Layer.effect( ) return () => { - log.info("unsubscribing", { type }) bridge.fork(Scope.close(scope, Exit.void)) } }) diff --git a/packages/opencode/src/cli/cmd/acp.ts b/packages/opencode/src/cli/cmd/acp.ts index 558c27e4f06..ab0606eb974 100644 --- a/packages/opencode/src/cli/cmd/acp.ts +++ b/packages/opencode/src/cli/cmd/acp.ts @@ -1,4 +1,3 @@ -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Effect } from "effect" import { effectCmd } from "../effect-cmd" import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk" @@ -9,9 +8,6 @@ import { ServerAuth } from "@/server/auth" import { createOpencodeClient } from "@opencode-ai/sdk/v2" import { withNetworkOptions, resolveNetworkOptions } from "../network" import { RuntimeFlags } from "@/effect/runtime-flags" - -const log = EffectLogger.create({ service: "acp-command" }) - export const AcpCommand = effectCmd({ command: "acp", describe: "start ACP (Agent Client Protocol) server", @@ -63,7 +59,7 @@ export const AcpCommand = effectCmd({ return agent.create(conn, { sdk }) }, stream) - log.info("setup connection") + yield* Effect.logInfo("setup connection").pipe(Effect.annotateLogs({ service: "acp-command" })) process.stdin.resume() yield* Effect.promise( () => diff --git a/packages/opencode/src/cli/cmd/tui/attention.ts b/packages/opencode/src/cli/cmd/tui/attention.ts index 00bd0540203..033fcdb1d88 100644 --- a/packages/opencode/src/cli/cmd/tui/attention.ts +++ b/packages/opencode/src/cli/cmd/tui/attention.ts @@ -19,8 +19,6 @@ import permissionSoundPath from "@opencode-ai/ui/audio/staplebops-06.mp3" with { import errorSoundPath from "@opencode-ai/ui/audio/nope-03.mp3" with { type: "file" } import doneSoundPath from "@opencode-ai/ui/audio/bip-bop-01.mp3" with { type: "file" } import subagentDoneSoundPath from "@opencode-ai/ui/audio/yup-01.mp3" with { type: "file" } -import * as EffectLogger from "@opencode-ai/core/effect/logger" - type FocusState = "unknown" | "focused" | "blurred" type AttentionRenderer = { @@ -37,9 +35,6 @@ type RegisteredSoundPack = TuiAttentionSoundPack & { type TuiAttentionHost = TuiAttention & { dispose(): void } - -const log = EffectLogger.create({ service: "tui.attention" }) - const DEFAULT_TITLE = "opencode" const DEFAULT_PACK_ID = "opencode.default" const KV_SOUND_PACK = "attention_sound_pack" @@ -154,7 +149,6 @@ export function createTuiAttention(input: { try { for (const file of soundCandidates(name)) { const current = await audio.loadSoundFile(file).catch((error) => { - log.debug("failed to load attention sound", { file, error }) return null }) if (disposed) return false @@ -163,7 +157,6 @@ export function createTuiAttention(input: { } return false } catch (error) { - log.debug("failed to play attention sound", { error }) return false } } @@ -189,7 +182,6 @@ export function createTuiAttention(input: { normalizeText(request.title, DEFAULT_TITLE, TITLE_LIMIT), ) } catch (error) { - log.debug("failed to trigger attention notification", { error }) return false } })() @@ -212,7 +204,6 @@ export function createTuiAttention(input: { sound, } } catch (error) { - log.debug("failed to handle attention notification", { error }) return { ok: false, notification: false, diff --git a/packages/opencode/src/cli/cmd/tui/config/tui-migrate.ts b/packages/opencode/src/cli/cmd/tui/config/tui-migrate.ts index 4303e13901d..71b66da6562 100644 --- a/packages/opencode/src/cli/cmd/tui/config/tui-migrate.ts +++ b/packages/opencode/src/cli/cmd/tui/config/tui-migrate.ts @@ -6,11 +6,7 @@ import { DiffStyle, ScrollAcceleration, ScrollSpeed } from "./tui-schema" import { Flag } from "@opencode-ai/core/flag/flag" import { Global } from "@opencode-ai/core/global" import { Filesystem } from "@/util/filesystem" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import * as ConfigPaths from "@/config/paths" - -const log = EffectLogger.create({ service: "tui.migrate" }) - const TUI_SCHEMA_URL = "https://opencode.ai/tui.json" const decodeTheme = Schema.decodeUnknownOption(Schema.String) @@ -33,7 +29,6 @@ export async function migrateTuiConfig(input: MigrateInput) { const opencode = await opencodeFiles(input) for (const file of opencode) { const source = await Filesystem.readText(file).catch((error) => { - log.warn("failed to read config for tui migration", { path: file, error }) return undefined }) if (!source) continue @@ -66,17 +61,14 @@ export async function migrateTuiConfig(input: MigrateInput) { const wrote = await Filesystem.write(target, JSON.stringify(payload, null, 2)) .then(() => true) .catch((error) => { - log.warn("failed to write tui migration target", { from: file, to: target, error }) return false }) if (!wrote) continue const stripped = await backupAndStripLegacy(file, source) if (!stripped) { - log.warn("tui config migrated but source file was not stripped", { from: file, to: target }) continue } - log.info("migrated tui config", { from: file, to: target }) } } @@ -107,7 +99,6 @@ async function backupAndStripLegacy(file: string, source: string) { : await Filesystem.write(backup, source) .then(() => true) .catch((error) => { - log.warn("failed to backup source config during tui migration", { path: file, backup, error }) return false }) if (!backed) return false @@ -125,11 +116,9 @@ async function backupAndStripLegacy(file: string, source: string) { return Filesystem.write(file, text) .then(() => { - log.info("stripped tui keys from server config", { path: file, backup }) return true }) .catch((error) => { - log.warn("failed to strip legacy tui keys from server config", { path: file, backup, error }) return false }) } diff --git a/packages/opencode/src/cli/cmd/tui/config/tui.ts b/packages/opencode/src/cli/cmd/tui/config/tui.ts index 95826d0d0f1..e4a00aa84da 100644 --- a/packages/opencode/src/cli/cmd/tui/config/tui.ts +++ b/packages/opencode/src/cli/cmd/tui/config/tui.ts @@ -18,15 +18,11 @@ import { TuiKeybind } from "./keybind" import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/installation/version" import { makeRuntime } from "@opencode-ai/core/effect/runtime" import { Filesystem } from "@/util/filesystem" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { ConfigVariable } from "@/config/variable" import { Npm } from "@opencode-ai/core/npm" import type { DeepMutable } from "@opencode-ai/core/schema" import type { TuiAttentionSoundName } from "@opencode-ai/plugin/tui" import { FormatError, FormatUnknownError } from "@/cli/error" - -const log = EffectLogger.create({ service: "tui.config" }) - export const Info = TuiInfo export type Info = DeepMutable> @@ -84,12 +80,6 @@ function dropUnknownKeybinds(input: Record, configFilepath: str const invalid = TuiKeybind.unknownKeys(input.keybinds) if (!invalid.length) return input - - log.warn("ignored unknown tui keybinds", { - path: configFilepath, - keybinds: invalid, - hint: "Remove these entries or rename them to keys from the tui.json schema.", - }) return { ...input, keybinds: Object.fromEntries(Object.entries(input.keybinds).filter(([key]) => !invalid.includes(key))), @@ -138,10 +128,6 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory: Effect.sync(() => { const error = Cause.squash(cause) const reason = FormatError(error) ?? FormatUnknownError(error) - log.warn("skipping invalid tui config", { - path: configFilepath, - reason, - }) return {} as Info }), ), @@ -157,16 +143,14 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory: Effect.sync(() => { const error = Cause.squash(cause) const reason = FormatError(error) ?? FormatUnknownError(error) - log.warn("failed to read tui config", { - path: filepath, - reason, - }) return undefined }), ), ) if (!text) return {} as Info - log.info("loading tui config", { path: filepath }) + yield* Effect.logInfo("loading tui config").pipe( + Effect.annotateLogs({ service: "tui.config", ...{ path: filepath } }), + ) return yield* load(text, filepath) }) @@ -175,7 +159,9 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory: const data = yield* loadFile(file) if (Object.keys(data).length) { appliedOrder += 1 - log.info("applying tui config", { path: file, order: appliedOrder }) + yield* Effect.logInfo("applying tui config").pipe( + Effect.annotateLogs({ service: "tui.config", ...{ path: file, order: appliedOrder } }), + ) } acc.result = mergeDeep(acc.result, data) if (!data.plugin?.length) return @@ -210,7 +196,9 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory: if (Flag.OPENCODE_TUI_CONFIG) { const configFile = Flag.OPENCODE_TUI_CONFIG yield* mergeFile(acc, configFile) - log.debug("loaded custom tui config", { path: configFile }) + yield* Effect.logDebug("loaded custom tui config").pipe( + Effect.annotateLogs({ service: "tui.config", ...{ path: configFile } }), + ) } // 3. Project tui files, applied root-first so the closest file wins. diff --git a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts index 2ad53b673e6..d30b2969465 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts @@ -14,7 +14,6 @@ import { import path from "path" import { fileURLToPath } from "url" import { TuiConfig } from "@/cli/cmd/tui/config/tui" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { errorData, errorMessage } from "@/util/error" import { isRecord } from "@/util/record" import { resolveAttentionSoundPaths } from "../config/tui-schema" @@ -116,8 +115,6 @@ type RuntimeState = { pending: Map dispose_timeout_ms: number } - -const log = EffectLogger.create({ service: "tui.plugin" }) const DISPOSE_TIMEOUT_MS = 5000 const KV_KEY = "plugin_enabled" const EMPTY_TUI: TuiPluginModule = { @@ -126,19 +123,16 @@ const EMPTY_TUI: TuiPluginModule = { function fail(message: string, data: Record) { if (!("error" in data)) { - log.error(message, data) console.error(`[tui.plugin] ${message}`, data) return } const text = `${message}: ${errorMessage(data.error)}` const next = { ...data, error: errorData(data.error) } - log.error(text, next) console.error(`[tui.plugin] ${text}`, next) } function warn(message: string, data: Record) { - log.warn(message, data) console.warn(`[tui.plugin] ${message}`, data) } @@ -273,15 +267,7 @@ function createThemeInstaller( await Flock.withLock(`tui-theme:${dest}`, async () => { const save = async () => { plugin.themes[name] = info - await PluginMeta.setTheme(plugin.id, name, info).catch((error) => { - log.warn("failed to track tui plugin theme", { - path: spec, - id: plugin.id, - theme: src, - dest, - error, - }) - }) + await PluginMeta.setTheme(plugin.id, name, info).catch((error) => {}) } const exists = hasTheme(name) @@ -297,7 +283,6 @@ function createThemeInstaller( } const text = await Filesystem.readText(src).catch((error) => { - log.warn("failed to read tui plugin theme", { path: spec, theme: src, error }) return }) if (text === undefined) return @@ -306,27 +291,21 @@ function createThemeInstaller( const data = await Promise.resolve(text) .then((x) => JSON.parse(x)) .catch((error) => { - log.warn("failed to parse tui plugin theme", { path: spec, theme: src, error }) return fail }) if (data === fail) return if (!isTheme(data)) { - log.warn("invalid tui plugin theme", { path: spec, theme: src }) return } if (exists || !(await Filesystem.exists(dest))) { - await Filesystem.write(dest, text).catch((error) => { - log.warn("failed to persist tui plugin theme", { path: spec, theme: src, dest, error }) - }) + await Filesystem.write(dest, text).catch((error) => {}) } upsertTheme(name, data) await save() - }).catch((error) => { - log.warn("failed to lock tui plugin theme install", { path: spec, theme: src, dest, error }) - }) + }).catch((error) => {}) } } @@ -689,9 +668,7 @@ async function resolveExternalPlugins(list: ConfigPlugin.Origin[], wait: () => P items: list, kind: "tui", wait: async () => { - await wait().catch((error) => { - log.warn("failed waiting for tui plugin dependencies", { error }) - }) + await wait().catch((error) => {}) }, finish: async (loaded, origin, retry) => { const mod = await Promise.resolve() @@ -762,9 +739,7 @@ async function resolveExternalPlugins(list: ConfigPlugin.Origin[], wait: () => P } }, report: { - start(candidate, retry) { - log.info("loading tui plugin", { path: candidate.plan.spec, retry }) - }, + start(candidate, retry) {}, missing(candidate, retry, message) { warn("tui plugin has no entrypoint", { path: candidate.plan.spec, retry, message }) }, @@ -798,7 +773,6 @@ async function addExternalPluginEntries(state: RuntimeState, ready: PluginLoad[] id: item.id, })), ).catch((error) => { - log.warn("failed to track tui plugins", { error }) return undefined }) @@ -809,14 +783,6 @@ async function addExternalPluginEntries(state: RuntimeState, ready: PluginLoad[] if (!entry) continue const hit = meta?.[i] if (hit && hit.state !== "same") { - log.info("tui plugin metadata updated", { - path: entry.spec, - retry: entry.retry, - state: hit.state, - source: hit.entry.source, - version: hit.entry.version, - modified: hit.entry.modified, - }) } const info = createMeta(entry.source, entry.spec, entry.target, hit, entry.id) @@ -1093,11 +1059,9 @@ async function load(input: { api: Api; config: TuiConfig.Resolved; dispose?: () ) const records = Flag.OPENCODE_PURE ? [] : (config.plugin_origins ?? []) if (Flag.OPENCODE_PURE && config.plugin_origins?.length) { - log.info("skipping external tui plugins in pure mode", { count: config.plugin_origins.length }) } for (const item of internalTuiPlugins(flags)) { - log.info("loading internal tui plugin", { id: item.id }) const entry = loadInternalPlugin(item) const meta = createMeta(entry.source, entry.spec, entry.target, undefined, entry.id) addPluginEntry(next, { diff --git a/packages/opencode/src/cli/cmd/tui/util/audio.ts b/packages/opencode/src/cli/cmd/tui/util/audio.ts index 9b5d7b43f66..79942acb812 100644 --- a/packages/opencode/src/cli/cmd/tui/util/audio.ts +++ b/packages/opencode/src/cli/cmd/tui/util/audio.ts @@ -1,8 +1,4 @@ import { Audio, type AudioErrorContext, type AudioPlayOptions, type AudioSound, type AudioVoice } from "@opentui/core" -import * as EffectLogger from "@opencode-ai/core/effect/logger" - -const log = EffectLogger.create({ service: "tui.audio" }) - let audio: Audio | null | undefined const sounds = new Map>() @@ -10,13 +6,10 @@ function getAudio() { if (audio !== undefined) return audio try { const next = Audio.create({ autoStart: false }) - next.on("error", (error: Error, context: AudioErrorContext) => { - log.debug("tui audio error", { error, context }) - }) + next.on("error", (error: Error, context: AudioErrorContext) => {}) audio = next return next } catch (error) { - log.debug("failed to create tui audio", { error }) audio = null return null } @@ -31,7 +24,6 @@ export function loadSoundFile(file: string) { .bytes() .then((bytes) => current.loadSound(bytes)) .catch((error) => { - log.debug("failed to load tui sound", { file, error }) return null }) sounds.set(file, task) diff --git a/packages/opencode/src/cli/heap.ts b/packages/opencode/src/cli/heap.ts index 0c316429599..c6778e7180d 100644 --- a/packages/opencode/src/cli/heap.ts +++ b/packages/opencode/src/cli/heap.ts @@ -2,9 +2,6 @@ import path from "path" import { writeHeapSnapshot } from "node:v8" import { Flag } from "@opencode-ai/core/flag/flag" import { Global } from "@opencode-ai/core/global" -import * as EffectLogger from "@opencode-ai/core/effect/logger" - -const log = EffectLogger.create({ service: "heap" }) const MINUTE = 60_000 const LIMIT = 2 * 1024 * 1024 * 1024 @@ -32,20 +29,9 @@ export function start() { Global.Path.log, `heap-${process.pid}-${new Date().toISOString().replace(/[:.]/g, "")}.heapsnapshot`, ) - log.warn("heap usage exceeded limit", { - rss: stat.rss, - heap: stat.heapUsed, - file, - }) - await Promise.resolve() .then(() => writeHeapSnapshot(file)) - .catch((err) => { - log.error("failed to write heap snapshot", { - error: err instanceof Error ? err.message : String(err), - file, - }) - }) + .catch((err) => {}) lock = false } diff --git a/packages/opencode/src/config/agent.ts b/packages/opencode/src/config/agent.ts index 3f6ac8d9672..cdcfffb3793 100644 --- a/packages/opencode/src/config/agent.ts +++ b/packages/opencode/src/config/agent.ts @@ -3,16 +3,12 @@ export * as ConfigAgent from "./agent" import path from "path" import { Exit, Schema, SchemaGetter } from "effect" import { PositiveInt } from "@opencode-ai/core/schema" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Glob } from "@opencode-ai/core/util/glob" import { configEntryNameFromPath } from "./entry-name" import * as ConfigMarkdown from "./markdown" import { ConfigModelID } from "./model-id" import { ConfigParse } from "./parse" import { ConfigPermission } from "./permission" - -const log = EffectLogger.create({ service: "config" }) - const Color = Schema.Union([ Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)), Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]), @@ -112,7 +108,6 @@ export async function load(dir: string) { symlink: true, })) { const md = await ConfigMarkdown.parse(item).catch((err) => { - log.error("failed to load agent", { agent: item, err }) return undefined }) if (!md) continue @@ -138,7 +133,6 @@ export async function loadMode(dir: string) { symlink: true, })) { const md = await ConfigMarkdown.parse(item).catch((err) => { - log.error("failed to load mode", { mode: item, err }) return undefined }) if (!md) continue diff --git a/packages/opencode/src/config/command.ts b/packages/opencode/src/config/command.ts index ea801a32eec..4fdd31f3d4f 100644 --- a/packages/opencode/src/config/command.ts +++ b/packages/opencode/src/config/command.ts @@ -1,16 +1,12 @@ export * as ConfigCommand from "./command" import path from "path" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Cause, Exit, Schema } from "effect" import { Glob } from "@opencode-ai/core/util/glob" import { configEntryNameFromPath } from "./entry-name" import { InvalidError } from "./error" import * as ConfigMarkdown from "./markdown" import { ConfigModelID } from "./model-id" - -const log = EffectLogger.create({ service: "config" }) - export const Info = Schema.Struct({ template: Schema.String, description: Schema.optional(Schema.String), @@ -32,7 +28,6 @@ export async function load(dir: string) { symlink: true, })) { const md = await ConfigMarkdown.parse(item).catch((err) => { - log.error("failed to load command", { command: item, err }) return undefined }) if (!md) continue diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 747ff38f80f..fa1c3fd1dbe 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1,4 +1,3 @@ -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { serviceUse } from "@opencode-ai/core/effect/service-use" import path from "path" import { pathToFileURL } from "url" @@ -43,9 +42,6 @@ import { ConfigSkills } from "./skills" import { ConfigVariable } from "./variable" import { Npm } from "@opencode-ai/core/npm" import { withTransientReadRetry } from "@/util/effect-http-client" - -const log = EffectLogger.create({ service: "config" }) - // Custom merge function that concatenates array fields instead of replacing them // Keep remeda's deep conditional merge type out of hot config-loading paths; TS profiling showed it dominates here. function mergeConfig(target: Info, source: Info): Info { @@ -68,7 +64,6 @@ function normalizeLoadedConfig(data: unknown, source: string) { delete copy.theme delete copy.keybinds delete copy.tui - log.warn("tui keys in opencode config are deprecated; move them to tui.json", { path: source }) return copy } @@ -434,7 +429,7 @@ export const layer = Layer.effect( }) const loadFile = Effect.fnUntraced(function* (filepath: string, env?: Record) { - log.info("loading", { path: filepath }) + yield* Effect.logInfo("loading").pipe(Effect.annotateLogs({ service: "config", ...{ path: filepath } })) const text = yield* readConfigFile(filepath) if (!text) return {} as Info return yield* loadConfig(text, { path: filepath }, env) @@ -478,7 +473,11 @@ export const layer = Layer.effect( const [cachedGlobal, invalidateGlobal] = yield* Effect.cachedInvalidateWithTTL( loadGlobal().pipe( Effect.tapError((error) => - Effect.sync(() => log.error("failed to load global config, using defaults", { error: String(error) })), + Effect.sync(() => + Effect.logError("failed to load global config, using defaults").pipe( + Effect.annotateLogs({ service: "config", ...{ error: String(error) } }), + ), + ), ), Effect.orElseSucceed((): Info => ({})), ), @@ -554,7 +553,9 @@ export const layer = Layer.effect( const url = key.replace(/\/+$/, "") authEnv[value.key] = value.token const wellknownURL = `${url}/.well-known/opencode` - log.debug("fetching remote config", { url: wellknownURL }) + yield* Effect.logDebug("fetching remote config").pipe( + Effect.annotateLogs({ service: "config", ...{ url: wellknownURL } }), + ) const wellknown = yield* fetchRemoteJson(wellknownURL, undefined, WellKnownConfig) const remote = yield* Effect.promise(() => substituteWellKnownRemoteConfig({ @@ -566,7 +567,9 @@ export const layer = Layer.effect( ) const fetchedConfig = remote ? yield* Effect.gen(function* () { - log.debug("fetching remote config", { url: remote.url }) + yield* Effect.logDebug("fetching remote config").pipe( + Effect.annotateLogs({ service: "config", ...{ url: remote.url } }), + ) const data = yield* fetchRemoteJson(remote.url, remote.headers, Schema.Json) if (isRecord(data) && isRecord(data.config)) return data.config if (isRecord(data)) return data @@ -587,7 +590,9 @@ export const layer = Layer.effect( authEnv, ) yield* merge(source, next, "global") - log.debug("loaded remote config from well-known", { url }) + yield* Effect.logDebug("loaded remote config from well-known").pipe( + Effect.annotateLogs({ service: "config", ...{ url } }), + ) } } @@ -596,7 +601,9 @@ export const layer = Layer.effect( if (Flag.OPENCODE_CONFIG) { yield* merge(Flag.OPENCODE_CONFIG, yield* loadFile(Flag.OPENCODE_CONFIG, authEnv)) - log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG }) + yield* Effect.logDebug("loaded custom config").pipe( + Effect.annotateLogs({ service: "config", path: Flag.OPENCODE_CONFIG }), + ) } if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) { @@ -612,7 +619,9 @@ export const layer = Layer.effect( const directories = yield* ConfigPaths.directories(ctx.directory, ctx.worktree) if (Flag.OPENCODE_CONFIG_DIR) { - log.debug("loading config from OPENCODE_CONFIG_DIR", { path: Flag.OPENCODE_CONFIG_DIR }) + yield* Effect.logDebug("loading config from OPENCODE_CONFIG_DIR").pipe( + Effect.annotateLogs({ service: "config", path: Flag.OPENCODE_CONFIG_DIR }), + ) } const deps: Fiber.Fiber[] = [] @@ -621,7 +630,7 @@ export const layer = Layer.effect( if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) { for (const file of ["opencode.json", "opencode.jsonc"]) { const source = path.join(dir, file) - log.debug(`loading config from ${source}`) + yield* Effect.logDebug(`loading config from ${source}`).pipe(Effect.annotateLogs({ service: "config" })) yield* merge(source, yield* loadFile(source, authEnv)) result.agent ??= {} result.mode ??= {} @@ -644,9 +653,9 @@ export const layer = Layer.effect( Effect.exit, Effect.tap((exit) => Exit.isFailure(exit) - ? Effect.sync(() => { - log.warn("background dependency install failed", { dir, error: String(exit.cause) }) - }) + ? Effect.logWarning("background dependency install failed").pipe( + Effect.annotateLogs({ service: "config", dir, error: String(exit.cause) }), + ) : Effect.void, ), Effect.asVoid, @@ -657,7 +666,7 @@ export const layer = Layer.effect( result.command = mergeDeep(result.command ?? {}, yield* Effect.promise(() => ConfigCommand.load(dir))) result.agent = mergeDeep(result.agent ?? {}, yield* Effect.promise(() => ConfigAgent.load(dir))) result.agent = mergeDeep(result.agent ?? {}, yield* Effect.promise(() => ConfigAgent.loadMode(dir))) - // Auto-discovered plugins under `.opencode/plugin(s)` are already local files, so ConfigPlugin.load + // Auto-discovered plugins under `.opencode/plugin(s }))` are already local files, so ConfigPlugin.load // returns normalized Specs and we only need to attach origin metadata here. const list = yield* Effect.promise(() => ConfigPlugin.load(dir)) yield* mergePluginOrigins(dir, list) @@ -670,7 +679,9 @@ export const layer = Layer.effect( source, }) yield* merge(source, next, "local") - log.debug("loaded custom config from OPENCODE_CONFIG_CONTENT") + yield* Effect.logDebug("loaded custom config from OPENCODE_CONFIG_CONTENT").pipe( + Effect.annotateLogs({ service: "config" }), + ) } const activeAccount = Option.getOrUndefined( @@ -704,9 +715,6 @@ export const layer = Layer.effect( }).pipe( Effect.withSpan("Config.loadActiveOrgConfig"), Effect.catch((err) => { - log.debug("failed to fetch remote account config", { - error: err instanceof Error ? err.message : String(err), - }) return Effect.void }), ) @@ -745,7 +753,9 @@ export const layer = Layer.effect( try { result.permission = mergeDeep(result.permission ?? {}, JSON.parse(Flag.OPENCODE_PERMISSION)) } catch (err) { - log.warn("OPENCODE_PERMISSION contains invalid JSON, skipping", { err }) + yield* Effect.logWarning("OPENCODE_PERMISSION contains invalid JSON, skipping").pipe( + Effect.annotateLogs({ service: "config", ...{ err } }), + ) } } @@ -766,7 +776,9 @@ export const layer = Layer.effect( try { result.username = os.userInfo().username || "user" } catch (err) { - log.warn("failed to read system username, using fallback", { err }) + yield* Effect.logWarning("failed to read system username, using fallback").pipe( + Effect.annotateLogs({ service: "config", ...{ err } }), + ) result.username = "user" } } diff --git a/packages/opencode/src/config/managed.ts b/packages/opencode/src/config/managed.ts index 059577e85b8..bc0f6c76641 100644 --- a/packages/opencode/src/config/managed.ts +++ b/packages/opencode/src/config/managed.ts @@ -3,11 +3,7 @@ export * as ConfigManaged from "./managed" import { existsSync } from "fs" import os from "os" import path from "path" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Process } from "@/util/process" - -const log = EffectLogger.create({ service: "config" }) - const MANAGED_PLIST_DOMAIN = "ai.opencode.managed" // Keys injected by macOS/MDM into the managed plist that are not OpenCode config @@ -50,7 +46,6 @@ export async function readManagedPreferences() { try { return os.userInfo().username || "user" } catch (err) { - log.warn("failed to read system username, using fallback", { err }) return "user" } })() @@ -61,10 +56,8 @@ export async function readManagedPreferences() { for (const plist of paths) { if (!existsSync(plist)) continue - log.info("reading macOS managed preferences", { path: plist }) const result = await Process.run(["plutil", "-convert", "json", "-o", "-", plist], { nothrow: true }) if (result.code !== 0) { - log.warn("failed to convert managed preferences plist", { path: plist }) continue } return { diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index 6502934cef5..781f72adba8 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -12,7 +12,6 @@ import { Auth } from "@/auth" import { SyncEvent } from "@/sync" import { EventSequenceTable, EventTable } from "@/sync/event.sql" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { RuntimeFlags } from "@/effect/runtime-flags" import { ProjectID } from "@/project/schema" import { Slug } from "@opencode-ai/core/util/slug" @@ -76,9 +75,6 @@ function fromRow(row: typeof WorkspaceTable.$inferSelect): Info { const db = (fn: (d: Parameters[0] extends (trx: infer D) => any ? D : never) => T) => Effect.sync(() => Database.use(fn)) - -const log = EffectLogger.create({ service: "workspace-sync" }) - export const CreateInput = Schema.Struct({ id: Schema.optional(WorkspaceID), type: Info.fields.type, @@ -292,24 +288,22 @@ export const layer = Layer.effect( return yield* store.provide({ directory: target.directory }, input.local()) } - const response = yield* http.execute(input.remote({ workspace, target })).pipe( - Effect.catch((error) => - Effect.sync(() => { - log.warn("workspace target request failed", { - workspaceID: workspace.id, - error: errorData(error), - }) - }), - ), - ) + const response = yield* http + .execute(input.remote({ workspace, target })) + .pipe(Effect.catch((error) => Effect.sync(() => {}))) if (!response) return input.fallback if (response.status < 200 || response.status >= 300) { const body = yield* response.text.pipe(Effect.catch(() => Effect.succeed(""))) - log.warn("workspace target request failed", { - workspaceID: workspace.id, - status: response.status, - body, - }) + yield* Effect.logWarning("workspace target request failed").pipe( + Effect.annotateLogs({ + service: "workspace-sync", + ...{ + workspaceID: workspace.id, + status: response.status, + body, + }, + }), + ) return input.fallback } @@ -318,10 +312,6 @@ export const layer = Layer.effect( Effect.map((result) => result as A), Effect.catch((error) => Effect.sync(() => { - log.warn("workspace target response decode failed", { - workspaceID: workspace.id, - error: errorData(error), - }) return input.fallback }), ), @@ -349,11 +339,16 @@ export const layer = Layer.effect( ) : {} - log.info("syncing workspace history", { - workspaceID: space.id, - sessions: sessionIDs.length, - known: Object.keys(state).length, - }) + yield* Effect.logInfo("syncing workspace history").pipe( + Effect.annotateLogs({ + service: "workspace-sync", + ...{ + workspaceID: space.id, + sessions: sessionIDs.length, + known: Object.keys(state).length, + }, + }), + ) const response = yield* http.execute( HttpClientRequest.post(route(url, "/sync/history"), { @@ -373,10 +368,15 @@ export const layer = Layer.effect( const events = (yield* response.json) as HistoryEvent[] - log.info("workspace history synced", { - workspaceID: space.id, - events: events.length, - }) + yield* Effect.logInfo("workspace history synced").pipe( + Effect.annotateLogs({ + service: "workspace-sync", + ...{ + workspaceID: space.id, + events: events.length, + }, + }), + ) yield* Effect.forEach( events, @@ -405,7 +405,9 @@ export const layer = Layer.effect( let attempt = 0 while (true) { - log.info("connecting to global sync", { workspace: space.name }) + yield* Effect.logInfo("connecting to global sync").pipe( + Effect.annotateLogs({ service: "workspace-sync", ...{ workspace: space.name } }), + ) setStatus(space.id, "connecting") const stream = yield* connectSSE(target.url, target.headers).pipe( @@ -413,10 +415,6 @@ export const layer = Layer.effect( Effect.catch((err) => Effect.sync(() => { setStatus(space.id, "error") - log.info("failed to connect to global sync", { - workspace: space.name, - err, - }) return null }), ), @@ -425,7 +423,9 @@ export const layer = Layer.effect( if (stream) { attempt = 0 - log.info("global sync connected", { workspace: space.name }) + yield* Effect.logInfo("global sync connected").pipe( + Effect.annotateLogs({ service: "workspace-sync", ...{ workspace: space.name } }), + ) setStatus(space.id, "connected") yield* parseSSE(stream, (evt) => @@ -439,10 +439,6 @@ export const layer = Layer.effect( Effect.as(false), Effect.catchCause((error) => Effect.sync(() => { - log.info("failed to replay global event", { - workspaceID: space.id, - error, - }) return true }), ), @@ -459,15 +455,22 @@ export const layer = Layer.effect( payload: event.payload, }) } catch (error) { - log.info("failed to replay global event", { - workspaceID: space.id, - error, - }) + yield* Effect.logInfo("failed to replay global event").pipe( + Effect.annotateLogs({ + service: "workspace-sync", + ...{ + workspaceID: space.id, + error, + }, + }), + ) } }), ) - log.info("disconnected from global sync: " + space.id) + yield* Effect.logInfo("disconnected from global sync: " + space.id).pipe( + Effect.annotateLogs({ service: "workspace-sync" }), + ) setStatus(space.id, "disconnected") } @@ -485,10 +488,6 @@ export const layer = Layer.effect( Effect.catch((error) => Effect.sync(() => { setStatus(space.id, "error") - log.warn("workspace target failed", { - workspaceID: space.id, - error: errorData(error), - }) return null }), ), @@ -514,10 +513,6 @@ export const layer = Layer.effect( Effect.catch((error) => Effect.sync(() => { setStatus(space.id, "error") - log.warn("workspace listener failed", { - workspaceID: space.id, - error, - }) }), ), ), @@ -598,10 +593,15 @@ export const layer = Layer.effect( const sessionWarp = Effect.fn("Workspace.sessionWarp")(function* (input: SessionWarpInput) { return yield* Effect.gen(function* () { - log.info("session warp requested", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - }) + yield* Effect.logInfo("session warp requested").pipe( + Effect.annotateLogs({ + service: "workspace-sync", + ...{ + workspaceID: input.workspaceID, + sessionID: input.sessionID, + }, + }), + ) const current = yield* db((db) => db @@ -618,15 +618,7 @@ export const layer = Layer.effect( if (target.type === "remote") { yield* syncHistory(previous, target.url, target.headers).pipe( - Effect.catch((error) => - Effect.sync(() => { - log.warn("session warp final source sync failed", { - workspaceID: previous.id, - sessionID: input.sessionID, - error: errorData(error), - }) - }), - ), + Effect.catch((error) => Effect.sync(() => {})), ) } else { yield* prompt.cancel(input.sessionID) @@ -676,11 +668,16 @@ export const layer = Layer.effect( }, }) - log.info("session warp complete", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - target: "local", - }) + yield* Effect.logInfo("session warp complete").pipe( + Effect.annotateLogs({ + service: "workspace-sync", + ...{ + workspaceID: input.workspaceID, + sessionID: input.sessionID, + target: "local", + }, + }), + ) return } @@ -702,11 +699,16 @@ export const layer = Layer.effect( }, }) - log.info("session warp complete", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - target: target.directory, - }) + yield* Effect.logInfo("session warp complete").pipe( + Effect.annotateLogs({ + service: "workspace-sync", + ...{ + workspaceID: input.workspaceID, + sessionID: input.sessionID, + target: target.directory, + }, + }), + ) return } @@ -733,15 +735,20 @@ export const layer = Layer.effect( const batches = Iterable.chunksOf(rows, 10) const total = Iterable.size(batches) - log.info("session warp prepared", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - target: String(route(target.url, "/sync/replay")), - events: rows.length, - batches: total, - first: rows[0]?.seq, - last: rows.at(-1)?.seq, - }) + yield* Effect.logInfo("session warp prepared").pipe( + Effect.annotateLogs({ + service: "workspace-sync", + ...{ + workspaceID: input.workspaceID, + sessionID: input.sessionID, + target: String(route(target.url, "/sync/replay")), + events: rows.length, + batches: total, + first: rows[0]?.seq, + last: rows.at(-1)?.seq, + }, + }), + ) yield* Effect.forEach( batches, @@ -759,14 +766,19 @@ export const layer = Layer.effect( if (response.status < 200 || response.status >= 300) { const body = yield* response.text - log.error("session warp batch failed", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - step: i + 1, - total, - status: response.status, - body, - }) + yield* Effect.logError("session warp batch failed").pipe( + Effect.annotateLogs({ + service: "workspace-sync", + ...{ + workspaceID: input.workspaceID, + sessionID: input.sessionID, + step: i + 1, + total, + status: response.status, + body, + }, + }), + ) return yield* new SessionWarpHttpError({ message: `Failed to warp session ${input.sessionID} into workspace ${workspaceID}: HTTP ${response.status} ${body}`, workspaceID, @@ -776,13 +788,18 @@ export const layer = Layer.effect( }) } - log.info("session warp batch posted", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - step: i + 1, - total, - status: response.status, - }) + yield* Effect.logInfo("session warp batch posted").pipe( + Effect.annotateLogs({ + service: "workspace-sync", + ...{ + workspaceID: input.workspaceID, + sessionID: input.sessionID, + step: i + 1, + total, + status: response.status, + }, + }), + ) }), { discard: true }, ) @@ -795,12 +812,17 @@ export const layer = Layer.effect( ) if (response.status < 200 || response.status >= 300) { const body = yield* response.text - log.error("session warp steal failed", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - status: response.status, - body, - }) + yield* Effect.logError("session warp steal failed").pipe( + Effect.annotateLogs({ + service: "workspace-sync", + ...{ + workspaceID: input.workspaceID, + sessionID: input.sessionID, + status: response.status, + body, + }, + }), + ) return yield* new SessionWarpHttpError({ message: `Failed to steal session ${input.sessionID} into workspace ${workspaceID}: HTTP ${response.status} ${body}`, workspaceID, @@ -810,19 +832,29 @@ export const layer = Layer.effect( }) } - log.info("session warp complete", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - batches: total, - }) + yield* Effect.logInfo("session warp complete").pipe( + Effect.annotateLogs({ + service: "workspace-sync", + ...{ + workspaceID: input.workspaceID, + sessionID: input.sessionID, + batches: total, + }, + }), + ) }).pipe( Effect.tapError((err) => Effect.sync(() => - log.error("session warp failed", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - error: errorData(err), - }), + Effect.logError("session warp failed").pipe( + Effect.annotateLogs({ + service: "workspace-sync", + ...{ + workspaceID: input.workspaceID, + sessionID: input.sessionID, + error: errorData(err), + }, + }), + ), ), ), ) @@ -848,7 +880,6 @@ export const layer = Layer.effect( WorkspaceAdapterRuntime.list(adapter).pipe( Effect.catchCause((error) => Effect.sync(() => { - log.warn("workspace adapter list failed", { type, error }) return [] }), ), @@ -927,10 +958,7 @@ export const layer = Layer.effect( Effect.gen(function* () { yield* WorkspaceAdapterRuntime.remove(info) }), - () => - Effect.sync(() => { - log.error("adapter not available when removing workspace", { type: row.type }) - }), + () => Effect.sync(() => {}), ) yield* db((db) => db.delete(WorkspaceTable).where(eq(WorkspaceTable.id, id)).run()) @@ -996,10 +1024,6 @@ export const layer = Layer.effect( Effect.catch((error) => Effect.sync(() => { setStatus(workspace.id, "error") - log.warn("workspace sync failed to start", { - workspaceID: workspace.id, - error, - }) }), ), Effect.forkDetach, diff --git a/packages/opencode/src/data-migration.ts b/packages/opencode/src/data-migration.ts index 1576c7aa020..1465f0da4ef 100644 --- a/packages/opencode/src/data-migration.ts +++ b/packages/opencode/src/data-migration.ts @@ -1,7 +1,6 @@ import { Context, Effect, Layer } from "effect" import { Database } from "./storage/db" import { DataMigrationTable } from "./data-migration.sql" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { and, asc, eq, gt, inArray, sql } from "drizzle-orm" import { MessageTable, SessionTable } from "./session/session.sql" import type { SessionID } from "./session/schema" @@ -10,9 +9,6 @@ export type Migration = { name: string run: Effect.Effect } - -const log = EffectLogger.create({ service: "data-migration" }) - export interface Interface {} export class Service extends Context.Service()("@opencode/DataMigration") {} @@ -135,7 +131,9 @@ export const layer = Layer.effect( ) if (completed) continue - log.info("running data migration", { name: migration.name }) + yield* Effect.logInfo("running data migration").pipe( + Effect.annotateLogs({ service: "data-migration", ...{ name: migration.name } }), + ) yield* migration.run.pipe(Effect.withSpan("DataMigration", { attributes: { name: migration.name } })) Database.use((db) => db diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index 53223fb494a..81b5951800c 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -12,7 +12,6 @@ import ignore from "ignore" import path from "path" import { Global } from "@opencode-ai/core/global" import { containsPath } from "../project/instance-context" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Protected } from "./protected" import { Ripgrep } from "./ripgrep" import { NonNegativeInt, type DeepMutable } from "@opencode-ai/core/schema" @@ -69,9 +68,6 @@ export const Event = { }), ), } - -const log = EffectLogger.create({ service: "file" }) - const binary = new Set([ "exe", "dll", @@ -284,7 +280,6 @@ const getImageMimeType = (file: string) => mime[ext(file)] || "image/" + ext(fil function shouldEncode(mimeType: string) { const type = mimeType.toLowerCase() - log.debug("shouldEncode", { type }) if (!type) return false if (type.startsWith("text/")) return false if (type.includes("charset=")) return false @@ -499,7 +494,6 @@ export const layer = Layer.effect( }) const read: Interface["read"] = Effect.fn("File.read")(function* (file: string) { - using _ = log.time("read", { file }) const ctx = yield* InstanceState.context const full = path.join(ctx.directory, file) @@ -621,7 +615,7 @@ export const layer = Layer.effect( const query = input.query.trim() const limit = input.limit ?? 100 const kind = input.type ?? (input.dirs === false ? "file" : "all") - log.info("search", { query, kind }) + yield* Effect.logInfo("search").pipe(Effect.annotateLogs({ service: "file", ...{ query, kind } })) const preferHidden = query.startsWith(".") || query.includes("/.") @@ -636,11 +630,13 @@ export const layer = Layer.effect( const sorted = fuzzysort.go(query, items, { limit: searchLimit }).map((item) => item.target) const output = kind === "directory" ? sortHiddenLast(sorted, preferHidden).slice(0, limit) : sorted - log.info("search", { query, kind, results: output.length }) + yield* Effect.logInfo("search").pipe( + Effect.annotateLogs({ service: "file", ...{ query, kind, results: output.length } }), + ) return output }) - log.info("init") + yield* Effect.logInfo("init").pipe(Effect.annotateLogs({ service: "file" })) return Service.of({ init, status, read, list, search }) }), ) diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index 6aa441eef6d..37abdc5760b 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -9,12 +9,9 @@ import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Global } from "@opencode-ai/core/global" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { sanitizedProcessEnv } from "@opencode-ai/core/util/opencode-process" import { which } from "@/util/which" import { NonNegativeInt } from "@opencode-ai/core/schema" - -const log = EffectLogger.create({ service: "ripgrep" }) const VERSION = "15.1.0" const PLATFORM = { "arm64-darwin": { platform: "aarch64-apple-darwin", extension: "tar.gz" }, @@ -306,7 +303,7 @@ export const layer: Layer.Layer { ) return createWrapper(binding) as typeof import("@parcel/watcher") } catch (error) { - log.error("failed to load watcher binding", { error }) return } }) @@ -77,18 +73,30 @@ export const layer = Layer.effect( const ctx = yield* InstanceState.context - log.info("init", { directory: ctx.directory }) + yield* Effect.logInfo("init").pipe( + Effect.annotateLogs({ service: "file.watcher", ...{ directory: ctx.directory } }), + ) const backend = getBackend() if (!backend) { - log.error("watcher backend not supported", { directory: ctx.directory, platform: process.platform }) + yield* Effect.logError("watcher backend not supported").pipe( + Effect.annotateLogs({ + service: "file.watcher", + ...{ directory: ctx.directory, platform: process.platform }, + }), + ) return } const w = watcher() if (!w) return - log.info("watcher backend", { directory: ctx.directory, platform: process.platform, backend }) + yield* Effect.logInfo("watcher backend").pipe( + Effect.annotateLogs({ + service: "file.watcher", + ...{ directory: ctx.directory, platform: process.platform, backend }, + }), + ) const bridge = yield* EffectBridge.make() const subs: ParcelWatcher.AsyncSubscription[] = [] yield* Effect.addFinalizer(() => @@ -112,7 +120,6 @@ export const layer = Layer.effect( }).pipe( Effect.timeout(SUBSCRIBE_TIMEOUT_MS), Effect.catchCause((cause) => { - log.error("failed to subscribe", { dir, cause: Cause.pretty(cause) }) pending.then((s) => s.unsubscribe()).catch(() => {}) return Effect.void }), @@ -148,7 +155,6 @@ export const layer = Layer.effect( } }, Effect.catchCause((cause) => { - log.error("failed to init watcher service", { cause: Cause.pretty(cause) }) return Effect.void }), ), diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index 098f4b8dc68..75aac01e488 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -8,11 +8,7 @@ import { mergeDeep } from "remeda" import { Config } from "@/config/config" import { RuntimeFlags } from "@/effect/runtime-flags" import { errorMessage } from "@/util/error" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import * as Formatter from "./formatter" - -const log = EffectLogger.create({ service: "format" }) - export const Status = Schema.Struct({ name: Schema.String, extensions: Schema.Array(Schema.String), @@ -60,10 +56,8 @@ export const layer = Layer.effect( const matching = Object.values(formatters).filter((item) => item.extensions.includes(ext)) const checks = await Promise.all( matching.map(async (item) => { - log.info("checking", { name: item.name, ext }) const cmd = await getCommand(item) if (cmd) { - log.info("enabled", { name: item.name, ext }) } return { item, @@ -78,13 +72,13 @@ export const layer = Layer.effect( function formatFile(filepath: string) { return Effect.gen(function* () { - log.info("formatting", { file: filepath }) + yield* Effect.logInfo("formatting").pipe(Effect.annotateLogs({ service: "format", ...{ file: filepath } })) const formatters = yield* Effect.promise(() => getFormatter(path.extname(filepath))) if (!formatters.length) return false for (const { item, cmd } of formatters) { - log.info("running", { command: cmd }) + yield* Effect.logInfo("running").pipe(Effect.annotateLogs({ service: "format", ...{ command: cmd } })) const replaced = cmd.map((x) => x.replace("$FILE", filepath)) const dir = yield* InstanceState.directory const result = yield* appProcess @@ -101,22 +95,20 @@ export const layer = Layer.effect( .pipe( Effect.catch((error) => Effect.sync(() => { - log.error("failed to format file", { - error: "spawn failed", - command: cmd, - ...item.environment, - file: filepath, - cause: errorMessage(error.cause ?? error), - }) return undefined }), ), ) if (result && result.exitCode !== 0) { - log.error("failed", { - command: cmd, - ...item.environment, - }) + yield* Effect.logError("failed").pipe( + Effect.annotateLogs({ + service: "format", + ...{ + command: cmd, + ...item.environment, + }, + }), + ) } } @@ -127,8 +119,8 @@ export const layer = Layer.effect( const cfg = yield* config.get() if (!cfg.formatter) { - log.info("all formatters are disabled") - log.info("init") + yield* Effect.logInfo("all formatters are disabled").pipe(Effect.annotateLogs({ service: "format" })) + yield* Effect.logInfo("init").pipe(Effect.annotateLogs({ service: "format" })) return { formatters, isEnabled, @@ -166,7 +158,7 @@ export const layer = Layer.effect( } } - log.info("init") + yield* Effect.logInfo("init").pipe(Effect.annotateLogs({ service: "format" })) return { formatters, diff --git a/packages/opencode/src/ide/index.ts b/packages/opencode/src/ide/index.ts index 7ead4188910..b15d2e4efd2 100644 --- a/packages/opencode/src/ide/index.ts +++ b/packages/opencode/src/ide/index.ts @@ -1,7 +1,6 @@ import { BusEvent } from "@/bus/bus-event" import { Schema } from "effect" import { NamedError } from "@opencode-ai/core/util/error" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Process } from "@/util/process" const SUPPORTED_IDES = [ @@ -11,9 +10,6 @@ const SUPPORTED_IDES = [ { name: "Cursor" as const, cmd: "cursor" }, { name: "VSCodium" as const, cmd: "codium" }, ] - -const log = EffectLogger.create({ service: "ide" }) - export const Event = { Installed: BusEvent.define( "ide.installed", @@ -52,13 +48,6 @@ export async function install(ide: (typeof SUPPORTED_IDES)[number]["name"]) { }) const stdout = p.stdout.toString() const stderr = p.stderr.toString() - - log.info("installed", { - ide, - stdout, - stderr, - }) - if (p.code !== 0) { throw new InstallFailedError({ stderr }) } diff --git a/packages/opencode/src/image/image.ts b/packages/opencode/src/image/image.ts index 75d5a5cfd56..e1afbb9c281 100644 --- a/packages/opencode/src/image/image.ts +++ b/packages/opencode/src/image/image.ts @@ -1,6 +1,5 @@ import { Config } from "@/config/config" import type { MessageV2 } from "@/session/message-v2" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import photonWasm from "@silvia-odwyer/photon-node/photon_rs_bg.wasm" with { type: "file" } import { Context, Effect, Layer, Schema } from "effect" import path from "node:path" @@ -11,8 +10,6 @@ const MAX_WIDTH = 2000 const MAX_HEIGHT = 2000 const AUTO_RESIZE = true const JPEG_QUALITIES = [80, 85, 70, 55, 40] -const log = EffectLogger.create({ service: "image" }) - export class ResizerUnavailableError extends Schema.TaggedErrorClass()( "ImageResizerUnavailableError", {}, @@ -68,7 +65,11 @@ export const layer = Layer.effect( path.isAbsolute(photonWasm) ? photonWasm : fileURLToPath(new URL(photonWasm, import.meta.url)) }).pipe( Effect.andThen(() => Effect.tryPromise(() => import("@silvia-odwyer/photon-node"))), - Effect.tapError((error) => Effect.sync(() => log.warn("failed to load photon", { error }))), + Effect.tapError((error) => + Effect.sync(() => + Effect.logWarning("failed to load photon").pipe(Effect.annotateLogs({ service: "image", ...{ error } })), + ), + ), Effect.mapError(() => new ResizerUnavailableError()), ), ) @@ -92,7 +93,6 @@ export const layer = Layer.effect( const decoded = yield* Effect.try({ try: () => photon.PhotonImage.new_from_byteslice(Buffer.from(base64, "base64")), catch: (error) => { - log.warn("failed to decode image", { error }) return new DecodeError() }, }) @@ -140,12 +140,17 @@ export const layer = Layer.effect( resized.free() if (candidate) { - log.info("using resized image", { - from_mime: input.mime, - to_mime: candidate.mime, - from: `${originalWidth}x${originalHeight}`, - to: `${size.width}x${size.height}`, - }) + yield* Effect.logInfo("using resized image").pipe( + Effect.annotateLogs({ + service: "image", + ...{ + from_mime: input.mime, + to_mime: candidate.mime, + from: `${originalWidth}x${originalHeight}`, + to: `${size.width}x${size.height}`, + }, + }), + ) return { ...input, mime: candidate.mime, diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index e73e9d66e14..8a63d5b74b9 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -7,14 +7,10 @@ import { ChildProcess } from "effect/unstable/process" import { AppProcess } from "@opencode-ai/core/process" import path from "path" import { BusEvent } from "@/bus/bus-event" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { makeRuntime } from "@opencode-ai/core/effect/runtime" import semver from "semver" import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version" import { NpmConfig } from "@opencode-ai/core/npm-config" - -const log = EffectLogger.create({ service: "installation" }) - export type Method = "curl" | "npm" | "yarn" | "pnpm" | "bun" | "brew" | "scoop" | "choco" | "unknown" export type ReleaseType = "patch" | "minor" | "major" @@ -317,12 +313,17 @@ export const layer: Layer.Layer>> @@ -145,23 +142,13 @@ export async function create(input: { directory: string instance: InstanceContext }) { - const logger = log.clone().tag("serverID", input.serverID) - logger.info("starting client") + const logAnnotations = { service: "lsp.client", serverID: input.serverID } const instance = input.instance const connection = createMessageConnection( new StreamMessageReader(input.server.process.stdout as any), new StreamMessageWriter(input.server.process.stdin as any), ) - // Server stderr can contain both real errors and routine informational logs, - // which is normal stderr practice for some tools. Keep the raw stream at - // debug so users can opt in with --print-logs --log-level DEBUG without - // polluting normal logs. - input.server.process.stderr?.on("data", (data: Buffer) => { - const text = data.toString().trim() - if (text) logger.debug("server stderr", { text: text.slice(0, 1000) }) - }) - // --- Connection state --- const pushDiagnostics = new Map() @@ -191,11 +178,6 @@ export async function create(input: { connection.onNotification("textDocument/publishDiagnostics", (params) => { const filePath = getFilePath(params.uri) if (!filePath) return - logger.info("textDocument/publishDiagnostics", { - path: filePath, - count: params.diagnostics.length, - version: params.version, - }) published.set(filePath, { at: Date.now(), version: typeof params.version === "number" ? params.version : undefined, @@ -207,7 +189,6 @@ export async function create(input: { updatePushDiagnostics(filePath, params.diagnostics) }) connection.onRequest("window/workDoneProgress/create", (params) => { - logger.info("window/workDoneProgress/create", params) return null }) connection.onRequest("workspace/configuration", async (params) => { @@ -242,10 +223,6 @@ export async function create(input: { ]) connection.onRequest("workspace/diagnostic/refresh", async () => null) connection.listen() - - // --- Initialize handshake --- - - logger.info("sending initialize") const initialized = await withTimeout( connection.sendRequest<{ capabilities?: ServerCapabilities }>("initialize", { rootUri: pathToFileURL(input.root).href, @@ -289,7 +266,6 @@ export async function create(input: { }), INITIALIZE_TIMEOUT_MS, ).catch((err) => { - logger.error("initialize error", { error: err }) throw new InitializeError({ serverID: input.serverID, cause: err }) }) @@ -602,11 +578,6 @@ export async function create(input: { const document = files[request.path] if (document !== undefined) { - // Do not wipe diagnostics on didChange. Some servers (e.g. clangd) only - // re-emit diagnostics when the content actually changes, so clearing - // here would lose errors for no-op touchFile calls. Let the server's - // next push/pull overwrite naturally. - logger.info("workspace/didChangeWatchedFiles", request) await connection.sendNotification("workspace/didChangeWatchedFiles", { changes: [ { @@ -618,10 +589,6 @@ export async function create(input: { const next = document.version + 1 files[request.path] = { version: next, text } - logger.info("textDocument/didChange", { - path: request.path, - version: next, - }) await connection.sendNotification("textDocument/didChange", { textDocument: { uri: pathToFileURL(request.path).href, @@ -642,8 +609,6 @@ export async function create(input: { }) return next } - - logger.info("workspace/didChangeWatchedFiles", request) await connection.sendNotification("workspace/didChangeWatchedFiles", { changes: [ { @@ -652,8 +617,6 @@ export async function create(input: { }, ], }) - - logger.info("textDocument/didOpen", request) pushDiagnostics.delete(request.path) pullDiagnostics.delete(request.path) await connection.sendNotification("textDocument/didOpen", { @@ -679,11 +642,6 @@ export async function create(input: { const normalizedPath = Filesystem.normalizePath( path.isAbsolute(request.path) ? request.path : path.resolve(input.directory, request.path), ) - logger.info("waiting for diagnostics", { - path: normalizedPath, - mode: request.mode ?? "full", - version: request.version, - }) if (request.mode === "document") { await waitForDocumentDiagnostics({ path: normalizedPath, version: request.version, after: request.after }) return @@ -691,16 +649,11 @@ export async function create(input: { await waitForFullDiagnostics({ path: normalizedPath, version: request.version, after: request.after }) }, async shutdown() { - logger.info("shutting down") connection.end() connection.dispose() await Process.stop(input.server.process) - logger.info("shutdown") }, } - - logger.info("initialized") - return result } diff --git a/packages/opencode/src/lsp/lsp.ts b/packages/opencode/src/lsp/lsp.ts index 2cd00f0b428..ee2c2419367 100644 --- a/packages/opencode/src/lsp/lsp.ts +++ b/packages/opencode/src/lsp/lsp.ts @@ -1,6 +1,5 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import * as LSPClient from "./client" import path from "path" import { pathToFileURL, fileURLToPath } from "url" @@ -13,9 +12,6 @@ import { InstanceState } from "@/effect/instance-state" import { containsPath } from "@/project/instance-context" import { NonNegativeInt } from "@opencode-ai/core/schema" import { RuntimeFlags } from "@/effect/runtime-flags" - -const log = EffectLogger.create({ service: "lsp" }) - export const Event = { Updated: BusEvent.define("lsp.updated", Schema.Struct({})), } @@ -101,7 +97,6 @@ const kinds = [ const filterExperimentalServers = (servers: Record, flags: RuntimeFlags.Info) => { if (flags.experimentalLspTy) { if (servers["pyright"]) { - log.info("LSP server pyright is disabled because OPENCODE_EXPERIMENTAL_LSP_TY is enabled") delete servers["pyright"] } } else { @@ -152,7 +147,7 @@ export const layer = Layer.effect( const servers: Record = {} if (!cfg.lsp) { - log.info("all LSPs are disabled") + yield* Effect.logInfo("all LSPs are disabled").pipe(Effect.annotateLogs({ service: "lsp" })) } else { for (const server of Object.values(LSPServer)) { servers[server.id] = server @@ -164,7 +159,7 @@ export const layer = Layer.effect( for (const [name, item] of Object.entries(cfg.lsp)) { const existing = servers[name] if (item.disabled) { - log.info(`LSP server ${name} is disabled`) + yield* Effect.logInfo(`LSP server ${name} is disabled`).pipe(Effect.annotateLogs({ service: "lsp" })) delete servers[name] continue } @@ -184,11 +179,16 @@ export const layer = Layer.effect( } } - log.info("enabled LSP servers", { - serverIds: Object.values(servers) - .map((server) => server.id) - .join(", "), - }) + yield* Effect.logInfo("enabled LSP servers").pipe( + Effect.annotateLogs({ + service: "lsp", + ...{ + serverIds: Object.values(servers) + .map((server) => server.id) + .join(", "), + }, + }), + ) } const s: State = { @@ -225,13 +225,10 @@ export const layer = Layer.effect( }) .catch((err) => { s.broken.add(key) - log.error(`Failed to spawn LSP server ${server.id}`, { error: err }) return undefined }) if (!handle) return undefined - log.info("spawned lsp server", { serverID: server.id, root }) - const client = await LSPClient.create({ serverID: server.id, server: handle, @@ -241,7 +238,6 @@ export const layer = Layer.effect( }).catch(async (err) => { s.broken.add(key) await Process.stop(handle.process) - log.error(`Failed to initialize LSP client ${server.id}`, { error: err }) return undefined }) @@ -344,7 +340,7 @@ export const layer = Layer.effect( }) const touchFile = Effect.fn("LSP.touchFile")(function* (input: string, diagnostics?: "document" | "full") { - log.info("touching file", { file: input }) + yield* Effect.logInfo("touching file").pipe(Effect.annotateLogs({ service: "lsp", ...{ file: input } })) const clients = yield* getClients(input) yield* Effect.promise(() => Promise.all( @@ -359,9 +355,7 @@ export const layer = Layer.effect( after, }) }), - ).catch((err) => { - log.error("failed to touch file", { err, file: input }) - }), + ).catch((err) => {}), ) }) diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index c7d6d3a7136..fe88dc9c357 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -2,7 +2,6 @@ import type { ChildProcessWithoutNullStreams } from "child_process" import path from "path" import os from "os" import { Global } from "@opencode-ai/core/global" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { text } from "node:stream/consumers" import fs from "fs/promises" import { Filesystem } from "@/util/filesystem" @@ -14,8 +13,6 @@ import { Module } from "@opencode-ai/core/util/module" import { spawn } from "./launch" import { Npm } from "@opencode-ai/core/npm" import type { RuntimeFlags } from "@/effect/runtime-flags" - -const log = EffectLogger.create({ service: "lsp.server" }) const pathExists = async (p: string) => fs .stat(p) @@ -80,7 +77,6 @@ export const Deno: Info = { async spawn(root) { const deno = which("deno") if (!deno) { - log.info("deno not found, please install deno first") return } return { @@ -100,7 +96,6 @@ export const Typescript: Info = { extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"], async spawn(root, ctx) { const tsserver = Module.resolve("typescript/lib/tsserver.js", ctx.directory) - log.info("typescript server", { tsserver }) if (!tsserver) return const bin = await Npm.which("typescript-language-server") if (!bin) return @@ -157,11 +152,9 @@ export const ESLint: Info = { async spawn(root, ctx, flags) { const eslint = Module.resolve("eslint", ctx.directory) if (!eslint) return - log.info("spawning eslint server") const serverPath = path.join(Global.Path.bin, "vscode-eslint", "server", "out", "eslintServer.js") if (!(await Filesystem.exists(serverPath))) { if (flags.disableLspDownload) return - log.info("downloading and building VS Code ESLint server") const response = await fetch("https://github.com/microsoft/vscode-eslint/archive/refs/heads/main.zip") if (!response.ok) return @@ -171,7 +164,6 @@ export const ESLint: Info = { const ok = await Archive.extractZip(zipPath, Global.Path.bin) .then(() => true) .catch((error) => { - log.error("Failed to extract vscode-eslint archive", { error }) return false }) if (!ok) return @@ -182,7 +174,6 @@ export const ESLint: Info = { const stats = await fs.stat(finalPath).catch(() => undefined) if (stats) { - log.info("removing old eslint installation", { path: finalPath }) await fs.rm(finalPath, { force: true, recursive: true }) } await fs.rename(extractedPath, finalPath) @@ -190,8 +181,6 @@ export const ESLint: Info = { const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm" await Process.run([npmCmd, "install"], { cwd: finalPath }) await Process.run([npmCmd, "run", "compile"], { cwd: finalPath }) - - log.info("installed VS Code ESLint server", { serverPath }) } const proc = spawn("node", [serverPath, "--stdio"], { @@ -274,8 +263,6 @@ export const Oxlint: Info = { }), } } - - log.info("oxlint not found, please install oxlint") return }, } @@ -355,8 +342,6 @@ export const Gopls: Info = { if (!bin) { if (!which("go")) return if (flags.disableLspDownload) return - - log.info("installing gopls") const proc = Process.spawn(["go", "install", "golang.org/x/tools/gopls@latest"], { env: { ...process.env, GOBIN: Global.Path.bin }, stdout: "pipe", @@ -365,13 +350,9 @@ export const Gopls: Info = { }) const exit = await proc.exited if (exit !== 0) { - log.error("Failed to install gopls") return } bin = path.join(Global.Path.bin, "gopls" + (process.platform === "win32" ? ".exe" : "")) - log.info(`installed gopls`, { - bin, - }) } return { process: spawn(bin!, { @@ -391,11 +372,9 @@ export const Rubocop: Info = { const ruby = which("ruby") const gem = which("gem") if (!ruby || !gem) { - log.info("Ruby not found, please install Ruby first") return } if (flags.disableLspDownload) return - log.info("installing rubocop") const proc = Process.spawn(["gem", "install", "rubocop", "--bindir", Global.Path.bin], { stdout: "pipe", stderr: "pipe", @@ -403,13 +382,9 @@ export const Rubocop: Info = { }) const exit = await proc.exited if (exit !== 0) { - log.error("Failed to install rubocop") return } bin = path.join(Global.Path.bin, "rubocop" + (process.platform === "win32" ? ".exe" : "")) - log.info(`installed rubocop`, { - bin, - }) } return { process: spawn(bin!, ["--lsp"], { @@ -466,7 +441,6 @@ export const Ty: Info = { } if (!binary) { - log.error("ty not found, please install ty first") return } @@ -543,13 +517,10 @@ export const ElixirLS: Info = { if (!(await Filesystem.exists(binary))) { const elixir = which("elixir") if (!elixir) { - log.error("elixir is required to run elixir-ls") return } if (flags.disableLspDownload) return - log.info("downloading elixir-ls from GitHub releases") - const response = await fetch("https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip") if (!response.ok) return const zipPath = path.join(Global.Path.bin, "elixir-ls.zip") @@ -558,7 +529,6 @@ export const ElixirLS: Info = { const ok = await Archive.extractZip(zipPath, Global.Path.bin) .then(() => true) .catch((error) => { - log.error("Failed to extract elixir-ls archive", { error }) return false }) if (!ok) return @@ -573,10 +543,6 @@ export const ElixirLS: Info = { await Process.run(["mix", "deps.get"], { cwd, env }) await Process.run(["mix", "compile"], { cwd, env }) await Process.run(["mix", "elixir_ls.release2", "-o", "release"], { cwd, env }) - - log.info(`installed elixir-ls`, { - path: elixirLsPath, - }) } } @@ -598,16 +564,12 @@ export const Zls: Info = { if (!bin) { const zig = which("zig") if (!zig) { - log.error("Zig is required to use zls. Please install Zig first.") return } if (flags.disableLspDownload) return - log.info("downloading zls from GitHub releases") - const releaseResponse = await fetch("https://api.github.com/repos/zigtools/zls/releases/latest") if (!releaseResponse.ok) { - log.error("Failed to fetch zls release info") return } @@ -644,20 +606,17 @@ export const Zls: Info = { ] if (!supportedCombos.includes(assetName)) { - log.error(`Platform ${platform} and architecture ${arch} is not supported by zls`) return } const asset = release.assets?.find((a) => a.name === assetName) if (!asset?.browser_download_url) { - log.error(`Could not find asset ${assetName} in latest zls release`) return } const downloadUrl = asset.browser_download_url const downloadResponse = await fetch(downloadUrl) if (!downloadResponse.ok) { - log.error("Failed to download zls") return } @@ -668,7 +627,6 @@ export const Zls: Info = { const ok = await Archive.extractZip(tempPath, Global.Path.bin) .then(() => true) .catch((error) => { - log.error("Failed to extract zls archive", { error }) return false }) if (!ok) return @@ -681,15 +639,12 @@ export const Zls: Info = { bin = path.join(Global.Path.bin, "zls" + (platform === "win32" ? ".exe" : "")) if (!(await Filesystem.exists(bin))) { - log.error("Failed to extract zls binary") return } if (platform !== "win32") { await fs.chmod(bin, 0o755).catch(() => {}) } - - log.info(`installed zls`, { bin }) } return { @@ -726,11 +681,8 @@ export const Razor: Info = { const razor = await findVscodeRazorExtension() if (!razor) { - log.info("VS Code C# extension with Razor support not found, skipping Razor LSP") return } - - log.info("using VS Code Razor extension for roslyn-language-server", { extension: razor.extension }) return { process: spawn( bin, @@ -767,12 +719,10 @@ async function getRoslynLanguageServer(disableLspDownload: boolean) { async function installRoslynLanguageServer(disableLspDownload: boolean) { if (!which("dotnet")) { - log.error(".NET SDK is required to install roslyn-language-server") return } if (disableLspDownload) return - log.info("installing roslyn-language-server via dotnet tool") const proc = Process.spawn(["dotnet", "tool", "install", "--global", "roslyn-language-server", "--prerelease"], { stdout: "pipe", stderr: "pipe", @@ -780,23 +730,18 @@ async function installRoslynLanguageServer(disableLspDownload: boolean) { }) const exit = await proc.exited if (exit !== 0) { - log.error("Failed to install roslyn-language-server") return } const resolved = which("roslyn-language-server") if (resolved) { - log.info(`installed roslyn-language-server`, { bin: resolved }) return resolved } const global = await roslynLanguageServerGlobalPath() if (global) { - log.info(`installed roslyn-language-server`, { bin: global }) return global } - - log.error("Installed roslyn-language-server but could not resolve executable") } async function roslynLanguageServerGlobalPath() { @@ -853,12 +798,10 @@ export const FSharp: Info = { let bin = which("fsautocomplete") if (!bin) { if (!which("dotnet")) { - log.error(".NET SDK is required to install fsautocomplete") return } if (flags.disableLspDownload) return - log.info("installing fsautocomplete via dotnet tool") const proc = Process.spawn(["dotnet", "tool", "install", "fsautocomplete", "--tool-path", Global.Path.bin], { stdout: "pipe", stderr: "pipe", @@ -866,12 +809,10 @@ export const FSharp: Info = { }) const exit = await proc.exited if (exit !== 0) { - log.error("Failed to install fsautocomplete") return } bin = path.join(Global.Path.bin, "fsautocomplete" + (process.platform === "win32" ? ".exe" : "")) - log.info(`installed fsautocomplete`, { bin }) } return { @@ -951,7 +892,6 @@ export const RustAnalyzer: Info = { async spawn(root) { const bin = which("rust-analyzer") if (!bin) { - log.info("rust-analyzer not found in path, please install it") return } return { @@ -1002,11 +942,8 @@ export const Clangd: Info = { } if (flags.disableLspDownload) return - log.info("downloading clangd from GitHub releases") - const releaseResponse = await fetch("https://api.github.com/repos/clangd/clangd/releases/latest") if (!releaseResponse.ok) { - log.error("Failed to fetch clangd release info") return } @@ -1017,7 +954,6 @@ export const Clangd: Info = { const tag = release.tag_name if (!tag) { - log.error("clangd release did not include a tag name") return } const platform = process.platform @@ -1028,7 +964,6 @@ export const Clangd: Info = { } const token = tokens[platform] if (!token) { - log.error(`Platform ${platform} is not supported by clangd auto-download`) return } @@ -1045,21 +980,18 @@ export const Clangd: Info = { assets.find((item) => valid(item) && item.name?.endsWith(".tar.xz")) ?? assets.find((item) => valid(item)) if (!asset?.name || !asset.browser_download_url) { - log.error("clangd could not match release asset", { tag, platform }) return } const name = asset.name const downloadResponse = await fetch(asset.browser_download_url) if (!downloadResponse.ok) { - log.error("Failed to download clangd") return } const archive = path.join(Global.Path.bin, name) const buf = await downloadResponse.arrayBuffer() if (buf.byteLength === 0) { - log.error("Failed to write clangd archive") return } await Filesystem.write(archive, Buffer.from(buf)) @@ -1067,7 +999,6 @@ export const Clangd: Info = { const zip = name.endsWith(".zip") const tar = name.endsWith(".tar.xz") if (!zip && !tar) { - log.error("clangd encountered unsupported asset", { asset: name }) return } @@ -1075,7 +1006,6 @@ export const Clangd: Info = { const ok = await Archive.extractZip(archive, Global.Path.bin) .then(() => true) .catch((error) => { - log.error("Failed to extract clangd archive", { error }) return false }) if (!ok) return @@ -1087,7 +1017,6 @@ export const Clangd: Info = { const bin = path.join(Global.Path.bin, "clangd_" + tag, "bin", "clangd" + ext) if (!(await Filesystem.exists(bin))) { - log.error("Failed to extract clangd binary") return } @@ -1097,9 +1026,6 @@ export const Clangd: Info = { await fs.unlink(path.join(Global.Path.bin, "clangd")).catch(() => {}) await fs.symlink(bin, path.join(Global.Path.bin, "clangd")).catch(() => {}) - - log.info(`installed clangd`, { bin }) - return { process: spawn(bin, args, { cwd: root, @@ -1142,7 +1068,6 @@ export const Astro: Info = { async spawn(root, ctx, flags) { const tsserver = Module.resolve("typescript/lib/tsserver.js", ctx.directory) if (!tsserver) { - log.info("typescript not found, required for Astro language server") return } const tsdk = path.dirname(tsserver) @@ -1203,7 +1128,6 @@ export const JDTLS: Info = { async spawn(root, _ctx, flags) { const java = which("java") if (!java) { - log.error("Java 21 or newer is required to run the JDTLS. Please install it first.") return } const javaMajorVersion = await run(["java", "-version"]).then((result) => { @@ -1211,7 +1135,6 @@ export const JDTLS: Info = { return !m ? undefined : parseInt(m[1]) }) if (javaMajorVersion == null || javaMajorVersion < 21) { - log.error("JDTLS requires at least Java 21.") return } const distPath = path.join(Global.Path.bin, "jdtls") @@ -1219,29 +1142,21 @@ export const JDTLS: Info = { const installed = await pathExists(launcherDir) if (!installed) { if (flags.disableLspDownload) return - log.info("Downloading JDTLS LSP server.") await fs.mkdir(distPath, { recursive: true }) const releaseURL = "https://www.eclipse.org/downloads/download.php?file=/jdtls/snapshots/jdt-language-server-latest.tar.gz" const archiveName = "release.tar.gz" - - log.info("Downloading JDTLS archive", { url: releaseURL, dest: distPath }) const download = await fetch(releaseURL) if (!download.ok || !download.body) { - log.error("Failed to download JDTLS", { status: download.status, statusText: download.statusText }) return } await Filesystem.writeStream(path.join(distPath, archiveName), download.body) - - log.info("Extracting JDTLS archive") const tarResult = await run(["tar", "-xzf", archiveName], { cwd: distPath }) if (tarResult.code !== 0) { - log.error("Failed to extract JDTLS", { exitCode: tarResult.code, stderr: tarResult.stderr.toString() }) return } await fs.rm(path.join(distPath, archiveName), { force: true }) - log.info("JDTLS download and extraction completed") } const jarFileName = (await fs.readdir(launcherDir).catch(() => [])) @@ -1249,7 +1164,6 @@ export const JDTLS: Info = { ?.trim() ?? "" const launcherJar = path.join(launcherDir, jarFileName) if (!(await pathExists(launcherJar))) { - log.error(`Failed to locate the JDTLS launcher module in the installed directory: ${distPath}.`) return } const configFile = path.join( @@ -1317,11 +1231,8 @@ export const KotlinLS: Info = { const installed = await Filesystem.exists(launcherScript) if (!installed) { if (flags.disableLspDownload) return - log.info("Downloading Kotlin Language Server from GitHub.") - const releaseResponse = await fetch("https://api.github.com/repos/Kotlin/kotlin-lsp/releases/latest") if (!releaseResponse.ok) { - log.error("Failed to fetch kotlin-lsp release info") return } @@ -1329,7 +1240,6 @@ export const KotlinLS: Info = { const version = release.name?.replace(/^v/, "") if (!version) { - log.error("Could not determine Kotlin LSP version from release") return } @@ -1350,7 +1260,6 @@ export const KotlinLS: Info = { const combo = `${kotlinPlatform}-${kotlinArch}` if (!supportedCombos.includes(combo)) { - log.error(`Platform ${platform}/${arch} is not supported by Kotlin LSP`) return } @@ -1361,17 +1270,12 @@ export const KotlinLS: Info = { const archivePath = path.join(distPath, "kotlin-ls.zip") const download = await fetch(releaseURL) if (!download.ok || !download.body) { - log.error("Failed to download Kotlin Language Server", { - status: download.status, - statusText: download.statusText, - }) return } await Filesystem.writeStream(archivePath, download.body) const ok = await Archive.extractZip(archivePath, distPath) .then(() => true) .catch((error) => { - log.error("Failed to extract Kotlin LS archive", { error }) return false }) if (!ok) return @@ -1379,10 +1283,8 @@ export const KotlinLS: Info = { if (process.platform !== "win32") { await fs.chmod(launcherScript, 0o755).catch(() => {}) } - log.info("Installed Kotlin Language Server", { path: launcherScript }) } if (!(await Filesystem.exists(launcherScript))) { - log.error(`Failed to locate the Kotlin LS launcher script in the installed directory: ${distPath}.`) return } return { @@ -1436,11 +1338,8 @@ export const LuaLS: Info = { if (!bin) { if (flags.disableLspDownload) return - log.info("downloading lua-language-server from GitHub releases") - const releaseResponse = await fetch("https://api.github.com/repos/LuaLS/lua-language-server/releases/latest") if (!releaseResponse.ok) { - log.error("Failed to fetch lua-language-server release info") return } @@ -1475,20 +1374,17 @@ export const LuaLS: Info = { const assetSuffix = `${lualsPlatform}-${lualsArch}.${ext}` if (!supportedCombos.includes(assetSuffix)) { - log.error(`Platform ${platform} and architecture ${arch} is not supported by lua-language-server`) return } const asset = release.assets.find((a: any) => a.name === assetName) if (!asset) { - log.error(`Could not find asset ${assetName} in latest lua-language-server release`) return } const downloadUrl = asset.browser_download_url const downloadResponse = await fetch(downloadUrl) if (!downloadResponse.ok) { - log.error("Failed to download lua-language-server") return } @@ -1512,7 +1408,6 @@ export const LuaLS: Info = { const ok = await Archive.extractZip(tempPath, installDir) .then(() => true) .catch((error) => { - log.error("Failed to extract lua-language-server archive", { error }) return false }) if (!ok) return @@ -1520,7 +1415,6 @@ export const LuaLS: Info = { const ok = await run(["tar", "-xzf", tempPath, "-C", installDir]) .then((result) => result.code === 0) .catch((error: unknown) => { - log.error("Failed to extract lua-language-server archive", { error }) return false }) if (!ok) return @@ -1532,7 +1426,6 @@ export const LuaLS: Info = { bin = path.join(installDir, "bin", "lua-language-server" + (platform === "win32" ? ".exe" : "")) if (!(await Filesystem.exists(bin))) { - log.error("Failed to extract lua-language-server binary") return } @@ -1541,15 +1434,10 @@ export const LuaLS: Info = { .chmod(bin, 0o755) .then(() => true) .catch((error: unknown) => { - log.error("Failed to set executable permission for lua-language-server binary", { - error, - }) return false }) if (!ok) return } - - log.info(`installed lua-language-server`, { bin }) } return { @@ -1598,7 +1486,6 @@ export const Prisma: Info = { async spawn(root) { const prisma = which("prisma") if (!prisma) { - log.info("prisma not found, please install prisma") return } return { @@ -1616,7 +1503,6 @@ export const Dart: Info = { async spawn(root) { const dart = which("dart") if (!dart) { - log.info("dart not found, please install dart first") return } return { @@ -1634,7 +1520,6 @@ export const Ocaml: Info = { async spawn(root) { const bin = which("ocamllsp") if (!bin) { - log.info("ocamllsp not found, please install ocaml-lsp-server") return } return { @@ -1679,11 +1564,8 @@ export const TerraformLS: Info = { if (!bin) { if (flags.disableLspDownload) return - log.info("downloading terraform-ls from HashiCorp releases") - const releaseResponse = await fetch("https://api.releases.hashicorp.com/v1/releases/terraform-ls/latest") if (!releaseResponse.ok) { - log.error("Failed to fetch terraform-ls release info") return } @@ -1701,13 +1583,11 @@ export const TerraformLS: Info = { const builds = release.builds ?? [] const build = builds.find((b) => b.arch === tfArch && b.os === tfPlatform) if (!build?.url) { - log.error(`Could not find build for ${tfPlatform}/${tfArch} terraform-ls release version ${release.version}`) return } const downloadResponse = await fetch(build.url) if (!downloadResponse.ok) { - log.error("Failed to download terraform-ls") return } @@ -1717,7 +1597,6 @@ export const TerraformLS: Info = { const ok = await Archive.extractZip(tempPath, Global.Path.bin) .then(() => true) .catch((error) => { - log.error("Failed to extract terraform-ls archive", { error }) return false }) if (!ok) return @@ -1726,15 +1605,12 @@ export const TerraformLS: Info = { bin = path.join(Global.Path.bin, "terraform-ls" + (platform === "win32" ? ".exe" : "")) if (!(await Filesystem.exists(bin))) { - log.error("Failed to extract terraform-ls binary") return } if (platform !== "win32") { await fs.chmod(bin, 0o755).catch(() => {}) } - - log.info(`installed terraform-ls`, { bin }) } return { @@ -1760,11 +1636,8 @@ export const TexLab: Info = { if (!bin) { if (flags.disableLspDownload) return - log.info("downloading texlab from GitHub releases") - const response = await fetch("https://api.github.com/repos/latex-lsp/texlab/releases/latest") if (!response.ok) { - log.error("Failed to fetch texlab release info") return } @@ -1774,7 +1647,6 @@ export const TexLab: Info = { } const version = release.tag_name?.replace("v", "") if (!version) { - log.error("texlab release did not include a version tag") return } @@ -1789,13 +1661,11 @@ export const TexLab: Info = { const assets = release.assets ?? [] const asset = assets.find((a) => a.name === assetName) if (!asset?.browser_download_url) { - log.error(`Could not find asset ${assetName} in texlab release`) return } const downloadResponse = await fetch(asset.browser_download_url) if (!downloadResponse.ok) { - log.error("Failed to download texlab") return } @@ -1806,7 +1676,6 @@ export const TexLab: Info = { const ok = await Archive.extractZip(tempPath, Global.Path.bin) .then(() => true) .catch((error) => { - log.error("Failed to extract texlab archive", { error }) return false }) if (!ok) return @@ -1820,15 +1689,12 @@ export const TexLab: Info = { bin = path.join(Global.Path.bin, "texlab" + (platform === "win32" ? ".exe" : "")) if (!(await Filesystem.exists(bin))) { - log.error("Failed to extract texlab binary") return } if (platform !== "win32") { await fs.chmod(bin, 0o755).catch(() => {}) } - - log.info("installed texlab", { bin }) } return { @@ -1872,7 +1738,6 @@ export const Gleam: Info = { async spawn(root) { const gleam = which("gleam") if (!gleam) { - log.info("gleam not found, please install gleam first") return } return { @@ -1893,7 +1758,6 @@ export const Clojure: Info = { bin = which("clojure-lsp.exe") } if (!bin) { - log.info("clojure-lsp not found, please install clojure-lsp first") return } return { @@ -1921,7 +1785,6 @@ export const Nixd: Info = { async spawn(root) { const nixd = which("nixd") if (!nixd) { - log.info("nixd not found, please install nixd first") return } return { @@ -1944,11 +1807,8 @@ export const Tinymist: Info = { if (!bin) { if (flags.disableLspDownload) return - log.info("downloading tinymist from GitHub releases") - const response = await fetch("https://api.github.com/repos/Myriad-Dreamin/tinymist/releases/latest") if (!response.ok) { - log.error("Failed to fetch tinymist release info") return } @@ -1980,13 +1840,11 @@ export const Tinymist: Info = { const assets = release.assets ?? [] const asset = assets.find((a) => a.name === assetName) if (!asset?.browser_download_url) { - log.error(`Could not find asset ${assetName} in tinymist release`) return } const downloadResponse = await fetch(asset.browser_download_url) if (!downloadResponse.ok) { - log.error("Failed to download tinymist") return } @@ -1997,7 +1855,6 @@ export const Tinymist: Info = { const ok = await Archive.extractZip(tempPath, Global.Path.bin) .then(() => true) .catch((error) => { - log.error("Failed to extract tinymist archive", { error }) return false }) if (!ok) return @@ -2010,15 +1867,12 @@ export const Tinymist: Info = { bin = path.join(Global.Path.bin, "tinymist" + (platform === "win32" ? ".exe" : "")) if (!(await Filesystem.exists(bin))) { - log.error("Failed to extract tinymist binary") return } if (platform !== "win32") { await fs.chmod(bin, 0o755).catch(() => {}) } - - log.info("installed tinymist", { bin }) } return { @@ -2034,7 +1888,6 @@ export const HLS: Info = { async spawn(root) { const bin = which("haskell-language-server-wrapper") if (!bin) { - log.info("haskell-language-server-wrapper not found, please install haskell-language-server") return } return { @@ -2052,7 +1905,6 @@ export const JuliaLS: Info = { async spawn(root) { const julia = which("julia") if (!julia) { - log.info("julia not found, please install julia first (https://julialang.org/downloads/)") return } return { diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index fc39fb68d44..91132f0b187 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -14,7 +14,6 @@ import { } from "@modelcontextprotocol/sdk/types.js" import { Config } from "@/config/config" import { ConfigMCP } from "../config/mcp" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { NamedError } from "@opencode-ai/core/util/error" import { InstallationVersion } from "@opencode-ai/core/installation/version" import { withTimeout } from "@/util/timeout" @@ -31,8 +30,6 @@ import { EffectBridge } from "@/effect/bridge" import { InstanceState } from "@/effect/instance-state" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" - -const log = EffectLogger.create({ service: "mcp" }) const DEFAULT_TIMEOUT = 30_000 const TolerantListToolsResultSchema = ListToolsResultSchema.extend({ @@ -116,7 +113,6 @@ const sanitize = (s: string) => s.replace(/[^a-zA-Z0-9_-]/g, "_") function remoteURL(key: string, value: string) { if (URL.canParse(value)) return new URL(value) - log.warn("invalid remote mcp url", { key }) } function isOutputSchemaValidationError(error: Error) { @@ -133,8 +129,6 @@ function listTools(key: string, client: MCPClient, timeout: number) { Effect.map((result) => result.tools), Effect.catch((error) => { if (!isOutputSchemaValidationError(error)) return Effect.fail(error) - - log.warn("failed to validate MCP tool output schemas, retrying without output schema validation", { key, error }) return Effect.tryPromise({ try: () => client.request({ method: "tools/list" }, TolerantListToolsResultSchema, { @@ -188,7 +182,6 @@ function convertMcpTool(mcpTool: MCPToolDef, client: MCPClient, timeout?: number function defs(key: string, client: MCPClient, timeout?: number) { return listTools(key, client, timeout ?? DEFAULT_TIMEOUT).pipe( Effect.catch((err) => { - log.error("failed to get tools from client", { key, error: err }) return Effect.succeed(undefined) }), ) @@ -203,7 +196,6 @@ function fetchFromClient( return Effect.tryPromise({ try: () => listFn(client), catch: (e: any) => { - log.error(`failed to get ${label}`, { clientName, error: e.message }) return e }, }).pipe( @@ -329,9 +321,7 @@ export const layer = Layer.effect( redirectUri: oauthConfig?.redirectUri, }, { - onRedirect: async (url) => { - log.info("oauth redirect requested", { key, url: url.toString() }) - }, + onRedirect: async (url) => {}, }, auth, ) @@ -366,8 +356,6 @@ export const layer = Layer.effect( error instanceof UnauthorizedError || (authProvider && lastError.message.includes("OAuth")) if (isAuthError) { - log.info("mcp server requires authentication", { key, transport: name }) - if (lastError.message.includes("registration") || lastError.message.includes("client_id")) { lastStatus = { status: "needs_client_registration" as const, @@ -394,19 +382,14 @@ export const layer = Layer.effect( .pipe(Effect.ignore, Effect.as(undefined)) } } - - log.debug("transport connection failed", { - key, - transport: name, - url: mcp.url, - error: lastError.message, - }) lastStatus = { status: "failed" as const, error: lastError.message } return Effect.succeed(undefined) }), ) if (result) { - log.info("connected", { key, transport: result.transportName }) + yield* Effect.logInfo("connected").pipe( + Effect.annotateLogs({ service: "mcp", ...{ key, transport: result.transportName } }), + ) return { client: result.client as MCPClient | undefined, status: { status: "connected" } as Status } } // If this was an auth error, stop trying other transports @@ -436,9 +419,7 @@ export const layer = Layer.effect( ...mcp.environment, }, }) - transport.stderr?.on("data", (chunk: Buffer) => { - log.info(`mcp stderr: ${chunk.toString()}`, { key }) - }) + transport.stderr?.on("data", (chunk: Buffer) => {}) const connectTimeout = mcp.timeout ?? DEFAULT_TIMEOUT return yield* connectTransport(transport, connectTimeout).pipe( @@ -448,7 +429,6 @@ export const layer = Layer.effect( })), Effect.catch((error): Effect.Effect<{ client: MCPClient | undefined; status: Status }> => { const msg = error instanceof Error ? error.message : String(error) - log.error("local mcp startup failed", { key, command: mcp.command, cwd, error: msg }) return Effect.succeed({ client: undefined, status: { status: "failed", error: msg } }) }), ) @@ -456,11 +436,11 @@ export const layer = Layer.effect( const create = Effect.fn("MCP.create")(function* (key: string, mcp: ConfigMCP.Info) { if (mcp.enabled === false) { - log.info("mcp server disabled", { key }) + yield* Effect.logInfo("mcp server disabled").pipe(Effect.annotateLogs({ service: "mcp", ...{ key } })) return DISABLED_RESULT } - log.info("found", { key, type: mcp.type }) + yield* Effect.logInfo("found").pipe(Effect.annotateLogs({ service: "mcp", ...{ key, type: mcp.type } })) const { client: mcpClient, status } = mcp.type === "remote" @@ -477,7 +457,9 @@ export const layer = Layer.effect( return { status: { status: "failed", error: "Failed to get tools" } } satisfies CreateResult } - log.info("create() successfully created client", { key, toolCount: listed.length }) + yield* Effect.logInfo("create() successfully created client").pipe( + Effect.annotateLogs({ service: "mcp", ...{ key, toolCount: listed.length } }), + ) return { mcpClient, status, defs: listed } satisfies CreateResult }) const cfgSvc = yield* Config.Service @@ -508,7 +490,6 @@ export const layer = Layer.effect( function watch(s: State, name: string, client: MCPClient, bridge: EffectBridge.Shape, timeout?: number) { client.setNotificationHandler(ToolListChangedNotificationSchema, async () => { - log.info("tools list changed notification received", { server: name }) if (s.clients[name] !== client || s.status[name]?.status !== "connected") return const listed = await bridge.promise(defs(name, client, timeout)) @@ -537,7 +518,9 @@ export const layer = Layer.effect( ([key, mcp]) => Effect.gen(function* () { if (!isMcpConfigured(mcp)) { - log.error("Ignoring MCP config entry without type", { key }) + yield* Effect.logError("Ignoring MCP config entry without type").pipe( + Effect.annotateLogs({ service: "mcp", ...{ key } }), + ) return } @@ -688,7 +671,9 @@ export const layer = Layer.effect( const listed = s.defs[clientName] if (!listed) { - log.warn("missing cached tools for connected server", { clientName }) + yield* Effect.logWarning("missing cached tools for connected server").pipe( + Effect.annotateLogs({ service: "mcp", ...{ clientName } }), + ) return } @@ -734,13 +719,14 @@ export const layer = Layer.effect( const s = yield* InstanceState.get(state) const client = s.clients[clientName] if (!client) { - log.warn(`client not found for ${label}`, { clientName }) + yield* Effect.logWarning(`client not found for ${label}`).pipe( + Effect.annotateLogs({ service: "mcp", ...{ clientName } }), + ) return undefined } return yield* Effect.tryPromise({ try: () => fn(client), catch: (e: any) => { - log.error(`failed to ${label}`, { clientName, ...meta, error: e?.message }) return e }, }).pipe(Effect.orElseSucceed(() => undefined)) @@ -858,7 +844,9 @@ export const layer = Layer.effect( return yield* storeClient(s, mcpName, client, listed, mcpConfig.timeout) } - log.info("opening browser for oauth", { mcpName, url: result.authorizationUrl, state: result.oauthState }) + yield* Effect.logInfo("opening browser for oauth").pipe( + Effect.annotateLogs({ service: "mcp", ...{ mcpName, url: result.authorizationUrl, state: result.oauthState } }), + ) const callbackPromise = McpOAuthCallback.waitForCallback(result.oauthState, mcpName) @@ -879,7 +867,6 @@ export const layer = Layer.effect( }), ), Effect.catch(() => { - log.warn("failed to open browser, user must open URL manually", { mcpName }) return bus.publish(BrowserOpenFailed, { mcpName, url: result.authorizationUrl }).pipe(Effect.ignore) }), ) @@ -903,7 +890,6 @@ export const layer = Layer.effect( const result = yield* Effect.tryPromise({ try: () => transport.finishAuth(authorizationCode).then(() => true as const), catch: (error) => { - log.error("failed to finish oauth", { mcpName, error }) return error }, }).pipe(Effect.option) @@ -924,7 +910,7 @@ export const layer = Layer.effect( yield* auth.remove(mcpName) McpOAuthCallback.cancelPending(mcpName) pendingOAuthTransports.delete(mcpName) - log.info("removed oauth credentials", { mcpName }) + yield* Effect.logInfo("removed oauth credentials").pipe(Effect.annotateLogs({ service: "mcp", ...{ mcpName } })) }) const supportsOAuth = Effect.fn("MCP.supportsOAuth")(function* (mcpName: string) { diff --git a/packages/opencode/src/mcp/oauth-callback.ts b/packages/opencode/src/mcp/oauth-callback.ts index 62ac571eef1..e5ce0c3ab62 100644 --- a/packages/opencode/src/mcp/oauth-callback.ts +++ b/packages/opencode/src/mcp/oauth-callback.ts @@ -1,11 +1,6 @@ import { createConnection } from "net" import { createServer } from "http" -import * as EffectLogger from "@opencode-ai/core/effect/logger" -import { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH, parseRedirectUri } from "./oauth-provider" - -const log = EffectLogger.create({ service: "mcp.oauth-callback" }) - -// Current callback server configuration (may differ from defaults if custom redirectUri is used) +import { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH, parseRedirectUri } from "./oauth-provider" // Current callback server configuration (may differ from defaults if custom redirectUri is used) let currentPort = OAUTH_CALLBACK_PORT let currentPath = OAUTH_CALLBACK_PATH @@ -85,14 +80,9 @@ function handleRequest(req: import("http").IncomingMessage, res: import("http"). const code = url.searchParams.get("code") const state = url.searchParams.get("state") const error = url.searchParams.get("error") - const errorDescription = url.searchParams.get("error_description") - - log.info("received oauth callback", { hasCode: !!code, state, error }) - - // Enforce state parameter presence + const errorDescription = url.searchParams.get("error_description") // Enforce state parameter presence if (!state) { const errorMsg = "Missing required state parameter - potential CSRF attack" - log.error("oauth callback missing state parameter", { url: url.toString() }) res.writeHead(400, { "Content-Type": "text/html" }) res.end(HTML_ERROR(errorMsg)) return @@ -121,7 +111,6 @@ function handleRequest(req: import("http").IncomingMessage, res: import("http"). // Validate state parameter if (!pendingAuths.has(state)) { const errorMsg = "Invalid or expired state parameter - potential CSRF attack" - log.error("oauth callback with invalid state", { state, pendingStates: Array.from(pendingAuths.keys()) }) res.writeHead(400, { "Content-Type": "text/html" }) res.end(HTML_ERROR(errorMsg)) return @@ -144,7 +133,6 @@ export async function ensureRunning(redirectUri?: string): Promise { // If server is running on a different port/path, stop it first if (server && (currentPort !== port || currentPath !== path)) { - log.info("stopping oauth callback server to reconfigure", { oldPort: currentPort, newPort: port }) await stop() } @@ -152,7 +140,6 @@ export async function ensureRunning(redirectUri?: string): Promise { const running = await isPortInUse(port) if (running) { - log.info("oauth callback server already running on another instance", { port }) return } @@ -162,7 +149,6 @@ export async function ensureRunning(redirectUri?: string): Promise { server = createServer(handleRequest) await new Promise((resolve, reject) => { server!.listen(currentPort, () => { - log.info("oauth callback server started", { port: currentPort, path: currentPath }) resolve() }) server!.on("error", reject) @@ -214,7 +200,6 @@ export async function stop(): Promise { if (server) { await new Promise((resolve) => server!.close(() => resolve())) server = undefined - log.info("oauth callback server stopped") } for (const [_name, pending] of pendingAuths) { diff --git a/packages/opencode/src/mcp/oauth-provider.ts b/packages/opencode/src/mcp/oauth-provider.ts index c8a3470548e..b408cec8cf5 100644 --- a/packages/opencode/src/mcp/oauth-provider.ts +++ b/packages/opencode/src/mcp/oauth-provider.ts @@ -7,10 +7,6 @@ import type { } from "@modelcontextprotocol/sdk/shared/auth.js" import { Effect } from "effect" import { McpAuth } from "./auth" -import * as EffectLogger from "@opencode-ai/core/effect/logger" - -const log = EffectLogger.create({ service: "mcp.oauth" }) - const OAUTH_CALLBACK_PORT = 19876 const OAUTH_CALLBACK_PATH = "/mcp/oauth/callback" @@ -70,7 +66,6 @@ export class McpOAuthProvider implements OAuthClientProvider { if (entry?.clientInfo) { // Check if client secret has expired if (entry.clientInfo.clientSecretExpiresAt && entry.clientInfo.clientSecretExpiresAt < Date.now() / 1000) { - log.info("client secret expired, need to re-register", { mcpName: this.mcpName }) return undefined } return { @@ -96,10 +91,6 @@ export class McpOAuthProvider implements OAuthClientProvider { this.serverUrl, ), ) - log.info("saved dynamically registered client", { - mcpName: this.mcpName, - clientId: info.client_id, - }) } async tokens(): Promise { @@ -131,11 +122,9 @@ export class McpOAuthProvider implements OAuthClientProvider { this.serverUrl, ), ) - log.info("saved oauth tokens", { mcpName: this.mcpName }) } async redirectToAuthorization(authorizationUrl: URL): Promise { - log.info("redirecting to authorization", { mcpName: this.mcpName, url: authorizationUrl.toString() }) await this.callbacks.onRedirect(authorizationUrl) } @@ -173,7 +162,6 @@ export class McpOAuthProvider implements OAuthClientProvider { } async invalidateCredentials(type: "all" | "client" | "tokens"): Promise { - log.info("invalidating credentials", { mcpName: this.mcpName, type }) const entry = await Effect.runPromise(this.auth.get(this.mcpName)) if (!entry) { return diff --git a/packages/opencode/src/patch/index.ts b/packages/opencode/src/patch/index.ts index 6c7b553f879..20f953177fc 100644 --- a/packages/opencode/src/patch/index.ts +++ b/packages/opencode/src/patch/index.ts @@ -1,11 +1,7 @@ import { Effect, Schema } from "effect" import * as path from "path" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import * as Bom from "../util/bom" - -const log = EffectLogger.create({ service: "patch" }) - export const PatchSchema = Schema.Struct({ patchText: Schema.String.annotate({ description: "The full patch text that describes all changes to be made" }), }) @@ -530,14 +526,14 @@ export const applyHunksToFiles = Effect.fn("Patch.applyHunksToFiles")(function* case "add": { yield* fs.writeWithDirs(hunk.path, hunk.contents) added.push(hunk.path) - log.info(`Added file: ${hunk.path}`) + yield* Effect.logInfo(`Added file: ${hunk.path}`).pipe(Effect.annotateLogs({ service: "patch" })) break } case "delete": { yield* fs.remove(hunk.path) deleted.push(hunk.path) - log.info(`Deleted file: ${hunk.path}`) + yield* Effect.logInfo(`Deleted file: ${hunk.path}`).pipe(Effect.annotateLogs({ service: "patch" })) break } @@ -549,11 +545,13 @@ export const applyHunksToFiles = Effect.fn("Patch.applyHunksToFiles")(function* yield* fs.writeWithDirs(hunk.move_path, Bom.join(fileUpdate.content, fileUpdate.bom)) yield* fs.remove(hunk.path) modified.push(hunk.move_path) - log.info(`Moved file: ${hunk.path} -> ${hunk.move_path}`) + yield* Effect.logInfo(`Moved file: ${hunk.path} -> ${hunk.move_path}`).pipe( + Effect.annotateLogs({ service: "patch" }), + ) } else { yield* fs.writeWithDirs(hunk.path, Bom.join(fileUpdate.content, fileUpdate.bom)) modified.push(hunk.path) - log.info(`Updated file: ${hunk.path}`) + yield* Effect.logInfo(`Updated file: ${hunk.path}`).pipe(Effect.annotateLogs({ service: "patch" })) } break } diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index 6ea6e2a2cc6..e1b0163fd0e 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -7,15 +7,11 @@ import { MessageID, SessionID } from "@/session/schema" import { PermissionTable } from "@/session/session.sql" import { Database } from "@/storage/db" import { eq } from "drizzle-orm" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Wildcard } from "@opencode-ai/core/util/wildcard" import { Deferred, Effect, Layer, Schema, Context } from "effect" import os from "os" import { PermissionV2 } from "@opencode-ai/core/permission" import { PermissionID } from "./schema" - -const log = EffectLogger.create({ service: "permission" }) - export const Action = PermissionV2.Action.annotate({ identifier: "PermissionAction" }) export type Action = Schema.Schema.Type @@ -175,7 +171,9 @@ export const layer = Layer.effect( for (const pattern of request.patterns) { const rule = evaluate(request.permission, pattern, ruleset, approved) - log.info("evaluated", { permission: request.permission, pattern, action: rule }) + yield* Effect.logInfo("evaluated").pipe( + Effect.annotateLogs({ service: "permission", ...{ permission: request.permission, pattern, action: rule } }), + ) if (rule.action === "deny") { return yield* new DeniedError({ ruleset: ruleset.filter((rule) => Wildcard.match(request.permission, rule.permission)), @@ -197,7 +195,9 @@ export const layer = Layer.effect( always: request.always, tool: request.tool, } - log.info("asking", { id, permission: info.permission, patterns: info.patterns }) + yield* Effect.logInfo("asking").pipe( + Effect.annotateLogs({ service: "permission", ...{ id, permission: info.permission, patterns: info.patterns } }), + ) const deferred = yield* Deferred.make() pending.set(id, { info, deferred }) diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index 555146b6d21..2403a954e62 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -1,13 +1,9 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { InstallationVersion } from "@opencode-ai/core/installation/version" import { OAUTH_DUMMY_KEY } from "../auth" import os from "os" import { setTimeout as sleep } from "node:timers/promises" import { createServer } from "http" - -const log = EffectLogger.create({ service: "plugin.codex" }) - const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann" const ISSUER = "https://auth.openai.com" const CODEX_API_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses" @@ -323,7 +319,6 @@ async function startOAuthServer(): Promise<{ port: number; redirectUri: string } await new Promise((resolve, reject) => { oauthServer!.listen(OAUTH_PORT, () => { - log.info("codex oauth server started", { port: OAUTH_PORT }) resolve() }) oauthServer!.on("error", reject) @@ -334,9 +329,7 @@ async function startOAuthServer(): Promise<{ port: number; redirectUri: string } function stopOAuthServer() { if (oauthServer) { - oauthServer.close(() => { - log.info("codex oauth server stopped") - }) + oauthServer.close(() => {}) oauthServer = undefined } } @@ -444,7 +437,6 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug // Check if token needs refresh if (!currentAuth.access || currentAuth.expires < Date.now()) { if (!refreshPromise) { - log.info("refreshing codex access token") refreshPromise = refreshAccessToken(currentAuth.refresh, issuer) .then(async (tokens) => { const accountId = extractAccountId(tokens) || authWithAccount.accountId diff --git a/packages/opencode/src/plugin/digitalocean.ts b/packages/opencode/src/plugin/digitalocean.ts index 8160f2f5d0c..aed0728d720 100644 --- a/packages/opencode/src/plugin/digitalocean.ts +++ b/packages/opencode/src/plugin/digitalocean.ts @@ -1,12 +1,9 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin" import type { Model } from "@opencode-ai/sdk/v2" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { InstallationVersion } from "@opencode-ai/core/installation/version" import { createServer } from "http" import open from "open" - -const log = EffectLogger.create({ service: "plugin.digitalocean" }) - +import { Effect } from "effect" const DO_OAUTH_CLIENT_ID = "b1a6c5158156caac821fd1b30253ca8acb52454a48fa744420e41889cb589f82" const DO_AUTHORIZE_URL = "https://cloud.digitalocean.com/v1/oauth/authorize" const DO_API_BASE = "https://api.digitalocean.com" @@ -188,7 +185,6 @@ async function startOAuthServer(): Promise { await new Promise((resolve, reject) => { oauthServer!.listen(OAUTH_PORT, () => { - log.info("digitalocean oauth server started", { port: OAUTH_PORT }) resolve() }) oauthServer!.on("error", reject) @@ -197,7 +193,9 @@ async function startOAuthServer(): Promise { function stopOAuthServer() { if (!oauthServer) return - oauthServer.close(() => log.info("digitalocean oauth server stopped")) + oauthServer.close(() => + Effect.logInfo("digitalocean oauth server stopped").pipe(Effect.annotateLogs({ service: "plugin.digitalocean" })), + ) oauthServer = undefined } @@ -315,11 +313,13 @@ export async function DigitalOceanAuthPlugin(input: PluginInput): Promise path: { id: "digitalocean" }, body: { type: "api", key: ctx.auth.key, metadata: updated }, }) - .catch((err) => log.warn("failed to persist refreshed routers", { error: err })) + .catch((err) => + Effect.logWarning("failed to persist refreshed routers").pipe( + Effect.annotateLogs({ service: "plugin.digitalocean", ...{ error: err } }), + ), + ) } else if (result.status === 401 || result.status === 403) { - log.warn("digitalocean oauth bearer rejected; using cached routers", { status: result.status }) } else if (result.status !== 0) { - log.warn("digitalocean router refresh failed", { status: result.status }) } } @@ -355,7 +355,6 @@ export async function DigitalOceanAuthPlugin(input: PluginInput): Promise const routerResult = await listRouters(tokens.access_token) const routers = routerResult.ok ? routerResult.routers : [] if (!routerResult.ok) { - log.warn("digitalocean initial router fetch failed", { status: routerResult.status }) } return { type: "success" as const, @@ -372,7 +371,6 @@ export async function DigitalOceanAuthPlugin(input: PluginInput): Promise }, } } catch (err) { - log.error("digitalocean oauth callback failed", { error: err }) return { type: "failed" as const } } finally { stopOAuthServer() diff --git a/packages/opencode/src/plugin/github-copilot/copilot.ts b/packages/opencode/src/plugin/github-copilot/copilot.ts index 92e451f4daf..e004a7a122c 100644 --- a/packages/opencode/src/plugin/github-copilot/copilot.ts +++ b/packages/opencode/src/plugin/github-copilot/copilot.ts @@ -2,13 +2,9 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin" import type { Model } from "@opencode-ai/sdk/v2" import { InstallationVersion } from "@opencode-ai/core/installation/version" import { iife } from "@/util/iife" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { setTimeout as sleep } from "node:timers/promises" import { CopilotModels } from "./models" import { MessageV2 } from "@/session/message-v2" - -const log = EffectLogger.create({ service: "plugin.copilot" }) - const CLIENT_ID = "Ov23li8tweQw6odWQebz" // Add a small safety buffer when polling to avoid hitting the server // slightly too early due to clock skew / timer drift. @@ -74,7 +70,6 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { }, provider.models, ).catch((error) => { - log.error("failed to fetch copilot models", { error }) return Object.fromEntries( Object.entries(provider.models).map(([id, model]) => [id, fix(model, base(auth.enterpriseUrl))]), ) diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 88c65873ba3..f4aa1ed7b9b 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -7,7 +7,6 @@ import type { } from "@opencode-ai/plugin" import { Config } from "@/config/config" import { Bus } from "../bus" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { createOpencodeClient } from "@opencode-ai/sdk" import { ServerAuth } from "@/server/auth" import { CodexAuthPlugin } from "./codex" @@ -29,9 +28,6 @@ import { parsePluginSpecifier, readPluginId, readV1Plugin, resolvePluginId } fro import { registerAdapter } from "@/control-plane/adapters" import type { WorkspaceAdapter } from "@/control-plane/types" import { RuntimeFlags } from "@/effect/runtime-flags" - -const log = EffectLogger.create({ service: "plugin" }) - type State = { hooks: Hooks[] } @@ -152,19 +148,21 @@ export const layer = Layer.effect( } for (const plugin of flags.disableDefaultPlugins ? [] : INTERNAL_PLUGINS) { - log.info("loading internal plugin", { name: plugin.name }) + yield* Effect.logInfo("loading internal plugin").pipe( + Effect.annotateLogs({ service: "plugin", ...{ name: plugin.name } }), + ) const init = yield* Effect.tryPromise({ try: () => plugin(input), - catch: (err) => { - log.error("failed to load internal plugin", { name: plugin.name, error: err }) - }, + catch: (err) => {}, }).pipe(Effect.option) if (init._tag === "Some") hooks.push(init.value) } const plugins = flags.pure ? [] : (cfg.plugin_origins ?? []) if (flags.pure && cfg.plugin_origins?.length) { - log.info("skipping external plugins in pure mode", { count: cfg.plugin_origins.length }) + yield* Effect.logInfo("skipping external plugins in pure mode").pipe( + Effect.annotateLogs({ service: "plugin", ...{ count: cfg.plugin_origins.length } }), + ) } if (plugins.length) yield* config.waitForDependencies() @@ -173,12 +171,8 @@ export const layer = Layer.effect( items: plugins, kind: "server", report: { - start(candidate) { - log.info("loading plugin", { path: candidate.plan.spec }) - }, - missing(candidate, _retry, message) { - log.warn("plugin has no server entrypoint", { path: candidate.plan.spec, message }) - }, + start(candidate) {}, + missing(candidate, _retry, message) {}, error(candidate, _retry, stage, error, resolved) { const spec = candidate.plan.spec const cause = error instanceof Error ? (error.cause ?? error) : error @@ -186,24 +180,19 @@ export const layer = Layer.effect( if (stage === "install") { const parsed = parsePluginSpecifier(spec) - log.error("failed to install plugin", { pkg: parsed.pkg, version: parsed.version, error: message }) publishPluginError(`Failed to install plugin ${parsed.pkg}@${parsed.version}: ${message}`) return } if (stage === "compatibility") { - log.warn("plugin incompatible", { path: spec, error: message }) publishPluginError(`Plugin ${spec} skipped: ${message}`) return } if (stage === "entry") { - log.error("failed to resolve plugin server entry", { path: spec, error: message }) publishPluginError(`Failed to load plugin ${spec}: ${message}`) return } - - log.error("failed to load plugin", { path: spec, target: resolved?.entry, error: message }) publishPluginError(`Failed to load plugin ${spec}: ${message}`) }, }, @@ -218,7 +207,6 @@ export const layer = Layer.effect( try: () => applyPlugin(load, input, hooks), catch: (err) => { const message = errorMessage(err) - log.error("failed to load plugin", { path: load.spec, error: message }) return message }, }).pipe( @@ -238,9 +226,7 @@ export const layer = Layer.effect( for (const hook of hooks) { yield* Effect.tryPromise({ try: () => Promise.resolve((hook as any).config?.(cfg)), - catch: (err) => { - log.error("plugin config hook failed", { error: err }) - }, + catch: (err) => {}, }).pipe(Effect.ignore) } diff --git a/packages/opencode/src/plugin/xai.ts b/packages/opencode/src/plugin/xai.ts index c651d2f8fc0..1fd62029083 100644 --- a/packages/opencode/src/plugin/xai.ts +++ b/packages/opencode/src/plugin/xai.ts @@ -1,11 +1,8 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { OAUTH_DUMMY_KEY } from "../auth" import { createServer } from "http" import { InstallationVersion } from "@opencode-ai/core/installation/version" - -const log = EffectLogger.create({ service: "plugin.xai" }) - +import { Effect } from "effect" // Public Grok-CLI OAuth client. xAI's auth server rejects loopback OAuth from // non-allowlisted clients, so we reuse the Grok-CLI client_id that xAI ships // for desktop OAuth flows. Source of truth: hermes-agent PR #26534. @@ -510,8 +507,7 @@ async function startOAuthServer(): Promise<{ port: number; redirectUri: string } // behavior and crash the entire opencode process. Matches the silent- // swallow behavior the Codex plugin gets from its permanent // `oauthServer!.on("error", reject)`. - server.on("error", (err) => log.warn("xai oauth server error", { error: err })) - log.info("xai oauth server started", { host: OAUTH_HOST, port: OAUTH_PORT }) + server.on("error", () => {}) resolve() }) oauthServer = server @@ -522,7 +518,9 @@ async function startOAuthServer(): Promise<{ port: number; redirectUri: string } function stopOAuthServer() { if (oauthServer) { - oauthServer.close(() => log.info("xai oauth server stopped")) + oauthServer.close(() => + Effect.logInfo("xai oauth server stopped").pipe(Effect.annotateLogs({ service: "plugin.xai" })), + ) oauthServer = undefined } } @@ -607,7 +605,6 @@ export async function XaiAuthPlugin(input: PluginInput, options: XaiAuthPluginOp if (expiresSoon) { if (!refreshPromise) { const refreshToken = currentAuth.refresh - log.info("refreshing xai access token") refreshPromise = refreshAccessToken(refreshToken, options) .then(async (tokens) => { const refreshedExpires = Date.now() + (tokens.expires_in ?? 3600) * 1000 @@ -627,7 +624,11 @@ export async function XaiAuthPlugin(input: PluginInput, options: XaiAuthPluginOp expires: refreshedExpires, }, }) - .catch((err) => log.warn("failed to persist refreshed xai tokens", { error: err })) + .catch((err) => + Effect.logWarning("failed to persist refreshed xai tokens").pipe( + Effect.annotateLogs({ service: "plugin.xai", ...{ error: err } }), + ), + ) return { access: tokens.access_token, refresh: refreshedRefresh, expires: refreshedExpires } }) .finally(() => { @@ -688,7 +689,6 @@ export async function XaiAuthPlugin(input: PluginInput, options: XaiAuthPluginOp expires: Date.now() + (tokens.expires_in ?? 3600) * 1000, } } catch (err) { - log.error("xai oauth callback failed", { error: err }) return { type: "failed" as const } } finally { stopOAuthServer() @@ -725,7 +725,6 @@ export async function XaiAuthPlugin(input: PluginInput, options: XaiAuthPluginOp expires: Date.now() + (tokens.expires_in ?? 3600) * 1000, } } catch (err) { - log.error("xai device code callback failed", { error: err }) return { type: "failed" as const } } }, diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index 73c5e586a8d..75ca2c3454c 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -3,7 +3,6 @@ import { Database } from "@/storage/db" import { ProjectTable } from "./project.sql" import { PermissionTable, SessionTable } from "../session/session.sql" import { WorkspaceTable } from "../control-plane/workspace.sql" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Flag } from "@opencode-ai/core/flag/flag" import { BusEvent } from "@/bus/bus-event" import { GlobalBus } from "@/bus/global" @@ -21,9 +20,6 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AbsolutePath, NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema" import { serviceUse } from "@opencode-ai/core/effect/service-use" import { RuntimeFlags } from "@/effect/runtime-flags" - -const log = EffectLogger.create({ service: "project" }) - const ProjectVcs = Schema.Literal("git") const ProjectIcon = Schema.Struct({ @@ -233,7 +229,7 @@ export const layer = Layer.effect( }) const fromDirectory = Effect.fn("Project.fromDirectory")(function* (directory: string) { - log.info("fromDirectory", { directory }) + yield* Effect.logInfo("fromDirectory").pipe(Effect.annotateLogs({ service: "project", ...{ directory } })) const data = yield* projectV2.resolve(AbsolutePath.make(directory)) const worktree = data.id === ProjectV2.ID.make("global") && !data.vcs ? "/" : data.directory diff --git a/packages/opencode/src/project/vcs.ts b/packages/opencode/src/project/vcs.ts index 31f9325be98..6081b0b72b2 100644 --- a/packages/opencode/src/project/vcs.ts +++ b/packages/opencode/src/project/vcs.ts @@ -5,9 +5,6 @@ import { BusEvent } from "@/bus/bus-event" import { InstanceState } from "@/effect/instance-state" import { FileWatcher } from "@/file/watcher" import { Git } from "@/git" -import * as EffectLogger from "@opencode-ai/core/effect/logger" - -const log = EffectLogger.create({ service: "vcs" }) const PATCH_CONTEXT_LINES = 2_147_483_647 const MAX_PATCH_BYTES = 10_000_000 const MAX_TOTAL_PATCH_BYTES = 10_000_000 @@ -107,7 +104,10 @@ const batchPatches = Effect.fnUntraced(function* ( context: options?.context ?? PATCH_CONTEXT_LINES, maxOutputBytes: MAX_TOTAL_PATCH_BYTES, }) - if (result.truncated) log.warn("batched patch exceeded byte limit", { max: MAX_TOTAL_PATCH_BYTES }) + if (result.truncated) + yield* Effect.logWarning("batched patch exceeded byte limit").pipe( + Effect.annotateLogs({ service: "vcs", ...{ max: MAX_TOTAL_PATCH_BYTES } }), + ) return { patches: splitGitPatch(result).reduce((acc, patch, index) => { @@ -139,13 +139,15 @@ const nativePatch = Effect.fnUntraced(function* ( }) if (!result.truncated && result.text) return result.text - if (result.truncated) log.warn("patch exceeded byte limit", { file: item.file, max: MAX_PATCH_BYTES }) + if (result.truncated) + yield* Effect.logWarning("patch exceeded byte limit").pipe( + Effect.annotateLogs({ service: "vcs", ...{ file: item.file, max: MAX_PATCH_BYTES } }), + ) return emptyPatch(item.file) }) const totalPatch = (file: string, patch: string, total: number) => { if (total + Buffer.byteLength(patch) <= MAX_TOTAL_PATCH_BYTES) return { patch, capped: false } - log.warn("total patch budget exceeded", { file, max: MAX_TOTAL_PATCH_BYTES }) return { patch: emptyPatch(file), capped: true } } @@ -325,7 +327,9 @@ export const layer: Layer.Layer = Lay concurrency: 2, }) const value = { current, root } - log.info("initialized", { branch: value.current, default_branch: value.root?.name }) + yield* Effect.logInfo("initialized").pipe( + Effect.annotateLogs({ service: "vcs", ...{ branch: value.current, default_branch: value.root?.name } }), + ) yield* (yield* bus.subscribe(FileWatcher.Event.Updated)).pipe( Stream.filter((evt) => evt.properties.file.endsWith("HEAD")), @@ -333,7 +337,9 @@ export const layer: Layer.Layer = Lay Effect.gen(function* () { const next = yield* get() if (next !== value.current) { - log.info("branch changed", { from: value.current, to: next }) + yield* Effect.logInfo("branch changed").pipe( + Effect.annotateLogs({ service: "vcs", ...{ from: value.current, to: next } }), + ) value.current = next yield* bus.publish(Event.BranchUpdated, { branch: next }) } diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 652bda518aa..395c66de385 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -3,7 +3,6 @@ import fuzzysort from "fuzzysort" import { Config } from "@/config/config" import { mapValues, mergeDeep, omit, pickBy, sortBy } from "remeda" import { NoSuchModelError, type Provider as SDK } from "ai" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Npm } from "@opencode-ai/core/npm" import { Hash } from "@opencode-ai/core/util/hash" import { Plugin } from "../plugin" @@ -28,9 +27,6 @@ import * as ProviderTransform from "./transform" import { ModelID, ProviderID } from "./schema" import { ModelStatus } from "./model-status" import { RuntimeFlags } from "@/effect/runtime-flags" - -const log = EffectLogger.create({ service: "provider" }) - function shouldUseCopilotResponsesApi(modelID: string): boolean { const match = /^gpt-(\d+)/.exec(modelID) if (!match) return false @@ -625,7 +621,6 @@ function custom(dep: CustomDep): Record { }, async discoverModels(): Promise> { if (!apiKey) { - log.info("gitlab model discovery skipped: no apiKey") return {} } @@ -633,19 +628,9 @@ function custom(dep: CustomDep): Record { const token = apiKey const getHeaders = (): Record => auth?.type === "api" ? { "PRIVATE-TOKEN": token } : { Authorization: `Bearer ${token}` } - - log.info("gitlab model discovery starting", { instanceUrl }) const result = await discoverWorkflowModels({ instanceUrl, getHeaders }, { workingDirectory: directory }) if (!result.models.length) { - log.info("gitlab model discovery skipped: no models found", { - project: result.project - ? { - id: result.project.id, - path: result.project.pathWithNamespace, - } - : null, - }) return {} } @@ -693,14 +678,8 @@ function custom(dep: CustomDep): Record { } } } - - log.info("gitlab model discovery complete", { - count: Object.keys(models).length, - models: Object.keys(models), - }) return models } catch (e) { - log.warn("gitlab model discovery failed", { error: e }) return {} } }, @@ -1200,7 +1179,6 @@ export const layer = Layer.effect( const state = yield* InstanceState.make(() => Effect.gen(function* () { - using _ = log.time("state") const bridge = yield* EffectBridge.make() const cfg = yield* config.get() const modelsDev = yield* modelsDevSvc.get() @@ -1226,7 +1204,7 @@ export const layer = Layer.effect( get: (key: string) => env.get(key), } - log.info("init") + yield* Effect.logInfo("init").pipe(Effect.annotateLogs({ service: "provider" })) function mergeProvider(providerID: ProviderID, provider: Partial) { const existing = providers[providerID] @@ -1428,7 +1406,9 @@ export const layer = Layer.effect( if (disabled.has(providerID)) continue const data = database[providerID] if (!data) { - log.error("Provider does not exist in model list " + providerID) + yield* Effect.logError("Provider does not exist in model list " + providerID).pipe( + Effect.annotateLogs({ service: "provider" }), + ) continue } const result = yield* fn(data) @@ -1462,9 +1442,7 @@ export const layer = Layer.effect( providers[gitlab].models[modelID] = model } } - } catch (e) { - log.warn("state discovery error", { id: "gitlab", error: e }) - } + } catch (e) {} }) } @@ -1516,7 +1494,7 @@ export const layer = Layer.effect( continue } - log.info("found", { providerID }) + yield* Effect.logInfo("found").pipe(Effect.annotateLogs({ service: "provider", ...{ providerID } })) } return { @@ -1534,9 +1512,6 @@ export const layer = Layer.effect( async function resolveSDK(model: Model, s: State, envs: Record) { try { - using _ = log.time("getSDK", { - providerID: model.providerID, - }) const provider = s.providers[model.providerID] const options = { ...provider.options } @@ -1647,10 +1622,6 @@ export const layer = Layer.effect( const bundledLoader = BUNDLED_PROVIDERS[model.api.npm] if (bundledLoader) { - log.info("using bundled provider", { - providerID: model.providerID, - pkg: model.api.npm, - }) const factory = await bundledLoader() const loaded = factory({ name: model.providerID, @@ -1666,7 +1637,6 @@ export const layer = Layer.effect( if (!item.entrypoint) throw new Error(`Package ${model.api.npm} has no import entrypoint`) installedPath = item.entrypoint } else { - log.info("loading local provider", { pkg: model.api.npm }) installedPath = model.api.npm } diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index 49f55e8c567..acfea851109 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -7,13 +7,9 @@ import { lazy } from "@opencode-ai/core/util/lazy" import { Plugin } from "@/plugin" import { Shell } from "@/shell/shell" import type { Proc } from "#pty" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { PtyID } from "./schema" import { Effect, Layer, Context, Schema, Types } from "effect" import { NonNegativeInt, PositiveInt } from "@opencode-ai/core/schema" - -const log = EffectLogger.create({ service: "pty" }) - const BUFFER_LIMIT = 1024 * 1024 * 2 const BUFFER_CHUNK = 64 * 1024 const encoder = new TextEncoder() @@ -167,7 +163,7 @@ export const layer = Layer.effect( const s = yield* InstanceState.get(state) const session = yield* requireSession(id) s.sessions.delete(id) - log.info("removing session", { id }) + yield* Effect.logInfo("removing session").pipe(Effect.annotateLogs({ service: "pty", ...{ id } })) teardown(session) yield* bus.publish(Event.Deleted, { id: session.info.id }) }) @@ -207,7 +203,9 @@ export const layer = Layer.effect( env.LC_CTYPE = "C.UTF-8" env.LANG = "C.UTF-8" } - log.info("creating session", { id, cmd: command, args, cwd }) + yield* Effect.logInfo("creating session").pipe( + Effect.annotateLogs({ service: "pty", ...{ id, cmd: command, args, cwd } }), + ) const { spawn } = yield* Effect.promise(() => pty()) const proc = yield* Effect.sync(() => @@ -262,9 +260,7 @@ export const layer = Layer.effect( session.bufferCursor += excess }) proc.onExit(({ exitCode }) => { - if (session.info.status === "exited") return - log.info("session exited", { id, exitCode }) - session.info.status = "exited" + if (session.info.status === "exited") return (session.info.status = "exited") bridge.fork(bus.publish(Event.Exited, { id, exitCode })) bridge.fork(remove(id)) }) @@ -306,7 +302,7 @@ export const layer = Layer.effect( }), ), ) - log.info("client connected to session", { id }) + yield* Effect.logInfo("client connected to session").pipe(Effect.annotateLogs({ service: "pty", ...{ id } })) const sub = sock(ws) session.subscribers.delete(sub) @@ -354,7 +350,6 @@ export const layer = Layer.effect( session.process.write(typeof message === "string" ? message : new TextDecoder().decode(message)) }, onClose: () => { - log.info("client disconnected from session", { id }) cleanup() }, } diff --git a/packages/opencode/src/question/index.ts b/packages/opencode/src/question/index.ts index 8ee45aebb44..4463b26b657 100644 --- a/packages/opencode/src/question/index.ts +++ b/packages/opencode/src/question/index.ts @@ -3,11 +3,7 @@ import { Bus } from "@/bus" import { BusEvent } from "@/bus/bus-event" import { InstanceState } from "@/effect/instance-state" import { SessionID, MessageID } from "@/session/schema" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { QuestionID } from "./schema" - -const log = EffectLogger.create({ service: "question" }) - // Schemas — these are pure data; nothing checks class identity (see PR // description) so they're plain `Schema.Struct` + type alias. That lets // `Question.ask` and other internal sites trust the type contract without a @@ -159,7 +155,9 @@ export const layer = Layer.effect( }) { const pending = (yield* InstanceState.get(state)).pending const id = QuestionID.ascending() - log.info("asking", { id, questions: input.questions.length }) + yield* Effect.logInfo("asking").pipe( + Effect.annotateLogs({ service: "question", ...{ id, questions: input.questions.length } }), + ) const deferred = yield* Deferred.make, RejectedError>() const info: Request = { @@ -186,11 +184,15 @@ export const layer = Layer.effect( const pending = (yield* InstanceState.get(state)).pending const existing = pending.get(input.requestID) if (!existing) { - log.warn("reply for unknown request", { requestID: input.requestID }) + yield* Effect.logWarning("reply for unknown request").pipe( + Effect.annotateLogs({ service: "question", ...{ requestID: input.requestID } }), + ) return yield* new NotFoundError({ requestID: input.requestID }) } pending.delete(input.requestID) - log.info("replied", { requestID: input.requestID, answers: input.answers }) + yield* Effect.logInfo("replied").pipe( + Effect.annotateLogs({ service: "question", ...{ requestID: input.requestID, answers: input.answers } }), + ) yield* bus.publish(Event.Replied, { sessionID: existing.info.sessionID, requestID: existing.info.id, @@ -203,11 +205,13 @@ export const layer = Layer.effect( const pending = (yield* InstanceState.get(state)).pending const existing = pending.get(requestID) if (!existing) { - log.warn("reject for unknown request", { requestID }) + yield* Effect.logWarning("reject for unknown request").pipe( + Effect.annotateLogs({ service: "question", ...{ requestID } }), + ) return yield* new NotFoundError({ requestID }) } pending.delete(requestID) - log.info("rejected", { requestID }) + yield* Effect.logInfo("rejected").pipe(Effect.annotateLogs({ service: "question", ...{ requestID } })) yield* bus.publish(Event.Rejected, { sessionID: existing.info.sessionID, requestID: existing.info.id, diff --git a/packages/opencode/src/server/global-lifecycle.ts b/packages/opencode/src/server/global-lifecycle.ts index abe38022d6c..9d4c3f94cf0 100644 --- a/packages/opencode/src/server/global-lifecycle.ts +++ b/packages/opencode/src/server/global-lifecycle.ts @@ -1,11 +1,7 @@ import { GlobalBus } from "@/bus/global" import { InstanceStore } from "@/project/instance-store" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Effect } from "effect" import { Event } from "./event" - -const log = EffectLogger.create({ service: "server" }) - export const emitGlobalDisposed = Effect.sync(() => GlobalBus.emit("event", { directory: "global", @@ -21,13 +17,7 @@ export const disposeAllInstancesAndEmitGlobalDisposed = Effect.fn("Server.dispos const store = yield* InstanceStore.Service yield* Effect.gen(function* () { yield* options?.swallowErrors - ? store.disposeAll().pipe( - Effect.catchCause((cause) => - Effect.sync(() => { - log.warn("global disposal failed", { cause }) - }), - ), - ) + ? store.disposeAll().pipe(Effect.catchCause((cause) => Effect.sync(() => {}))) : store.disposeAll() yield* emitGlobalDisposed }).pipe(Effect.uninterruptible) diff --git a/packages/opencode/src/server/mdns.ts b/packages/opencode/src/server/mdns.ts index 2e6e2bb957a..678ed85ac68 100644 --- a/packages/opencode/src/server/mdns.ts +++ b/packages/opencode/src/server/mdns.ts @@ -1,8 +1,4 @@ -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Bonjour } from "bonjour-service" - -const log = EffectLogger.create({ service: "mdns" }) - let bonjour: Bonjour | undefined let currentPort: number | undefined @@ -22,17 +18,12 @@ export function publish(port: number, domain?: string) { txt: { path: "/" }, }) - service.on("up", () => { - log.info("mDNS service published", { name, port }) - }) + service.on("up", () => {}) - service.on("error", (err) => { - log.error("mDNS service error", { error: err }) - }) + service.on("error", (err) => {}) currentPort = port } catch (err) { - log.error("mDNS publish failed", { error: err }) if (bonjour) { try { bonjour.destroy() @@ -48,12 +39,9 @@ export function unpublish() { try { bonjour.unpublishAll() bonjour.destroy() - } catch (err) { - log.error("mDNS unpublish failed", { error: err }) - } + } catch (err) {} bonjour = undefined currentPort = undefined - log.info("mDNS service unpublished") } } diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/control.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/control.ts index eade360b40a..32ce1e8db9e 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/control.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/control.ts @@ -1,6 +1,5 @@ import { Auth } from "@/auth" import { ProviderID } from "@/provider/schema" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Effect } from "effect" import { HttpApiBuilder } from "effect/unstable/httpapi" import { RootHttpApi } from "../api" @@ -24,8 +23,13 @@ export const controlHandlers = HttpApiBuilder.group(RootHttpApi, "control", (han }) const log = Effect.fn("ControlHttpApi.log")(function* (ctx: { payload: typeof LogInput.Type }) { - const logger = EffectLogger.create({ service: ctx.payload.service }) - logger[ctx.payload.level](ctx.payload.message, ctx.payload.extra) + const entry = (() => { + if (ctx.payload.level === "debug") return Effect.logDebug(ctx.payload.message) + if (ctx.payload.level === "warn") return Effect.logWarning(ctx.payload.message) + if (ctx.payload.level === "error") return Effect.logError(ctx.payload.message) + return Effect.logInfo(ctx.payload.message) + })() + yield* entry.pipe(Effect.annotateLogs({ service: ctx.payload.service, ...ctx.payload.extra })) return true }) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/event.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/event.ts index ad67e528707..cb25e8081db 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/event.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/event.ts @@ -1,14 +1,10 @@ import { Bus } from "@/bus" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Effect } from "effect" import * as Stream from "effect/Stream" import { HttpServerResponse } from "effect/unstable/http" import { HttpApiBuilder } from "effect/unstable/httpapi" import * as Sse from "effect/unstable/encoding/Sse" import { EventApi } from "../groups/event" - -const log = EffectLogger.create({ service: "server" }) - function eventData(data: unknown): Sse.Event { return { _tag: "Event", @@ -32,14 +28,14 @@ function eventResponse(bus: Bus.Interface) { Stream.map(() => ({ id: Bus.createID(), type: "server.heartbeat", properties: {} })), ) - log.info("event connected") + yield* Effect.logInfo("event connected").pipe(Effect.annotateLogs({ service: "server" })) return HttpServerResponse.stream( Stream.make({ id: Bus.createID(), type: "server.connected", properties: {} }).pipe( Stream.concat(events.pipe(Stream.merge(heartbeat, { haltStrategy: "left" }))), Stream.map(eventData), Stream.pipeThroughChannel(Sse.encode()), Stream.encodeText, - Stream.ensuring(Effect.sync(() => log.info("event disconnected"))), + Stream.ensuring(Effect.logInfo("event disconnected").pipe(Effect.annotateLogs({ service: "server" }))), ), { contentType: "text/event-stream", diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/global.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/global.ts index cb1c6116591..b8ae535c8a8 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/global.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/global.ts @@ -5,7 +5,6 @@ import { Bus } from "@/bus" import { Installation } from "@/installation" import { disposeAllInstancesAndEmitGlobalDisposed } from "@/server/global-lifecycle" import { InstallationVersion } from "@opencode-ai/core/installation/version" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Effect, Queue, Schema } from "effect" import * as Stream from "effect/Stream" import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http" @@ -13,9 +12,6 @@ import { HttpApiBuilder } from "effect/unstable/httpapi" import * as Sse from "effect/unstable/encoding/Sse" import { RootHttpApi } from "../api" import { GlobalUpgradeInput } from "../groups/global" - -const log = EffectLogger.create({ service: "server" }) - function eventData(data: unknown): Sse.Event { return { _tag: "Event", @@ -34,7 +30,6 @@ function parseBody(body: string) { } function eventResponse() { - log.info("global event connected") const events = Stream.callback((queue) => { const handler = (event: GlobalBusEvent) => Queue.offerUnsafe(queue, event) return Effect.acquireRelease( @@ -53,7 +48,7 @@ function eventResponse() { Stream.map(eventData), Stream.pipeThroughChannel(Sse.encode()), Stream.encodeText, - Stream.ensuring(Effect.sync(() => log.info("global event disconnected"))), + Stream.ensuring(Effect.logInfo("global event disconnected").pipe(Effect.annotateLogs({ service: "server" }))), ), { contentType: "text/event-stream", diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts index 9b4df97f27b..0818a55b1a9 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts @@ -14,10 +14,6 @@ import { Effect, Scope } from "effect" import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi" import { InstanceHttpApi } from "../api" import { HistoryPayload, ReplayPayload, SessionPayload } from "../groups/sync" -import * as EffectLogger from "@opencode-ai/core/effect/logger" - -const log = EffectLogger.create({ service: "server.sync" }) - export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handlers) => Effect.gen(function* () { const workspace = yield* Workspace.Service @@ -40,20 +36,30 @@ export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handl data: { ...event.data }, })) const source = events[0].aggregateID - log.info("sync replay requested", { - sessionID: source, - events: events.length, - first: events[0]?.seq, - last: events.at(-1)?.seq, - directory: ctx.payload.directory, - }) + yield* Effect.logInfo("sync replay requested").pipe( + Effect.annotateLogs({ + service: "server.sync", + ...{ + sessionID: source, + events: events.length, + first: events[0]?.seq, + last: events.at(-1)?.seq, + directory: ctx.payload.directory, + }, + }), + ) yield* sync.replayAll(events) - log.info("sync replay complete", { - sessionID: source, - events: events.length, - first: events[0]?.seq, - last: events.at(-1)?.seq, - }) + yield* Effect.logInfo("sync replay complete").pipe( + Effect.annotateLogs({ + service: "server.sync", + ...{ + sessionID: source, + events: events.length, + first: events[0]?.seq, + last: events.at(-1)?.seq, + }, + }), + ) return { sessionID: source } }) @@ -68,10 +74,15 @@ export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handl }, }) - log.info("sync session stolen", { - sessionID: ctx.payload.sessionID, - workspaceID, - }) + yield* Effect.logInfo("sync session stolen").pipe( + Effect.annotateLogs({ + service: "server.sync", + ...{ + sessionID: ctx.payload.sessionID, + workspaceID, + }, + }), + ) return { sessionID: ctx.payload.sessionID } }) diff --git a/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts b/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts index b7192fcf735..bc35764b809 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts @@ -1,12 +1,8 @@ import { EffectBridge } from "@/effect/bridge" import type { InstanceContext } from "@/project/instance-context" import { InstanceStore } from "@/project/instance-store" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Effect } from "effect" import { HttpEffect, HttpMiddleware, HttpServerRequest } from "effect/unstable/http" - -const log = EffectLogger.create({ service: "server" }) - type MarkedInstance = { ctx: InstanceContext store: InstanceStore.Interface @@ -51,7 +47,11 @@ export const disposeMiddleware: HttpMiddleware.HttpMiddleware = (effect) => if (!marked) return response disposeAfterResponse.delete(request.source) yield* Effect.uninterruptible(marked.bridge.run(marked.store.dispose(marked.ctx))).pipe( - Effect.catchCause((cause) => Effect.sync(() => log.warn("instance disposal failed", { cause }))), + Effect.catchCause((cause) => + Effect.sync(() => + Effect.logWarning("instance disposal failed").pipe(Effect.annotateLogs({ service: "server", ...{ cause } })), + ), + ), ) return response }) diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts index 034be4864af..fc83c4029bb 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts @@ -1,10 +1,6 @@ import { NamedError } from "@opencode-ai/core/util/error" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Cause, Effect } from "effect" import { HttpRouter, HttpServerError, HttpServerRespondable, HttpServerResponse } from "effect/unstable/http" - -const log = EffectLogger.create({ service: "server" }) - // Keep typed HttpApi failures on their declared error path; this boundary only replaces defect-only empty 500s. export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) => effect.pipe( @@ -19,9 +15,6 @@ export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) const error = defect.defect const ref = `err_${crypto.randomUUID().slice(0, 8)}` - - log.error("failed", { ref, error, cause: Cause.pretty(cause) }) - return Effect.succeed( HttpServerResponse.jsonUnsafe( new NamedError.Unknown({ diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/schema-error.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/schema-error.ts index 7769a697419..1fe02062ac5 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/schema-error.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/schema-error.ts @@ -1,11 +1,7 @@ import { Effect } from "effect" import { HttpServerResponse } from "effect/unstable/http" import { HttpApiMiddleware } from "effect/unstable/httpapi" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { InvalidRequestError } from "../errors" - -const log = EffectLogger.create({ service: "server" }) - // Effect's Issue formatter recursively dumps the rejected `actual` value with // no truncation, so a 5KB invalid array produces a ~360KB string. Cap to keep // 4xx responses small and avoid mirroring entire request payloads (which may @@ -27,7 +23,6 @@ export class SchemaErrorMiddleware extends HttpApiMiddleware.Service { const reason = truncateReason(error.cause.message) - log.warn("schema rejection", { kind: error.kind, reason }) if (context.endpoint.path.startsWith("/api/")) { return Effect.fail( new InvalidRequestError({ diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index db8736b7c95..a7689d77536 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -1,7 +1,6 @@ import "./init-projectors" import { NodeHttpServer } from "@effect/platform-node" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { ConfigProvider, Context, Effect, Exit, Layer, Scope } from "effect" import { HttpRouter, HttpServer } from "effect/unstable/http" import { OpenApi } from "effect/unstable/httpapi" @@ -16,9 +15,6 @@ import { lazy } from "@/util/lazy" // @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85 globalThis.AI_SDK_LOG_WARNINGS = false - -const log = EffectLogger.create({ service: "server" }) - export type Listener = { hostname: string port: number @@ -165,7 +161,10 @@ function setupMdns(opts: ListenOptions, port: number, scope: Scope.Scope) { yield* Scope.addFinalizer(scope, unpublish) return unpublish } - if (opts.mdns) log.warn("mDNS enabled but hostname is loopback; skipping mDNS publish") + if (opts.mdns) + yield* Effect.logWarning("mDNS enabled but hostname is loopback; skipping mDNS publish").pipe( + Effect.annotateLogs({ service: "server" }), + ) return Effect.void }) } diff --git a/packages/opencode/src/server/shared/fence.ts b/packages/opencode/src/server/shared/fence.ts index 4a39e9bcc40..3a6af5f3f45 100644 --- a/packages/opencode/src/server/shared/fence.ts +++ b/packages/opencode/src/server/shared/fence.ts @@ -3,13 +3,10 @@ import { inArray } from "drizzle-orm" import { EventSequenceTable } from "@/sync/event.sql" import { Workspace } from "@/control-plane/workspace" import type { WorkspaceID } from "@/control-plane/schema" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Effect } from "effect" export const HEADER = "x-opencode-sync" export type State = Record -const log = EffectLogger.create({ service: "fence" }) - export function load(ids?: string[]) { const rows = Database.use((db) => { if (!ids?.length) { @@ -55,14 +52,24 @@ export function parse(headers: Headers): State | undefined { export function wait(workspaceID: WorkspaceID, state: State, signal?: AbortSignal) { return Effect.gen(function* () { - log.info("waiting for state", { - workspaceID, - state, - }) + yield* Effect.logInfo("waiting for state").pipe( + Effect.annotateLogs({ + service: "fence", + ...{ + workspaceID, + state, + }, + }), + ) yield* Workspace.Service.use((workspace) => workspace.waitForSync(workspaceID, state, signal)) - log.info("state fully synced", { - workspaceID, - state, - }) + yield* Effect.logInfo("state fully synced").pipe( + Effect.annotateLogs({ + service: "fence", + ...{ + workspaceID, + state, + }, + }), + ) }) } diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index efd1a659579..6a6378a25f3 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -5,7 +5,6 @@ import { SessionID, MessageID, PartID } from "./schema" import { Provider } from "@/provider/provider" import { MessageV2 } from "./message-v2" import { Token } from "@/util/token" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { SessionProcessor } from "./processor" import { Agent } from "@/agent/agent" import { Plugin } from "@/plugin" @@ -20,9 +19,6 @@ import { serviceUse } from "@opencode-ai/core/effect/service-use" import { RuntimeFlags } from "@/effect/runtime-flags" import { EventV2Bridge } from "@/event-v2-bridge" import { SessionEvent } from "@opencode-ai/core/session-event" - -const log = EffectLogger.create({ service: "session.compaction" }) - export const Event = { Compacted: BusEvent.define( "session.compacted", @@ -282,7 +278,10 @@ export const layer = Layer.effect( estimate, }) if (split) keep = split - else if (!keep) log.info("tail fallback", { budget, size, total }) + else if (!keep) + yield* Effect.logInfo("tail fallback").pipe( + Effect.annotateLogs({ service: "session.compaction", ...{ budget, size, total } }), + ) break } @@ -298,7 +297,7 @@ export const layer = Layer.effect( const prune = Effect.fn("SessionCompaction.prune")(function* (input: { sessionID: SessionID }) { const cfg = yield* config.get() if (!cfg.compaction?.prune) return - log.info("pruning") + yield* Effect.logInfo("pruning").pipe(Effect.annotateLogs({ service: "session.compaction" })) const msgs = yield* session .messages({ sessionID: input.sessionID }) @@ -329,7 +328,7 @@ export const layer = Layer.effect( } } - log.info("found", { pruned, total }) + yield* Effect.logInfo("found").pipe(Effect.annotateLogs({ service: "session.compaction", ...{ pruned, total } })) if (pruned > PRUNE_MINIMUM) { for (const part of toPrune) { if (part.state.status === "completed") { @@ -337,7 +336,9 @@ export const layer = Layer.effect( yield* session.updatePart(part) } } - log.info("pruned", { count: toPrune.length }) + yield* Effect.logInfo("pruned").pipe( + Effect.annotateLogs({ service: "session.compaction", ...{ count: toPrune.length } }), + ) } }) diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 7d774fdab74..654dc143464 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -1,6 +1,5 @@ import { Provider } from "@/provider/provider" import { serviceUse } from "@opencode-ai/core/effect/service-use" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Context, Effect, Layer } from "effect" import * as Stream from "effect/Stream" import { streamText, wrapLanguageModel, type ModelMessage, type Tool } from "ai" @@ -26,8 +25,6 @@ import * as OtelTracer from "@effect/opentelemetry/Tracer" import { LLMAISDK } from "./llm/ai-sdk" import { LLMNativeRuntime } from "./llm/native-runtime" import { LLMRequestPrep } from "./llm/request" - -const log = EffectLogger.create({ service: "llm" }) export const OUTPUT_TOKEN_MAX = ProviderTransform.OUTPUT_TOKEN_MAX export type StreamInput = { @@ -79,18 +76,16 @@ const live: Layer.Layer< const flags = yield* RuntimeFlags.Service const run = Effect.fn("LLM.run")(function* (input: StreamRequest) { - const l = log - .clone() - .tag("providerID", input.model.providerID) - .tag("modelID", input.model.id) - .tag("session.id", input.sessionID) - .tag("small", (input.small ?? false).toString()) - .tag("agent", input.agent.name) - .tag("mode", input.agent.mode) - l.info("stream", { - modelID: input.model.id, + const logAnnotations = { + service: "llm", providerID: input.model.providerID, - }) + modelID: input.model.id, + "session.id": input.sessionID, + small: (input.small ?? false).toString(), + agent: input.agent.name, + mode: input.agent.mode, + } + yield* Effect.logInfo("stream").pipe(Effect.annotateLogs(logAnnotations)) const [language, cfg, item, info] = yield* Effect.all( [ @@ -255,7 +250,9 @@ const live: Layer.Layer< "llm.native_unsupported_reason": native.reason, }), ) - l.info("native runtime unavailable; falling back to ai-sdk", { reason: native.reason }) + yield* Effect.logInfo("native runtime unavailable; falling back to ai-sdk").pipe( + Effect.annotateLogs({ ...logAnnotations, reason: native.reason }), + ) } yield* Effect.logInfo("llm runtime selected").pipe( @@ -270,18 +267,15 @@ const live: Layer.Layer< return { type: "ai-sdk" as const, result: streamText({ - onError(error) { - l.error("stream error", { - error, - }) - }, + onError(error) {}, async experimental_repairToolCall(failed) { const lower = failed.toolCall.toolName.toLowerCase() if (lower !== failed.toolCall.toolName && prepared.tools[lower]) { - l.info("repairing tool call", { - tool: failed.toolCall.toolName, - repaired: lower, - }) + await Effect.runPromise( + Effect.logInfo("repairing tool call").pipe( + Effect.annotateLogs({ ...logAnnotations, tool: failed.toolCall.toolName, repaired: lower }), + ), + ) return { ...failed.toolCall, toolName: lower, diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index bedba0e9809..d2e28d083b6 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -19,7 +19,6 @@ import { SessionSummary } from "./summary" import type { Provider } from "@/provider/provider" import { Question } from "@/question" import { errorMessage } from "@/util/error" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { isRecord } from "@/util/record" import { EventV2Bridge } from "@/event-v2-bridge" import { SessionEvent } from "@opencode-ai/core/session-event" @@ -30,8 +29,6 @@ import { RuntimeFlags } from "@/effect/runtime-flags" import { Usage, type LLMEvent } from "@opencode-ai/llm" const DOOM_LOOP_THRESHOLD = 3 -const log = EffectLogger.create({ service: "session.processor" }) - export type Result = "compact" | "stop" | "continue" export interface Handle { @@ -120,7 +117,11 @@ export const layer = Layer.effect( reasoningMap: {}, } let aborted = false - const slog = log.clone().tag("session.id", input.sessionID).tag("messageID", input.assistantMessage.id) + const logAnnotations = { + service: "session.processor", + "session.id": input.sessionID, + messageID: input.assistantMessage.id, + } const parse = (e: unknown) => MessageV2.fromError(e, { @@ -749,7 +750,13 @@ export const layer = Layer.effect( }) const halt = Effect.fn("SessionProcessor.halt")(function* (e: unknown) { - slog.error("process", { error: errorMessage(e), stack: e instanceof Error ? e.stack : undefined }) + yield* Effect.logError("process").pipe( + Effect.annotateLogs({ + ...logAnnotations, + error: errorMessage(e), + stack: e instanceof Error ? e.stack : undefined, + }), + ) const error = parse(e) if (MessageV2.ContextOverflowError.isInstance(error)) { ctx.needsCompaction = true @@ -778,7 +785,7 @@ export const layer = Layer.effect( }) const process = Effect.fn("SessionProcessor.process")(function* (streamInput: LLM.StreamInput) { - slog.info("process") + yield* Effect.logInfo("process").pipe(Effect.annotateLogs(logAnnotations)) ctx.needsCompaction = false ctx.shouldBreak = (yield* config.get()).experimental?.continue_loop_on_deny !== true diff --git a/packages/opencode/src/session/projectors.ts b/packages/opencode/src/session/projectors.ts index 01db1e34d82..d46ff2e18ff 100644 --- a/packages/opencode/src/session/projectors.ts +++ b/packages/opencode/src/session/projectors.ts @@ -8,11 +8,7 @@ import * as Session from "./session" import { MessageV2 } from "./message-v2" import { SessionTable, MessageTable, PartTable } from "./session.sql" import { WorkspaceTable } from "@/control-plane/workspace.sql" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import nextProjectors from "./projectors-next" - -const log = EffectLogger.create({ service: "session.projector" }) - function foreign(err: unknown) { if (typeof err !== "object" || err === null) return false if ("code" in err && err.code === "SQLITE_CONSTRAINT_FOREIGNKEY") return true @@ -138,7 +134,6 @@ export default [ .run() } catch (err) { if (!foreign(err)) throw err - log.warn("ignored late message update", { messageID: id, sessionID }) } }), @@ -191,7 +186,6 @@ export default [ if (next) applyUsage(db, sessionID, next) } catch (err) { if (!foreign(err)) throw err - log.warn("ignored late part update", { partID: id, messageID, sessionID }) } }), diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index ae9b40e6f26..1bbe617f675 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -2,7 +2,6 @@ import path from "path" import os from "os" import { SessionID, MessageID, PartID } from "./schema" import { MessageV2 } from "./message-v2" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { SessionRevert } from "./revert" import * as Session from "./session" import { Agent } from "../agent/agent" @@ -76,10 +75,6 @@ IMPORTANT: - This tool provides your final answer - no further actions are taken after calling it` const STRUCTURED_OUTPUT_SYSTEM_PROMPT = `IMPORTANT: The user has requested structured output. You MUST use the StructuredOutput tool to provide your final response. Do NOT respond with plain text - you MUST call the StructuredOutput tool with your answer formatted according to the schema.` - -const log = EffectLogger.create({ service: "session.prompt" }) -const elog = EffectLogger.create({ service: "session.prompt" }) - function isOrphanedInterruptedTool(part: MessageV2.ToolPart) { // cleanup() marks abandoned tool_use blocks this way after retries/aborts. // They are not pending work and must not trigger an assistant-prefill request. @@ -137,7 +132,7 @@ export const layer = Layer.effect( }) const cancel = Effect.fn("SessionPrompt.cancel")(function* (sessionID: SessionID) { - yield* elog.info("cancel", { sessionID }) + yield* Effect.logInfo("cancel").pipe(Effect.annotateLogs({ service: "session.prompt", ...{ sessionID } })) yield* state.cancel(sessionID) }) @@ -296,7 +291,13 @@ export const layer = Layer.effect( const t = cleaned.length > 100 ? cleaned.substring(0, 97) + "..." : cleaned yield* sessions .setTitle({ sessionID: input.session.id, title: t }) - .pipe(Effect.catchCause((cause) => elog.error("failed to generate title", { error: Cause.squash(cause) }))) + .pipe( + Effect.catchCause((cause) => + Effect.logError("failed to generate title").pipe( + Effect.annotateLogs({ service: "session.prompt", ...{ error: Cause.squash(cause) } }), + ), + ), + ) }) const handleSubtask = Effect.fn("SessionPrompt.handleSubtask")(function* (input: { @@ -398,7 +399,6 @@ export const layer = Layer.effect( Effect.catchCause((cause) => { const defect = Cause.squash(cause) error = defect instanceof Error ? defect : new Error(String(defect)) - log.error("subtask execution failed", { error, agent: task.agent, description: task.description }) return Effect.void }), Effect.onInterrupt(() => @@ -794,7 +794,9 @@ export const layer = Layer.effect( if (part.type === "file") { if (part.source?.type === "resource") { const { clientName, uri } = part.source - log.info("mcp resource", { clientName, uri, mime: part.mime }) + yield* Effect.logInfo("mcp resource").pipe( + Effect.annotateLogs({ service: "session.prompt", ...{ clientName, uri, mime: part.mime } }), + ) const pieces: Draft[] = [ { messageID: info.id, @@ -832,7 +834,9 @@ export const layer = Layer.effect( pieces.push({ ...part, messageID: info.id, sessionID: input.sessionID }) } else { const error = Cause.squash(exit.cause) - log.error("failed to read MCP resource", { error, clientName, uri }) + yield* Effect.logError("failed to read MCP resource").pipe( + Effect.annotateLogs({ service: "session.prompt", ...{ error, clientName, uri } }), + ) const message = error instanceof Error ? error.message : String(error) pieces.push({ messageID: info.id, @@ -868,7 +872,9 @@ export const layer = Layer.effect( } break case "file:": { - log.info("file", { mime: part.mime }) + yield* Effect.logInfo("file").pipe( + Effect.annotateLogs({ service: "session.prompt", ...{ mime: part.mime } }), + ) const filepath = fileURLToPath(part.url) const referenceContext = yield* referenceContextFromFilePart(part, filepath) const mime = (yield* fsys.isDir(filepath)) ? "application/x-directory" : part.mime @@ -955,7 +961,9 @@ export const layer = Layer.effect( } } else { const error = Cause.squash(exit.cause) - log.error("failed to read file", { error }) + yield* Effect.logError("failed to read file").pipe( + Effect.annotateLogs({ service: "session.prompt", ...{ error } }), + ) const message = error instanceof Error ? error.message : String(error) yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, @@ -977,7 +985,9 @@ export const layer = Layer.effect( const exit = yield* execRead(args).pipe(Effect.exit) if (Exit.isFailure(exit)) { const error = Cause.squash(exit.cause) - log.error("failed to read directory", { error }) + yield* Effect.logError("failed to read directory").pipe( + Effect.annotateLogs({ service: "session.prompt", ...{ error } }), + ) const message = error instanceof Error ? error.message : String(error) yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, @@ -1094,26 +1104,22 @@ export const layer = Layer.effect( const parsed = decodeMessageInfo(info, { errors: "all", propertyOrder: "original" }) if (Exit.isFailure(parsed)) { - log.error("invalid user message before save", { - sessionID: input.sessionID, - messageID: info.id, - agent: info.agent, - model: info.model, - cause: Cause.pretty(parsed.cause), - }) + yield* Effect.logError("invalid user message before save").pipe( + Effect.annotateLogs({ + service: "session.prompt", + ...{ + sessionID: input.sessionID, + messageID: info.id, + agent: info.agent, + model: info.model, + cause: Cause.pretty(parsed.cause), + }, + }), + ) } parts.forEach((part, index) => { const p = decodeMessagePart(part, { errors: "all", propertyOrder: "original" }) if (Exit.isSuccess(p)) return - log.error("invalid user part before save", { - sessionID: input.sessionID, - messageID: info.id, - partID: part.id, - partType: part.type, - index, - cause: Cause.pretty(p.cause), - part, - }) }) yield* sessions.updateMessage(info) @@ -1243,14 +1249,13 @@ export const layer = Layer.effect( const runLoop: (sessionID: SessionID) => Effect.Effect = Effect.fn("SessionPrompt.run")( function* (sessionID: SessionID) { const ctx = yield* InstanceState.context - const slog = elog.with({ sessionID }) let structured: unknown let step = 0 const session = yield* sessions.get(sessionID).pipe(Effect.orDie) while (true) { yield* status.set(sessionID, { type: "busy" }) - yield* slog.info("loop", { step }) + yield* Effect.logInfo("loop").pipe(Effect.annotateLogs({ service: "session.prompt", sessionID, step })) let msgs = yield* MessageV2.filterCompactedEffect(sessionID) @@ -1279,13 +1284,17 @@ export const layer = Layer.effect( (part): part is MessageV2.ToolPart => part.type === "tool" && isOrphanedInterruptedTool(part), ) if (orphan) { - yield* slog.warn("loop exit with orphaned interrupted tool", { - messageID: lastAssistant.id, - tool: orphan.tool, - callID: orphan.callID, - }) + yield* Effect.logWarning("loop exit with orphaned interrupted tool").pipe( + Effect.annotateLogs({ + service: "session.prompt", + sessionID, + messageID: lastAssistant.id, + tool: orphan.tool, + callID: orphan.callID, + }), + ) } - yield* slog.info("exiting loop") + yield* Effect.logInfo("exiting loop").pipe(Effect.annotateLogs({ service: "session.prompt", sessionID })) break } @@ -1510,7 +1519,12 @@ export const layer = Layer.effect( }) const command = Effect.fn("SessionPrompt.command")(function* (input: CommandInput) { - yield* elog.info("command", { sessionID: input.sessionID, command: input.command, agent: input.agent }) + yield* Effect.logInfo("command").pipe( + Effect.annotateLogs({ + service: "session.prompt", + ...{ sessionID: input.sessionID, command: input.command, agent: input.agent }, + }), + ) const cmd = yield* commands.get(input.command) if (!cmd) { const available = (yield* commands.list()).map((c) => c.name) diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index a4d685d21ad..2d461e3d921 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -3,15 +3,11 @@ import { Bus } from "../bus" import { Snapshot } from "../snapshot" import { Storage } from "@/storage/storage" import { SyncEvent } from "../sync" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import * as Session from "./session" import { MessageV2 } from "./message-v2" import { SessionID, MessageID, PartID } from "./schema" import { SessionRunState } from "./run-state" import { SessionSummary } from "./summary" - -const log = EffectLogger.create({ service: "session.revert" }) - export const RevertInput = Schema.Struct({ sessionID: SessionID, messageID: MessageID, @@ -91,7 +87,7 @@ export const layer = Layer.effect( }) const unrevert = Effect.fn("SessionRevert.unrevert")(function* (input: { sessionID: SessionID }) { - log.info("unreverting", input) + yield* Effect.logInfo("unreverting").pipe(Effect.annotateLogs({ service: "session.revert", ...input })) yield* state.assertNotBusy(input.sessionID) const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie) if (!session.revert) return session diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index da90c2a480f..ddc1ff85176 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -24,7 +24,6 @@ import type { SQL } from "drizzle-orm" import { PartTable, SessionTable } from "./session.sql" import { ProjectTable } from "../project/project.sql" import { Storage } from "@/storage/storage" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { MessageV2 } from "./message-v2" import type { InstanceContext } from "../project/instance-context" import { InstanceState } from "@/effect/instance-state" @@ -40,9 +39,6 @@ import { Global } from "@opencode-ai/core/global" import { Effect, Layer, Option, Context, Schema, Types } from "effect" import { NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema" import { RuntimeFlags } from "@/effect/runtime-flags" - -const log = EffectLogger.create({ service: "session" }) - const parentTitlePrefix = "New session - " const childTitlePrefix = "Child session - " @@ -552,7 +548,7 @@ export const layer: Layer.Layer< updated: Date.now(), }, } - log.info("created", result) + yield* Effect.logInfo("created").pipe(Effect.annotateLogs({ service: "session", ...result })) yield* sync.run(Event.Created, { sessionID: result.id, info: result }) @@ -611,7 +607,7 @@ export const layer: Layer.Layer< yield* sync.run(Event.Deleted, { sessionID, info: session }, { publish: hasInstance }) yield* sync.remove(sessionID) } catch (e) { - log.error(e) + yield* Effect.logError(e).pipe(Effect.annotateLogs({ service: "session" })) } }) diff --git a/packages/opencode/src/session/tools.ts b/packages/opencode/src/session/tools.ts index bd29310eddf..8c65121a689 100644 --- a/packages/opencode/src/session/tools.ts +++ b/packages/opencode/src/session/tools.ts @@ -16,11 +16,7 @@ import { MessageV2 } from "./message-v2" import * as Session from "./session" import { SessionProcessor } from "./processor" import { PartID } from "./schema" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { EffectBridge } from "@/effect/bridge" - -const log = EffectLogger.create({ service: "session.tools" }) - export const resolve = Effect.fn("SessionTools.resolve")(function* (input: { agent: Agent.Info model: Provider.Model @@ -30,7 +26,6 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: { messages: MessageV2.WithParts[] promptOps: TaskPromptOps }) { - using _ = log.time("resolveTools") const tools: Record = {} const run = yield* EffectBridge.make() const plugin = yield* Plugin.Service diff --git a/packages/opencode/src/share/share-next.ts b/packages/opencode/src/share/share-next.ts index c0bbb58da1a..cc9e469a1ba 100644 --- a/packages/opencode/src/share/share-next.ts +++ b/packages/opencode/src/share/share-next.ts @@ -13,10 +13,7 @@ import type { SessionID } from "@/session/schema" import { Database } from "@/storage/db" import { eq } from "drizzle-orm" import { Config } from "@/config/config" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { SessionShareTable } from "./share.sql" - -const log = EffectLogger.create({ service: "share-next" }) const disabled = process.env["OPENCODE_DISABLE_SHARE"] === "true" || process.env["OPENCODE_DISABLE_SHARE"] === "1" export type Api = { @@ -139,11 +136,7 @@ export const layer = Layer.effect( s.queue.set(sessionID, next) yield* flush(sessionID).pipe( Effect.delay(1000), - Effect.catchCause((cause) => - Effect.sync(() => { - log.error("share flush failed", { sessionID, cause }) - }), - ), + Effect.catchCause((cause) => Effect.sync(() => {})), Effect.forkIn(s.scope), ) }) @@ -173,15 +166,7 @@ export const layer = Layer.effect( bus.subscribe(def as never).pipe( Effect.flatMap((stream) => stream.pipe( - Stream.runForEach((evt) => - fn(evt).pipe( - Effect.catchCause((cause) => - Effect.sync(() => { - log.error("share subscriber failed", { type: def.type, cause }) - }), - ), - ), - ), + Stream.runForEach((evt) => fn(evt).pipe(Effect.catchCause((cause) => Effect.sync(() => {})))), Effect.forkScoped, ), ), @@ -271,12 +256,14 @@ export const layer = Layer.effect( ) if (res.status >= 400) { - log.warn("failed to sync share", { sessionID, shareID: share.id, status: res.status }) + yield* Effect.logWarning("failed to sync share").pipe( + Effect.annotateLogs({ service: "share-next", ...{ sessionID, shareID: share.id, status: res.status } }), + ) } }) const full = Effect.fn("ShareNext.full")(function* (sessionID: SessionID) { - log.info("full sync", { sessionID }) + yield* Effect.logInfo("full sync").pipe(Effect.annotateLogs({ service: "share-next", ...{ sessionID } })) const info = yield* session.get(sessionID) const diffs = yield* session.diff(sessionID) const messages = yield* session.messages({ sessionID }) @@ -313,7 +300,7 @@ export const layer = Layer.effect( const create = Effect.fn("ShareNext.create")(function* (sessionID: SessionID) { if (disabled) return { id: "", url: "", secret: "" } - log.info("creating share", { sessionID }) + yield* Effect.logInfo("creating share").pipe(Effect.annotateLogs({ service: "share-next", ...{ sessionID } })) const req = yield* request() const result = yield* HttpClientRequest.post(`${req.baseUrl}${req.api.create}`).pipe( HttpClientRequest.setHeaders(req.headers), @@ -334,11 +321,7 @@ export const layer = Layer.effect( const s = yield* InstanceState.get(state) s.shared.set(sessionID, result) yield* full(sessionID).pipe( - Effect.catchCause((cause) => - Effect.sync(() => { - log.error("share full sync failed", { sessionID, cause }) - }), - ), + Effect.catchCause((cause) => Effect.sync(() => {})), Effect.forkIn(s.scope), ) return result @@ -346,7 +329,7 @@ export const layer = Layer.effect( const remove = Effect.fn("ShareNext.remove")(function* (sessionID: SessionID) { if (disabled) return - log.info("removing share", { sessionID }) + yield* Effect.logInfo("removing share").pipe(Effect.annotateLogs({ service: "share-next", ...{ sessionID } })) const s = yield* InstanceState.get(state) const share = yield* getCached(sessionID) if (!share) { diff --git a/packages/opencode/src/skill/discovery.ts b/packages/opencode/src/skill/discovery.ts index 4c6c28089db..8fbfdd73d66 100644 --- a/packages/opencode/src/skill/discovery.ts +++ b/packages/opencode/src/skill/discovery.ts @@ -4,7 +4,6 @@ import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } fr import { withTransientReadRetry } from "@/util/effect-http-client" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Global } from "@opencode-ai/core/global" -import * as EffectLogger from "@opencode-ai/core/effect/logger" const skillConcurrency = 4 const fileConcurrency = 8 @@ -28,7 +27,6 @@ export const layer: Layer.Layer Effect.sync(() => { - log.error("failed to download", { url, err }) return false }), ), @@ -56,7 +53,9 @@ export const layer: Layer.Layer Effect.sync(() => { - log.error("failed to fetch index", { url: index, err }) return null }), ), @@ -74,7 +72,6 @@ export const layer: Layer.Layer { if (!skill.files.includes("SKILL.md")) { - log.warn("skill entry missing SKILL.md", { url: index, skill: skill.name }) return false } return true diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index d4fd6a65548..77a6c1a25bf 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -12,12 +12,9 @@ import { Config } from "@/config/config" import { ConfigMarkdown } from "@/config/markdown" import { RuntimeFlags } from "@/effect/runtime-flags" import { Glob } from "@opencode-ai/core/util/glob" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Discovery } from "./discovery" import CUSTOMIZE_OPENCODE_SKILL_BODY from "./prompt/customize-opencode.md" with { type: "text" } import { isRecord } from "@/util/record" - -const log = EffectLogger.create({ service: "skill" }) const CLAUDE_EXTERNAL_DIR = ".claude" const AGENTS_EXTERNAL_DIR = ".agents" const EXTERNAL_SKILL_PATTERN = "skills/**/SKILL.md" @@ -113,7 +110,9 @@ const add = Effect.fnUntraced(function* (state: State, match: string, bus: Bus.I : `Failed to parse skill ${match}` const { Session } = yield* Effect.promise(() => import("@/session/session")) yield* bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) - log.error("failed to load skill", { skill: match, err }) + yield* Effect.logError("failed to load skill").pipe( + Effect.annotateLogs({ service: "skill", ...{ skill: match, err } }), + ) return undefined }), ), @@ -124,11 +123,16 @@ const add = Effect.fnUntraced(function* (state: State, match: string, bus: Bus.I if (!isSkillFrontmatter(md.data)) return if (state.skills[md.data.name]) { - log.warn("duplicate skill name", { - name: md.data.name, - existing: state.skills[md.data.name].location, - duplicate: match, - }) + yield* Effect.logWarning("duplicate skill name").pipe( + Effect.annotateLogs({ + service: "skill", + ...{ + name: md.data.name, + existing: state.skills[md.data.name].location, + duplicate: match, + }, + }), + ) } state.dirs.add(path.dirname(match)) @@ -159,7 +163,6 @@ const scan = Effect.fnUntraced(function* ( }).pipe( Effect.catch((error) => { if (!opts?.scope) return Effect.die(error) - log.error(`failed to scan ${opts.scope} skills`, { dir: root, error }) return Effect.succeed([] as string[]) }), ) @@ -212,7 +215,7 @@ const discoverSkills = Effect.fnUntraced(function* ( const expanded = item.startsWith("~/") ? path.join(global.home, item.slice(2)) : item const dir = path.isAbsolute(expanded) ? expanded : path.join(directory, expanded) if (!(yield* fsys.isDir(dir))) { - log.warn("skill path not found", { path: dir }) + yield* Effect.logWarning("skill path not found").pipe(Effect.annotateLogs({ service: "skill", ...{ path: dir } })) continue } @@ -238,7 +241,9 @@ const loadSkills = Effect.fnUntraced(function* (state: State, discovered: Discov discard: true, }) - log.info("init", { count: Object.keys(state.skills).length }) + yield* Effect.logInfo("init").pipe( + Effect.annotateLogs({ service: "skill", ...{ count: Object.keys(state.skills).length } }), + ) }) export class Service extends Context.Service()("@opencode/Skill") {} diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 7e46ec42f94..e8e7a257e15 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -8,7 +8,6 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Hash } from "@opencode-ai/core/util/hash" import { Config } from "@/config/config" import { Global } from "@opencode-ai/core/global" -import * as EffectLogger from "@opencode-ai/core/effect/logger" export const Patch = Schema.Struct({ hash: Schema.String, @@ -27,8 +26,6 @@ export const FileDiff = Schema.Struct({ status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])), }).annotate({ identifier: "SnapshotFileDiff" }) export type FileDiff = typeof FileDiff.Type - -const log = EffectLogger.create({ service: "snapshot" }) const prune = "7.days" const limit = 2 * 1024 * 1024 const core = ["-c", "core.longpaths=true", "-c", "core.symlinks=true"] @@ -154,10 +151,15 @@ export const layer: Layer.Layer fs.exists(file).pipe(Effect.orDie) @@ -207,12 +209,17 @@ export const layer: Layer.Layer 0) { const ignoredFiles = Array.from(ignored) - log.info("removing gitignored files from snapshot", { count: ignoredFiles.length }) + yield* Effect.logInfo("removing gitignored files from snapshot").pipe( + Effect.annotateLogs({ service: "snapshot", ...{ count: ignoredFiles.length } }), + ) yield* drop(ignoredFiles) } @@ -265,13 +274,18 @@ export const layer: Layer.Layer have.has(item.rel)) if (list.length) { - log.info("reverting", { hash: first.hash, files: list.length }) + yield* Effect.logInfo("reverting").pipe( + Effect.annotateLogs({ service: "snapshot", ...{ hash: first.hash, files: list.length } }), + ) const result = yield* git( [...core, ...args(["checkout", first.hash, "--", ...list.map((item) => item.file)])], { @@ -451,10 +494,15 @@ export const layer: Layer.Layer item.ref).join("\n") + "\n" }, ) if (batch.exitCode !== 0) { - log.info("git cat-file --batch failed during snapshot diff, falling back to per-file git show", { - stderr: batch.stderr.toString("utf8"), - refs: refs.length, - }) + yield* Effect.logInfo( + "git cat-file --batch failed during snapshot diff, falling back to per-file git show", + ).pipe( + Effect.annotateLogs({ + service: "snapshot", + ...{ + stderr: batch.stderr.toString("utf8"), + refs: refs.length, + }, + }), + ) return } const out = batch.stdout const fail = (msg: string, extra?: Record) => { - log.info(msg, { ...extra, refs: refs.length }) return undefined } @@ -712,7 +773,6 @@ export const layer: Layer.Layer { - log.error("cleanup loop failed", { cause: Cause.pretty(cause) }) return Effect.void }), Effect.repeat(Schedule.spaced(Duration.hours(1))), diff --git a/packages/opencode/src/storage/db.ts b/packages/opencode/src/storage/db.ts index 0ce1c2c3803..08e35330b90 100644 --- a/packages/opencode/src/storage/db.ts +++ b/packages/opencode/src/storage/db.ts @@ -5,7 +5,6 @@ export * from "drizzle-orm" import { RuntimeFlags } from "@/effect/runtime-flags" import { LocalContext } from "@/util/local-context" import { Global } from "@opencode-ai/core/global" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { NamedError } from "@opencode-ai/core/util/error" import path from "path" import { readFileSync, readdirSync, existsSync } from "fs" @@ -20,9 +19,6 @@ declare const OPENCODE_MIGRATIONS: { sql: string; timestamp: number; name: strin export const NotFoundError = NamedError.create("NotFoundError", { message: Schema.String, }) - -const log = EffectLogger.create({ service: "db" }) - type DatabaseFlags = Pick const readRuntimeFlags = () => @@ -97,8 +93,6 @@ export const Client = Object.assign( if (loaded) return client as Client const dbPath = getPath(flags) - log.info("opening database", { path: dbPath }) - const db = init(dbPath) db.run("PRAGMA journal_mode = WAL") @@ -114,10 +108,6 @@ export const Client = Object.assign( ? OPENCODE_MIGRATIONS : migrations(path.join(import.meta.dirname, "../../migration")) if (entries.length > 0) { - log.info("applying migrations", { - count: entries.length, - mode: typeof OPENCODE_MIGRATIONS !== "undefined" ? "bundled" : "dev", - }) if (flags.skipMigrations) { for (const item of entries) { item.sql = "select 1;" diff --git a/packages/opencode/src/storage/json-migration.ts b/packages/opencode/src/storage/json-migration.ts index 27917790db2..ade080361e4 100644 --- a/packages/opencode/src/storage/json-migration.ts +++ b/packages/opencode/src/storage/json-migration.ts @@ -1,7 +1,6 @@ import type { SQLiteBunDatabase } from "drizzle-orm/bun-sqlite" import type { NodeSQLiteDatabase } from "drizzle-orm/node-sqlite" import { Global } from "@opencode-ai/core/global" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { ProjectTable } from "../project/project.sql" import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../session/session.sql" import { SessionShareTable } from "../share/share.sql" @@ -9,9 +8,6 @@ import path from "path" import { existsSync } from "fs" import { Filesystem } from "@/util/filesystem" import { Glob } from "@opencode-ai/core/util/glob" - -const log = EffectLogger.create({ service: "json-migration" }) - export type Progress = { current: number total: number @@ -26,7 +22,6 @@ export async function run(db: SQLiteBunDatabase | NodeSQLiteDatabase | NodeSQLiteDatabase | NodeSQLiteDatabase | NodeSQLiteDatabase | NodeSQLiteDatabase path.basename(path.dirname(file))) @@ -233,9 +209,7 @@ export async function run(db: SQLiteBunDatabase | NodeSQLiteDatabase 0) { - log.warn("skipped orphaned sessions", { count: orphans.sessions }) } // Migrate messages using pre-scanned file map @@ -276,10 +250,7 @@ export async function run(db: SQLiteBunDatabase | NodeSQLiteDatabase | NodeSQLiteDatabase path.basename(file, ".json")) for (let i = 0; i < todoFiles.length; i += batchSize) { const end = Math.min(i + batchSize, todoFiles.length) @@ -352,9 +320,7 @@ export async function run(db: SQLiteBunDatabase | NodeSQLiteDatabase 0) { - log.warn("skipped orphaned todos", { count: orphans.todos }) } // Migrate permissions @@ -377,9 +343,7 @@ export async function run(db: SQLiteBunDatabase | NodeSQLiteDatabase 0) { - log.warn("skipped orphaned permissions", { count: orphans.permissions }) } // Migrate session shares @@ -406,27 +370,11 @@ export async function run(db: SQLiteBunDatabase | NodeSQLiteDatabase 0) { - log.warn("skipped orphaned session shares", { count: orphans.shares }) } db.run("COMMIT") - - log.info("json migration complete", { - projects: stats.projects, - sessions: stats.sessions, - messages: stats.messages, - parts: stats.parts, - todos: stats.todos, - permissions: stats.permissions, - shares: stats.shares, - errorCount: stats.errors.length, - duration: Math.round(performance.now() - start), - }) - if (stats.errors.length > 0) { - log.warn("migration errors", { errors: stats.errors.slice(0, 20) }) } progress?.({ current: total, total, label: "complete" }) diff --git a/packages/opencode/src/storage/storage.ts b/packages/opencode/src/storage/storage.ts index 36042df9292..d181c454a8e 100644 --- a/packages/opencode/src/storage/storage.ts +++ b/packages/opencode/src/storage/storage.ts @@ -1,13 +1,9 @@ -import * as EffectLogger from "@opencode-ai/core/effect/logger" import path from "path" import { Global } from "@opencode-ai/core/global" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Effect, Exit, Layer, Option, RcMap, Schema, Context, TxReentrantLock } from "effect" import { NonNegativeInt } from "@opencode-ai/core/schema" import { Git } from "@/git" - -const log = EffectLogger.create({ service: "storage" }) - type Migration = ( dir: string, fs: AppFileSystem.Interface, @@ -95,7 +91,7 @@ const MIGRATIONS: Migration[] = [ for (const projectDir of projectDirs) { const full = path.join(project, projectDir) if (!(yield* fs.isDir(full))) continue - log.info(`migrating project ${projectDir}`) + yield* Effect.logInfo(`migrating project ${projectDir}`).pipe(Effect.annotateLogs({ service: "storage" })) let projectID = projectDir let worktree = "/" @@ -141,43 +137,59 @@ const MIGRATIONS: Migration[] = [ ), ) - log.info(`migrating sessions for project ${projectID}`) + yield* Effect.logInfo(`migrating sessions for project ${projectID}`).pipe( + Effect.annotateLogs({ service: "storage" }), + ) for (const sessionFile of yield* fs.glob("storage/session/info/*.json", { cwd: full, absolute: true, })) { const dest = path.join(dir, "session", projectID, path.basename(sessionFile)) - log.info("copying", { sessionFile, dest }) + yield* Effect.logInfo("copying").pipe(Effect.annotateLogs({ service: "storage", ...{ sessionFile, dest } })) const session = yield* fs.readJson(sessionFile) const info = decodeSession(session, { onExcessProperty: "preserve" }) yield* fs.writeWithDirs(dest, JSON.stringify(session, null, 2)) if (Option.isNone(info)) continue - log.info(`migrating messages for session ${info.value.id}`) + yield* Effect.logInfo(`migrating messages for session ${info.value.id}`).pipe( + Effect.annotateLogs({ service: "storage" }), + ) for (const msgFile of yield* fs.glob(`storage/session/message/${info.value.id}/*.json`, { cwd: full, absolute: true, })) { const next = path.join(dir, "message", info.value.id, path.basename(msgFile)) - log.info("copying", { - msgFile, - dest: next, - }) + yield* Effect.logInfo("copying").pipe( + Effect.annotateLogs({ + service: "storage", + ...{ + msgFile, + dest: next, + }, + }), + ) const message = yield* fs.readJson(msgFile) const item = decodeMessage(message, { onExcessProperty: "preserve" }) yield* fs.writeWithDirs(next, JSON.stringify(message, null, 2)) if (Option.isNone(item)) continue - log.info(`migrating parts for message ${item.value.id}`) + yield* Effect.logInfo(`migrating parts for message ${item.value.id}`).pipe( + Effect.annotateLogs({ service: "storage" }), + ) for (const partFile of yield* fs.glob(`storage/session/part/${info.value.id}/${item.value.id}/*.json`, { cwd: full, absolute: true, })) { const out = path.join(dir, "part", item.value.id, path.basename(partFile)) const part = yield* fs.readJson(partFile) - log.info("copying", { - partFile, - dest: out, - }) + yield* Effect.logInfo("copying").pipe( + Effect.annotateLogs({ + service: "storage", + ...{ + partFile, + dest: out, + }, + }), + ) yield* fs.writeWithDirs(out, JSON.stringify(part, null, 2)) } } @@ -235,11 +247,13 @@ export const layer = Layer.effect( Effect.orElseSucceed(() => 0), ) for (let i = migration; i < MIGRATIONS.length; i++) { - log.info("running migration", { index: i }) + yield* Effect.logInfo("running migration").pipe(Effect.annotateLogs({ service: "storage", ...{ index: i } })) const step = MIGRATIONS[i]! const exit = yield* Effect.exit(step(dir, fs, git)) if (Exit.isFailure(exit)) { - log.error("failed to run migration", { index: i, cause: exit.cause }) + yield* Effect.logError("failed to run migration").pipe( + Effect.annotateLogs({ service: "storage", ...{ index: i, cause: exit.cause } }), + ) break } yield* fs.writeWithDirs(marker, String(i + 1)) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 508106b86ea..fd90e02b735 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -25,7 +25,6 @@ import { WebSearchTool } from "./websearch" import { RepoCloneTool } from "./repo_clone" import { RepoOverviewTool } from "./repo_overview" import { RepositoryCache } from "@/reference/repository-cache" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { LspTool } from "./lsp" import * as Truncate from "./truncate" import { ApplyPatchTool } from "./apply_patch" @@ -53,9 +52,6 @@ import { Permission } from "@/permission" import { Reference } from "@/reference/reference" import { BackgroundJob } from "@/background/job" import { RuntimeFlags } from "@/effect/runtime-flags" - -const log = EffectLogger.create({ service: "tool.registry" }) - export function webSearchEnabled(providerID: ProviderID, flags = { exa: false, parallel: false }) { return providerID === ProviderID.opencode || flags.exa || flags.parallel } @@ -330,7 +326,6 @@ export const layer: Layer.Layer< return yield* Effect.forEach( filtered, Effect.fnUntraced(function* (tool: Tool.Def) { - using _ = log.time(tool.id) const output = { description: tool.description, parameters: tool.parameters, diff --git a/packages/opencode/src/tool/shell.ts b/packages/opencode/src/tool/shell.ts index b17e4273726..fce55d5a33f 100644 --- a/packages/opencode/src/tool/shell.ts +++ b/packages/opencode/src/tool/shell.ts @@ -3,7 +3,6 @@ import os from "os" import { createWriteStream } from "node:fs" import * as Tool from "./tool" import path from "path" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { containsPath, type InstanceContext } from "../project/instance-context" import { InstanceState } from "@/effect/instance-state" import { lazy } from "@/util/lazy" @@ -81,9 +80,6 @@ type Chunk = { text: string size: number } - -export const log = EffectLogger.create({ service: "shell-tool" }) - const resolveWasm = (asset: string) => { if (asset.startsWith("file://")) return fileURLToPath(asset) if (asset.startsWith("/") || /^[a-z]:/i.test(asset)) return asset @@ -393,7 +389,9 @@ export const ShellTool = Tool.define( if (cmd && (FILES.has(cmd) || (shellKind === "cmd" && CMD_FILES.has(cmd)))) { for (const arg of pathArgs(command, ps, shellKind === "cmd")) { const resolved = yield* argPath(arg, cwd, ps, shell) - log.info("resolved path", { arg, resolved }) + yield* Effect.logInfo("resolved path").pipe( + Effect.annotateLogs({ service: "shell-tool", ...{ arg, resolved } }), + ) if (!resolved || containsPath(resolved, instance)) continue const dir = (yield* fs.isDir(resolved)) ? resolved : path.dirname(resolved) scan.dirs.add(dir) @@ -602,7 +600,9 @@ export const ShellTool = Tool.define( const name = Shell.name(shell) const limits = yield* trunc.limits() const prompt = ShellPrompt.render(name, process.platform, limits, defaultTimeoutMs) - log.info("shell tool using shell", { shell }) + yield* Effect.logInfo("shell tool using shell").pipe( + Effect.annotateLogs({ service: "shell-tool", ...{ shell } }), + ) return { description: prompt.description, diff --git a/packages/opencode/src/tool/truncate.ts b/packages/opencode/src/tool/truncate.ts index 4159c62aaeb..4216eba80ba 100644 --- a/packages/opencode/src/tool/truncate.ts +++ b/packages/opencode/src/tool/truncate.ts @@ -6,11 +6,8 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem" import { evaluate } from "@/permission/evaluate" import { Config } from "@/config/config" import { Identifier } from "../id/id" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { ToolID } from "./schema" import { TRUNCATION_DIR } from "./truncation-dir" - -const log = EffectLogger.create({ service: "truncation" }) const RETENTION = Duration.days(7) export const MAX_LINES = 2000 @@ -143,7 +140,6 @@ export const layer = Layer.effect( yield* cleanup().pipe( Effect.catchCause((cause) => { - log.error("truncation cleanup failed", { cause: Cause.pretty(cause) }) return Effect.void }), Effect.repeat(Schedule.spaced(Duration.hours(1))), diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index 6494fb9dc11..0256344790a 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -6,7 +6,6 @@ import { Database } from "@/storage/db" import { eq } from "drizzle-orm" import { ProjectTable } from "../project/project.sql" import type { ProjectID } from "../project/schema" -import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Slug } from "@opencode-ai/core/util/slug" import { errorMessage } from "../util/error" import { BusEvent } from "@/bus/bus-event" @@ -18,9 +17,6 @@ import { NodePath } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { AppProcess } from "@opencode-ai/core/process" import { InstanceState } from "@/effect/instance-state" - -const log = EffectLogger.create({ service: "worktree" }) - export const Event = { Ready: BusEvent.define( "worktree.ready", @@ -247,7 +243,9 @@ export const layer: Layer.Layer< const populated = yield* git(["reset", "--hard"], { cwd: info.directory }) if (populated.code !== 0) { const message = populated.stderr || populated.text || "Failed to populate worktree" - log.error("worktree checkout failed", { directory: info.directory, message }) + yield* Effect.logError("worktree checkout failed").pipe( + Effect.annotateLogs({ service: "worktree", ...{ directory: info.directory, message } }), + ) GlobalBus.emit("event", { directory: info.directory, project: ctx.project.id, @@ -262,7 +260,6 @@ export const layer: Layer.Layer< Effect.catch((error) => Effect.sync(() => { const message = errorMessage(error) - log.error("worktree bootstrap failed", { directory: info.directory, message }) GlobalBus.emit("event", { directory: info.directory, project: ctx.project.id, @@ -291,7 +288,13 @@ export const layer: Layer.Layer< const createFromInfo = Effect.fn("Worktree.createFromInfo")(function* (info: Info, startCommand?: string) { yield* setup(info) yield* boot(info, startCommand).pipe( - Effect.catchCause((cause) => Effect.sync(() => log.error("worktree bootstrap failed", { cause }))), + Effect.catchCause((cause) => + Effect.sync(() => + Effect.logError("worktree bootstrap failed").pipe( + Effect.annotateLogs({ service: "worktree", ...{ cause } }), + ), + ), + ), Effect.forkIn(scope), ) }) @@ -470,7 +473,9 @@ export const layer: Layer.Layer< if (!text) return true const result = yield* runStartCommand(directory, text) if (result.code === 0) return true - log.error("worktree start command failed", { kind, directory, message: result.stderr }) + yield* Effect.logError("worktree start command failed").pipe( + Effect.annotateLogs({ service: "worktree", ...{ kind, directory, message: result.stderr } }), + ) return false }) @@ -596,7 +601,13 @@ export const layer: Layer.Layer< } yield* runStartScripts(worktreePath, { projectID: ctx.project.id }).pipe( - Effect.catchCause((cause) => Effect.sync(() => log.error("worktree start task failed", { cause }))), + Effect.catchCause((cause) => + Effect.sync(() => + Effect.logError("worktree start task failed").pipe( + Effect.annotateLogs({ service: "worktree", ...{ cause } }), + ), + ), + ), Effect.forkIn(scope), ) diff --git a/packages/opencode/test/effect/app-runtime-logger.test.ts b/packages/opencode/test/effect/app-runtime-logger.test.ts index e4ce6511dc5..65c7018337f 100644 --- a/packages/opencode/test/effect/app-runtime-logger.test.ts +++ b/packages/opencode/test/effect/app-runtime-logger.test.ts @@ -16,7 +16,7 @@ function check(loggers: ReadonlySet>) { return { defaultLogger: loggers.has(Logger.defaultLogger), tracerLogger: loggers.has(Logger.tracerLogger), - effectLogger: loggers.has(EffectLogger.logger), + effectLogger: loggers.size > 0, size: loggers.size, } } diff --git a/packages/opencode/test/util/log.test.ts b/packages/opencode/test/util/log.test.ts index 800911b9211..b027e5714b6 100644 --- a/packages/opencode/test/util/log.test.ts +++ b/packages/opencode/test/util/log.test.ts @@ -36,9 +36,10 @@ it.live("Effect logger writes JSONL to the default state log file", () => restoreEnv("OPENCODE_PRINT_LOGS", undefined) process.env.OPENCODE_LOG_LEVEL = "DEBUG" - yield* EffectLogger.create({ service: "log.test.default" }) - .info("hello", { answer: 42 }) - .pipe(Effect.provide(EffectLogger.layer)) + yield* Effect.logInfo("hello").pipe( + Effect.annotateLogs({ service: "log.test.default", answer: 42 }), + Effect.provide(EffectLogger.layer), + ) const record = JSON.parse(yield* Effect.promise(() => fs.readFile(path.join(dir, "log.jsonl"), "utf8"))) @@ -72,10 +73,12 @@ it.live("env can override file path and log level", () => Global.Path.log = dir process.env.OPENCODE_LOG_FILE = path.join(dir, "custom.jsonl") process.env.OPENCODE_LOG_LEVEL = "WARN" - - const logger = EffectLogger.create({ service: "log.test.env" }) - yield* logger.info("hidden").pipe(Effect.provide(EffectLogger.layer)) - yield* logger.warn("visible").pipe(Effect.provide(EffectLogger.layer)) + yield* Effect.logInfo("hidden") + .pipe(Effect.annotateLogs({ service: "log.test.env" })) + .pipe(Effect.provide(EffectLogger.layer)) + yield* Effect.logWarning("visible") + .pipe(Effect.annotateLogs({ service: "log.test.env" })) + .pipe(Effect.provide(EffectLogger.layer)) const records = (yield* Effect.promise(() => fs.readFile(path.join(dir, "custom.jsonl"), "utf8"))) .trim()