| 1 | import { consoleLogFloor, makeConsoleLogger } from "@/Cli/GlobalLog.ts"; |
| 2 | import { makePlainConsoleSink } from "@/Util/ConsoleSink.ts"; |
| 3 | import { describe, expect, it } from "alchemy-test"; |
| 4 | import * as Console from "effect/Console"; |
| 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Logger from "effect/Logger"; |
| 7 | import { MinimumLogLevel } from "effect/References"; |
| 8 | |
| 9 | /** |
| 10 | * The console sink keeps an Info floor by default so `~/.alchemy/logs` stays |
| 11 | * the place full Debug detail lives. `--log-level debug` has to lower that |
| 12 | * floor for the run, or the flag produces zero terminal output (#1231). |
| 13 | */ |
| 14 | describe("consoleLogFloor", { tags: ["unit", "local"] }, () => { |
| 15 | it("defaults to Info", () => { |
| 16 | expect(consoleLogFloor(["profile", "list"])).toBe("Info"); |
| 17 | }); |
| 18 | |
| 19 | it("reads --log-level in both spellings", () => { |
| 20 | expect(consoleLogFloor(["--log-level", "debug", "profile"])).toBe("Debug"); |
| 21 | expect(consoleLogFloor(["profile", "--log-level=trace"])).toBe("Trace"); |
| 22 | expect(consoleLogFloor(["deploy", "--log-level", "WARNING"])).toBe("Warn"); |
| 23 | }); |
| 24 | |
| 25 | it("ignores the flag after a -- separator and unknown values", () => { |
| 26 | expect(consoleLogFloor(["--", "--log-level", "debug"])).toBe("Info"); |
| 27 | expect(consoleLogFloor(["--log-level", "loud"])).toBe("Info"); |
| 28 | expect(consoleLogFloor(["--log-level"])).toBe("Info"); |
| 29 | }); |
| 30 | }); |
| 31 | |
| 32 | describe("makeConsoleLogger", { tags: ["unit", "local"] }, () => { |
| 33 | it.effect("forwards only records at or above the floor", () => |
| 34 | Effect.gen(function* () { |
| 35 | const seen: Array<{ level: string; message: unknown }> = []; |
| 36 | const probe = Logger.make<unknown, void>((options) => { |
| 37 | seen.push({ level: options.logLevel, message: options.message }); |
| 38 | }); |
| 39 | const emit = Effect.gen(function* () { |
| 40 | yield* Effect.logDebug("debug"); |
| 41 | yield* Effect.logInfo("info"); |
| 42 | }).pipe(Effect.provideService(MinimumLogLevel, "Debug")); |
| 43 | |
| 44 | yield* emit.pipe( |
| 45 | Effect.provide(Logger.layer([makeConsoleLogger("Info", probe)])), |
| 46 | ); |
| 47 | expect(seen.map((r) => r.level)).toEqual(["Info"]); |
| 48 | |
| 49 | seen.length = 0; |
| 50 | yield* emit.pipe( |
| 51 | Effect.provide(Logger.layer([makeConsoleLogger("Debug", probe)])), |
| 52 | ); |
| 53 | expect(seen.map((r) => r.level)).toEqual(["Debug", "Info"]); |
| 54 | }), |
| 55 | ); |
| 56 | |
| 57 | it.effect("keeps Effect prefixes without tinting the whole message", () => { |
| 58 | const lines: string[] = []; |
| 59 | const console = Object.assign(Object.create(globalThis.console), { |
| 60 | log: (...args: unknown[]) => lines.push(args.join(" ")), |
| 61 | }); |
| 62 | return Effect.logInfo("plain message").pipe( |
| 63 | Effect.provide(Logger.layer([makeConsoleLogger("Info")])), |
| 64 | Effect.provideService(Console.Console, console), |
| 65 | Effect.andThen( |
| 66 | Effect.sync(() => { |
| 67 | expect(lines).toHaveLength(1); |
| 68 | expect(lines[0]).toMatch( |
| 69 | /^\[\d{2}:\d{2}:\d{2}\.\d{3}\] INFO \(#\d+\): plain message$/, |
| 70 | ); |
| 71 | expect(lines[0]).not.toContain("\x1b["); |
| 72 | }), |
| 73 | ), |
| 74 | ); |
| 75 | }); |
| 76 | |
| 77 | it.effect("colors the Effect prefix but resets before the message", () => { |
| 78 | const lines: string[] = []; |
| 79 | const console = Object.assign(Object.create(globalThis.console), { |
| 80 | log: (...args: unknown[]) => lines.push(args.join(" ")), |
| 81 | }); |
| 82 | return Effect.logInfo("plain message").pipe( |
| 83 | Effect.provide( |
| 84 | Logger.layer([makeConsoleLogger("Info", makePlainConsoleSink(true))]), |
| 85 | ), |
| 86 | Effect.provideService(Console.Console, console), |
| 87 | Effect.andThen( |
| 88 | Effect.sync(() => { |
| 89 | expect(lines).toHaveLength(1); |
| 90 | expect(lines[0]).toContain("\x1b[32mINFO\x1b[0m"); |
| 91 | expect(lines[0]).toContain("\x1b[0mplain message"); |
| 92 | expect(lines[0]).not.toContain("\x1b[36mplain message"); |
| 93 | }), |
| 94 | ), |
| 95 | ); |
| 96 | }); |
| 97 | }); |