mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-13 22:37:14 +10:00
cleaner logging in dev and prod
This commit is contained in:
@@ -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",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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" });
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -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" });
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -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
@@ -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),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user