1import { loadEnv } from "../config/env.js";
2import type { Request } from "express";
3import { otelContext, otelTrace } from "@factory/observability";
4
5export interface Logger {
6 child(bindings: Record<string, unknown>): Logger;
7 debug(msg: string, extra?: Record<string, unknown>): void;
8 info(msg: string, extra?: Record<string, unknown>): void;
9 warn(msg: string, extra?: Record<string, unknown>): void;
10 error(msg: string, extra?: Record<string, unknown>): void;
11}
12
13class ConsoleLogger implements Logger {
14 constructor(private readonly base: Record<string, unknown> = {}) {}
15
16 child(bindings: Record<string, unknown>): Logger {
17 return new ConsoleLogger({ ...this.base, ...bindings });
18 }
19
20 private log(level: string, msg: string, extra?: Record<string, unknown>): void {
21 const env = safeEnv();
22 if (level === "debug" && env.NODE_ENV === "production") return;
23
24
25
26
27 const line: Record<string, unknown> = {
28 level,
29 time: new Date().toISOString(),
30 msg,
31 ...this.base,
32 ...(extra ?? {})
33 };
34 const sc = otelTrace.getSpanContext(otelContext.active());
35 if (sc && sc.traceId && sc.spanId) {
36 line.traceId = sc.traceId;
37 line.spanId = sc.spanId;
38 }
39 const out = JSON.stringify(line);
40 if (level === "error") process.stderr.write(out + "\n");
41 else if (level === "warn") process.stderr.write(out + "\n");
42 else process.stdout.write(out + "\n");
43 }
44
45 debug(msg: string, extra?: Record<string, unknown>): void {
46 this.log("debug", msg, extra);
47 }
48 info(msg: string, extra?: Record<string, unknown>): void {
49 this.log("info", msg, extra);
50 }
51 warn(msg: string, extra?: Record<string, unknown>): void {
52 this.log("warn", msg, extra);
53 }
54 error(msg: string, extra?: Record<string, unknown>): void {
55 this.log("error", msg, extra);
56 }
57}
58
59function safeEnv() {
60 try {
61 return loadEnv();
62 } catch {
63 return { NODE_ENV: "development" };
64 }
65}
66
67export const logger: Logger = new ConsoleLogger({ service: "factory-api" });
68
69export function requestId(req: Request): string {
70 return req.id ?? "unknown";
71}
72