mirror of
https://github.com/anomalyco/opencode.git
synced 2026-03-13 10:14:26 +00:00
Compare commits
2 Commits
production
...
effect-log
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e13d44684d | ||
|
|
9bbc874927 |
56
packages/opencode/src/util/effect-log.ts
Normal file
56
packages/opencode/src/util/effect-log.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Cause, Logger } from "effect"
|
||||
import { CurrentLogAnnotations, CurrentLogSpans } from "effect/References"
|
||||
|
||||
import { Log } from "./log"
|
||||
|
||||
function text(input: unknown): string {
|
||||
if (Array.isArray(input)) return input.map(text).join(" ")
|
||||
if (input instanceof Error) return input.message
|
||||
if (typeof input === "string") return input
|
||||
if (typeof input === "object" && input !== null) {
|
||||
try {
|
||||
return JSON.stringify(input)
|
||||
} catch {
|
||||
return String(input)
|
||||
}
|
||||
}
|
||||
return String(input)
|
||||
}
|
||||
|
||||
export function make(tags?: Record<string, unknown>) {
|
||||
const log = Log.create(tags)
|
||||
|
||||
return Logger.make<unknown, void>((options) => {
|
||||
const annotations = options.fiber.getRef(CurrentLogAnnotations as never) as Readonly<Record<string, unknown>>
|
||||
const spans = options.fiber.getRef(CurrentLogSpans as never) as ReadonlyArray<readonly [string, number]>
|
||||
const extra = {
|
||||
...annotations,
|
||||
fiber: options.fiber.id,
|
||||
spans: spans.length
|
||||
? spans.map(([label, start]) => ({
|
||||
label,
|
||||
duration: options.date.getTime() - start,
|
||||
}))
|
||||
: undefined,
|
||||
cause: options.cause.reasons.length ? Cause.pretty(options.cause) : undefined,
|
||||
}
|
||||
|
||||
if (options.logLevel === "Debug" || options.logLevel === "Trace") {
|
||||
return log.debug(text(options.message), extra)
|
||||
}
|
||||
|
||||
if (options.logLevel === "Info") {
|
||||
return log.info(text(options.message), extra)
|
||||
}
|
||||
|
||||
if (options.logLevel === "Warn") {
|
||||
return log.warn(text(options.message), extra)
|
||||
}
|
||||
|
||||
return log.error(text(options.message), extra)
|
||||
})
|
||||
}
|
||||
|
||||
export function layer(tags?: Record<string, unknown>, options?: { mergeWithExisting?: boolean }) {
|
||||
return Logger.layer([make(tags)], options)
|
||||
}
|
||||
99
packages/opencode/test/util/effect-log.test.ts
Normal file
99
packages/opencode/test/util/effect-log.test.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { afterEach, expect, mock, spyOn, test } from "bun:test"
|
||||
import { Cause, Effect } from "effect"
|
||||
import { CurrentLogAnnotations, CurrentLogSpans } from "effect/References"
|
||||
|
||||
import * as EffectLog from "../../src/util/effect-log"
|
||||
import { Log } from "../../src/util/log"
|
||||
|
||||
const debug = mock(() => {})
|
||||
const info = mock(() => {})
|
||||
const warn = mock(() => {})
|
||||
const error = mock(() => {})
|
||||
|
||||
const logger = {
|
||||
debug,
|
||||
info,
|
||||
warn,
|
||||
error,
|
||||
tag() {
|
||||
return logger
|
||||
},
|
||||
clone() {
|
||||
return logger
|
||||
},
|
||||
time() {
|
||||
return {
|
||||
stop() {},
|
||||
[Symbol.dispose]() {},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
debug.mockClear()
|
||||
info.mockClear()
|
||||
warn.mockClear()
|
||||
error.mockClear()
|
||||
})
|
||||
|
||||
test("EffectLog.layer routes info logs through util/log", async () => {
|
||||
using create = spyOn(Log, "create").mockReturnValue(logger)
|
||||
|
||||
await Effect.runPromise(Effect.logInfo("hello").pipe(Effect.provide(EffectLog.layer({ service: "effect-test" }))))
|
||||
|
||||
expect(create).toHaveBeenCalledWith({ service: "effect-test" })
|
||||
expect(info).toHaveBeenCalledWith("hello", expect.any(Object))
|
||||
})
|
||||
|
||||
test("EffectLog.layer forwards annotations and spans to util/log", async () => {
|
||||
using create = spyOn(Log, "create").mockReturnValue(logger)
|
||||
|
||||
await Effect.runPromise(
|
||||
Effect.logInfo("hello").pipe(
|
||||
Effect.annotateLogs({ requestId: "req-123" }),
|
||||
Effect.withLogSpan("provider-auth"),
|
||||
Effect.provide(EffectLog.layer({ service: "effect-test-meta" })),
|
||||
),
|
||||
)
|
||||
|
||||
expect(create).toHaveBeenCalledWith({ service: "effect-test-meta" })
|
||||
expect(info).toHaveBeenCalledWith(
|
||||
"hello",
|
||||
expect.objectContaining({
|
||||
requestId: "req-123",
|
||||
spans: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
label: "provider-auth",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test("EffectLog.make formats structured messages and causes for legacy logger", () => {
|
||||
using create = spyOn(Log, "create").mockReturnValue(logger)
|
||||
const effect = EffectLog.make({ service: "effect-test-struct" })
|
||||
|
||||
effect.log({
|
||||
message: { hello: "world" },
|
||||
logLevel: "Warn",
|
||||
cause: Cause.fail(new Error("boom")),
|
||||
fiber: {
|
||||
id: 123n,
|
||||
getRef(ref: unknown) {
|
||||
if (ref === CurrentLogAnnotations) return {}
|
||||
if (ref === CurrentLogSpans) return []
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
date: new Date(),
|
||||
} as never)
|
||||
|
||||
expect(create).toHaveBeenCalledWith({ service: "effect-test-struct" })
|
||||
expect(warn).toHaveBeenCalledWith(
|
||||
'{"hello":"world"}',
|
||||
expect.objectContaining({
|
||||
fiber: 123n,
|
||||
}),
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user