diff --git a/apps/web/utils/ai/choose-rule/execute.ts b/apps/web/utils/ai/choose-rule/execute.ts index 814393a934..83e63cc806 100644 --- a/apps/web/utils/ai/choose-rule/execute.ts +++ b/apps/web/utils/ai/choose-rule/execute.ts @@ -9,8 +9,6 @@ import { createScopedLogger } from "@/utils/logger"; import { markNeedsReply } from "@/utils/reply-tracker/inbound"; import { internalDateToDate } from "@/utils/date"; -const logger = createScopedLogger("ai-execute-act"); - type ExecutedRuleWithActionItems = Prisma.ExecutedRuleGetPayload<{ include: { actionItems: true }; }>; @@ -27,11 +25,13 @@ export async function executeAct({ userEmail: string; isReplyTrackingRule: boolean; }) { - logger.info("Executing rule", { - userEmail, + const logger = createScopedLogger("ai-execute-act").with({ + email: userEmail, executedRuleId: executedRule.id, ruleId: executedRule.ruleId, isReplyTrackingRule, + threadId: executedRule.threadId, + messageId: executedRule.messageId, }); async function labelActed() { @@ -55,10 +55,7 @@ export async function executeAct({ }); if (pendingRules.count === 0) { - logger.info("Executed rule is not pending or does not exist", { - userEmail, - executedRuleId: executedRule.id, - }); + logger.info("Executed rule is not pending or does not exist"); return; } @@ -88,13 +85,7 @@ export async function executeAct({ gmail, ); } catch (error) { - logger.error("Failed to create reply tracker", { - error, - userId: executedRule.userId, - email: userEmail, - threadId: executedRule.threadId, - messageId: executedRule.messageId, - }); + logger.error("Failed to create reply tracker", { error }); } } @@ -109,17 +100,12 @@ export async function executeAct({ if (updateResult.status === "rejected") { logger.error("Failed to update executed rule", { error: updateResult.reason, - userId: executedRule.userId, - email: userEmail, - ruleId: executedRule.ruleId, }); } if (labelResult.status === "rejected") { logger.error("Failed to label acted", { error: labelResult.reason, - userId: executedRule.userId, - email: userEmail, }); } } diff --git a/apps/web/utils/logger.ts b/apps/web/utils/logger.ts index 1878b96fa5..8b5eb06d96 100644 --- a/apps/web/utils/logger.ts +++ b/apps/web/utils/logger.ts @@ -2,6 +2,8 @@ import { log } from "next-axiom"; import { env } from "@/env"; +export type Logger = ReturnType; + type LogLevel = "info" | "error" | "warn" | "trace"; const colors = { @@ -15,50 +17,66 @@ const colors = { export function createScopedLogger(scope: string) { if (env.NEXT_PUBLIC_AXIOM_TOKEN) return createAxiomLogger(scope); - const formatMessage = (level: LogLevel, message: string, args: unknown[]) => { - const formattedArgs = args - .map((arg) => - typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg), - ) - .join(" "); - - const msg = `[${scope}]: ${message} ${formattedArgs}`; - - if (process.env.NODE_ENV === "development") - return `${colors[level]}${msg}${colors.reset}`; - return msg; - }; + const createLogger = (fields: Record = {}) => { + const formatMessage = ( + level: LogLevel, + message: string, + args: unknown[], + ) => { + const allArgs = [...args]; + if (Object.keys(fields).length > 0) { + allArgs.push(fields); + } - return { - info: (message: string, ...args: unknown[]) => - console.log(formatMessage("info", message, args)), + const formattedArgs = allArgs + .map((arg) => + typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg), + ) + .join(" "); - error: (message: string, ...args: unknown[]) => - console.error(formatMessage("error", message, args)), + const msg = `[${scope}]: ${message} ${formattedArgs}`; - warn: (message: string, ...args: unknown[]) => - console.warn(formatMessage("warn", message, args)), + if (process.env.NODE_ENV === "development") + return `${colors[level]}${msg}${colors.reset}`; + return msg; + }; - trace: (message: string, ...args: unknown[]) => { - if (process.env.NODE_ENV !== "production") { - console.log(formatMessage("trace", message, args)); - } - }, + return { + info: (message: string, ...args: unknown[]) => + console.log(formatMessage("info", message, args)), + error: (message: string, ...args: unknown[]) => + console.error(formatMessage("error", message, args)), + warn: (message: string, ...args: unknown[]) => + console.warn(formatMessage("warn", message, args)), + trace: (message: string, ...args: unknown[]) => { + if (process.env.NODE_ENV !== "production") { + console.log(formatMessage("trace", message, args)); + } + }, + with: (newFields: Record) => + createLogger({ ...fields, ...newFields }), + }; }; + + return createLogger(); } function createAxiomLogger(scope: string) { - return { + const createLogger = (fields: Record = {}) => ({ info: (message: string, args?: Record) => - log.info(message, { scope, ...args }), + log.info(message, { scope, ...fields, ...args }), error: (message: string, args?: Record) => - log.error(message, { scope, ...args }), + log.error(message, { scope, ...fields, ...args }), warn: (message: string, args?: Record) => - log.warn(message, { scope, ...args }), + log.warn(message, { scope, ...fields, ...args }), trace: (message: string, args?: Record) => { if (process.env.NODE_ENV !== "production") { - log.debug(message, { scope, ...args }); + log.debug(message, { scope, ...fields, ...args }); } }, - }; + with: (newFields: Record) => + createLogger({ ...fields, ...newFields }), + }); + + return createLogger(); } diff --git a/apps/web/utils/reply-tracker/inbound.ts b/apps/web/utils/reply-tracker/inbound.ts index 31f556d428..0ecf4e9be2 100644 --- a/apps/web/utils/reply-tracker/inbound.ts +++ b/apps/web/utils/reply-tracker/inbound.ts @@ -15,8 +15,6 @@ import { getEmailForLLM } from "@/utils/get-email-from-message"; import { aiChooseRule } from "@/utils/ai/choose-rule/ai-choose-rule"; import { getReplyTrackingRule } from "@/utils/reply-tracker"; -const logger = createScopedLogger("reply-tracker/inbound"); - export async function markNeedsReply( userId: string, threadId: string, @@ -24,6 +22,14 @@ export async function markNeedsReply( sentAt: Date, gmail: gmail_v1.Gmail, ) { + const logger = createScopedLogger("reply-tracker/inbound").with({ + userId, + threadId, + messageId, + }); + + logger.info("Marking thread as needs reply"); + const { awaitingReplyLabelId, needsReplyLabelId } = await getReplyTrackingLabels(gmail); @@ -69,29 +75,20 @@ export async function markNeedsReply( const [dbResult, removeLabelResult, newLabelResult] = await Promise.allSettled([dbPromise, removeLabelPromise, newLabelPromise]); - const errorOptions = { - userId, - threadId, - messageId, - }; - if (dbResult.status === "rejected") { logger.error("Failed to mark needs reply", { - ...errorOptions, error: dbResult.reason, }); } if (removeLabelResult.status === "rejected") { logger.error("Failed to remove awaiting reply label", { - ...errorOptions, error: removeLabelResult.reason, }); } if (newLabelResult.status === "rejected") { logger.error("Failed to label needs reply", { - ...errorOptions, error: newLabelResult.reason, }); } diff --git a/apps/web/utils/reply-tracker/outbound.ts b/apps/web/utils/reply-tracker/outbound.ts index cd6ef73d1d..1c6b4c9ea4 100644 --- a/apps/web/utils/reply-tracker/outbound.ts +++ b/apps/web/utils/reply-tracker/outbound.ts @@ -5,7 +5,7 @@ import { aiCheckIfNeedsReply } from "@/utils/ai/reply/check-if-needs-reply"; import prisma from "@/utils/prisma"; import { getThreadMessages } from "@/utils/gmail/thread"; import { ThreadTrackerType, type User } from "@prisma/client"; -import { createScopedLogger } from "@/utils/logger"; +import { createScopedLogger, type Logger } from "@/utils/logger"; import { getEmailForLLM } from "@/utils/get-email-from-message"; import { labelAwaitingReply, @@ -15,8 +15,6 @@ import { import { internalDateToDate } from "@/utils/date"; import { getReplyTrackingRule } from "@/utils/reply-tracker"; -const logger = createScopedLogger("outbound-reply"); - export async function handleOutboundReply( user: Pick & UserEmailWithAI, message: ParsedMessage, @@ -42,12 +40,15 @@ export async function handleOutboundReply( const threadMessages = await getThreadMessages(message.threadId, gmail); + const logger = createScopedLogger("reply-tracker/outbound").with({ + email: user.email, + userId: user.id, + messageId: message.id, + threadId: message.threadId, + }); + if (!threadMessages?.length) { - logger.error("No thread messages found", { - email: user.email, - messageId: message.id, - threadId: message.threadId, - }); + logger.error("No thread messages found"); return; } @@ -66,6 +67,7 @@ export async function handleOutboundReply( // if yes, create a tracker if (result.needsReply) { + logger.info("Needs reply. Creating reply tracker outbound"); await createReplyTrackerOutbound({ gmail, userId, @@ -73,6 +75,7 @@ export async function handleOutboundReply( messageId: message.id, awaitingReplyLabelId, sentAt: internalDateToDate(message.internalDate), + logger, }); } else { logger.info("No need to reply"); @@ -86,6 +89,7 @@ async function createReplyTrackerOutbound({ messageId, awaitingReplyLabelId, sentAt, + logger, }: { gmail: gmail_v1.Gmail; userId: string; @@ -93,6 +97,7 @@ async function createReplyTrackerOutbound({ messageId: string; awaitingReplyLabelId: string; sentAt: Date; + logger: Logger; }) { if (!threadId || !messageId) return; @@ -125,22 +130,14 @@ async function createReplyTrackerOutbound({ labelPromise, ]); - const errorOptions = { - userId, - threadId, - messageId, - }; - if (upsertResult.status === "rejected") { logger.error("Failed to upsert reply tracker", { - ...errorOptions, error: upsertResult.reason, }); } if (labelResult.status === "rejected") { logger.error("Failed to label reply tracker", { - ...errorOptions, error: labelResult.reason, }); }