diff --git a/apps/web/app/api/outlook/webhook/logger.ts b/apps/web/app/api/outlook/webhook/logger.ts deleted file mode 100644 index 4f71bee759..0000000000 --- a/apps/web/app/api/outlook/webhook/logger.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createScopedLogger } from "@/utils/logger"; - -export const logger = createScopedLogger("outlook/webhook"); diff --git a/apps/web/app/api/outlook/webhook/process-history-item.ts b/apps/web/app/api/outlook/webhook/process-history-item.ts deleted file mode 100644 index d851d56844..0000000000 --- a/apps/web/app/api/outlook/webhook/process-history-item.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { OutlookResourceData } from "@/app/api/outlook/webhook/types"; -import { logger as globalLogger } from "@/app/api/outlook/webhook/logger"; -import { processHistoryItem as processHistoryItemShared } from "@/utils/webhook/process-history-item"; -import type { RuleWithActions } from "@/utils/types"; -import type { EmailAccountWithAI } from "@/utils/llms/types"; -import type { EmailAccount } from "@prisma/client"; -import type { EmailProvider } from "@/utils/email/types"; - -type ProcessHistoryOptions = { - provider: EmailProvider; - rules: RuleWithActions[]; - hasAutomationRules: boolean; - hasAiAccess: boolean; - emailAccount: Pick & - EmailAccountWithAI; -}; - -export async function processHistoryItem( - resourceData: OutlookResourceData, - { - provider, - emailAccount, - hasAutomationRules, - hasAiAccess, - rules, - }: ProcessHistoryOptions, -) { - const messageId = resourceData.id; - const userEmail = emailAccount.email; - - const logger = globalLogger.with({ email: userEmail, messageId }); - - return processHistoryItemShared( - { messageId }, - { - provider, - emailAccount, - hasAutomationRules, - hasAiAccess, - rules, - logger, - }, - ); -} diff --git a/apps/web/app/api/outlook/webhook/process-history.ts b/apps/web/app/api/outlook/webhook/process-history.ts index 85bacdaa8f..178fbd6ea6 100644 --- a/apps/web/app/api/outlook/webhook/process-history.ts +++ b/apps/web/app/api/outlook/webhook/process-history.ts @@ -2,19 +2,21 @@ import { NextResponse } from "next/server"; import { captureException } from "@/utils/error"; import { createEmailProvider } from "@/utils/email/provider"; import type { OutlookResourceData } from "@/app/api/outlook/webhook/types"; -import { processHistoryItem } from "@/app/api/outlook/webhook/process-history-item"; -import { logger } from "@/app/api/outlook/webhook/logger"; +import { processHistoryItem } from "@/utils/webhook/process-history-item"; import { validateWebhookAccount, getWebhookEmailAccount, } from "@/utils/webhook/validate-webhook-account"; +import type { Logger } from "@/utils/logger"; export async function processHistoryForUser({ subscriptionId, resourceData, + logger, }: { subscriptionId: string; resourceData: OutlookResourceData; + logger: Logger; }) { const emailAccount = await getWebhookEmailAccount( { @@ -23,9 +25,17 @@ export async function processHistoryForUser({ logger, ); + logger = logger.with({ + email: emailAccount?.email, + emailAccountId: emailAccount?.id, + }); + const validation = await validateWebhookAccount(emailAccount, logger); if (!validation.success) { + logger.error("Error validating webhook account", { + error: validation.response.status, + }); return validation.response; } @@ -44,16 +54,20 @@ export async function processHistoryForUser({ }); try { - await processHistoryItem(resourceData, { - provider, - hasAutomationRules, - hasAiAccess: userHasAiAccess, - rules: validatedEmailAccount.rules, - emailAccount: { - ...validatedEmailAccount, - account: { provider: accountProvider }, + await processHistoryItem( + { messageId: resourceData.id }, + { + provider, + emailAccount: { + ...validatedEmailAccount, + account: { provider: accountProvider }, + }, + hasAutomationRules, + hasAiAccess: userHasAiAccess, + rules: validatedEmailAccount.rules, + logger, }, - }); + ); return NextResponse.json({ ok: true }); } catch (error) { @@ -68,7 +82,6 @@ export async function processHistoryForUser({ validatedEmailAccount.email, ); logger.error("Error processing webhook", { - subscriptionId, resourceData, email: validatedEmailAccount.email, error: diff --git a/apps/web/app/api/outlook/webhook/route.ts b/apps/web/app/api/outlook/webhook/route.ts index bff2aed907..0017262fab 100644 --- a/apps/web/app/api/outlook/webhook/route.ts +++ b/apps/web/app/api/outlook/webhook/route.ts @@ -2,7 +2,7 @@ import type { z } from "zod"; import { after, NextResponse } from "next/server"; import { withError } from "@/utils/middleware"; import { processHistoryForUser } from "@/app/api/outlook/webhook/process-history"; -import { logger } from "@/app/api/outlook/webhook/logger"; +import { createScopedLogger, type Logger } from "@/utils/logger"; import { env } from "@/env"; import { webhookBodySchema } from "@/app/api/outlook/webhook/types"; import { handleWebhookError } from "@/utils/webhook/error-handler"; @@ -14,6 +14,8 @@ export const POST = withError(async (request) => { const searchParams = new URL(request.url).searchParams; const validationToken = searchParams.get("validationToken"); + const logger = createScopedLogger("outlook/webhook"); + if (validationToken) { logger.info("Received validation request", { validationToken }); return new NextResponse(validationToken, { @@ -65,19 +67,20 @@ export const POST = withError(async (request) => { // Process notifications asynchronously using after() to avoid Microsoft webhook timeout // Microsoft expects a response within 3 seconds - after(() => processNotificationsAsync(notifications)); + after(() => processNotificationsAsync(notifications, logger)); return NextResponse.json({ ok: true }); }); async function processNotificationsAsync( notifications: z.infer["value"], + log: Logger, ) { for (const notification of notifications) { const { subscriptionId, resourceData } = notification; + const logger = log.with({ subscriptionId, messageId: resourceData.id }); logger.info("Processing notification", { - subscriptionId, changeType: notification.changeType, }); @@ -85,6 +88,7 @@ async function processNotificationsAsync( await processHistoryForUser({ subscriptionId, resourceData, + logger, }); } catch (error) { const emailAccount = await getWebhookEmailAccount( @@ -93,7 +97,6 @@ async function processNotificationsAsync( ).catch((error) => { logger.error("Error getting email account", { error: error instanceof Error ? error.message : error, - subscriptionId, }); return null; }); @@ -107,7 +110,6 @@ async function processNotificationsAsync( } else { logger.error("Error processing notification (no email account found)", { error: error instanceof Error ? error.message : error, - subscriptionId, }); } } diff --git a/apps/web/utils/email/google.ts b/apps/web/utils/email/google.ts index b3bcd5408e..b56f3fcfea 100644 --- a/apps/web/utils/email/google.ts +++ b/apps/web/utils/email/google.ts @@ -65,7 +65,7 @@ import type { EmailFilter, EmailSignature, } from "@/utils/email/types"; -import { createScopedLogger } from "@/utils/logger"; +import { createScopedLogger, type Logger } from "@/utils/logger"; import { extractEmailAddress } from "@/utils/email"; import { getGmailSignatures } from "@/utils/gmail/signature-settings"; @@ -923,6 +923,7 @@ export class GmailProvider implements EmailProvider { id: string; conversationId?: string; }; + logger?: Logger; }): Promise { await processHistoryForUser( { @@ -932,7 +933,7 @@ export class GmailProvider implements EmailProvider { { startHistoryId: options.startHistoryId?.toString(), }, - logger, + options.logger || logger, ); } diff --git a/apps/web/utils/email/microsoft.ts b/apps/web/utils/email/microsoft.ts index 630ff54d1c..0555221d07 100644 --- a/apps/web/utils/email/microsoft.ts +++ b/apps/web/utils/email/microsoft.ts @@ -33,7 +33,7 @@ import { import { trashThread } from "@/utils/outlook/trash"; import { markSpam } from "@/utils/outlook/spam"; import { handlePreviousDraftDeletion } from "@/utils/ai/choose-rule/draft-management"; -import { createScopedLogger } from "@/utils/logger"; +import { type Logger, createScopedLogger } from "@/utils/logger"; import { getThreadMessages, getThreadsFromSenderWithSubject, @@ -1241,6 +1241,7 @@ export class OutlookProvider implements EmailProvider { id: string; conversationId?: string; }; + logger?: Logger; }): Promise { if (!options.subscriptionId) { throw new Error( @@ -1254,6 +1255,7 @@ export class OutlookProvider implements EmailProvider { id: options.historyId?.toString() || "0", conversationId: options.startHistoryId?.toString() || null, }, + logger: options.logger || logger, }); } diff --git a/apps/web/utils/email/types.ts b/apps/web/utils/email/types.ts index b459024e67..27ab309ff2 100644 --- a/apps/web/utils/email/types.ts +++ b/apps/web/utils/email/types.ts @@ -2,6 +2,7 @@ import type { ParsedMessage } from "@/utils/types"; import type { InboxZeroLabel } from "@/utils/label"; import type { ThreadsQuery } from "@/app/api/threads/validation"; import type { OutlookFolder } from "@/utils/outlook/folders"; +import type { Logger } from "@/utils/logger"; export interface EmailThread { id: string; @@ -220,6 +221,7 @@ export interface EmailProvider { id: string; conversationId?: string; }; + logger?: Logger; }): Promise; watchEmails(): Promise<{ expirationDate: Date; diff --git a/version.txt b/version.txt index 7ad1d2ad1c..a47b601784 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v2.17.40 +v2.17.41