2026-03-12 21:00:20 +00:00
|
|
|
import { afterEach, expect, mock, spyOn, test } from "bun:test"
|
2026-03-12 20:43:11 +00:00
|
|
|
import { Cause, Effect } from "effect"
|
|
|
|
|
import { CurrentLogAnnotations, CurrentLogSpans } from "effect/References"
|
|
|
|
|
|
2026-03-12 21:00:20 +00:00
|
|
|
import * as EffectLog from "../../src/util/effect-log"
|
|
|
|
|
import { Log } from "../../src/util/log"
|
|
|
|
|
|
2026-03-12 20:43:11 +00:00
|
|
|
const debug = mock(() => {})
|
|
|
|
|
const info = mock(() => {})
|
|
|
|
|
const warn = mock(() => {})
|
|
|
|
|
const error = mock(() => {})
|
2026-03-12 21:00:20 +00:00
|
|
|
|
|
|
|
|
const logger = {
|
2026-03-12 20:43:11 +00:00
|
|
|
debug,
|
|
|
|
|
info,
|
|
|
|
|
warn,
|
|
|
|
|
error,
|
|
|
|
|
tag() {
|
2026-03-12 21:00:20 +00:00
|
|
|
return logger
|
2026-03-12 20:43:11 +00:00
|
|
|
},
|
|
|
|
|
clone() {
|
2026-03-12 21:00:20 +00:00
|
|
|
return logger
|
2026-03-12 20:43:11 +00:00
|
|
|
},
|
|
|
|
|
time() {
|
|
|
|
|
return {
|
|
|
|
|
stop() {},
|
|
|
|
|
[Symbol.dispose]() {},
|
|
|
|
|
}
|
|
|
|
|
},
|
2026-03-12 21:00:20 +00:00
|
|
|
}
|
2026-03-12 20:43:11 +00:00
|
|
|
|
2026-03-12 21:00:20 +00:00
|
|
|
afterEach(() => {
|
2026-03-12 20:43:11 +00:00
|
|
|
debug.mockClear()
|
|
|
|
|
info.mockClear()
|
|
|
|
|
warn.mockClear()
|
|
|
|
|
error.mockClear()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("EffectLog.layer routes info logs through util/log", async () => {
|
2026-03-12 21:00:20 +00:00
|
|
|
using create = spyOn(Log, "create").mockReturnValue(logger)
|
|
|
|
|
|
2026-03-12 20:43:11 +00:00
|
|
|
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 () => {
|
2026-03-12 21:00:20 +00:00
|
|
|
using create = spyOn(Log, "create").mockReturnValue(logger)
|
|
|
|
|
|
2026-03-12 20:43:11 +00:00
|
|
|
await Effect.runPromise(
|
|
|
|
|
Effect.logInfo("hello").pipe(
|
|
|
|
|
Effect.annotateLogs({ requestId: "req-123" }),
|
|
|
|
|
Effect.withLogSpan("provider-auth"),
|
|
|
|
|
Effect.provide(EffectLog.layer({ service: "effect-test-meta" })),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
2026-03-12 21:00:20 +00:00
|
|
|
expect(create).toHaveBeenCalledWith({ service: "effect-test-meta" })
|
2026-03-12 20:43:11 +00:00
|
|
|
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", () => {
|
2026-03-12 21:00:20 +00:00
|
|
|
using create = spyOn(Log, "create").mockReturnValue(logger)
|
|
|
|
|
const effect = EffectLog.make({ service: "effect-test-struct" })
|
2026-03-12 20:43:11 +00:00
|
|
|
|
2026-03-12 21:00:20 +00:00
|
|
|
effect.log({
|
2026-03-12 20:43:11 +00:00
|
|
|
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)
|
|
|
|
|
|
2026-03-12 21:00:20 +00:00
|
|
|
expect(create).toHaveBeenCalledWith({ service: "effect-test-struct" })
|
2026-03-12 20:43:11 +00:00
|
|
|
expect(warn).toHaveBeenCalledWith(
|
|
|
|
|
'{"hello":"world"}',
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
fiber: 123n,
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
})
|