import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { assert, describe, it } from "@effect/vitest"; import { Effect, Layer, Logger, References, Tracer } from "effect"; import type { EffectTraceRecord } from "./TraceRecord.ts"; import { makeLocalFileTracer } from "./LocalFileTracer.ts"; const makeTestLayer = (tracePath: string) => Layer.mergeAll( Layer.effect( Tracer.Tracer, makeLocalFileTracer({ filePath: tracePath, maxBytes: 1024 * 1024, maxFiles: 2, batchWindowMs: 10_000, }), ), Logger.layer([Logger.tracerLogger], { mergeWithExisting: false }), Layer.succeed(References.MinimumLogLevel, "Info"), ); const readTraceRecords = (tracePath: string): Array => fs .readFileSync(tracePath, "utf8") .trim() .split("\n") .filter((line) => line.length > 0) .map((line) => JSON.parse(line) as EffectTraceRecord); describe("LocalFileTracer", () => { it.effect("writes nested spans to disk and captures log messages as span events", () => Effect.gen(function* () { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-local-tracer-")); const tracePath = path.join(tempDir, "server.trace.ndjson"); try { yield* Effect.scoped( Effect.gen(function* () { const program = Effect.gen(function* () { yield* Effect.annotateCurrentSpan({ "demo.parent": true, }); yield* Effect.logInfo("parent event"); yield* Effect.gen(function* () { yield* Effect.annotateCurrentSpan({ "demo.child": true, }); yield* Effect.logInfo("child event"); }).pipe(Effect.withSpan("child-span")); }).pipe(Effect.withSpan("parent-span")); yield* program.pipe(Effect.provide(makeTestLayer(tracePath))); }), ); const records = readTraceRecords(tracePath); assert.equal(records.length, 2); const parent = records.find((record) => record.name === "parent-span"); const child = records.find((record) => record.name === "child-span"); assert.notEqual(parent, undefined); assert.notEqual(child, undefined); if (!parent || !child) { return; } assert.equal(child.parentSpanId, parent.spanId); assert.equal(parent.attributes["demo.parent"], true); assert.equal(child.attributes["demo.child"], true); assert.equal( parent.events.some((event) => event.name === "parent event"), true, ); assert.equal( child.events.some((event) => event.name === "child event"), true, ); assert.equal( child.events.some((event) => event.attributes["effect.logLevel"] === "INFO"), true, ); } finally { fs.rmSync(tempDir, { recursive: true, force: true }); } }), ); it.effect("serializes interrupted spans with an interrupted exit status", () => Effect.gen(function* () { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-local-tracer-")); const tracePath = path.join(tempDir, "server.trace.ndjson"); try { yield* Effect.scoped( Effect.exit( Effect.interrupt.pipe( Effect.withSpan("interrupt-span"), Effect.provide(makeTestLayer(tracePath)), ), ), ); const records = readTraceRecords(tracePath); assert.equal(records.length, 1); assert.equal(records[0]?.name, "interrupt-span"); assert.equal(records[0]?.exit._tag, "Interrupted"); } finally { fs.rmSync(tempDir, { recursive: true, force: true }); } }), ); });