Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | 556x 556x 556x 556x 550x 549x 5x 2x 2x 4x 2x 3x 556x 549x 95x 306x 549x 7x 7x 7x 4x 4x 3x 1x 1x 2x 2x 2x 7x 7x 7x 7x 3x 1x 1x 43x 309x 1x 95x 151x | // Structured logger for the Hono API. The single allowed sink for route-side
// observability — Biome blocks raw `console.*` under `src/routes/**`.
//
// API: `logger.info(...args)`, `logger.warn(...args)`, `logger.error(...args)`
// mirrors `console.log/warn/error` so route call sites can migrate by name.
//
// - In dev/test, prints `[level] <args…>` to the console.
// - In preview/production (Cloudflare Workers), emits a single JSON line per
// call: `{"level":"...","message":"...","extra":[...]}` so Workers logpush
// can ingest cleanly. The first string arg becomes `message`; remaining
// args are stashed in `extra` (Errors serialized to plain objects).
//
// The logger never throws. If the environment is unreadable, it falls back to
// dev formatting (verbose but harmless).
type LogLevel = "info" | "warn" | "error";
function isDevEnv(): boolean {
try {
// biome-ignore lint/suspicious/noExplicitAny: globalThis.process is optional on Workers
const env = (globalThis as any).process?.env;
Iif (!env) return true;
if (env.NODE_ENV === "production") return false;
if (env.ENVIRONMENT === "preview" || env.ENVIRONMENT === "production") return false;
return true;
} catch {
return true;
}
}
function serialize(value: unknown): unknown {
if (value instanceof Error) {
// `name` is inherited from Error.prototype (not an own property in V8), so capture it explicitly.
// Custom properties (code, status, details, etc.) are own properties — picked up by getOwnPropertyNames.
const obj: Record<string, unknown> = {
name: value.name,
message: value.message,
stack: value.stack,
};
for (const key of Object.getOwnPropertyNames(value)) {
Iif (!(key in obj)) obj[key] = (value as unknown as Record<string, unknown>)[key];
}
return obj;
}
return value;
}
// The Biome `noConsole` rule is scoped via overrides to `src/routes/**` only,
// so the raw `console.*` calls below are not flagged here. No suppression
// comments are needed (and adding them produces a `suppressions/unused`
// error in CI).
function emit(level: LogLevel, args: unknown[]): void {
if (isDevEnv()) {
if (level === "error") console.error(...args);
else if (level === "warn") console.warn(...args);
else console.log(...args);
return;
}
try {
const [first, ...rest] = args;
let message: string;
let extra: unknown[] | undefined;
if (typeof first === "string") {
message = first;
if (rest.length > 0) extra = rest.map(serialize);
} else if (first instanceof Error) {
// Put the Error in extra; use its message as the log message
message = first.message;
extra = args.map(serialize);
} else if (first !== undefined) {
// Structured object passed directly — avoid double-encoding into message
message = "[Structured Log]";
extra = args.map(serialize);
} else E{
message = "";
}
const entry: Record<string, unknown> = { level, message };
if (extra !== undefined) entry.extra = extra;
const line = JSON.stringify(entry);
if (level === "error") console.error(lineI);
else if (level === "warn") console.warn(line);
else console.log(line);
} catch {
// Fallback if JSON.stringify throws (circular refs, BigInt, etc.)
Iif (level === "error") console.error(...argsI);
else if (level === "warn") console.warn(...args);
else console.log(...args);
}
}
export const logger = {
info: (...args: unknown[]) => emit("info", args),
log: (...args: unknown[]) => emit("info", args),
warn: (...args: unknown[]) => emit("warn", args),
error: (...args: unknown[]) => emit("error", args),
};
export type Logger = typeof logger;
|