cleaner logging in dev and prod

This commit is contained in:
Amruth Pillai
2026-03-19 12:55:37 +01:00
parent 7858efbd2b
commit bbc17b8995
12 changed files with 234 additions and 114 deletions
+5 -12
View File
@@ -45,23 +45,16 @@ export const sendEmail = async (options: SendEmailOptions) => {
};
if (!transport) {
logger.info("SMTP not configured; skipping email send", {
to: payload.to,
subject: payload.subject,
});
logger.info({ to: payload.to, subject: payload.subject }, "SMTP not configured; skipping email send");
return;
}
try {
await transport.sendMail({ ...options, from });
} catch (error) {
logger.error("SMTP send failed", {
smtpHost: env.SMTP_HOST,
smtpPort: env.SMTP_PORT,
smtpSecure: env.SMTP_SECURE,
to: payload.to,
subject: payload.subject,
error,
});
logger.error(
{ err: error, smtpHost: env.SMTP_HOST, smtpPort: env.SMTP_PORT, to: payload.to, subject: payload.subject },
"SMTP send failed",
);
}
};
+3 -9
View File
@@ -8,17 +8,14 @@ import { getRequestHeaders } from "@tanstack/react-start/server";
import router from "@/integrations/orpc/router";
import { getLocale } from "@/utils/locale";
import { logger } from "@/utils/logger";
import { logServerError } from "@/utils/logger";
export const getORPCClient = createIsomorphicFn()
.server((): RouterClient<typeof router> => {
return createRouterClient(router, {
interceptors: [
onError((error) => {
logger.error("oRPC server client error", {
scope: "server",
error,
});
logServerError("oRPC server client error", error, { scope: "server" });
}),
],
context: async () => {
@@ -45,10 +42,7 @@ export const getORPCClient = createIsomorphicFn()
interceptors: [
onError((error) => {
if (error instanceof DOMException && error.name === "AbortError") return;
logger.error("oRPC browser client error", {
scope: "client",
error,
});
console.warn("[oRPC client]", error instanceof Error ? error.message : error);
}),
],
});
+5 -4
View File
@@ -33,6 +33,7 @@ import {
} from "@/integrations/ai/tools/patch-resume";
import { defaultResumeData, resumeDataSchema } from "@/schema/resume/data";
import { type TailorOutput, tailorOutputSchema } from "@/schema/tailor";
import { logger } from "@/utils/logger";
import { isObject } from "@/utils/sanitize";
const aiExtractionTemplate = {
@@ -183,10 +184,10 @@ function mergeDefaults<T extends Record<string, unknown>, S extends Record<strin
function logAndRethrow(context: string, error: unknown): never {
if (error instanceof Error) {
console.error(`${context}:`, error.message);
logger.error({ err: error }, context);
throw error;
}
console.error(`Unknown error in ${context}:`, error);
logger.error({ err: error }, `Unknown error in ${context}`);
throw new Error(`An unknown error occurred during ${context}.`);
}
@@ -223,11 +224,11 @@ function parseAndValidateResumeJson(resultText: string): ResumeData {
});
} catch (error: unknown) {
if (error instanceof ZodError) {
console.error("Zod Validation Errors:", JSON.stringify(flattenError(error), null, 2));
logger.error({ err: flattenError(error) }, "Zod validation failed during resume parsing");
throw error;
}
console.error("Unknown error:", error);
logger.error({ err: error }, "Unknown error during resume data validation");
throw new Error("An unknown error occurred while validating the merged resume data.");
}
}
+1 -4
View File
@@ -43,10 +43,7 @@ export const authService = {
try {
await db.delete(schema.user).where(eq(schema.user.id, input.userId));
} catch (err) {
logger.error("Failed to delete user record", {
userId: input.userId,
error: err,
});
logger.error({ err, userId: input.userId }, "Failed to delete user record");
throw new ORPCError("INTERNAL_SERVER_ERROR");
}
+1 -6
View File
@@ -72,12 +72,7 @@ async function healthHandler() {
};
if (status === "unhealthy") {
logger.warn("Healthcheck failed", {
route: "/api/health",
database,
printer,
storage,
});
logger.warn({ route: "/api/health", database, printer, storage }, "Healthcheck failed");
}
const headers = new Headers();
+2 -5
View File
@@ -10,7 +10,7 @@ import router from "@/integrations/orpc/router";
import { resumeDataSchema } from "@/schema/resume/data";
import { env } from "@/utils/env";
import { getLocale } from "@/utils/locale";
import { logger } from "@/utils/logger";
import { logServerError } from "@/utils/logger";
const openAPIHandler = new OpenAPIHandler(router, {
plugins: [
@@ -23,10 +23,7 @@ const openAPIHandler = new OpenAPIHandler(router, {
],
interceptors: [
onError((error) => {
logger.error("OpenAPI handler error", {
route: "/api/openapi",
error,
});
logServerError("OpenAPI handler error", error, { route: "/api/openapi" });
}),
],
});
+2 -5
View File
@@ -5,16 +5,13 @@ import { createFileRoute } from "@tanstack/react-router";
import router from "@/integrations/orpc/router";
import { getLocale } from "@/utils/locale";
import { logger } from "@/utils/logger";
import { logServerError } from "@/utils/logger";
const rpcHandler = new RPCHandler(router, {
plugins: [new BatchHandlerPlugin(), new RequestHeadersPlugin(), new StrictGetMethodPlugin()],
interceptors: [
onError((error) => {
logger.error("oRPC server error", {
route: "/api/rpc",
error,
});
logServerError("oRPC server error", error, { route: "/api/rpc" });
}),
],
});
+1 -4
View File
@@ -54,10 +54,7 @@ export const Route = createFileRoute("/mcp/")({
return await transport.handleRequest(request);
} catch (error) {
logger.error("MCP request failed", {
route: "/mcp",
error,
});
logger.error({ err: error, route: "/mcp" }, "MCP request failed");
return Response.json({
id: null,
+60 -62
View File
@@ -1,73 +1,71 @@
type LogLevel = "debug" | "info" | "warn" | "error";
import pino from "pino";
type LogContext = Record<string, unknown> & {
error?: unknown;
};
const isDev = process.env.NODE_ENV !== "production";
const level = process.env.LOG_LEVEL ?? (isDev ? "debug" : "info");
type SerializedError = {
name: string;
message: string;
stack?: string;
};
export const logger = pino({
level,
timestamp: pino.stdTimeFunctions.isoTime,
formatters: {
level: (label) => ({ level: label }),
},
redact: {
paths: [
"apiKey",
"password",
"secret",
"token",
"authorization",
"*.apiKey",
"*.password",
"*.secret",
"*.token",
"error.stack",
],
censor: "[REDACTED]",
},
...(isDev && {
transport: {
target: "pino-pretty",
options: {
colorize: true,
translateTime: "HH:MM:ss.l",
ignore: "pid,hostname",
},
},
}),
});
function serializeError(error: unknown): SerializedError | undefined {
if (!error) return undefined;
const EXPECTED_ERROR_PATTERNS = [
"Unauthorized",
"Not Found",
"UNAUTHORIZED",
"NOT_FOUND",
"State mismatch",
"User not found",
"Credential account not found",
"Invalid API key",
] as const;
if (error instanceof Error) {
return {
name: error.name,
message: error.message,
stack: error.stack,
};
}
if (typeof error === "string") {
return {
name: "Error",
message: error,
};
}
if (typeof error === "object") {
return {
name: "Error",
message: JSON.stringify(error),
};
}
return {
name: "Error",
message: String(error as string),
};
export function isExpectedError(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error);
return EXPECTED_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
}
function log(level: LogLevel, message: string, context: LogContext = {}) {
const { error, ...rest } = context;
const payload = {
ts: new Date().toISOString(),
level,
message,
...rest,
error: serializeError(error),
};
const json = JSON.stringify(payload);
export function getErrorSummary(error: unknown): { name: string; message: string } {
if (error instanceof Error) return { name: error.name, message: error.message };
if (typeof error === "string") return { name: "Error", message: error };
if (typeof error === "object" && error !== null) return { name: "Error", message: JSON.stringify(error) };
return { name: "Error", message: String(error) };
}
if (level === "error") {
console.error(json);
export function logServerError(context: string, error: unknown, extra: Record<string, unknown> = {}): void {
const summary = getErrorSummary(error);
if (isExpectedError(error)) {
logger.warn({ err: summary, ...extra }, context);
return;
}
if (level === "warn") {
console.warn(json);
return;
}
console.log(json);
logger.error({ err: error instanceof Error ? error : summary, ...extra }, context);
}
export const logger = {
debug: (message: string, context?: LogContext) => log("debug", message, context),
info: (message: string, context?: LogContext) => log("info", message, context),
warn: (message: string, context?: LogContext) => log("warn", message, context),
error: (message: string, context?: LogContext) => log("error", message, context),
};