117 lines · 3.8 KB · TypeScript
RawDownload
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<EffectTraceRecord> =>  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 });      }    }),  );});