Files
EpicNext-Cms/src/lib/logger.ts
T
openhands 59a45d2165
CI / check (push) Successful in 28s
CI / tests-ui (push) Failing after 32s
CI / tests-unit (push) Successful in 1m43s
CI / tests-quality (push) Successful in 1m14s
CI / tests-integration (push) Successful in 1m50s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
CI / tests-imaging-browsers (push) Successful in 1m56s
refactor: universal logger and remove N+1 catalog queries
- Replace all 39 console.* calls with universal logger (pino server, console browser)
- Set server-only marker on import-badge.ts
- Disable DB query logging in production
- Add bulkInsertCatalogItems: one transaction instead of N per-item inserts
- Add reserveCatalogItemIds helper for bulk ID allocation
- Optimize translateCatalogItems: single inArray batch query instead of N per-item selects
- Test mock updated for logger.debug
- biome clean, tsc clean, 3569 tests passing
2026-10-17 14:29:14 +02:00

96 lines
2.7 KiB
TypeScript

import pino from "pino";
import { env } from "@/env";
import { captureCmsError } from "./error-monitor";
import { getOperationContext } from "./foundation/request-context";
type LogLevel = "debug" | "info" | "warn" | "error";
const isBrowser =
typeof window !== "undefined" && typeof window.console !== "undefined";
const level: LogLevel =
(env.LOG_LEVEL as LogLevel | undefined) ??
(env.NODE_ENV === "production" ? "info" : "debug");
const isProd = env.NODE_ENV === "production";
const isTest = env.NODE_ENV === "test" || process.env.VITEST === "true";
const pinoLogger = pino({
level,
base: { service: "atomcms-next" },
...(isProd || isTest
? {}
: {
transport: {
target: "pino-pretty",
options: { colorize: true, translateTime: "SYS:standard" },
},
}),
});
let requestIdCounter = 0;
export function generateRequestId(): string {
requestIdCounter += 1;
return `${Date.now().toString(36)}-${requestIdCounter.toString(36)}`;
}
function formatMeta(meta: Record<string, unknown> = {}): string {
const keys = Object.keys(meta);
if (keys.length === 0) return "";
return keys
.map((k) => {
const v = meta[k];
return `${k}=${v instanceof Error ? v.message : String(v)}`;
})
.join(" ");
}
/** Universal logger: pino on server, console in browser. Same interface everywhere. */
export const logger = {
debug(message: string, meta: Record<string, unknown> = {}): void {
if (isBrowser) {
console.debug(`[debug] ${message} ${formatMeta(meta)}`.trim());
return;
}
pinoLogger.debug({ ...meta, ...getOperationContext() }, message);
},
info(message: string, meta: Record<string, unknown> = {}): void {
if (isBrowser) {
console.info(`[info] ${message} ${formatMeta(meta)}`.trim());
return;
}
pinoLogger.info({ ...meta, ...getOperationContext() }, message);
},
warn(message: string, meta: Record<string, unknown> = {}): void {
if (isBrowser) {
console.warn(`[warn] ${message} ${formatMeta(meta)}`.trim());
return;
}
pinoLogger.warn({ ...meta, ...getOperationContext() }, message);
},
error(message: string, meta: Record<string, unknown> = {}): string {
if (isBrowser) {
console.error(`[error] ${message} ${formatMeta(meta)}`.trim());
return "";
}
const error =
meta.err instanceof Error
? meta.err
: meta.error instanceof Error
? meta.error
: new Error(
typeof meta.err === "string"
? meta.err
: typeof meta.error === "string"
? meta.error
: message,
);
const operation = getOperationContext();
const context = { ...operation, ...meta, ...operation };
const errorId = captureCmsError("server", message, error, context);
pinoLogger.error({ ...context, errorId }, message);
return errorId;
},
};