diff --git a/.agents/skills/add-trigger/SKILL.md b/.agents/skills/add-trigger/SKILL.md index 53311498b56..33473112aa0 100644 --- a/.agents/skills/add-trigger/SKILL.md +++ b/.agents/skills/add-trigger/SKILL.md @@ -411,7 +411,7 @@ export const {service}PollingHandler: PollingProviderHandler = { await markWebhookSuccess(webhookId, logger) return 'success' } catch (error) { - logger.error(`[${requestId}] Error processing {service} webhook ${webhookId}:`, error) + logger.error('Error processing {service} webhook:', error) await markWebhookFailed(webhookId, logger) return 'failure' } diff --git a/AGENTS.md b/AGENTS.md index f866dc555d9..c0893c0170d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ This file (also `AGENTS.md`) holds the repo-wide rules. Area detail lives in `.c ## Global Standards - **Package manager**: `bun` and `bunx`, never `npm` and `npx`. -- **Logging**: `createLogger` from `@sim/logger`; `logger.info` / `logger.warn` / `logger.error`, never `console.log`. Inside `withRouteHandler` the logger already carries the request ID — no manual `withMetadata({ requestId })`. +- **Logging**: `createLogger` from `@sim/logger`; `logger.info` / `logger.warn` / `logger.error`, never `console.log`. Messages are static text; ids go in metadata (`logger.info('Sync started', { requestId, documentId })`), never a `[${requestId}]` tag or an interpolated request id, so lines group and filter by field (`check:source-conventions` rule `log-message-tag`). Inside `withRouteHandler` every line already carries the request ID, so a `generateRequestId()` id there needs no field and no manual `withMetadata({ requestId })`. - **Comments**: name things so the code explains itself. TSDoc documents exported APIs and non-obvious modules. An inline `//` is only for a terse, non-obvious *why*, or for a script-enforced `// : ` annotation (`boundary-raw-fetch`, `double-cast-allowed`, `boundary-raw-json`, `untyped-response`, `rq-lint-allow`, `client-boundary-allow`, `utils-lint-allow`, …). History belongs in the commit message. No `====` separators, commented-out code, or ESLint directives (`eslint-disable`/`eslint-enable`; nothing runs ESLint; `check:comment-hygiene` enforces all three). The `/you-might-not-need-a-comment` skill applies this to a diff. - **ID generation**: `generateId()` (UUID v4, the default) or `generateShortId(size?)` (URL-safe, 21 chars by default) from `@sim/utils/id` — never `crypto.randomUUID()`, `nanoid`, or `uuid`. Both use `crypto.getRandomValues()`, so they also work in non-secure (HTTP) browsers. For other randomness, `@sim/utils/random` (`randomInt`, `randomFloat`, `randomItem`, `generateRandomBytes`, `generateRandomHex`) — never `Math.random()` or `crypto.randomBytes()`. - **Common utilities**: use the shared helpers from the `@sim/utils/` subpaths instead of inline implementations (`check:utils` bans most of the inline forms below): diff --git a/CLAUDE.md b/CLAUDE.md index 981af4c0214..6162cd1934b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,7 +5,7 @@ This file (also `AGENTS.md`) holds the repo-wide rules. Area detail lives in `.c ## Global Standards - **Package manager**: `bun` and `bunx`, never `npm` and `npx`. -- **Logging**: `createLogger` from `@sim/logger`; `logger.info` / `logger.warn` / `logger.error`, never `console.log`. Inside `withRouteHandler` the logger already carries the request ID — no manual `withMetadata({ requestId })`. +- **Logging**: `createLogger` from `@sim/logger`; `logger.info` / `logger.warn` / `logger.error`, never `console.log`. Messages are static text; ids go in metadata (`logger.info('Sync started', { requestId, documentId })`), never a `[${requestId}]` tag or an interpolated request id, so lines group and filter by field (`check:source-conventions` rule `log-message-tag`). Inside `withRouteHandler` every line already carries the request ID, so a `generateRequestId()` id there needs no field and no manual `withMetadata({ requestId })`. - **Comments**: name things so the code explains itself. TSDoc documents exported APIs and non-obvious modules. An inline `//` is only for a terse, non-obvious *why*, or for a script-enforced `// : ` annotation (`boundary-raw-fetch`, `double-cast-allowed`, `boundary-raw-json`, `untyped-response`, `rq-lint-allow`, `client-boundary-allow`, `utils-lint-allow`, …). History belongs in the commit message. No `====` separators, commented-out code, or ESLint directives (`eslint-disable`/`eslint-enable`; nothing runs ESLint; `check:comment-hygiene` enforces all three). The `/you-might-not-need-a-comment` skill applies this to a diff. - **ID generation**: `generateId()` (UUID v4, the default) or `generateShortId(size?)` (URL-safe, 21 chars by default) from `@sim/utils/id` — never `crypto.randomUUID()`, `nanoid`, or `uuid`. Both use `crypto.getRandomValues()`, so they also work in non-secure (HTTP) browsers. For other randomness, `@sim/utils/random` (`randomInt`, `randomFloat`, `randomItem`, `generateRandomBytes`, `generateRandomHex`) — never `Math.random()` or `crypto.randomBytes()`. - **Common utilities**: use the shared helpers from the `@sim/utils/` subpaths instead of inline implementations (`check:utils` bans most of the inline forms below): diff --git a/apps/sim/app/api/auth/oauth/credentials/route.ts b/apps/sim/app/api/auth/oauth/credentials/route.ts index 318e7c7ac98..bffd86acf57 100644 --- a/apps/sim/app/api/auth/oauth/credentials/route.ts +++ b/apps/sim/app/api/auth/oauth/credentials/route.ts @@ -7,7 +7,6 @@ import { type NextRequest, NextResponse } from 'next/server' import { oauthCredentialsQuerySchema } from '@/lib/api/contracts/credentials' import { getValidationErrorMessage } from '@/lib/api/server' import { AuthType, type AuthTypeValue, checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { canUseCredential, getCredentialActorContext } from '@/lib/credentials/access' import { syncWorkspaceOAuthCredentialsForUser } from '@/lib/credentials/oauth' @@ -84,8 +83,6 @@ async function integrationsWithheldFromSession( * Get credentials for a specific provider */ export const GET = withRouteHandler(async (request: NextRequest) => { - const requestId = generateRequestId() - try { const { searchParams } = new URL(request.url) const rawQuery = { @@ -100,11 +97,11 @@ export const GET = withRouteHandler(async (request: NextRequest) => { if (!parseResult.success) { const refinementError = parseResult.error.issues.find((err) => err.code === 'custom') if (refinementError) { - logger.warn(`[${requestId}] Invalid query parameters: ${refinementError.message}`) + logger.warn(`Invalid query parameters: ${refinementError.message}`) return NextResponse.json({ error: refinementError.message }, { status: 400 }) } - logger.warn(`[${requestId}] Invalid query parameters`, { + logger.warn('Invalid query parameters', { errors: parseResult.error.issues, }) @@ -119,7 +116,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { // Authenticate requester (supports session and internal JWT) const authResult = await checkSessionOrInternalAuth(request) if (!authResult.success || !authResult.userId) { - logger.warn(`[${requestId}] Unauthenticated credentials request rejected`) + logger.warn('Unauthenticated credentials request rejected') return NextResponse.json({ error: 'User not authenticated' }, { status: 401 }) } const requesterUserId = authResult.userId @@ -132,7 +129,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { action: 'read', }) if (!workflowAuthorization.allowed) { - logger.warn(`[${requestId}] Forbidden credentials request for workflow`, { + logger.warn('Forbidden credentials request for workflow', { requesterUserId, workflowId, status: workflowAuthorization.status, @@ -378,7 +375,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ credentials: [] }, { status: 200 }) } catch (error) { - logger.error(`[${requestId}] Error fetching OAuth credentials`, error) + logger.error('Error fetching OAuth credentials', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/auth/oauth/token/route.ts b/apps/sim/app/api/auth/oauth/token/route.ts index e6f7015b1bf..99e7dcc986b 100644 --- a/apps/sim/app/api/auth/oauth/token/route.ts +++ b/apps/sim/app/api/auth/oauth/token/route.ts @@ -33,7 +33,7 @@ const logger = createLogger('OAuthTokenAPI') export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() - logger.info(`[${requestId}] OAuth token API POST request received`) + logger.info('OAuth token API POST request received') try { const parsed = await parseRequest( @@ -42,7 +42,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { {}, { validationErrorResponse: (error) => { - logger.warn(`[${requestId}] Invalid token request`, { errors: error.issues }) + logger.warn('Invalid token request', { errors: error.issues }) return NextResponse.json( { error: getValidationErrorMessage(error, 'Validation failed') }, { status: 400 } @@ -64,14 +64,14 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const callerUserId = parsed.data.query.userId if (credentialAccountUserId && providerId) { - logger.info(`[${requestId}] Fetching token by credentialAccountUserId + providerId`, { + logger.info('Fetching token by credentialAccountUserId + providerId', { credentialAccountUserId, providerId, }) const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!auth.success || auth.authType !== AuthType.SESSION || !auth.userId) { - logger.warn(`[${requestId}] Unauthorized request for credentialAccountUserId path`, { + logger.warn('Unauthorized request for credentialAccountUserId path', { success: auth.success, authType: auth.authType, }) @@ -80,7 +80,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { if (auth.userId !== credentialAccountUserId) { logger.warn( - `[${requestId}] User ${auth.userId} attempted to access credentials for ${credentialAccountUserId}` + `User ${auth.userId} attempted to access credentials for ${credentialAccountUserId}` ) return NextResponse.json({ error: 'Unauthorized' }, { status: 403 }) } @@ -117,7 +117,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ accessToken }, { status: 200 }) } catch (error) { const message = getErrorMessage(error, 'Failed to get OAuth token') - logger.warn(`[${requestId}] OAuth token error: ${message}`) + logger.warn(`OAuth token error: ${message}`) return NextResponse.json({ error: message }, { status: 403 }) } } @@ -148,7 +148,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json(result.token, { status: 200 }) } catch (error) { - logger.error(`[${requestId}] Error getting access token`, error) + logger.error('Error getting access token', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) @@ -166,7 +166,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { {}, { validationErrorResponse: (error) => { - logger.warn(`[${requestId}] Invalid query parameters`, { errors: error.issues }) + logger.warn('Invalid query parameters', { errors: error.issues }) return NextResponse.json( { error: getValidationErrorMessage(error, 'Validation failed') }, { status: 400 } @@ -203,7 +203,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { } if (!credential.accessToken) { - logger.warn(`[${requestId}] No access token available for credential`) + logger.warn('No access token available for credential') return NextResponse.json({ error: 'No access token available' }, { status: 400 }) } @@ -225,7 +225,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { return NextResponse.json(result.token, { status: 200 }) } catch (error) { - logger.error(`[${requestId}] Error fetching access token`, error) + logger.error('Error fetching access token', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/billing/update-cost/route.ts b/apps/sim/app/api/billing/update-cost/route.ts index 97d0f31fd81..76ca4f278b6 100644 --- a/apps/sim/app/api/billing/update-cost/route.ts +++ b/apps/sim/app/api/billing/update-cost/route.ts @@ -196,12 +196,12 @@ async function updateCostInner(req: NextRequest, span: Span): Promise { - logger.warn(`[${requestId}] Invalid request body`, { + logger.warn('Invalid request body', { errors: error.issues, }) span.setAttribute(TraceAttr.BillingOutcome, BillingRouteOutcome.InvalidBody) @@ -324,7 +324,7 @@ async function updateCostInner(req: NextRequest, span: Span): Promise }) => { const { identifier } = await context.params - const requestId = generateRequestId() try { const parsed = await parseRequest(verifyChatEmailOtpContract, request, context, { @@ -184,7 +183,7 @@ export const PUT = withRouteHandler( .limit(1) if (deploymentResult.length === 0) { - logger.warn(`[${requestId}] Chat not found for identifier: ${identifier}`) + logger.warn(`Chat not found for identifier: ${identifier}`) return createCodedErrorResponse('Chat not found', 404) } @@ -206,14 +205,14 @@ export const PUT = withRouteHandler( if (attempts >= MAX_OTP_ATTEMPTS) { await deleteOTP('chat', deployment.id, email) - logger.warn(`[${requestId}] OTP already locked out for ${email}`) + logger.warn(`OTP already locked out for ${email}`) return createCodedErrorResponse('Too many failed attempts. Please request a new code.', 429) } if (storedOTP !== otp) { const result = await incrementOTPAttempts('chat', deployment.id, email, storedValue) if (result === 'locked') { - logger.warn(`[${requestId}] OTP invalidated after max failed attempts for ${email}`) + logger.warn(`OTP invalidated after max failed attempts for ${email}`) return createCodedErrorResponse( 'Too many failed attempts. Please request a new code.', 429 @@ -238,7 +237,7 @@ export const PUT = withRouteHandler( return response } catch (error) { - logger.error(`[${requestId}] Error verifying OTP:`, error) + logger.error('Error verifying OTP:', error) return createCodedErrorResponse('Failed to process request', 500) } } diff --git a/apps/sim/app/api/chat/[identifier]/route.ts b/apps/sim/app/api/chat/[identifier]/route.ts index f2f00ac62ee..ae99d4ade4d 100644 --- a/apps/sim/app/api/chat/[identifier]/route.ts +++ b/apps/sim/app/api/chat/[identifier]/route.ts @@ -152,14 +152,14 @@ export const POST = withRouteHandler( .limit(1) if (deploymentResult.length === 0) { - logger.warn(`[${requestId}] Chat not found for identifier: ${identifier}`) + logger.warn(`Chat not found for identifier: ${identifier}`) return createCodedErrorResponse('Chat not found', 404) } const deployment = deploymentResult[0] if (!deployment.isActive) { - logger.warn(`[${requestId}] Chat is not active: ${identifier}`) + logger.warn(`Chat is not active: ${identifier}`) return createCodedErrorResponse('This chat is currently unavailable', 403) } @@ -231,7 +231,7 @@ export const POST = withRouteHandler( }) if (!preprocessResult.success) { - logger.warn(`[${requestId}] Preprocessing failed: ${preprocessResult.error?.message}`) + logger.warn(`Preprocessing failed: ${preprocessResult.error?.message}`) return createCodedErrorResponse( preprocessResult.error?.message || 'Failed to process request', preprocessResult.error?.statusCode || 500 @@ -242,7 +242,7 @@ export const POST = withRouteHandler( const resolvedActorUserId = actorUserId! const workspaceId = workflowRecord?.workspaceId if (!workspaceId) { - logger.error(`[${requestId}] Workflow ${deployment.workflowId} has no workspaceId`) + logger.error(`Workflow ${deployment.workflowId} has no workspaceId`) // preprocessExecution reserved a billing concurrency slot; release it on // this early exit since no LoggingSession will finalize to free it. await releaseExecutionSlot(executionId) @@ -289,10 +289,10 @@ export const POST = withRouteHandler( if (uploadedFiles.length > 0) { workflowInput.files = uploadedFiles - logger.info(`[${requestId}] Successfully processed ${uploadedFiles.length} files`) + logger.info(`Successfully processed ${uploadedFiles.length} files`) } } catch (fileError: any) { - logger.error(`[${requestId}] Failed to process chat files:`, fileError) + logger.error('Failed to process chat files:', fileError) await loggingSession.safeStart({ userId: resolvedActorUserId, @@ -402,14 +402,14 @@ export const POST = withRouteHandler( }) return streamResponse } catch (error: any) { - logger.error(`[${requestId}] Error processing chat request:`, error) + logger.error('Error processing chat request:', error) // Setup failed before the workflow stream took over slot release; // free the reserved billing slot (idempotent if already released). await releaseExecutionSlot(executionId) return createCodedErrorResponse(error.message || 'Failed to process request', 500) } } catch (error: any) { - logger.error(`[${requestId}] Error processing chat request:`, error) + logger.error('Error processing chat request:', error) return createCodedErrorResponse(error.message || 'Failed to process request', 500) } finally { ticket.release() @@ -443,28 +443,26 @@ export const GET = withRouteHandler( .limit(1) if (deploymentResult.length === 0) { - logger.warn(`[${requestId}] Chat not found for identifier: ${identifier}`) + logger.warn(`Chat not found for identifier: ${identifier}`) return createCodedErrorResponse('Chat not found', 404) } const deployment = deploymentResult[0] if (!deployment.isActive) { - logger.warn(`[${requestId}] Chat is not active: ${identifier}`) + logger.warn(`Chat is not active: ${identifier}`) return createCodedErrorResponse('This chat is currently unavailable', 403) } const authResult = await validateChatAuth(requestId, deployment, request) if (!authResult.authorized) { - logger.info( - `[${requestId}] Authentication required for chat: ${identifier}, type: ${deployment.authType}` - ) + logger.info(`Authentication required for chat: ${identifier}, type: ${deployment.authType}`) return createCodedErrorResponse(authResult.error || 'Authentication required', 401) } return createSuccessResponse(toChatConfigResponse(deployment)) } catch (error: any) { - logger.error(`[${requestId}] Error fetching chat info:`, error) + logger.error('Error fetching chat info:', error) return createCodedErrorResponse(error.message || 'Failed to fetch chat information', 500) } } diff --git a/apps/sim/app/api/chat/[identifier]/sso/route.ts b/apps/sim/app/api/chat/[identifier]/sso/route.ts index 82a525b75f6..725a2dd97b9 100644 --- a/apps/sim/app/api/chat/[identifier]/sso/route.ts +++ b/apps/sim/app/api/chat/[identifier]/sso/route.ts @@ -8,7 +8,7 @@ import { parseRequest } from '@/lib/api/server' import type { TokenBucketConfig } from '@/lib/core/rate-limiter' import { RateLimiter } from '@/lib/core/rate-limiter' import { isEmailAllowed } from '@/lib/core/security/deployment' -import { generateRequestId, getClientIp } from '@/lib/core/utils/request' +import { getClientIp } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { createCodedErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' @@ -39,8 +39,6 @@ function rateLimited(retryAfterMs: number | undefined, fallbackMs: number) { export const POST = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ identifier: string }> }) => { - const requestId = generateRequestId() - const ip = getClientIp(request) if (ip) { const ipRateLimit = await rateLimiter.checkRateLimitDirect( @@ -49,7 +47,7 @@ export const POST = withRouteHandler( { failClosed: true } ) if (!ipRateLimit.allowed) { - logger.warn(`[${requestId}] SSO eligibility rate limit exceeded from ${ip}`) + logger.warn(`SSO eligibility rate limit exceeded from ${ip}`) return rateLimited(ipRateLimit.retryAfterMs, SSO_IP_RATE_LIMIT.refillIntervalMs) } } @@ -72,7 +70,7 @@ export const POST = withRouteHandler( .limit(1) if (!deployment || !deployment.isActive) { - logger.warn(`[${requestId}] SSO check on missing/inactive chat: ${identifier}`) + logger.warn(`SSO check on missing/inactive chat: ${identifier}`) return createCodedErrorResponse('Chat not found', 404) } @@ -86,7 +84,7 @@ export const POST = withRouteHandler( { failClosed: true } ) if (!resourceRateLimit.allowed) { - logger.warn(`[${requestId}] SSO eligibility resource rate limit exceeded`, { + logger.warn('SSO eligibility resource rate limit exceeded', { deploymentId: deployment.id, }) return rateLimited(resourceRateLimit.retryAfterMs, SSO_RESOURCE_RATE_LIMIT.refillIntervalMs) diff --git a/apps/sim/app/api/contact/route.ts b/apps/sim/app/api/contact/route.ts index 69df8e97457..066ffaac11c 100644 --- a/apps/sim/app/api/contact/route.ts +++ b/apps/sim/app/api/contact/route.ts @@ -11,7 +11,7 @@ import { env } from '@/lib/core/config/env' import type { TokenBucketConfig } from '@/lib/core/rate-limiter' import { RateLimiter } from '@/lib/core/rate-limiter' import { isTurnstileConfigured, verifyTurnstileToken } from '@/lib/core/security/turnstile' -import { generateRequestId, getClientIp } from '@/lib/core/utils/request' +import { getClientIp } from '@/lib/core/utils/request' import { getEmailDomain } from '@/lib/core/utils/urls' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { sendEmail } from '@/lib/messaging/email/mailer' @@ -50,12 +50,10 @@ const TOO_MANY_REQUESTS_RESPONSE = { error: 'Too many requests. Please try again * tokens. */ export const POST = withRouteHandler(async (req: NextRequest) => { - const requestId = generateRequestId() - try { const ip = getClientIp(req) if (!ip) { - logger.warn(`[${requestId}] Unable to resolve client IP for public rate limit`) + logger.warn('Unable to resolve client IP for public rate limit') return NextResponse.json(TOO_MANY_REQUESTS_RESPONSE, { status: 429 }) } const storageKey = `public:contact:${ip}` @@ -65,7 +63,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { ) if (!allowed) { - logger.warn(`[${requestId}] Rate limit exceeded for IP ${ip}`, { remaining, resetAt }) + logger.warn(`Rate limit exceeded for IP ${ip}`, { remaining, resetAt }) return NextResponse.json(TOO_MANY_REQUESTS_RESPONSE, { status: 429, headers: { 'Retry-After': String(Math.ceil((resetAt.getTime() - Date.now()) / 1000)) }, @@ -74,7 +72,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const parsed = await parseRequest(submitContactContract, req, {}) if (!parsed.success) { - logger.warn(`[${requestId}] Invalid contact request data`) + logger.warn('Invalid contact request data') return parsed.response } @@ -82,7 +80,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { parsed.data.body if (typeof website === 'string' && website.trim().length > 0) { - logger.warn(`[${requestId}] Honeypot triggered, discarding`, { ip }) + logger.warn('Honeypot triggered, discarding', { ip }) return NextResponse.json(SUCCESS_RESPONSE, { status: 201 }) } @@ -96,7 +94,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { if (verification.success) { captchaVerified = true } else if (!verification.transportError) { - logger.warn(`[${requestId}] Captcha verification failed`, { + logger.warn('Captcha verification failed', { ip, errorCodes: verification.errorCodes, }) @@ -105,10 +103,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { { status: 400 } ) } else { - logger.warn( - `[${requestId}] Captcha transport error, falling back to no-captcha rate limit`, - { ip } - ) + logger.warn('Captcha transport error, falling back to no-captcha rate limit', { ip }) } } @@ -120,7 +115,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { { failClosed: true } ) if (!nocaptchaAllowed) { - logger.warn(`[${requestId}] Rate limit rejected (no-captcha) for IP ${ip}`) + logger.warn(`Rate limit rejected (no-captcha) for IP ${ip}`) return NextResponse.json(TOO_MANY_REQUESTS_RESPONSE, { status: 429 }) } } @@ -128,7 +123,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const topicLabel = getContactTopicLabel(topic) - logger.info(`[${requestId}] Processing contact request`, { + logger.info('Processing contact request', { email: `${email.substring(0, 3)}***`, topic, }) @@ -157,11 +152,11 @@ ${message} }) if (!emailResult.success) { - logger.error(`[${requestId}] Error sending contact request email`, emailResult.message) + logger.error('Error sending contact request email', emailResult.message) return NextResponse.json({ error: 'Failed to send message' }, { status: 500 }) } - logger.info(`[${requestId}] Contact request email sent successfully`) + logger.info('Contact request email sent successfully') try { const confirmationHtml = await renderHelpConfirmationEmail( @@ -178,17 +173,17 @@ ${message} emailType: 'transactional', }) } catch (err) { - logger.warn(`[${requestId}] Failed to send contact confirmation email`, err) + logger.warn('Failed to send contact confirmation email', err) } return NextResponse.json(SUCCESS_RESPONSE, { status: 201 }) } catch (error) { if (error instanceof Error && error.message.includes('not configured')) { - logger.error(`[${requestId}] Email service configuration error`, error) + logger.error('Email service configuration error', error) return NextResponse.json({ error: 'Email service configuration error.' }, { status: 500 }) } - logger.error(`[${requestId}] Error processing contact request`, error) + logger.error('Error processing contact request', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/copilot/confirm/route.ts b/apps/sim/app/api/copilot/confirm/route.ts index 286b9e2da3f..02d96580eeb 100644 --- a/apps/sim/app/api/copilot/confirm/route.ts +++ b/apps/sim/app/api/copilot/confirm/route.ts @@ -502,7 +502,8 @@ export const POST = withRouteHandler((req: NextRequest) => { } if (reconciledOutcome !== 'updated') { - logger.error(`[${tracker.requestId}] Failed to update tool call status`, { + logger.error('Failed to update tool call status', { + requestId: tracker.requestId, userId: authenticatedUserId, toolCallId, status: effectiveStatus, @@ -523,7 +524,8 @@ export const POST = withRouteHandler((req: NextRequest) => { } catch (error) { const duration = tracker.getDuration() - logger.error(`[${tracker.requestId}] Unexpected error:`, { + logger.error('Unexpected error:', { + requestId: tracker.requestId, duration, error: getErrorMessage(error, 'Unknown error'), stack: error instanceof Error ? error.stack : undefined, diff --git a/apps/sim/app/api/copilot/feedback/route.ts b/apps/sim/app/api/copilot/feedback/route.ts index e973145c4f0..bc42f69c40a 100644 --- a/apps/sim/app/api/copilot/feedback/route.ts +++ b/apps/sim/app/api/copilot/feedback/route.ts @@ -40,7 +40,8 @@ export const POST = withRouteHandler(async (req: NextRequest) => { { invalidJson: 'throw', validationErrorResponse: (error) => { - logger.error(`[${tracker.requestId}] Validation error:`, { + logger.error('Validation error:', { + requestId: tracker.requestId, duration: tracker.getDuration(), errors: error.issues, }) @@ -53,7 +54,8 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const { chatId, userQuery, agentResponse, isPositiveFeedback, feedback, workflowYaml } = parsed.data.body - logger.info(`[${tracker.requestId}] Processing copilot feedback submission`, { + logger.info('Processing copilot feedback submission', { + requestId: tracker.requestId, userId: authenticatedUserId, chatId, isPositiveFeedback, @@ -78,7 +80,8 @@ export const POST = withRouteHandler(async (req: NextRequest) => { }) .returning() - logger.info(`[${tracker.requestId}] Successfully saved copilot feedback`, { + logger.info('Successfully saved copilot feedback', { + requestId: tracker.requestId, feedbackId: feedbackRecord.feedbackId, userId: authenticatedUserId, isPositive: isPositiveFeedback, @@ -103,7 +106,8 @@ export const POST = withRouteHandler(async (req: NextRequest) => { } catch (error) { const duration = tracker.getDuration() - logger.error(`[${tracker.requestId}] Error submitting copilot feedback:`, { + logger.error('Error submitting copilot feedback:', { + requestId: tracker.requestId, duration, error: getErrorMessage(error, 'Unknown error'), stack: error instanceof Error ? error.stack : undefined, @@ -145,7 +149,9 @@ export const GET = withRouteHandler(async () => { .from(copilotFeedback) .where(eq(copilotFeedback.userId, authenticatedUserId)) - logger.info(`[${tracker.requestId}] Retrieved ${feedbackRecords.length} feedback records`) + logger.info(`Retrieved ${feedbackRecords.length} feedback records`, { + requestId: tracker.requestId, + }) return NextResponse.json({ success: true, @@ -156,7 +162,7 @@ export const GET = withRouteHandler(async () => { }, }) } catch (error) { - logger.error(`[${tracker.requestId}] Error retrieving copilot feedback:`, error) + logger.error('Error retrieving copilot feedback:', error, { requestId: tracker.requestId }) return createInternalServerErrorResponse('Failed to retrieve feedback') } }) diff --git a/apps/sim/app/api/copilot/tool-permission/route.ts b/apps/sim/app/api/copilot/tool-permission/route.ts index 86691d296cd..a9121dbc06a 100644 --- a/apps/sim/app/api/copilot/tool-permission/route.ts +++ b/apps/sim/app/api/copilot/tool-permission/route.ts @@ -202,7 +202,8 @@ export const POST = withRouteHandler((req: NextRequest) => { return NextResponse.json({ success: true, results }) } catch (error) { - logger.error(`[${tracker.requestId}] Unexpected error:`, { + logger.error('Unexpected error:', { + requestId: tracker.requestId, duration: tracker.getDuration(), error: getErrorMessage(error, 'Unknown error'), stack: error instanceof Error ? error.stack : undefined, diff --git a/apps/sim/app/api/demo-requests/route.ts b/apps/sim/app/api/demo-requests/route.ts index 2c5a1905364..dd101b3b3e9 100644 --- a/apps/sim/app/api/demo-requests/route.ts +++ b/apps/sim/app/api/demo-requests/route.ts @@ -9,7 +9,7 @@ import { parseRequest } from '@/lib/api/server' import { env } from '@/lib/core/config/env' import type { TokenBucketConfig } from '@/lib/core/rate-limiter' import { RateLimiter } from '@/lib/core/rate-limiter' -import { generateRequestId, getClientIp } from '@/lib/core/utils/request' +import { getClientIp } from '@/lib/core/utils/request' import { getEmailDomain } from '@/lib/core/utils/urls' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { sendEmail } from '@/lib/messaging/email/mailer' @@ -25,12 +25,10 @@ const PUBLIC_ENDPOINT_RATE_LIMIT: TokenBucketConfig = { } export const POST = withRouteHandler(async (req: NextRequest) => { - const requestId = generateRequestId() - try { const ip = getClientIp(req) if (!ip) { - logger.warn(`[${requestId}] Unable to resolve client IP for public rate limit`) + logger.warn('Unable to resolve client IP for public rate limit') return NextResponse.json( { error: 'Too many requests. Please try again later.' }, { status: 429 } @@ -43,7 +41,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { ) if (!allowed) { - logger.warn(`[${requestId}] Rate limit exceeded for IP ${ip}`, { remaining, resetAt }) + logger.warn(`Rate limit exceeded for IP ${ip}`, { remaining, resetAt }) return NextResponse.json( { error: 'Too many requests. Please try again later.' }, { @@ -55,7 +53,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const parsed = await parseRequest(submitDemoRequestContract, req, {}) if (!parsed.success) { - logger.warn(`[${requestId}] Invalid demo request data`) + logger.warn('Invalid demo request data') return parsed.response } @@ -63,7 +61,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { parsed.data.body const attribution = formatAttributionForNotification((name) => req.cookies.get(name)?.value) - logger.info(`[${requestId}] Processing demo request`, { + logger.info('Processing demo request', { email: `${companyEmail.substring(0, 3)}***`, companySize, }) @@ -89,18 +87,18 @@ ${attribution ? `\nAttribution:\n${attribution}\n` : ''}` }) if (!emailResult.success) { - logger.error(`[${requestId}] Error sending demo request email`, emailResult.message) + logger.error('Error sending demo request email', emailResult.message) return NextResponse.json({ error: 'Failed to submit request' }, { status: 500 }) } - logger.info(`[${requestId}] Demo request email sent successfully`) + logger.info('Demo request email sent successfully') return NextResponse.json( { success: true, message: 'Thanks! Our team will reach out shortly.' }, { status: 201 } ) } catch (error) { - logger.error(`[${requestId}] Error processing demo request`, error) + logger.error('Error processing demo request', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/environment/route.ts b/apps/sim/app/api/environment/route.ts index dbef90ae0e7..f5882faae0f 100644 --- a/apps/sim/app/api/environment/route.ts +++ b/apps/sim/app/api/environment/route.ts @@ -10,7 +10,6 @@ import { savePersonalEnvironmentContract } from '@/lib/api/contracts/environment import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { lockPersonalEnvMap } from '@/lib/credentials/env-locks' import { syncPersonalEnvCredentialsForUser } from '@/lib/credentials/environment' @@ -20,12 +19,10 @@ import { captureServerEvent } from '@/lib/posthog/server' const logger = createLogger('EnvironmentAPI') export const POST = withRouteHandler(async (req: NextRequest) => { - const requestId = generateRequestId() - try { const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized environment variables update attempt`) + logger.warn('Unauthorized environment variables update attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -35,7 +32,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { {}, { validationErrorResponse: (error) => { - logger.warn(`[${requestId}] Invalid environment variables data`, { errors: error.issues }) + logger.warn('Invalid environment variables data', { errors: error.issues }) return NextResponse.json( { error: 'Invalid request data', details: error.issues }, { status: 400 } @@ -110,18 +107,16 @@ export const POST = withRouteHandler(async (req: NextRequest) => { return NextResponse.json({ success: true }) } catch (error) { - logger.error(`[${requestId}] Error updating environment variables`, error) + logger.error('Error updating environment variables', error) return NextResponse.json({ error: 'Failed to update environment variables' }, { status: 500 }) } }) export const GET = withRouteHandler(async () => { - const requestId = generateRequestId() - try { const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized environment variables access attempt`) + logger.warn('Unauthorized environment variables access attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -145,7 +140,7 @@ export const GET = withRouteHandler(async () => { const { decrypted } = await decryptSecret(encryptedValue) return [key, { key, value: decrypted }] as const } catch (error) { - logger.error(`[${requestId}] Error decrypting variable ${key}`, error) + logger.error(`Error decrypting variable ${key}`, error) return [key, { key, value: '' }] as const } }) @@ -157,7 +152,7 @@ export const GET = withRouteHandler(async () => { return NextResponse.json({ data: decryptedVariables }, { status: 200 }) } catch (error) { - logger.error(`[${requestId}] Environment fetch error`, error) + logger.error('Environment fetch error', error) return NextResponse.json({ error: toError(error).message }, { status: 500 }) } }) diff --git a/apps/sim/app/api/files/authorization.ts b/apps/sim/app/api/files/authorization.ts index 41234edc614..f3cb17a9143 100644 --- a/apps/sim/app/api/files/authorization.ts +++ b/apps/sim/app/api/files/authorization.ts @@ -797,12 +797,12 @@ export async function assertToolFileAccess( routeLogger: ReturnType ): Promise { if (typeof key !== 'string' || key.length === 0) { - routeLogger.warn(`[${requestId}] File access check rejected: missing key`) + routeLogger.warn('File access check rejected: missing key', { requestId }) return NextResponse.json({ success: false, error: 'File not found' }, { status: 404 }) } const hasAccess = await verifyFileAccess(key, userId) if (!hasAccess) { - routeLogger.warn(`[${requestId}] File access denied for user`, { userId, key }) + routeLogger.warn('File access denied for user', { requestId, userId, key }) return NextResponse.json({ success: false, error: 'File not found' }, { status: 404 }) } return null diff --git a/apps/sim/app/api/folders/[id]/duplicate/route.ts b/apps/sim/app/api/folders/[id]/duplicate/route.ts index a13ae1464cd..737ce8af5fb 100644 --- a/apps/sim/app/api/folders/[id]/duplicate/route.ts +++ b/apps/sim/app/api/folders/[id]/duplicate/route.ts @@ -58,7 +58,7 @@ export const POST = withRouteHandler( const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized folder duplication attempt for ${sourceFolderId}`) + logger.warn(`Unauthorized folder duplication attempt for ${sourceFolderId}`) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -67,7 +67,7 @@ export const POST = withRouteHandler( if (!parsed.success) return parsed.response const { name, workspaceId, parentId, newId: clientNewId } = parsed.data.body - logger.info(`[${requestId}] Duplicating folder ${sourceFolderId} for user ${session.user.id}`) + logger.info(`Duplicating folder ${sourceFolderId} for user ${session.user.id}`) const sourceFolder = await db .select() @@ -212,7 +212,7 @@ export const POST = withRouteHandler( const elapsed = Date.now() - startTime logger.info( - `[${requestId}] Successfully duplicated folder ${sourceFolderId} to ${newFolderId} in ${elapsed}ms`, + `Successfully duplicated folder ${sourceFolderId} to ${newFolderId} in ${elapsed}ms`, { foldersCount: folderMapping.size, workflowsCount: workflowStats.total, @@ -252,7 +252,7 @@ export const POST = withRouteHandler( } if (error instanceof FolderDuplicationError) { - logger.warn(`[${requestId}] Folder duplication rejected: ${error.message}`, { + logger.warn(`Folder duplication rejected: ${error.message}`, { sourceFolderId, userId: session.user.id, }) @@ -267,7 +267,7 @@ export const POST = withRouteHandler( */ const orchestrationError = asOrchestrationError(error) if (orchestrationError) { - logger.warn(`[${requestId}] Folder duplication rejected: ${orchestrationError.message}`, { + logger.warn(`Folder duplication rejected: ${orchestrationError.message}`, { sourceFolderId, userId: session.user.id, }) @@ -278,10 +278,7 @@ export const POST = withRouteHandler( } const elapsed = Date.now() - startTime - logger.error( - `[${requestId}] Error duplicating folder ${sourceFolderId} after ${elapsed}ms:`, - error - ) + logger.error(`Error duplicating folder ${sourceFolderId} after ${elapsed}ms:`, error) return NextResponse.json({ error: 'Failed to duplicate folder' }, { status: 500 }) } } diff --git a/apps/sim/app/api/folders/reorder/route.ts b/apps/sim/app/api/folders/reorder/route.ts index 88408b7a2c1..3d5525d230f 100644 --- a/apps/sim/app/api/folders/reorder/route.ts +++ b/apps/sim/app/api/folders/reorder/route.ts @@ -7,7 +7,6 @@ import { type NextRequest, NextResponse } from 'next/server' import { reorderFoldersContract } from '@/lib/api/contracts' import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { withTransactionRetry } from '@/lib/db/transaction' import { acquireFolderMutationLock } from '@/lib/folders/locks' @@ -17,11 +16,10 @@ import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('FolderReorderAPI') export const PUT = withRouteHandler(async (req: NextRequest) => { - const requestId = generateRequestId() const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized folder reorder attempt`) + logger.warn('Unauthorized folder reorder attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -32,9 +30,7 @@ export const PUT = withRouteHandler(async (req: NextRequest) => { const permission = await getUserEntityPermissions(session.user.id, 'workspace', workspaceId) if (!permission || permission === 'read') { - logger.warn( - `[${requestId}] User ${session.user.id} lacks write permission for workspace ${workspaceId}` - ) + logger.warn(`User ${session.user.id} lacks write permission for workspace ${workspaceId}`) return NextResponse.json({ error: 'Write access required' }, { status: 403 }) } @@ -171,7 +167,7 @@ export const PUT = withRouteHandler(async (req: NextRequest) => { } logger.info( - `[${requestId}] Reordered ${validUpdates.length} ${resourceType} folders in workspace ${workspaceId}` + `Reordered ${validUpdates.length} ${resourceType} folders in workspace ${workspaceId}` ) return NextResponse.json({ success: true, updated: validUpdates.length }) @@ -194,7 +190,7 @@ export const PUT = withRouteHandler(async (req: NextRequest) => { ) } - logger.error(`[${requestId}] Error reordering folders`, error) + logger.error('Error reordering folders', error) return NextResponse.json({ error: 'Failed to reorder folders' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/help/integration-request/route.ts b/apps/sim/app/api/help/integration-request/route.ts index 19084dc1e16..9634bf2732b 100644 --- a/apps/sim/app/api/help/integration-request/route.ts +++ b/apps/sim/app/api/help/integration-request/route.ts @@ -5,7 +5,7 @@ import { parseRequest, validationErrorResponse } from '@/lib/api/server' import { env } from '@/lib/core/config/env' import type { TokenBucketConfig } from '@/lib/core/rate-limiter' import { RateLimiter } from '@/lib/core/rate-limiter' -import { generateRequestId, getClientIp } from '@/lib/core/utils/request' +import { getClientIp } from '@/lib/core/utils/request' import { getEmailDomain } from '@/lib/core/utils/urls' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { sendEmail } from '@/lib/messaging/email/mailer' @@ -22,12 +22,10 @@ const PUBLIC_ENDPOINT_RATE_LIMIT: TokenBucketConfig = { } export const POST = withRouteHandler(async (req: NextRequest) => { - const requestId = generateRequestId() - try { const ip = getClientIp(req) if (!ip) { - logger.warn(`[${requestId}] Unable to resolve client IP for public rate limit`) + logger.warn('Unable to resolve client IP for public rate limit') return NextResponse.json( { error: 'Too many requests. Please try again later.' }, { status: 429 } @@ -40,7 +38,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { ) if (!allowed) { - logger.warn(`[${requestId}] Rate limit exceeded for IP ${ip}`, { remaining, resetAt }) + logger.warn(`Rate limit exceeded for IP ${ip}`, { remaining, resetAt }) return NextResponse.json( { error: 'Too many requests. Please try again later.' }, { @@ -62,7 +60,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const { integrationName, email, useCase } = parsed.data.body - logger.info(`[${requestId}] Processing integration request`, { + logger.info('Processing integration request', { integrationName, email: `${email.substring(0, 3)}***`, }) @@ -84,11 +82,11 @@ ${useCase ? `Use Case:\n${useCase}` : 'No use case provided.'} }) if (!emailResult.success) { - logger.error(`[${requestId}] Error sending integration request email`, emailResult.message) + logger.error('Error sending integration request email', emailResult.message) return NextResponse.json({ error: 'Failed to send request' }, { status: 500 }) } - logger.info(`[${requestId}] Integration request email sent successfully`) + logger.info('Integration request email sent successfully') return NextResponse.json( { success: true, message: 'Integration request submitted successfully' }, @@ -96,14 +94,14 @@ ${useCase ? `Use Case:\n${useCase}` : 'No use case provided.'} ) } catch (error) { if (error instanceof Error && error.message.includes('not configured')) { - logger.error(`[${requestId}] Email service configuration error`, error) + logger.error('Email service configuration error', error) return NextResponse.json( { error: 'Email service is temporarily unavailable. Please try again later.' }, { status: 500 } ) } - logger.error(`[${requestId}] Error processing integration request`, error) + logger.error('Error processing integration request', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/help/route.ts b/apps/sim/app/api/help/route.ts index 3bbb7fef636..e190ac5dfd4 100644 --- a/apps/sim/app/api/help/route.ts +++ b/apps/sim/app/api/help/route.ts @@ -4,7 +4,6 @@ import { getRequestConfirmationSubject, renderHelpConfirmationEmail } from '@/co import { helpFormBodySchema } from '@/lib/api/contracts/common' import { validationErrorResponse } from '@/lib/api/server' import { getSession } from '@/lib/auth' -import { generateRequestId } from '@/lib/core/utils/request' import { isMultipartFieldValidationError, isPayloadSizeLimitError, @@ -27,12 +26,10 @@ const logger = createLogger('HelpAPI') const MAX_HELP_FORM_BYTES = MAX_WORKSPACE_FORMDATA_FILE_SIZE + MAX_MULTIPART_OVERHEAD_BYTES export const POST = withRouteHandler(async (req: NextRequest) => { - const requestId = generateRequestId() - try { const session = await getSession() if (!session?.user?.email) { - logger.warn(`[${requestId}] Unauthorized help request attempt`) + logger.warn('Unauthorized help request attempt') return NextResponse.json({ error: 'Authentication required' }, { status: 401 }) } @@ -50,7 +47,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const workspaceId = formData.get('workspaceId') as string const userAgent = formData.get('userAgent') as string | null - logger.info(`[${requestId}] Processing help request`, { + logger.info('Processing help request', { type, email: `${email.substring(0, 3)}***`, // Log partial email for privacy }) @@ -62,7 +59,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { }) if (!validationResult.success) { - logger.warn(`[${requestId}] Invalid help request data`, { + logger.warn('Invalid help request data', { issues: validationResult.error.issues, }) return validationErrorResponse(validationResult.error) @@ -117,11 +114,11 @@ ${message} }) if (!emailResult.success) { - logger.error(`[${requestId}] Error sending help request email`, emailResult.message) + logger.error('Error sending help request email', emailResult.message) return NextResponse.json({ error: 'Failed to send email' }, { status: 500 }) } - logger.info(`[${requestId}] Help request email sent successfully`) + logger.info('Help request email sent successfully') try { const confirmationHtml = await renderHelpConfirmationEmail( @@ -138,7 +135,7 @@ ${message} emailType: 'transactional', }) } catch (err) { - logger.warn(`[${requestId}] Failed to send confirmation email`, err) + logger.warn('Failed to send confirmation email', err) } return NextResponse.json( @@ -150,14 +147,14 @@ ${message} return NextResponse.json({ error: error.message }, { status: 400 }) } if (isPayloadSizeLimitError(error)) { - logger.warn(`[${requestId}] Help request form data too large`, { message: error.message }) + logger.warn('Help request form data too large', { message: error.message }) return NextResponse.json( { error: `Request body exceeds the maximum allowed size of ${MAX_HELP_FORM_BYTES} bytes` }, { status: 413 } ) } if (error instanceof Error && error.message.includes('not configured')) { - logger.error(`[${requestId}] Email service configuration error`, error) + logger.error('Email service configuration error', error) return NextResponse.json( { error: @@ -167,7 +164,7 @@ ${message} ) } - logger.error(`[${requestId}] Error processing help request`, error) + logger.error('Error processing help request', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/jobs/[jobId]/route.ts b/apps/sim/app/api/jobs/[jobId]/route.ts index 73015ddb79b..5a8aa084b5f 100644 --- a/apps/sim/app/api/jobs/[jobId]/route.ts +++ b/apps/sim/app/api/jobs/[jobId]/route.ts @@ -6,7 +6,6 @@ import { parseRequest } from '@/lib/api/server' import { WORKSPACE_KEY_SCOPE_DENIED } from '@/lib/api-key/policy-messages' import { checkHybridAuth } from '@/lib/auth/hybrid' import { getJobQueue } from '@/lib/core/async-jobs' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { createCodedErrorResponse } from '@/app/api/workflows/utils' @@ -17,12 +16,11 @@ export const GET = withRouteHandler( const parsed = await parseRequest(getJobStatusContract, request, context) if (!parsed.success) return parsed.response const { jobId: taskId } = parsed.data.params - const requestId = generateRequestId() try { const authResult = await checkHybridAuth(request, { requireWorkflowId: false }) if (!authResult.success || !authResult.userId) { - logger.warn(`[${requestId}] Unauthorized task status request`) + logger.warn('Unauthorized task status request') return createCodedErrorResponse(authResult.error || 'Authentication required', 401) } @@ -47,7 +45,7 @@ export const GET = withRouteHandler( action: 'read', }) if (!accessCheck.allowed) { - logger.warn(`[${requestId}] Access denied to workflow ${metadataToCheck.workflowId}`) + logger.warn(`Access denied to workflow ${metadataToCheck.workflowId}`) return createCodedErrorResponse('Access denied', 403) } @@ -59,10 +57,10 @@ export const GET = withRouteHandler( } } } else if (metadataToCheck?.userId && metadataToCheck.userId !== authenticatedUserId) { - logger.warn(`[${requestId}] Access denied to user ${metadataToCheck.userId}`) + logger.warn(`Access denied to user ${metadataToCheck.userId}`) return createCodedErrorResponse('Access denied', 403) } else if (!metadataToCheck?.userId && !metadataToCheck?.workflowId) { - logger.warn(`[${requestId}] Access denied to job ${taskId}`) + logger.warn(`Access denied to job ${taskId}`) return createCodedErrorResponse('Access denied', 403) } @@ -79,7 +77,7 @@ export const GET = withRouteHandler( return NextResponse.json(response) } catch (error: unknown) { const errorMessage = toError(error).message - logger.error(`[${requestId}] Error fetching task status:`, error) + logger.error('Error fetching task status:', error) if (errorMessage?.includes('not found')) { return createCodedErrorResponse('Task not found', 404) diff --git a/apps/sim/app/api/knowledge/connectors/member-sync/route.ts b/apps/sim/app/api/knowledge/connectors/member-sync/route.ts index d7af3b8abc0..562c8d1e639 100644 --- a/apps/sim/app/api/knowledge/connectors/member-sync/route.ts +++ b/apps/sim/app/api/knowledge/connectors/member-sync/route.ts @@ -105,7 +105,7 @@ function logRowNotHeldByLiveRun(staleCutoff: Date): SQL { */ export const GET = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() - logger.info(`[${requestId}] Connector member sync scheduler triggered`) + logger.info('Connector member sync scheduler triggered') const authError = verifyCronAuth(request, 'Connector member sync scheduler') if (authError) return authError @@ -157,28 +157,28 @@ export const GET = withRouteHandler(async (request: NextRequest) => { ]) if (recoveredRunning.length > 0) { - logger.warn(`[${requestId}] Recovered ${recoveredRunning.length} stale member sync run(s)`, { + logger.warn(`Recovered ${recoveredRunning.length} stale member sync run(s)`, { ids: recoveredRunning.map((row) => row.id), }) } if (recoveredPending.length > 0) { logger.warn( - `[${requestId}] Recovered ${recoveredPending.length} connector(s) whose queued member sync never started`, + `Recovered ${recoveredPending.length} connector(s) whose queued member sync never started`, { ids: recoveredPending.map((row) => row.id) } ) } if (closedLogs.length > 0) { - logger.warn(`[${requestId}] Closed ${closedLogs.length} orphaned member sync log(s)`) + logger.warn(`Closed ${closedLogs.length} orphaned member sync log(s)`) } /** Observation hygiene never holds back dispatch; an unfinished sweep resumes next tick. */ try { const sweep = await sweepStaleMemberObservations(now) if (sweep.members > 0) { - logger.warn(`[${requestId}] Swept observations of ${sweep.members} stale member(s)`, sweep) + logger.warn(`Swept observations of ${sweep.members} stale member(s)`, sweep) } } catch (error) { - logger.error(`[${requestId}] Stale member observation sweep failed`, { + logger.error('Stale member observation sweep failed', { error: getErrorMessage(error), }) } @@ -207,7 +207,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { .orderBy(asc(knowledgeConnector.nextMemberSyncAt)) .limit(MAX_DISPATCHES_PER_TICK) - logger.info(`[${requestId}] Found ${dueConnectors.length} connectors due for member sync`) + logger.info(`Found ${dueConnectors.length} connectors due for member sync`) if (dueConnectors.length === 0) { return NextResponse.json({ @@ -231,10 +231,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { requireRunnable: true, }) } catch (error) { - logger.error( - `[${requestId}] Failed to dispatch member sync for connector ${connector.id}`, - error - ) + logger.error(`Failed to dispatch member sync for connector ${connector.id}`, error) } }) @@ -244,7 +241,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { count: dueConnectors.length, }) } catch (error) { - logger.error(`[${requestId}] Connector member sync scheduler error`, error) + logger.error('Connector member sync scheduler error', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/knowledge/connectors/sync/route.ts b/apps/sim/app/api/knowledge/connectors/sync/route.ts index f0a2f8d4513..338adb3645a 100644 --- a/apps/sim/app/api/knowledge/connectors/sync/route.ts +++ b/apps/sim/app/api/knowledge/connectors/sync/route.ts @@ -160,7 +160,7 @@ function reclaimPayload(message: string) { */ export const GET = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() - logger.info(`[${requestId}] Connector sync scheduler triggered`) + logger.info('Connector sync scheduler triggered') const authError = verifyCronAuth(request, 'Connector sync scheduler') if (authError) { @@ -283,21 +283,20 @@ export const GET = withRouteHandler(async (request: NextRequest) => { ]) if (recoveredConnectors.length > 0) { - logger.warn( - `[${requestId}] Recovered ${recoveredConnectors.length} stale syncing connectors`, - { ids: recoveredConnectors.map((c) => c.id) } - ) + logger.warn(`Recovered ${recoveredConnectors.length} stale syncing connectors`, { + ids: recoveredConnectors.map((c) => c.id), + }) } if (recoveredPendingConnectors.length > 0) { logger.warn( - `[${requestId}] Recovered ${recoveredPendingConnectors.length} connectors whose queued sync was never started`, + `Recovered ${recoveredPendingConnectors.length} connectors whose queued sync was never started`, { ids: recoveredPendingConnectors.map((c) => c.id) } ) } if (closedSyncLogs.length > 0) { - logger.warn(`[${requestId}] Closed ${closedSyncLogs.length} orphaned connector sync log(s)`) + logger.warn(`Closed ${closedSyncLogs.length} orphaned connector sync log(s)`) } const dueConnectors = await db @@ -323,7 +322,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { .orderBy(asc(knowledgeConnector.nextSyncAt)) .limit(MAX_DISPATCHES_PER_TICK) - logger.info(`[${requestId}] Found ${dueConnectors.length} connectors due for sync`) + logger.info(`Found ${dueConnectors.length} connectors due for sync`) if (dueConnectors.length === 0) { return NextResponse.json({ @@ -347,7 +346,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { requireRunnable: true, }) } catch (error) { - logger.error(`[${requestId}] Failed to dispatch sync for connector ${connector.id}`, error) + logger.error(`Failed to dispatch sync for connector ${connector.id}`, error) } }) @@ -357,7 +356,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { count: dueConnectors.length, }) } catch (error) { - logger.error(`[${requestId}] Connector sync scheduler error`, error) + logger.error('Connector sync scheduler error', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/logs/stats/route.ts b/apps/sim/app/api/logs/stats/route.ts index d560d65b713..73e9ab54703 100644 --- a/apps/sim/app/api/logs/stats/route.ts +++ b/apps/sim/app/api/logs/stats/route.ts @@ -5,7 +5,6 @@ import { type NextRequest, NextResponse } from 'next/server' import { type DashboardStatsResponse, statsQueryParamsSchema } from '@/lib/api/contracts/logs' import { isZodError } from '@/lib/api/server' import { getSession } from '@/lib/auth' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { buildFilterConditions } from '@/lib/logs/filters' import { expandFolderIdsWithDescendants } from '@/lib/logs/folder-expansion' @@ -31,12 +30,10 @@ export const revalidate = 0 * check and consumes only the surface-neutral halves. */ export const GET = withRouteHandler(async (request: NextRequest) => { - const requestId = generateRequestId() - try { const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized logs stats access attempt`) + logger.warn('Unauthorized logs stats access attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -105,7 +102,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { return NextResponse.json(stats, { status: 200 }) } catch (validationError) { if (isZodError(validationError)) { - logger.warn(`[${requestId}] Invalid logs stats request parameters`, { + logger.warn('Invalid logs stats request parameters', { errors: validationError.issues, }) return NextResponse.json( @@ -119,7 +116,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { throw validationError } } catch (error: any) { - logger.error(`[${requestId}] logs stats fetch error`, error) + logger.error('logs stats fetch error', error) return NextResponse.json({ error: error.message }, { status: 500 }) } }) diff --git a/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts b/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts index f7ec2c4ff23..90138bc1755 100644 --- a/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts +++ b/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts @@ -147,7 +147,8 @@ async function syncToolSchemasToWorkflows( if (updatedWorkflowIds.size > 0) { logger.info( - `[${requestId}] Synced tool schemas to ${updatedWorkflowIds.size} workflow(s) for server ${serverId}` + `Synced tool schemas to ${updatedWorkflowIds.size} workflow(s) for server ${serverId}`, + { requestId } ) } @@ -166,7 +167,7 @@ export const POST = withRouteHandler( const paramsValidation = mcpServerIdParamsSchema.safeParse(await params) if (!paramsValidation.success) return validationErrorResponse(paramsValidation.error) const { id: serverId } = paramsValidation.data - logger.info(`[${requestId}] Refreshing MCP server: ${serverId}`) + logger.info(`Refreshing MCP server: ${serverId}`, { requestId }) const [server] = await db .select() @@ -200,14 +201,12 @@ export const POST = withRouteHandler( workspaceId, 'force' ) - logger.info( - `[${requestId}] Discovered ${discoveredTools.length} tools from server ${serverId}` - ) + logger.info(`Discovered ${discoveredTools.length} tools from server ${serverId}`, { + requestId, + }) } catch (error) { discoveryError = truncate(categorizeError(error).message, 200, '') - logger.warn(`[${requestId}] Failed to connect to server ${serverId}`, { - error: discoveryError, - }) + logger.warn(`Failed to connect to server ${serverId}`, { requestId, error: discoveryError }) } if (discoveryError === null) { @@ -223,7 +222,8 @@ export const POST = withRouteHandler( // Discovery already persisted status and cached tools; a workflow-sync // failure is a secondary propagation and must not fail the refresh with // a 500. Surface it as zero workflows updated instead. - logger.warn(`[${requestId}] Tool schema sync failed after successful discovery`, { + logger.warn('Tool schema sync failed after successful discovery', { + requestId, error: getErrorMessage(error), }) } @@ -288,7 +288,7 @@ export const POST = withRouteHandler( updatedWorkflowIds: syncResult.updatedWorkflowIds, }) } catch (error) { - logger.error(`[${requestId}] Error refreshing MCP server:`, error) + logger.error('Error refreshing MCP server:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to refresh MCP server', 500) } }) diff --git a/apps/sim/app/api/mcp/servers/[id]/route.ts b/apps/sim/app/api/mcp/servers/[id]/route.ts index e13a844f911..5f6cc2bcb11 100644 --- a/apps/sim/app/api/mcp/servers/[id]/route.ts +++ b/apps/sim/app/api/mcp/servers/[id]/route.ts @@ -45,13 +45,11 @@ export const PATCH = withRouteHandler( const body = parsedBody.data - logger.info( - `[${requestId}] Updating MCP server: ${serverId} in workspace: ${workspaceId}`, - { - userId, - updates: Object.keys(body).filter((k) => k !== 'workspaceId'), - } - ) + logger.info(`Updating MCP server: ${serverId} in workspace: ${workspaceId}`, { + requestId, + userId, + updates: Object.keys(body).filter((k) => k !== 'workspaceId'), + }) const result = await performUpdateMcpServer({ workspaceId, @@ -84,7 +82,7 @@ export const PATCH = withRouteHandler( } const updatedServer = result.server - logger.info(`[${requestId}] Successfully updated MCP server: ${serverId}`) + logger.info(`Successfully updated MCP server: ${serverId}`, { requestId }) /** * The echoed row never carries header values: the caller just supplied @@ -97,7 +95,7 @@ export const PATCH = withRouteHandler( } catch (error) { const bodyErrorResponse = mcpBodyReadErrorResponse(error, request) if (bodyErrorResponse) return bodyErrorResponse - logger.error(`[${requestId}] Error updating MCP server:`, error) + logger.error('Error updating MCP server:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to update MCP server', 500) } } diff --git a/apps/sim/app/api/mcp/servers/route.ts b/apps/sim/app/api/mcp/servers/route.ts index dde2f6c082c..09e4d248340 100644 --- a/apps/sim/app/api/mcp/servers/route.ts +++ b/apps/sim/app/api/mcp/servers/route.ts @@ -34,7 +34,7 @@ export const GET = withRouteHandler( 'mcp_tools.use' )(async (_request: NextRequest, { workspaceId, requestId, permission }) => { try { - logger.info(`[${requestId}] Listing MCP servers for workspace ${workspaceId}`) + logger.info(`Listing MCP servers for workspace ${workspaceId}`, { requestId }) const rows = await db .select() @@ -50,12 +50,12 @@ export const GET = withRouteHandler( const includeHeaderValues = permissionSatisfies(permission, 'write') const servers = rows.map((row) => projectInternalMcpServer(row, { includeHeaderValues })) - logger.info( - `[${requestId}] Listed ${servers.length} MCP servers for workspace ${workspaceId}` - ) + logger.info(`Listed ${servers.length} MCP servers for workspace ${workspaceId}`, { + requestId, + }) return createMcpSuccessResponse({ servers }) } catch (error) { - logger.error(`[${requestId}] Error listing MCP servers:`, error) + logger.error('Error listing MCP servers:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to list MCP servers', 500) } }) @@ -79,7 +79,8 @@ export const POST = withRouteHandler( const body = parsedBody.data - logger.info(`[${requestId}] Registering MCP server:`, { + logger.info('Registering MCP server:', { + requestId, name: body.name, transport: body.transport, workspaceId, @@ -124,9 +125,9 @@ export const POST = withRouteHandler( ) } - logger.info( - `[${requestId}] Successfully registered MCP server: ${body.name} (ID: ${result.serverId})` - ) + logger.info(`Successfully registered MCP server: ${body.name} (ID: ${result.serverId})`, { + requestId, + }) return createMcpSuccessResponse( result.updated @@ -137,7 +138,7 @@ export const POST = withRouteHandler( } catch (error) { const bodyErrorResponse = mcpBodyReadErrorResponse(error, request) if (bodyErrorResponse) return bodyErrorResponse - logger.error(`[${requestId}] Error registering MCP server:`, error) + logger.error('Error registering MCP server:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to register MCP server', 500) } }) @@ -171,7 +172,7 @@ export const DELETE = withRouteHandler( ) } - logger.info(`[${requestId}] Deleting MCP server: ${serverId} from workspace: ${workspaceId}`) + logger.info(`Deleting MCP server: ${serverId} from workspace: ${workspaceId}`, { requestId }) const result = await performDeleteMcpServer({ workspaceId, @@ -190,11 +191,11 @@ export const DELETE = withRouteHandler( ) } - logger.info(`[${requestId}] Successfully deleted MCP server: ${serverId}`) + logger.info(`Successfully deleted MCP server: ${serverId}`, { requestId }) return createMcpSuccessResponse({ message: `Server ${serverId} deleted successfully` }) } catch (error) { - logger.error(`[${requestId}] Error deleting MCP server:`, error) + logger.error('Error deleting MCP server:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to delete MCP server', 500) } }) diff --git a/apps/sim/app/api/mcp/servers/test-connection/route.ts b/apps/sim/app/api/mcp/servers/test-connection/route.ts index 3f147f75998..8387c540414 100644 --- a/apps/sim/app/api/mcp/servers/test-connection/route.ts +++ b/apps/sim/app/api/mcp/servers/test-connection/route.ts @@ -98,7 +98,8 @@ export const POST = withRouteHandler( const body = parsedBody.data - logger.info(`[${requestId}] Testing MCP server connection:`, { + logger.info('Testing MCP server connection:', { + requestId, name: body.name, transport: body.transport, url: body.url ? `${body.url.substring(0, 50)}...` : undefined, // Partial URL for security @@ -158,7 +159,7 @@ export const POST = withRouteHandler( ) if (missingVars.length > 0) { - logger.warn(`[${requestId}] Some environment variables not found:`, { missingVars }) + logger.warn('Some environment variables not found:', { requestId, missingVars }) } // Re-validate domain and SSRF after env var resolution @@ -226,7 +227,7 @@ export const POST = withRouteHandler( result.toolCount = tools.length result.success = true } catch { - logger.warn(`[${requestId}] Connection established but could not list tools`) + logger.warn('Connection established but could not list tools', { requestId }) result.success = false result.error = 'Connection established but could not list tools' result.warnings = result.warnings || [] @@ -243,7 +244,8 @@ export const POST = withRouteHandler( ) } - logger.info(`[${requestId}] MCP server test successful:`, { + logger.info('MCP server test successful:', { + requestId, name: body.name, negotiatedVersion: result.negotiatedVersion, toolCount: result.toolCount, @@ -252,13 +254,13 @@ export const POST = withRouteHandler( } catch (error) { result.success = false result.error = sanitizeConnectionError(error) - logger.warn(`[${requestId}] MCP server test failed`, { error: result.error }) + logger.warn('MCP server test failed', { requestId, error: result.error }) } finally { if (client) { try { await client.disconnect() } catch { - logger.debug(`[${requestId}] Test client disconnect error (expected)`) + logger.debug('Test client disconnect error (expected)', { requestId }) } } } @@ -267,7 +269,7 @@ export const POST = withRouteHandler( } catch (error) { const bodyErrorResponse = mcpBodyReadErrorResponse(error, request) if (bodyErrorResponse) return bodyErrorResponse - logger.error(`[${requestId}] Error testing MCP server connection:`, error) + logger.error('Error testing MCP server connection:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to test server connection', 500) } }) diff --git a/apps/sim/app/api/mcp/tools/discover/route.ts b/apps/sim/app/api/mcp/tools/discover/route.ts index dded0044831..fd8e7f87e75 100644 --- a/apps/sim/app/api/mcp/tools/discover/route.ts +++ b/apps/sim/app/api/mcp/tools/discover/route.ts @@ -38,16 +38,18 @@ export const dynamic = 'force-dynamic' */ function discoveryFailureResponse(requestId: string, error: unknown) { if (isMcpServerUnavailableError(error)) { - logger.warn(`[${requestId}] MCP server unavailable for tool discovery`, { + logger.warn('MCP server unavailable for tool discovery', { + requestId, error: getErrorMessage(error), }) return createMcpErrorResponse(error, 'Failed to discover MCP tools', 422) } const { message, status } = categorizeError(error) if (status >= 500) { - logger.error(`[${requestId}] Error discovering MCP tools:`, error) + logger.error('Error discovering MCP tools:', error, { requestId }) } else { - logger.warn(`[${requestId}] Failed to discover MCP tools`, { + logger.warn('Failed to discover MCP tools', { + requestId, error: getErrorMessage(error), status, }) @@ -101,7 +103,7 @@ export const GET = withRouteHandler( const serverId = query.serverId const forceRefresh = query.refresh === 'true' - logger.info(`[${requestId}] Discovering MCP tools`, { serverId, workspaceId, forceRefresh }) + logger.info('Discovering MCP tools', { requestId, serverId, workspaceId, forceRefresh }) const tools = serverId ? await mcpService.discoverServerTools( @@ -127,9 +129,9 @@ export const GET = withRouteHandler( byServer, } - logger.info( - `[${requestId}] Discovered ${tools.length} tools from ${Object.keys(byServer).length} servers` - ) + logger.info(`Discovered ${tools.length} tools from ${Object.keys(byServer).length} servers`, { + requestId, + }) return createMcpSuccessResponse(responseData) } catch (error) { if ( @@ -159,7 +161,7 @@ export const POST = withRouteHandler( const { serverIds } = parsedBody.data - logger.info(`[${requestId}] Refreshing tools for ${serverIds.length} servers`) + logger.info(`Refreshing tools for ${serverIds.length} servers`, { requestId }) const results = await settleWithConcurrency( serverIds, @@ -185,7 +187,7 @@ export const POST = withRouteHandler( } }) - logger.info(`[${requestId}] Refresh completed: ${successes.length}/${serverIds.length}`) + logger.info(`Refresh completed: ${successes.length}/${serverIds.length}`, { requestId }) return createMcpSuccessResponse({ refreshed: successes, failed: failures, @@ -204,7 +206,7 @@ export const POST = withRouteHandler( ) { return createMcpErrorResponse(error, 'OAuth re-authorization required', 401) } - logger.error(`[${requestId}] Error refreshing tool discovery:`, error) + logger.error('Error refreshing tool discovery:', error, { requestId }) const { message, status } = categorizeError(error) return createMcpErrorResponse(new Error(message), 'Failed to refresh tool discovery', status) } diff --git a/apps/sim/app/api/mcp/tools/stored/route.ts b/apps/sim/app/api/mcp/tools/stored/route.ts index a06bdfc1229..5cda364b600 100644 --- a/apps/sim/app/api/mcp/tools/stored/route.ts +++ b/apps/sim/app/api/mcp/tools/stored/route.ts @@ -19,7 +19,7 @@ export const GET = withRouteHandler( 'mcp_tools.use' )(async (_request: NextRequest, { workspaceId, requestId }) => { try { - logger.info(`[${requestId}] Fetching stored MCP tools for workspace ${workspaceId}`) + logger.info(`Fetching stored MCP tools for workspace ${workspaceId}`, { requestId }) const workflows = await db .select({ id: workflow.id, name: workflow.name }) @@ -69,12 +69,13 @@ export const GET = withRouteHandler( } logger.info( - `[${requestId}] Found ${storedTools.length} stored MCP tools across ${workflows.length} workflows` + `Found ${storedTools.length} stored MCP tools across ${workflows.length} workflows`, + { requestId } ) return createMcpSuccessResponse({ tools: storedTools }) } catch (error) { - logger.error(`[${requestId}] Error fetching stored MCP tools:`, error) + logger.error('Error fetching stored MCP tools:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to fetch stored MCP tools', 500) } }) diff --git a/apps/sim/app/api/mcp/workflow-servers/[id]/route.ts b/apps/sim/app/api/mcp/workflow-servers/[id]/route.ts index 367a2bb6345..1120c4855e0 100644 --- a/apps/sim/app/api/mcp/workflow-servers/[id]/route.ts +++ b/apps/sim/app/api/mcp/workflow-servers/[id]/route.ts @@ -43,7 +43,7 @@ export const GET = withRouteHandler( try { const { id: serverId } = workflowMcpServerParamsSchema.parse(await params) - logger.info(`[${requestId}] Getting workflow MCP server: ${serverId}`) + logger.info(`Getting workflow MCP server: ${serverId}`, { requestId }) const [server] = await db .select({ @@ -75,13 +75,13 @@ export const GET = withRouteHandler( .from(workflowMcpTool) .where(and(eq(workflowMcpTool.serverId, serverId), isNull(workflowMcpTool.archivedAt))) - logger.info( - `[${requestId}] Found workflow MCP server: ${server.name} with ${tools.length} tools` - ) + logger.info(`Found workflow MCP server: ${server.name} with ${tools.length} tools`, { + requestId, + }) return createMcpSuccessResponse({ server, tools }) } catch (error) { - logger.error(`[${requestId}] Error getting workflow MCP server:`, error) + logger.error('Error getting workflow MCP server:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to get workflow MCP server', 500) } }) @@ -111,7 +111,7 @@ export const PATCH = withRouteHandler( const body = parsedBody.data - logger.info(`[${requestId}] Updating workflow MCP server: ${serverId}`) + logger.info(`Updating workflow MCP server: ${serverId}`, { requestId }) const result = await performUpdateWorkflowMcpServer({ serverId, @@ -135,13 +135,13 @@ export const PATCH = withRouteHandler( const updatedServer = result.server - logger.info(`[${requestId}] Successfully updated workflow MCP server: ${serverId}`) + logger.info(`Successfully updated workflow MCP server: ${serverId}`, { requestId }) return createMcpSuccessResponse({ server: updatedServer }) } catch (error) { const bodyErrorResponse = mcpBodyReadErrorResponse(error, request) if (bodyErrorResponse) return bodyErrorResponse - logger.error(`[${requestId}] Error updating workflow MCP server:`, error) + logger.error('Error updating workflow MCP server:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to update workflow MCP server', 500) } } @@ -164,7 +164,7 @@ export const DELETE = withRouteHandler( try { const { id: serverId } = workflowMcpServerParamsSchema.parse(await params) - logger.info(`[${requestId}] Deleting workflow MCP server: ${serverId}`) + logger.info(`Deleting workflow MCP server: ${serverId}`, { requestId }) const result = await performDeleteWorkflowMcpServer({ serverId, @@ -180,11 +180,11 @@ export const DELETE = withRouteHandler( mcpOrchestrationStatus(result.errorCode) ) } - logger.info(`[${requestId}] Successfully deleted workflow MCP server: ${serverId}`) + logger.info(`Successfully deleted workflow MCP server: ${serverId}`, { requestId }) return createMcpSuccessResponse({ message: `Server ${serverId} deleted successfully` }) } catch (error) { - logger.error(`[${requestId}] Error deleting workflow MCP server:`, error) + logger.error('Error deleting workflow MCP server:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to delete workflow MCP server', 500) } } diff --git a/apps/sim/app/api/mcp/workflow-servers/[id]/tools/[toolId]/route.ts b/apps/sim/app/api/mcp/workflow-servers/[id]/tools/[toolId]/route.ts index 41965115308..22b6aab3fbf 100644 --- a/apps/sim/app/api/mcp/workflow-servers/[id]/tools/[toolId]/route.ts +++ b/apps/sim/app/api/mcp/workflow-servers/[id]/tools/[toolId]/route.ts @@ -41,7 +41,7 @@ export const GET = withRouteHandler( try { const { id: serverId, toolId } = workflowMcpToolParamsSchema.parse(await params) - logger.info(`[${requestId}] Getting tool ${toolId} from server ${serverId}`) + logger.info(`Getting tool ${toolId} from server ${serverId}`, { requestId }) const [server] = await db .select({ id: workflowMcpServer.id }) @@ -77,7 +77,7 @@ export const GET = withRouteHandler( return createMcpSuccessResponse({ tool }) } catch (error) { - logger.error(`[${requestId}] Error getting tool:`, error) + logger.error('Error getting tool:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to get tool', 500) } }) @@ -107,7 +107,7 @@ export const PATCH = withRouteHandler( const body = parsedBody.data - logger.info(`[${requestId}] Updating tool ${toolId} in server ${serverId}`) + logger.info(`Updating tool ${toolId} in server ${serverId}`, { requestId }) const result = await performUpdateWorkflowMcpTool({ serverId, @@ -131,13 +131,13 @@ export const PATCH = withRouteHandler( const updatedTool = result.tool - logger.info(`[${requestId}] Successfully updated tool ${toolId}`) + logger.info(`Successfully updated tool ${toolId}`, { requestId }) return createMcpSuccessResponse({ tool: updatedTool }) } catch (error) { const bodyErrorResponse = mcpBodyReadErrorResponse(error, request) if (bodyErrorResponse) return bodyErrorResponse - logger.error(`[${requestId}] Error updating tool:`, error) + logger.error('Error updating tool:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to update tool', 500) } } @@ -160,7 +160,7 @@ export const DELETE = withRouteHandler( try { const { id: serverId, toolId } = workflowMcpToolParamsSchema.parse(await params) - logger.info(`[${requestId}] Deleting tool ${toolId} from server ${serverId}`) + logger.info(`Deleting tool ${toolId} from server ${serverId}`, { requestId }) const result = await performDeleteWorkflowMcpTool({ serverId, @@ -177,11 +177,11 @@ export const DELETE = withRouteHandler( mcpOrchestrationStatus(result.errorCode) ) } - logger.info(`[${requestId}] Successfully deleted tool ${toolId}`) + logger.info(`Successfully deleted tool ${toolId}`, { requestId }) return createMcpSuccessResponse({ message: `Tool ${toolId} deleted successfully` }) } catch (error) { - logger.error(`[${requestId}] Error deleting tool:`, error) + logger.error('Error deleting tool:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to delete tool', 500) } } diff --git a/apps/sim/app/api/mcp/workflow-servers/[id]/tools/route.ts b/apps/sim/app/api/mcp/workflow-servers/[id]/tools/route.ts index 49613fc08f6..9991ddc7fb4 100644 --- a/apps/sim/app/api/mcp/workflow-servers/[id]/tools/route.ts +++ b/apps/sim/app/api/mcp/workflow-servers/[id]/tools/route.ts @@ -40,7 +40,7 @@ export const GET = withRouteHandler( try { const { id: serverId } = workflowMcpServerParamsSchema.parse(await params) - logger.info(`[${requestId}] Listing tools for workflow MCP server: ${serverId}`) + logger.info(`Listing tools for workflow MCP server: ${serverId}`, { requestId }) const [server] = await db .select({ id: workflowMcpServer.id }) @@ -80,11 +80,11 @@ export const GET = withRouteHandler( ) .where(and(eq(workflowMcpTool.serverId, serverId), isNull(workflowMcpTool.archivedAt))) - logger.info(`[${requestId}] Found ${tools.length} tools for server ${serverId}`) + logger.info(`Found ${tools.length} tools for server ${serverId}`, { requestId }) return createMcpSuccessResponse({ tools }) } catch (error) { - logger.error(`[${requestId}] Error listing tools:`, error) + logger.error('Error listing tools:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to list tools', 500) } }) @@ -114,7 +114,8 @@ export const POST = withRouteHandler( const body = parsedBody.data - logger.info(`[${requestId}] Adding tool to workflow MCP server: ${serverId}`, { + logger.info(`Adding tool to workflow MCP server: ${serverId}`, { + requestId, workflowId: body.workflowId, }) @@ -141,14 +142,15 @@ export const POST = withRouteHandler( const tool = result.tool logger.info( - `[${requestId}] Successfully added tool ${tool.toolName} (workflow: ${body.workflowId}) to server ${serverId}` + `Successfully added tool ${tool.toolName} (workflow: ${body.workflowId}) to server ${serverId}`, + { requestId } ) return createMcpSuccessResponse({ tool }, 201) } catch (error) { const bodyErrorResponse = mcpBodyReadErrorResponse(error, request) if (bodyErrorResponse) return bodyErrorResponse - logger.error(`[${requestId}] Error adding tool:`, error) + logger.error('Error adding tool:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to add tool', 500) } } diff --git a/apps/sim/app/api/mcp/workflow-servers/route.ts b/apps/sim/app/api/mcp/workflow-servers/route.ts index f7a8f90fb19..29779dd4bb1 100644 --- a/apps/sim/app/api/mcp/workflow-servers/route.ts +++ b/apps/sim/app/api/mcp/workflow-servers/route.ts @@ -31,7 +31,7 @@ export const GET = withRouteHandler( 'deploy.mcp' )(async (_request: NextRequest, { workspaceId, requestId }) => { try { - logger.info(`[${requestId}] Listing workflow MCP servers for workspace ${workspaceId}`) + logger.info(`Listing workflow MCP servers for workspace ${workspaceId}`, { requestId }) const servers = await db .select({ @@ -85,12 +85,12 @@ export const GET = withRouteHandler( toolNames: toolNamesByServer[server.id] || [], })) - logger.info( - `[${requestId}] Listed ${servers.length} workflow MCP servers for workspace ${workspaceId}` - ) + logger.info(`Listed ${servers.length} workflow MCP servers for workspace ${workspaceId}`, { + requestId, + }) return createMcpSuccessResponse({ servers: serversWithToolNames }) } catch (error) { - logger.error(`[${requestId}] Error listing workflow MCP servers:`, error) + logger.error('Error listing workflow MCP servers:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to list workflow MCP servers', 500) } }) @@ -118,7 +118,8 @@ export const POST = withRouteHandler( const body = parsedBody.data - logger.info(`[${requestId}] Creating workflow MCP server:`, { + logger.info('Creating workflow MCP server:', { + requestId, name: body.name, workspaceId, workflowIds: body.workflowIds, @@ -146,15 +147,15 @@ export const POST = withRouteHandler( const { server } = result const addedTools = result.addedTools || [] - logger.info( - `[${requestId}] Successfully created workflow MCP server: ${body.name} (ID: ${server.id})` - ) + logger.info(`Successfully created workflow MCP server: ${body.name} (ID: ${server.id})`, { + requestId, + }) return createMcpSuccessResponse({ server, addedTools }, 201) } catch (error) { const bodyErrorResponse = mcpBodyReadErrorResponse(error, request) if (bodyErrorResponse) return bodyErrorResponse - logger.error(`[${requestId}] Error creating workflow MCP server:`, error) + logger.error('Error creating workflow MCP server:', error, { requestId }) return createMcpErrorResponse(toError(error), 'Failed to create workflow MCP server', 500) } } diff --git a/apps/sim/app/api/resume/poll/route.ts b/apps/sim/app/api/resume/poll/route.ts index d16de246b13..aa8cb8fd115 100644 --- a/apps/sim/app/api/resume/poll/route.ts +++ b/apps/sim/app/api/resume/poll/route.ts @@ -2,7 +2,6 @@ import { db } from '@sim/db' import { pausedExecutions } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' -import { generateShortId } from '@sim/utils/id' import { isRecordLike } from '@sim/utils/object' import { and, asc, inArray, isNotNull, lte, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' @@ -11,6 +10,7 @@ import { acquireLock, releaseLock } from '@/lib/core/config/redis' import { logFailureOnce } from '@/lib/core/errors/failure-log' import { isRetryableInfrastructureError } from '@/lib/core/errors/retryable-infrastructure' import { mapWithConcurrency } from '@/lib/core/utils/concurrency' +import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { preprocessExecution } from '@/lib/execution/preprocessing' import { @@ -58,7 +58,7 @@ interface RowResult { } export const GET = withRouteHandler(async (request: NextRequest) => { - const requestId = generateShortId() + const requestId = generateRequestId() const authError = verifyCronAuth(request, 'Time-pause resume poll') if (authError) return authError diff --git a/apps/sim/app/api/schedules/[id]/route.ts b/apps/sim/app/api/schedules/[id]/route.ts index b810d183aa8..2aa958fe13d 100644 --- a/apps/sim/app/api/schedules/[id]/route.ts +++ b/apps/sim/app/api/schedules/[id]/route.ts @@ -35,12 +35,12 @@ async function fetchAndAuthorize( .limit(1) if (!schedule) { - logger.warn(`[${requestId}] Schedule not found: ${scheduleId}`) + logger.warn(`Schedule not found: ${scheduleId}`, { requestId }) return NextResponse.json({ error: 'Schedule not found' }, { status: 404 }) } if (!schedule.workflowId) { - logger.warn(`[${requestId}] Schedule has no workflow: ${scheduleId}`) + logger.warn(`Schedule has no workflow: ${scheduleId}`, { requestId }) return NextResponse.json({ error: 'Schedule has no associated workflow' }, { status: 400 }) } @@ -51,12 +51,12 @@ async function fetchAndAuthorize( }) if (!authorization.workflow) { - logger.warn(`[${requestId}] Workflow not found for schedule: ${scheduleId}`) + logger.warn(`Workflow not found for schedule: ${scheduleId}`, { requestId }) return NextResponse.json({ error: 'Workflow not found' }, { status: 404 }) } if (!authorization.allowed) { - logger.warn(`[${requestId}] User not authorized to modify schedule: ${scheduleId}`) + logger.warn(`User not authorized to modify schedule: ${scheduleId}`, { requestId }) return NextResponse.json( { error: authorization.message || 'Not authorized to modify this schedule' }, { status: authorization.status } @@ -91,7 +91,7 @@ export const GET = withRouteHandler( return NextResponse.json({ schedule: authResult.schedule }) } catch (error) { - logger.error(`[${requestId}] Failed to get schedule`, { error }) + logger.error('Failed to get schedule', { error }) return NextResponse.json({ error: 'Failed to get schedule' }, { status: 500 }) } } @@ -104,7 +104,7 @@ export const PUT = withRouteHandler( try { const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized schedule update attempt`) + logger.warn('Unauthorized schedule update attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -136,7 +136,7 @@ export const PUT = withRouteHandler( .set({ status: 'disabled', nextRunAt: null, lastQueuedAt: null, updatedAt: new Date() }) .where(and(eq(workflowSchedule.id, scheduleId), isNull(workflowSchedule.archivedAt))) - logger.info(`[${requestId}] Disabled schedule: ${scheduleId}`) + logger.info(`Disabled schedule: ${scheduleId}`) recordAudit({ workspaceId, @@ -165,13 +165,13 @@ export const PUT = withRouteHandler( } if (!schedule.cronExpression) { - logger.error(`[${requestId}] Schedule has no cron expression: ${scheduleId}`) + logger.error(`Schedule has no cron expression: ${scheduleId}`) return NextResponse.json({ error: 'Schedule has no cron expression' }, { status: 400 }) } const cronResult = validateCronExpression(schedule.cronExpression, schedule.timezone || 'UTC') if (!cronResult.isValid || !cronResult.nextRun) { - logger.error(`[${requestId}] Invalid cron expression for schedule: ${scheduleId}`) + logger.error(`Invalid cron expression for schedule: ${scheduleId}`) return NextResponse.json({ error: 'Schedule has invalid cron expression' }, { status: 400 }) } @@ -183,7 +183,7 @@ export const PUT = withRouteHandler( .set({ status: 'active', failedCount: 0, infraRetryCount: 0, updatedAt: now, nextRunAt }) .where(and(eq(workflowSchedule.id, scheduleId), isNull(workflowSchedule.archivedAt))) - logger.info(`[${requestId}] Reactivated schedule: ${scheduleId}`) + logger.info(`Reactivated schedule: ${scheduleId}`) recordAudit({ workspaceId, @@ -210,7 +210,7 @@ export const PUT = withRouteHandler( return NextResponse.json({ error: error.message }, { status: error.status }) } - logger.error(`[${requestId}] Error updating schedule`, error) + logger.error('Error updating schedule', error) return NextResponse.json({ error: 'Failed to update schedule' }, { status: 500 }) } } @@ -225,7 +225,7 @@ export const DELETE = withRouteHandler( const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized schedule delete attempt`) + logger.warn('Unauthorized schedule delete attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -235,7 +235,7 @@ export const DELETE = withRouteHandler( await db.delete(workflowSchedule).where(eq(workflowSchedule.id, scheduleId)) - logger.info(`[${requestId}] Deleted schedule: ${scheduleId}`) + logger.info(`Deleted schedule: ${scheduleId}`) recordAudit({ workspaceId, @@ -257,7 +257,7 @@ export const DELETE = withRouteHandler( return NextResponse.json({ message: 'Schedule deleted successfully' }) } catch (error) { - logger.error(`[${requestId}] Error deleting schedule`, error) + logger.error('Error deleting schedule', error) return NextResponse.json({ error: 'Failed to delete schedule' }, { status: 500 }) } } diff --git a/apps/sim/app/api/schedules/execute/route.ts b/apps/sim/app/api/schedules/execute/route.ts index d92f4f68b45..bff1dbad8ec 100644 --- a/apps/sim/app/api/schedules/execute/route.ts +++ b/apps/sim/app/api/schedules/execute/route.ts @@ -363,12 +363,13 @@ async function restoreScheduleClaim( ) .returning({ id: workflowSchedule.id }) .catch((error) => { - logger.error(`[${requestId}] ${context}`, error) + logger.error(context, error, { requestId }) throw error }) if (!restored) { - logger.warn(`[${requestId}] ${context}`, { + logger.warn(context, { + requestId, scheduleId, currentClaim: currentClaim.toISOString(), activeClaim: activeClaim.toISOString(), @@ -743,7 +744,8 @@ async function reconcileExistingScheduleJob(params: { } if (!scheduleWorkflowId) { - logger.warn(`[${requestId}] Cannot reconcile schedule job without a workflow`, { + logger.warn('Cannot reconcile schedule job without a workflow', { + requestId, scheduleId: schedule.id, jobId: job.id, }) @@ -759,7 +761,8 @@ async function reconcileExistingScheduleJob(params: { `Failed to restore schedule ${schedule.id} claim for recovery` ) if (!restored) { - logger.info(`[${requestId}] Skipped schedule reconciliation after claim changed`, { + logger.info('Skipped schedule reconciliation after claim changed', { + requestId, scheduleId: schedule.id, jobId: job.id, }) @@ -819,7 +822,8 @@ async function deferClaimedScheduleAfterQueueFailure( const retryDelayMs = calculateScheduleInfraRetryDelayMs(retryAttempt) const nextRetryAt = new Date(now.getTime() + retryDelayMs) - logger.warn(`[${requestId}] Deferring schedule after queue infrastructure failure`, { + logger.warn('Deferring schedule after queue infrastructure failure', { + requestId, scheduleId: schedule.id, workflowId: schedule.workflowId, retryAttempt, @@ -843,7 +847,7 @@ async function deferClaimedScheduleAfterQueueFailure( ) ) .catch((updateError) => { - logger.error(`[${requestId}] ${context}`, updateError) + logger.error(context, updateError, { requestId }) throw updateError }) } @@ -870,7 +874,8 @@ async function handleClaimedScheduleSetupFailure( return } - logger.error(`[${requestId}] Non-retryable schedule setup failure`, { + logger.error('Non-retryable schedule setup failure', { + requestId, scheduleId: schedule.id, workflowId: schedule.workflowId, error: toError(error).message, @@ -1152,7 +1157,8 @@ async function executeDatabaseScheduleJob( tryStartDatabaseScheduleJob(jobId) ) if (startResult === 'not_pending') { - logger.info(`[${requestId}] Database schedule execution job is no longer pending`, { + logger.info('Database schedule execution job is no longer pending', { + requestId, scheduleId: schedule.id, workflowId: schedule.workflowId, jobId, @@ -1161,7 +1167,8 @@ async function executeDatabaseScheduleJob( } if (startResult === 'capacity_full') { - logger.info(`[${requestId}] Deferred database schedule execution because capacity is full`, { + logger.info('Deferred database schedule execution because capacity is full', { + requestId, scheduleId: schedule.id, workflowId: schedule.workflowId, jobId, @@ -1182,7 +1189,8 @@ async function executeDatabaseScheduleJob( await jobQueue.completeJob(jobId, output ?? null) } catch (error) { const errorMessage = toError(error).message - logger.error(`[${requestId}] Schedule execution failed for workflow ${schedule.workflowId}`, { + logger.error(`Schedule execution failed for workflow ${schedule.workflowId}`, { + requestId, scheduleId: schedule.id, jobId, error: errorMessage, @@ -1262,7 +1270,8 @@ async function resumePendingDatabaseScheduleJobs( const claimState = await getScheduleClaimState(recoveryMetadata, claimedAt) if (claimState === 'released') { - logger.info(`[${requestId}] Cancelling stale pending schedule execution job`, { + logger.info('Cancelling stale pending schedule execution job', { + requestId, scheduleId: recoveryMetadata.scheduleId, workflowId: recoveryMetadata.workflowId, jobId: job.id, @@ -1270,7 +1279,8 @@ async function resumePendingDatabaseScheduleJobs( return cancelReleasedPendingDatabaseScheduleCarrier(job.id) } if (claimState === 'claimed_by_other') { - logger.info(`[${requestId}] Leaving pending schedule execution job for active claimant`, { + logger.info('Leaving pending schedule execution job for active claimant', { + requestId, scheduleId: recoveryMetadata.scheduleId, workflowId: recoveryMetadata.workflowId, jobId: job.id, @@ -1281,7 +1291,8 @@ async function resumePendingDatabaseScheduleJobs( const payloadValidation = getSchedulePayloadValidation(job.payload) if (!payloadValidation.success) { const error = `Invalid pending schedule execution payload: ${payloadValidation.error}` - logger.warn(`[${requestId}] Rejecting invalid pending schedule execution payload`, { + logger.warn('Rejecting invalid pending schedule execution payload', { + requestId, scheduleId: recoveryMetadata.scheduleId, workflowId: recoveryMetadata.workflowId, jobId: job.id, @@ -1299,7 +1310,8 @@ async function resumePendingDatabaseScheduleJobs( return true } - logger.info(`[${requestId}] Resuming pending database schedule execution job`, { + logger.info('Resuming pending database schedule execution job', { + requestId, scheduleId: recoveryMetadata.scheduleId, workflowId: recoveryMetadata.workflowId, jobId: job.id, @@ -1326,7 +1338,8 @@ async function resumePendingDatabaseScheduleJobs( } if (result.status === 'rejected') { - logger.error(`[${requestId}] Failed to resume pending database schedule execution job`, { + logger.error('Failed to resume pending database schedule execution job', { + requestId, jobId: pendingJobs[index]?.id, error: toError(result.reason).message, }) @@ -1364,7 +1377,8 @@ async function processScheduleItem( (existingJob.status === JOB_STATUS.PENDING && existingJob.attempts > 0)) ) { await recoverStaleDatabaseScheduleJobs(new Date()) - logger.info(`[${requestId}] Reconciled claimed database schedule execution jobs`, { + logger.info('Reconciled claimed database schedule execution jobs', { + requestId, scheduleId: schedule.id, jobId: scheduleJobId, }) @@ -1380,7 +1394,8 @@ async function processScheduleItem( activeJobClaim && isStaleScheduleClaim(activeJobClaim, existingJob) ) { - logger.warn(`[${requestId}] Cancelling stale schedule execution job`, { + logger.warn('Cancelling stale schedule execution job', { + requestId, scheduleId: schedule.id, jobId: existingJob.id, claimedAt: activeJobClaim.toISOString(), @@ -1404,7 +1419,8 @@ async function processScheduleItem( const payloadValidation = getSchedulePayloadValidation(databaseJob.payload) if (!payloadValidation.success) { const error = `Invalid pending schedule execution payload: ${payloadValidation.error}` - logger.warn(`[${requestId}] Rejecting invalid pending schedule execution payload`, { + logger.warn('Rejecting invalid pending schedule execution payload', { + requestId, scheduleId: schedule.id, workflowId: schedule.workflowId, jobId: scheduleJobId, @@ -1423,7 +1439,8 @@ async function processScheduleItem( return } - logger.info(`[${requestId}] Resuming pending database schedule execution job`, { + logger.info('Resuming pending database schedule execution job', { + requestId, scheduleId: schedule.id, jobId: scheduleJobId, }) @@ -1455,7 +1472,8 @@ async function processScheduleItem( databaseJob.status !== JOB_STATUS.PENDING && databaseJob.status !== JOB_STATUS.PROCESSING ) { - logger.info(`[${requestId}] Database schedule execution job reached terminal state`, { + logger.info('Database schedule execution job reached terminal state', { + requestId, scheduleId: schedule.id, jobId: scheduleJobId, status: databaseJob.status, @@ -1471,7 +1489,8 @@ async function processScheduleItem( return } - logger.info(`[${requestId}] Schedule execution job already exists`, { + logger.info('Schedule execution job already exists', { + requestId, scheduleId: schedule.id, jobId: scheduleJobId, status: databaseJob?.status ?? existingJob.status, @@ -1494,7 +1513,8 @@ async function processScheduleItem( return } if (existingJob) { - logger.info(`[${requestId}] Reconciling schedule claim for finished job`, { + logger.info('Reconciling schedule claim for finished job', { + requestId, scheduleId: schedule.id, jobId: scheduleJobId, status: existingJob.status, @@ -1581,9 +1601,9 @@ async function processScheduleItem( const classifiedError = isAsyncJobEnqueueError(error) ? error : null const acceptance = classifiedError?.acceptance ?? 'unknown' logger.error( - `[${requestId}] Failed to enqueue schedule execution for workflow ${schedule.workflowId}`, + `Failed to enqueue schedule execution for workflow ${schedule.workflowId}`, error, - { acceptance, jobId: scheduleJobId } + { requestId, acceptance, jobId: scheduleJobId } ) /** * The queue may have accepted the job before the response was lost, and @@ -1603,12 +1623,13 @@ async function processScheduleItem( return } - logger.info( - `[${requestId}] Queued schedule execution task ${jobId} for workflow ${schedule.workflowId}` - ) + logger.info(`Queued schedule execution task ${jobId} for workflow ${schedule.workflowId}`, { + requestId, + }) if (useDatabaseFallback) { - logger.info(`[${requestId}] Executing durable database schedule execution job`, { + logger.info('Executing durable database schedule execution job', { + requestId, scheduleId: schedule.id, workflowId: schedule.workflowId, jobId, @@ -1629,7 +1650,8 @@ async function processScheduleItem( const queuedJob = await jobQueue.getJob(jobId) if (queuedJob && !['pending', 'processing'].includes(queuedJob.status)) { - logger.info(`[${requestId}] Schedule execution job already finished`, { + logger.info('Schedule execution job already finished', { + requestId, scheduleId: schedule.id, jobId, status: queuedJob.status, @@ -1650,7 +1672,8 @@ async function processScheduleItem( ) if (queuedJobClaim) { if (isStaleScheduleClaim(queuedJobClaim, queuedJob)) { - logger.warn(`[${requestId}] Cancelling stale queued schedule execution job`, { + logger.warn('Cancelling stale queued schedule execution job', { + requestId, scheduleId: schedule.id, jobId, claimedAt: queuedJobClaim.toISOString(), @@ -1676,7 +1699,8 @@ async function processScheduleItem( } } - logger.info(`[${requestId}] Schedule execution task accepted`, { + logger.info('Schedule execution task accepted', { + requestId, scheduleId: schedule.id, workflowId: schedule.workflowId, jobId, @@ -1686,8 +1710,9 @@ async function processScheduleItem( }) } catch (error) { logger.error( - `[${requestId}] Failed after queueing schedule execution for workflow ${schedule.workflowId}`, - error + `Failed after queueing schedule execution for workflow ${schedule.workflowId}`, + error, + { requestId } ) if (!enqueuedJobId && !carrierObservedOrLookupUncertain) { await handleClaimedScheduleSetupFailure( @@ -1701,7 +1726,8 @@ async function processScheduleItem( return } - logger.warn(`[${requestId}] Preserved schedule occurrence after carrier uncertainty`, { + logger.warn('Preserved schedule occurrence after carrier uncertainty', { + requestId, scheduleId: schedule.id, jobId: enqueuedJobId ?? scheduleJobId, error: toError(error).message, @@ -1771,8 +1797,9 @@ export async function runScheduleTick(requestId: string): Promise { const requestId = generateRequestId() - logger.info(`[${requestId}] Scheduled execution triggered at ${new Date().toISOString()}`) + logger.info(`Scheduled execution triggered at ${new Date().toISOString()}`) const authError = verifyCronAuth(request, 'Schedule execution') if (authError) { diff --git a/apps/sim/app/api/schedules/route.ts b/apps/sim/app/api/schedules/route.ts index f3dc25aa919..bae568dedec 100644 --- a/apps/sim/app/api/schedules/route.ts +++ b/apps/sim/app/api/schedules/route.ts @@ -32,7 +32,7 @@ export const GET = withRouteHandler(async (req: NextRequest) => { try { const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized schedule query attempt`) + logger.warn('Unauthorized schedule query attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -64,7 +64,7 @@ export const GET = withRouteHandler(async (req: NextRequest) => { ) } - logger.info(`[${requestId}] Getting schedule for workflow ${workflowId}`) + logger.info(`Getting schedule for workflow ${workflowId}`) const conditions = [eq(workflowSchedule.workflowId, workflowId)] if (blockId) { @@ -114,7 +114,7 @@ export const GET = withRouteHandler(async (req: NextRequest) => { { headers } ) } catch (error) { - logger.error(`[${requestId}] Error retrieving workflow schedule`, error) + logger.error('Error retrieving workflow schedule', error) return NextResponse.json({ error: 'Failed to retrieve workflow schedule' }, { status: 500 }) } }) @@ -125,7 +125,7 @@ async function handleWorkspaceSchedules(requestId: string, userId: string, works return NextResponse.json({ error: 'Not authorized' }, { status: 403 }) } - logger.info(`[${requestId}] Getting all schedules for workspace ${workspaceId}`) + logger.info(`Getting all schedules for workspace ${workspaceId}`, { requestId }) const workflowRows = await db .select({ diff --git a/apps/sim/app/api/skills/route.ts b/apps/sim/app/api/skills/route.ts index 456ff2b8d1d..a9ec91c2635 100644 --- a/apps/sim/app/api/skills/route.ts +++ b/apps/sim/app/api/skills/route.ts @@ -13,7 +13,6 @@ import { messageForOrchestrationError, statusForOrchestrationError, } from '@/lib/core/orchestration/types' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { captureServerEvent } from '@/lib/posthog/server' import { @@ -71,12 +70,10 @@ const withReadOnly = (skills: T[]) => /** GET - Fetch all skills for a workspace */ export const GET = withRouteHandler(async (request: NextRequest) => { - const requestId = generateRequestId() - try { const principal = await authenticatePrincipal() if (!principal) { - logger.warn(`[${requestId}] Unauthorized skills access attempt`) + logger.warn('Unauthorized skills access attempt') return unauthorized() } @@ -84,7 +81,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { Object.fromEntries(request.nextUrl.searchParams.entries()) ) if (!query.success) { - logger.warn(`[${requestId}] Invalid skills query`, { errors: query.error.issues }) + logger.warn('Invalid skills query', { errors: query.error.issues }) return NextResponse.json( { error: 'Invalid request data', details: query.error.issues }, { status: 400 } @@ -101,19 +98,17 @@ export const GET = withRouteHandler(async (request: NextRequest) => { } catch (error) { const projected = orchestrationErrorResponse(error, 'Failed to fetch skills') if (projected) return projected - logger.error(`[${requestId}] Error fetching skills:`, error) + logger.error('Error fetching skills:', error) return NextResponse.json({ error: 'Failed to fetch skills' }, { status: 500 }) } }) /** POST - Create or update skills */ export const POST = withRouteHandler(async (req: NextRequest) => { - const requestId = generateRequestId() - try { const principal = await authenticatePrincipal() if (!principal) { - logger.warn(`[${requestId}] Unauthorized skills update attempt`) + logger.warn('Unauthorized skills update attempt') return unauthorized() } @@ -123,7 +118,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { {}, { validationErrorResponse: (error) => { - logger.warn(`[${requestId}] Invalid skills data`, { errors: error.issues }) + logger.warn('Invalid skills data', { errors: error.issues }) return validationErrorResponse(error, 'Invalid request data') }, } @@ -164,22 +159,20 @@ export const POST = withRouteHandler(async (req: NextRequest) => { } catch (error) { const projected = orchestrationErrorResponse(error, 'Failed to update skills') if (projected) { - logger.warn(`[${requestId}] Skill write rejected`, { status: projected.status }) + logger.warn('Skill write rejected', { status: projected.status }) return projected } - logger.error(`[${requestId}] Error updating skills`, error) + logger.error('Error updating skills', error) return NextResponse.json({ error: 'Failed to update skills' }, { status: 500 }) } }) /** DELETE - Delete a skill by ID */ export const DELETE = withRouteHandler(async (request: NextRequest) => { - const requestId = generateRequestId() - try { const principal = await authenticatePrincipal() if (!principal) { - logger.warn(`[${requestId}] Unauthorized skill deletion attempt`) + logger.warn('Unauthorized skill deletion attempt') return unauthorized() } @@ -187,7 +180,7 @@ export const DELETE = withRouteHandler(async (request: NextRequest) => { Object.fromEntries(request.nextUrl.searchParams.entries()) ) if (!query.success) { - logger.warn(`[${requestId}] Invalid skill deletion query`, { errors: query.error.issues }) + logger.warn('Invalid skill deletion query', { errors: query.error.issues }) return NextResponse.json( { error: 'Invalid request data', details: query.error.issues }, { status: 400 } @@ -208,15 +201,15 @@ export const DELETE = withRouteHandler(async (request: NextRequest) => { { groups: { workspace: workspaceId } } ) - logger.info(`[${requestId}] Deleted skill: ${skillId}`) + logger.info(`Deleted skill: ${skillId}`) return NextResponse.json({ success: true }) } catch (error) { const projected = orchestrationErrorResponse(error, 'Failed to delete skill') if (projected) { - logger.warn(`[${requestId}] Skill delete rejected`, { status: projected.status }) + logger.warn('Skill delete rejected', { status: projected.status }) return projected } - logger.error(`[${requestId}] Error deleting skill:`, error) + logger.error('Error deleting skill:', error) return NextResponse.json({ error: 'Failed to delete skill' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/table/[tableId]/cancel-runs/route.ts b/apps/sim/app/api/table/[tableId]/cancel-runs/route.ts index ecd2ef8e611..5aff52b2834 100644 --- a/apps/sim/app/api/table/[tableId]/cancel-runs/route.ts +++ b/apps/sim/app/api/table/[tableId]/cancel-runs/route.ts @@ -56,7 +56,7 @@ export const POST = withRouteHandler(async (request: NextRequest, { params }: Ro excludeRowIds, }) logger.info( - `[${requestId}] cancel-runs: tableId=${tableId} scope=${scope}${ + `cancel-runs: tableId=${tableId} scope=${scope}${ rowId ? ` rowId=${rowId}` : '' } cancelled=${cancelled}` ) @@ -74,7 +74,7 @@ export const POST = withRouteHandler(async (request: NextRequest, { params }: Ro if (error instanceof TableQueryValidationError) { return NextResponse.json({ error: error.message }, { status: 400 }) } - logger.error(`[${requestId}] cancel-runs failed:`, error) + logger.error('cancel-runs failed:', error) return NextResponse.json({ error: 'Failed to cancel runs' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/table/[tableId]/columns/route.ts b/apps/sim/app/api/table/[tableId]/columns/route.ts index 57238d3acec..fb9a991d12c 100644 --- a/apps/sim/app/api/table/[tableId]/columns/route.ts +++ b/apps/sim/app/api/table/[tableId]/columns/route.ts @@ -37,7 +37,7 @@ export const POST = withRouteHandler(async (request: NextRequest, context: Colum try { const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!authResult.success || !authResult.userId) { - logger.warn(`[${requestId}] Unauthorized column creation attempt`) + logger.warn('Unauthorized column creation attempt') return NextResponse.json({ error: 'Authentication required' }, { status: 401 }) } @@ -84,7 +84,7 @@ export const POST = withRouteHandler(async (request: NextRequest, context: Colum return NextResponse.json({ error: msg }, { status: 404 }) } - logger.error(`[${requestId}] Error adding column to table ${tableId}:`, error) + logger.error(`Error adding column to table ${tableId}:`, error) return NextResponse.json({ error: 'Failed to add column' }, { status: 500 }) } }) @@ -97,7 +97,7 @@ export const PATCH = withRouteHandler(async (request: NextRequest, context: Colu try { const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!authResult.success || !authResult.userId) { - logger.warn(`[${requestId}] Unauthorized column update attempt`) + logger.warn('Unauthorized column update attempt') return NextResponse.json({ error: 'Authentication required' }, { status: 401 }) } @@ -140,7 +140,7 @@ export const PATCH = withRouteHandler(async (request: NextRequest, context: Colu return validationErrorResponse(error, 'Invalid request data') } - logger.error(`[${requestId}] Error updating column in table ${tableId}:`, error) + logger.error(`Error updating column in table ${tableId}:`, error) return NextResponse.json({ error: 'Failed to update column' }, { status: 500 }) } }) @@ -154,7 +154,7 @@ export const DELETE = withRouteHandler( try { const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!authResult.success || !authResult.userId) { - logger.warn(`[${requestId}] Unauthorized column deletion attempt`) + logger.warn('Unauthorized column deletion attempt') return NextResponse.json({ error: 'Authentication required' }, { status: 401 }) } @@ -202,7 +202,7 @@ export const DELETE = withRouteHandler( return NextResponse.json({ error: msg }, { status: 400 }) } - logger.error(`[${requestId}] Error deleting column from table ${tableId}:`, error) + logger.error(`Error deleting column from table ${tableId}:`, error) return NextResponse.json({ error: 'Failed to delete column' }, { status: 500 }) } } diff --git a/apps/sim/app/api/table/[tableId]/delete-async/route.ts b/apps/sim/app/api/table/[tableId]/delete-async/route.ts index 4ac28aa8772..d5c4b38cabc 100644 --- a/apps/sim/app/api/table/[tableId]/delete-async/route.ts +++ b/apps/sim/app/api/table/[tableId]/delete-async/route.ts @@ -126,7 +126,7 @@ export const POST = withRouteHandler(async (request: NextRequest, { params }: Ro throw error } - logger.info(`[${requestId}] Async row delete started`, { + logger.info('Async row delete started', { tableId, jobId, hasFilter: Boolean(filter), diff --git a/apps/sim/app/api/table/[tableId]/dispatches/route.ts b/apps/sim/app/api/table/[tableId]/dispatches/route.ts index 2043f2c4e4e..b086d19e1e9 100644 --- a/apps/sim/app/api/table/[tableId]/dispatches/route.ts +++ b/apps/sim/app/api/table/[tableId]/dispatches/route.ts @@ -63,7 +63,7 @@ export const GET = withRouteHandler(async (request: NextRequest, { params }: Rou }, }) } catch (error) { - logger.error(`[${requestId}] list-dispatches failed:`, error) + logger.error('list-dispatches failed:', error) return NextResponse.json({ error: 'Failed to list active dispatches' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/table/[tableId]/import/route.ts b/apps/sim/app/api/table/[tableId]/import/route.ts index 04e4915a632..6e5d10c9eef 100644 --- a/apps/sim/app/api/table/[tableId]/import/route.ts +++ b/apps/sim/app/api/table/[tableId]/import/route.ts @@ -105,7 +105,7 @@ export const POST = withRouteHandler(async (request: NextRequest, { params }: Ro if (table.workspaceId !== workspaceId) { logger.warn( - `[${requestId}] Workspace ID mismatch for table ${tableId}. Provided: ${workspaceId}, Actual: ${table.workspaceId}` + `Workspace ID mismatch for table ${tableId}. Provided: ${workspaceId}, Actual: ${table.workspaceId}` ) return NextResponse.json({ error: 'Invalid workspace ID' }, { status: 400 }) } @@ -195,7 +195,7 @@ export const POST = withRouteHandler(async (request: NextRequest, { params }: Ro } catch (error) { if (isMultipartError(error)) return multipartErrorResponse(error) - logger.error(`[${requestId}] CSV import into existing table failed:`, error) + logger.error('CSV import into existing table failed:', error) return NextResponse.json({ error: 'Failed to import CSV' }, { status: 500 }) } finally { fileStream?.destroy() diff --git a/apps/sim/app/api/table/[tableId]/metadata/route.ts b/apps/sim/app/api/table/[tableId]/metadata/route.ts index e455ec187e9..98feb44e0b2 100644 --- a/apps/sim/app/api/table/[tableId]/metadata/route.ts +++ b/apps/sim/app/api/table/[tableId]/metadata/route.ts @@ -23,7 +23,7 @@ export const PUT = withRouteHandler(async (request: NextRequest, context: TableR try { const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!authResult.success || !authResult.userId) { - logger.warn(`[${requestId}] Unauthorized metadata update attempt`) + logger.warn('Unauthorized metadata update attempt') return NextResponse.json({ error: 'Authentication required' }, { status: 401 }) } @@ -61,7 +61,7 @@ export const PUT = withRouteHandler(async (request: NextRequest, context: TableR return NextResponse.json({ success: true, data: { metadata: updated } }) } catch (error) { - logger.error(`[${requestId}] Error updating table metadata:`, error) + logger.error('Error updating table metadata:', error) return NextResponse.json({ error: 'Failed to update metadata' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/table/[tableId]/restore/route.ts b/apps/sim/app/api/table/[tableId]/restore/route.ts index 1d2f3e726c6..7356259e071 100644 --- a/apps/sim/app/api/table/[tableId]/restore/route.ts +++ b/apps/sim/app/api/table/[tableId]/restore/route.ts @@ -47,14 +47,14 @@ export const POST = withRouteHandler( return orchestrationOutcomeErrorResponse(result, 'Failed to restore table') } - logger.info(`[${requestId}] Restored table ${tableId}`) + logger.info(`Restored table ${tableId}`) return NextResponse.json({ success: true, data: { table: result.table }, }) } catch (error) { - logger.error(`[${requestId}] Error restoring table ${tableId}`, error) + logger.error(`Error restoring table ${tableId}`, error) return NextResponse.json({ error: 'Failed to restore table' }, { status: 500 }) } } diff --git a/apps/sim/app/api/table/[tableId]/route.ts b/apps/sim/app/api/table/[tableId]/route.ts index e868b2e3f6d..48d74e04a7a 100644 --- a/apps/sim/app/api/table/[tableId]/route.ts +++ b/apps/sim/app/api/table/[tableId]/route.ts @@ -96,7 +96,7 @@ export const PATCH = withRouteHandler( try { const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!authResult.success || !authResult.userId) { - logger.warn(`[${requestId}] Unauthorized table rename attempt`) + logger.warn('Unauthorized table rename attempt') return NextResponse.json({ error: 'Authentication required' }, { status: 401 }) } @@ -214,7 +214,7 @@ export const PATCH = withRouteHandler( return NextResponse.json({ error: error.message }, { status: 409 }) } - logger.error(`[${requestId}] Error updating table:`, error) + logger.error('Error updating table:', error) return NextResponse.json( { error: getErrorMessage(error, 'Failed to update table') }, { status: 500 } @@ -232,7 +232,7 @@ export const DELETE = withRouteHandler( try { const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!authResult.success || !authResult.userId) { - logger.warn(`[${requestId}] Unauthorized table delete attempt`) + logger.warn('Unauthorized table delete attempt') return NextResponse.json({ error: 'Authentication required' }, { status: 401 }) } @@ -277,7 +277,7 @@ export const DELETE = withRouteHandler( return validationErrorResponse(error) } - logger.error(`[${requestId}] Error deleting table:`, error) + logger.error('Error deleting table:', error) return NextResponse.json({ error: 'Failed to delete table' }, { status: 500 }) } } diff --git a/apps/sim/app/api/table/[tableId]/rows/find/route.ts b/apps/sim/app/api/table/[tableId]/rows/find/route.ts index de7b8034a0b..5721b9667e4 100644 --- a/apps/sim/app/api/table/[tableId]/rows/find/route.ts +++ b/apps/sim/app/api/table/[tableId]/rows/find/route.ts @@ -58,7 +58,7 @@ export const GET = withRouteHandler( if (validated.workspaceId !== table.workspaceId) { logger.warn( - `[${requestId}] Workspace ID mismatch for table ${tableId}. Provided: ${validated.workspaceId}, Actual: ${table.workspaceId}` + `Workspace ID mismatch for table ${tableId}. Provided: ${validated.workspaceId}, Actual: ${table.workspaceId}` ) return NextResponse.json({ error: 'Invalid workspace ID' }, { status: 400 }) } @@ -93,7 +93,7 @@ export const GET = withRouteHandler( return NextResponse.json({ error: error.message }, { status: 400 }) } - logger.error(`[${requestId}] Error finding rows:`, error) + logger.error('Error finding rows:', error) return NextResponse.json({ error: 'Failed to find rows' }, { status: 500 }) } } diff --git a/apps/sim/app/api/table/[tableId]/views/[viewId]/route.ts b/apps/sim/app/api/table/[tableId]/views/[viewId]/route.ts index 034d8059296..7ddf1f1a7ff 100644 --- a/apps/sim/app/api/table/[tableId]/views/[viewId]/route.ts +++ b/apps/sim/app/api/table/[tableId]/views/[viewId]/route.ts @@ -64,7 +64,7 @@ export const PATCH = withRouteHandler( if (error instanceof TableViewValidationError) { return NextResponse.json({ error: error.message }, { status: 400 }) } - logger.error(`[${requestId}] Error updating table view:`, error) + logger.error('Error updating table view:', error) return NextResponse.json({ error: 'Failed to update view' }, { status: 500 }) } } @@ -110,7 +110,7 @@ export const DELETE = withRouteHandler( if (error instanceof TableViewValidationError) { return NextResponse.json({ error: error.message }, { status: 400 }) } - logger.error(`[${requestId}] Error deleting table view:`, error) + logger.error('Error deleting table view:', error) return NextResponse.json({ error: 'Failed to delete view' }, { status: 500 }) } } diff --git a/apps/sim/app/api/table/[tableId]/views/route.ts b/apps/sim/app/api/table/[tableId]/views/route.ts index b191b9bf55f..df62afde72e 100644 --- a/apps/sim/app/api/table/[tableId]/views/route.ts +++ b/apps/sim/app/api/table/[tableId]/views/route.ts @@ -45,7 +45,7 @@ export const GET = withRouteHandler(async (request: NextRequest, context: TableR return NextResponse.json({ success: true, data: { views } }) } catch (error) { - logger.error(`[${requestId}] Error listing table views:`, error) + logger.error('Error listing table views:', error) return NextResponse.json({ error: 'Failed to list views' }, { status: 500 }) } }) @@ -90,7 +90,7 @@ export const POST = withRouteHandler(async (request: NextRequest, context: Table if (error instanceof TableViewValidationError) { return NextResponse.json({ error: error.message }, { status: 400 }) } - logger.error(`[${requestId}] Error creating table view:`, error) + logger.error('Error creating table view:', error) return NextResponse.json({ error: 'Failed to create view' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/table/import-csv/route.ts b/apps/sim/app/api/table/import-csv/route.ts index f3b03e66baa..056eebc7cbd 100644 --- a/apps/sim/app/api/table/import-csv/route.ts +++ b/apps/sim/app/api/table/import-csv/route.ts @@ -162,7 +162,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } catch (error) { if (isMultipartError(error)) return multipartErrorResponse(error) - logger.error(`[${requestId}] CSV import failed:`, error) + logger.error('CSV import failed:', error) return NextResponse.json({ error: 'Failed to import CSV' }, { status: 500 }) } finally { fileStream?.destroy() diff --git a/apps/sim/app/api/table/jobs/route.ts b/apps/sim/app/api/table/jobs/route.ts index f2225b308cd..ad7fd93dcdb 100644 --- a/apps/sim/app/api/table/jobs/route.ts +++ b/apps/sim/app/api/table/jobs/route.ts @@ -3,7 +3,6 @@ import { type NextRequest, NextResponse } from 'next/server' import { listTableJobsContract } from '@/lib/api/contracts/tables' import { parseRequest } from '@/lib/api/server' import { capabilityGovernedAuthUserId, checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { isWorkspaceCapabilityWithheld } from '@/lib/permission-groups/capability-assertions' import { listWorkspaceExportJobs } from '@/lib/table/jobs/service' @@ -21,8 +20,6 @@ export const dynamic = 'force-dynamic' * excluded from the table-level job derivation, so the tray reads them here. */ export const GET = withRouteHandler(async (request: NextRequest) => { - const requestId = generateRequestId() - const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!authResult.success || !authResult.userId) { return NextResponse.json({ error: 'Authentication required' }, { status: 401 }) @@ -58,6 +55,6 @@ export const GET = withRouteHandler(async (request: NextRequest) => { } const jobs = await listWorkspaceExportJobs(workspaceId) - logger.info(`[${requestId}] Listed ${jobs.length} export jobs`, { workspaceId }) + logger.info(`Listed ${jobs.length} export jobs`, { workspaceId }) return NextResponse.json({ success: true, data: { jobs } }) }) diff --git a/apps/sim/app/api/table/utils.ts b/apps/sim/app/api/table/utils.ts index 626cc311ace..ca8f1aadb92 100644 --- a/apps/sim/app/api/table/utils.ts +++ b/apps/sim/app/api/table/utils.ts @@ -347,14 +347,14 @@ export function accessError( context?: string ): NextResponse { if (result.capability) { - logger.warn( - `[${requestId}] ${capabilityRefusal(result.capability)}${context ? `: ${context}` : ''}` - ) + logger.warn(`${capabilityRefusal(result.capability)}${context ? `: ${context}` : ''}`, { + requestId, + }) return capabilityRefusalResponse(result.capability) } const message = result.status === 404 ? 'Table not found' : 'Access denied' - logger.warn(`[${requestId}] ${message}${context ? `: ${context}` : ''}`) + logger.warn(`${message}${context ? `: ${context}` : ''}`, { requestId }) return NextResponse.json({ error: message }, { status: result.status }) } diff --git a/apps/sim/app/api/tools/custom/route.ts b/apps/sim/app/api/tools/custom/route.ts index a15706753cd..baf4b307621 100644 --- a/apps/sim/app/api/tools/custom/route.ts +++ b/apps/sim/app/api/tools/custom/route.ts @@ -22,12 +22,10 @@ import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('CustomToolsAPI') export const GET = withRouteHandler(async (request: NextRequest) => { - const requestId = generateRequestId() - try { const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!authResult.success || !authResult.userId) { - logger.warn(`[${requestId}] Unauthorized custom tools access attempt`) + logger.warn('Unauthorized custom tools access attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -47,7 +45,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { action: 'read', }) if (!workflowAuthorization.allowed) { - logger.warn(`[${requestId}] Workflow authorization failed for custom tools`, { + logger.warn('Workflow authorization failed for custom tools', { workflowId, userId, status: workflowAuthorization.status, @@ -69,9 +67,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { resolvedWorkspaceId ) if (!userPermission) { - logger.warn( - `[${requestId}] User ${userId} does not have access to workspace ${resolvedWorkspaceId}` - ) + logger.warn(`User ${userId} does not have access to workspace ${resolvedWorkspaceId}`) return NextResponse.json({ error: 'Access denied' }, { status: 403 }) } } @@ -92,7 +88,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ data: result }, { status: 200 }) } catch (error) { - logger.error(`[${requestId}] Error fetching custom tools:`, error) + logger.error('Error fetching custom tools:', error) return NextResponse.json({ error: 'Failed to fetch custom tools' }, { status: 500 }) } }) @@ -103,7 +99,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { try { const authResult = await checkSessionOrInternalAuth(req, { requireWorkflowId: false }) if (!authResult.success || !authResult.userId) { - logger.warn(`[${requestId}] Unauthorized custom tools update attempt`) + logger.warn('Unauthorized custom tools update attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -114,7 +110,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { { invalidJson: 'throw', validationErrorResponse: (error) => { - logger.warn(`[${requestId}] Invalid custom tools data`, { errors: error.issues }) + logger.warn('Invalid custom tools data', { errors: error.issues }) return NextResponse.json( { error: 'Invalid request data', @@ -131,20 +127,18 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const { tools, workspaceId, source } = parsed.data.body if (!workspaceId) { - logger.warn(`[${requestId}] Missing workspaceId in request body`) + logger.warn('Missing workspaceId in request body') return NextResponse.json({ error: 'workspaceId is required' }, { status: 400 }) } const userPermission = await getUserEntityPermissions(userId, 'workspace', workspaceId) if (!userPermission) { - logger.warn(`[${requestId}] User ${userId} does not have access to workspace ${workspaceId}`) + logger.warn(`User ${userId} does not have access to workspace ${workspaceId}`) return NextResponse.json({ error: 'Access denied' }, { status: 403 }) } if (userPermission !== 'admin' && userPermission !== 'write') { - logger.warn( - `[${requestId}] User ${userId} does not have write permission for workspace ${workspaceId}` - ) + logger.warn(`User ${userId} does not have write permission for workspace ${workspaceId}`) return NextResponse.json({ error: 'Write permission required' }, { status: 403 }) } @@ -182,21 +176,20 @@ export const POST = withRouteHandler(async (req: NextRequest) => { return NextResponse.json({ success: true, data: resultTools }) } catch (error) { - logger.error(`[${requestId}] Error updating custom tools`, error) + logger.error('Error updating custom tools', error) const errorMessage = getErrorMessage(error, 'Failed to update custom tools') return NextResponse.json({ error: errorMessage }, { status: 500 }) } }) export const DELETE = withRouteHandler(async (request: NextRequest) => { - const requestId = generateRequestId() const parsed = await parseRequest( deleteCustomToolContract, request, {}, { validationErrorResponse: (error) => { - logger.warn(`[${requestId}] Missing tool ID for deletion`) + logger.warn('Missing tool ID for deletion') return NextResponse.json( { error: 'Tool ID is required', @@ -214,7 +207,7 @@ export const DELETE = withRouteHandler(async (request: NextRequest) => { try { const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!authResult.success || !authResult.userId) { - logger.warn(`[${requestId}] Unauthorized custom tool deletion attempt`) + logger.warn('Unauthorized custom tool deletion attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -227,7 +220,7 @@ export const DELETE = withRouteHandler(async (request: NextRequest) => { .limit(1) if (existingTool.length === 0) { - logger.warn(`[${requestId}] Tool not found: ${toolId}`) + logger.warn(`Tool not found: ${toolId}`) return NextResponse.json({ error: 'Tool not found' }, { status: 404 }) } @@ -235,33 +228,27 @@ export const DELETE = withRouteHandler(async (request: NextRequest) => { if (tool.workspaceId) { if (!workspaceId) { - logger.warn(`[${requestId}] Missing workspaceId for workspace-scoped tool`) + logger.warn('Missing workspaceId for workspace-scoped tool') return NextResponse.json({ error: 'workspaceId is required' }, { status: 400 }) } const userPermission = await getUserEntityPermissions(userId, 'workspace', workspaceId) if (!userPermission) { - logger.warn( - `[${requestId}] User ${userId} does not have access to workspace ${workspaceId}` - ) + logger.warn(`User ${userId} does not have access to workspace ${workspaceId}`) return NextResponse.json({ error: 'Access denied' }, { status: 403 }) } if (userPermission !== 'admin' && userPermission !== 'write') { - logger.warn( - `[${requestId}] User ${userId} does not have write permission for workspace ${workspaceId}` - ) + logger.warn(`User ${userId} does not have write permission for workspace ${workspaceId}`) return NextResponse.json({ error: 'Write permission required' }, { status: 403 }) } if (tool.workspaceId !== workspaceId) { - logger.warn(`[${requestId}] Tool ${toolId} does not belong to workspace ${workspaceId}`) + logger.warn(`Tool ${toolId} does not belong to workspace ${workspaceId}`) return NextResponse.json({ error: 'Tool not found' }, { status: 404 }) } } else if (tool.userId !== userId) { - logger.warn( - `[${requestId}] User ${userId} attempted to delete tool they don't own: ${toolId}` - ) + logger.warn(`User ${userId} attempted to delete tool they don't own: ${toolId}`) return NextResponse.json({ error: 'Access denied' }, { status: 403 }) } @@ -288,10 +275,10 @@ export const DELETE = withRouteHandler(async (request: NextRequest) => { metadata: { source }, }) - logger.info(`[${requestId}] Deleted tool: ${toolId}`) + logger.info(`Deleted tool: ${toolId}`) return NextResponse.json({ success: true }) } catch (error) { - logger.error(`[${requestId}] Error deleting custom tool:`, error) + logger.error('Error deleting custom tool:', error) return NextResponse.json({ error: 'Failed to delete custom tool' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/users/me/settings/unsubscribe/route.ts b/apps/sim/app/api/users/me/settings/unsubscribe/route.ts index 654324e85d4..259fd5b3e82 100644 --- a/apps/sim/app/api/users/me/settings/unsubscribe/route.ts +++ b/apps/sim/app/api/users/me/settings/unsubscribe/route.ts @@ -7,7 +7,6 @@ import { } from '@/lib/api/contracts/user' import { parseRequest } from '@/lib/api/server' import { enforceIpRateLimit } from '@/lib/core/rate-limiter' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import type { EmailType } from '@/lib/messaging/email/mailer' import { @@ -27,8 +26,6 @@ const UNSUBSCRIBE_RATE_LIMIT = { } export const GET = withRouteHandler(async (req: NextRequest) => { - const requestId = generateRequestId() - const rateLimited = await enforceIpRateLimit('unsubscribe', req, UNSUBSCRIBE_RATE_LIMIT) if (rateLimited) return rateLimited @@ -43,14 +40,14 @@ export const GET = withRouteHandler(async (req: NextRequest) => { } ) if (!parsed.success) { - logger.warn(`[${requestId}] Missing email or token in GET request`) + logger.warn('Missing email or token in GET request') return parsed.response } const { email, token } = parsed.data.query const tokenVerification = verifyUnsubscribeToken(email, token) if (!tokenVerification.valid) { - logger.warn(`[${requestId}] Invalid unsubscribe token for email: ${email}`) + logger.warn(`Invalid unsubscribe token for email: ${email}`) return NextResponse.json({ error: 'Invalid or expired unsubscribe link' }, { status: 400 }) } @@ -59,9 +56,7 @@ export const GET = withRouteHandler(async (req: NextRequest) => { const preferences = await getEmailPreferences(email) - logger.info( - `[${requestId}] Valid unsubscribe GET request for email: ${email}, type: ${emailType}` - ) + logger.info(`Valid unsubscribe GET request for email: ${email}, type: ${emailType}`) return NextResponse.json({ success: true, @@ -72,14 +67,12 @@ export const GET = withRouteHandler(async (req: NextRequest) => { currentPreferences: preferences || {}, }) } catch (error) { - logger.error(`[${requestId}] Error processing unsubscribe GET request:`, error) + logger.error('Error processing unsubscribe GET request:', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) export const POST = withRouteHandler(async (req: NextRequest) => { - const requestId = generateRequestId() - const rateLimited = await enforceIpRateLimit('unsubscribe', req, UNSUBSCRIBE_RATE_LIMIT) if (rateLimited) return rateLimited @@ -101,14 +94,14 @@ export const POST = withRouteHandler(async (req: NextRequest) => { } ) if (!parsed.success) { - logger.warn(`[${requestId}] One-click unsubscribe missing email or token in URL`) + logger.warn('One-click unsubscribe missing email or token in URL') return parsed.response } email = parsed.data.query.email token = parsed.data.query.token - logger.info(`[${requestId}] Processing one-click unsubscribe for: ${email}`) + logger.info(`Processing one-click unsubscribe for: ${email}`) } else { const parsed = await parseRequest( unsubscribePostContract, @@ -123,7 +116,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { } ) if (!parsed.success) { - logger.warn(`[${requestId}] Invalid unsubscribe POST data`) + logger.warn('Invalid unsubscribe POST data') return parsed.response } @@ -134,7 +127,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const tokenVerification = verifyUnsubscribeToken(email, token) if (!tokenVerification.valid) { - logger.warn(`[${requestId}] Invalid unsubscribe token for email: ${email}`) + logger.warn(`Invalid unsubscribe token for email: ${email}`) return NextResponse.json({ error: 'Invalid or expired unsubscribe link' }, { status: 400 }) } @@ -142,7 +135,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const isTransactional = isTransactionalEmail(emailType) if (isTransactional) { - logger.warn(`[${requestId}] Attempted to unsubscribe from transactional email: ${email}`) + logger.warn(`Attempted to unsubscribe from transactional email: ${email}`) return NextResponse.json( { error: 'Cannot unsubscribe from transactional emails', @@ -171,11 +164,11 @@ export const POST = withRouteHandler(async (req: NextRequest) => { } if (!success) { - logger.error(`[${requestId}] Failed to update unsubscribe preferences for: ${email}`) + logger.error(`Failed to update unsubscribe preferences for: ${email}`) return NextResponse.json({ error: 'Failed to process unsubscribe request' }, { status: 500 }) } - logger.info(`[${requestId}] Successfully unsubscribed ${email} from ${type}`) + logger.info(`Successfully unsubscribed ${email} from ${type}`) return NextResponse.json( { @@ -188,7 +181,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { { status: 200 } ) } catch (error) { - logger.error(`[${requestId}] Error processing unsubscribe POST request:`, error) + logger.error('Error processing unsubscribe POST request:', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/v1/admin/workflows/[id]/deploy/route.ts b/apps/sim/app/api/v1/admin/workflows/[id]/deploy/route.ts index fb2cfa6b847..0624329b233 100644 --- a/apps/sim/app/api/v1/admin/workflows/[id]/deploy/route.ts +++ b/apps/sim/app/api/v1/admin/workflows/[id]/deploy/route.ts @@ -65,7 +65,7 @@ export const POST = withRouteHandler( const isDeployed = Boolean(result.activeDeployment) const attemptActivated = result.latestDeploymentAttempt?.status === 'active' logger.info( - `[${requestId}] Admin API: Deployment ${attemptActivated ? 'activated' : 'accepted'} for workflow ${workflowId}` + `Admin API: Deployment ${attemptActivated ? 'activated' : 'accepted'} for workflow ${workflowId}` ) const response: AdminV1DeployResult = { diff --git a/apps/sim/app/api/v1/admin/workflows/[id]/versions/[versionId]/activate/route.ts b/apps/sim/app/api/v1/admin/workflows/[id]/versions/[versionId]/activate/route.ts index acc3c49884c..7591a85e05d 100644 --- a/apps/sim/app/api/v1/admin/workflows/[id]/versions/[versionId]/activate/route.ts +++ b/apps/sim/app/api/v1/admin/workflows/[id]/versions/[versionId]/activate/route.ts @@ -51,7 +51,7 @@ export const POST = withRouteHandler( } logger.info( - `[${requestId}] Admin API: ${result.latestDeploymentAttempt?.status === 'active' ? 'Activated' : 'Accepted activation for'} version ${versionNum} on workflow ${workflowId}` + `Admin API: ${result.latestDeploymentAttempt?.status === 'active' ? 'Activated' : 'Accepted activation for'} version ${versionNum} on workflow ${workflowId}` ) return singleResponse({ @@ -63,10 +63,7 @@ export const POST = withRouteHandler( latestDeploymentAttempt: result.latestDeploymentAttempt, }) } catch (error) { - logger.error( - `[${requestId}] Admin API: Failed to activate version for workflow ${workflowId}`, - { error } - ) + logger.error(`Admin API: Failed to activate version for workflow ${workflowId}`, { error }) return adminInternalErrorResponse('Failed to activate deployment version') } }) diff --git a/apps/sim/app/api/v1/audit-logs/[id]/route.ts b/apps/sim/app/api/v1/audit-logs/[id]/route.ts index 2cfc9b27606..9c575bfb83e 100644 --- a/apps/sim/app/api/v1/audit-logs/[id]/route.ts +++ b/apps/sim/app/api/v1/audit-logs/[id]/route.ts @@ -15,7 +15,6 @@ import { db } from '@sim/db' import { auditLog } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' -import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { v1GetAuditLogContract } from '@/lib/api/contracts/v1/audit-logs' @@ -41,8 +40,6 @@ export const revalidate = 0 */ export const GET = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ id: string }> }) => { - const requestId = generateId().slice(0, 8) - try { const rateLimit = await checkRateLimit(request, 'audit-logs') if (!rateLimit.allowed) { @@ -89,7 +86,7 @@ export const GET = withRouteHandler( return NextResponse.json(response.body, { headers: response.headers }) } catch (error: unknown) { const message = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Audit log detail fetch error`, { error: message }) + logger.error('Audit log detail fetch error', { error: message }) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } diff --git a/apps/sim/app/api/v1/audit-logs/route.ts b/apps/sim/app/api/v1/audit-logs/route.ts index 187e3a83b1e..b92911c4c05 100644 --- a/apps/sim/app/api/v1/audit-logs/route.ts +++ b/apps/sim/app/api/v1/audit-logs/route.ts @@ -21,7 +21,6 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' -import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { v1ListAuditLogsContract } from '@/lib/api/contracts/v1/audit-logs' import { parseRequest } from '@/lib/api/server' @@ -50,8 +49,6 @@ export const revalidate = 0 * is nothing here for a group to withhold. */ export const GET = withRouteHandler(async (request: NextRequest) => { - const requestId = generateId().slice(0, 8) - try { const rateLimit = await checkRateLimit(request, 'audit-logs') if (!rateLimit.allowed) { @@ -117,7 +114,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { return NextResponse.json(response.body, { headers: response.headers }) } catch (error: unknown) { const message = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Audit logs fetch error`, { error: message }) + logger.error('Audit logs fetch error', { error: message }) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/v1/files/[fileId]/route.ts b/apps/sim/app/api/v1/files/[fileId]/route.ts index 7b79984c1fc..bc1994d5d52 100644 --- a/apps/sim/app/api/v1/files/[fileId]/route.ts +++ b/apps/sim/app/api/v1/files/[fileId]/route.ts @@ -3,7 +3,6 @@ import { type NextRequest, NextResponse } from 'next/server' import { v1DeleteFileContract, v1DownloadFileContract } from '@/lib/api/contracts/v1/files' import { parseRequest } from '@/lib/api/server' import { asOrchestrationError, statusForOrchestrationError } from '@/lib/core/orchestration/types' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { captureServerEvent } from '@/lib/posthog/server' import { getWorkspaceFile } from '@/lib/uploads/contexts/workspace' @@ -37,8 +36,6 @@ interface FileRouteParams { * capability twice. */ export const GET = withRouteHandler(async (request: NextRequest, context: FileRouteParams) => { - const requestId = generateRequestId() - try { const rateLimit = await checkRateLimit(request, 'file-detail') if (!rateLimit.allowed) { @@ -91,15 +88,13 @@ export const GET = withRouteHandler(async (request: NextRequest, context: FileRo { status: statusForOrchestrationError(orchestrationError.code) } ) } - logger.error(`[${requestId}] Error downloading file:`, error) + logger.error('Error downloading file:', error) return NextResponse.json({ error: 'Failed to download file' }, { status: 500 }) } }) /** DELETE /api/v1/files/[fileId] — Archive a file. */ export const DELETE = withRouteHandler(async (request: NextRequest, context: FileRouteParams) => { - const requestId = generateRequestId() - try { const rateLimit = await checkRateLimit(request, 'file-detail') if (!rateLimit.allowed) { @@ -138,9 +133,7 @@ export const DELETE = withRouteHandler(async (request: NextRequest, context: Fil return NextResponse.json({ error: result.error }, { status: 500 }) } - logger.info( - `[${requestId}] Archived file: ${fileRecord.name} (${fileId}) from workspace ${workspaceId}` - ) + logger.info(`Archived file: ${fileRecord.name} (${fileId}) from workspace ${workspaceId}`) return NextResponse.json({ success: true, @@ -149,7 +142,7 @@ export const DELETE = withRouteHandler(async (request: NextRequest, context: Fil }, }) } catch (error) { - logger.error(`[${requestId}] Error deleting file:`, error) + logger.error('Error deleting file:', error) return NextResponse.json({ error: 'Failed to delete file' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/v1/files/route.ts b/apps/sim/app/api/v1/files/route.ts index 249694ef5b3..462eb24ec05 100644 --- a/apps/sim/app/api/v1/files/route.ts +++ b/apps/sim/app/api/v1/files/route.ts @@ -4,7 +4,6 @@ import { getErrorMessage } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { v1ListFilesContract, v1UploadFileFormFieldsSchema } from '@/lib/api/contracts/v1/files' import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' -import { generateRequestId } from '@/lib/core/utils/request' import { isMultipartFieldValidationError, isPayloadSizeLimitError, @@ -37,8 +36,6 @@ const MAX_FILE_SIZE = 100 * 1024 * 1024 /** GET /api/v1/files — List all files in a workspace. */ export const GET = withRouteHandler(async (request: NextRequest) => { - const requestId = generateRequestId() - try { const rateLimit = await checkRateLimit(request, 'files') if (!rateLimit.allowed) { @@ -80,15 +77,13 @@ export const GET = withRouteHandler(async (request: NextRequest) => { }, }) } catch (error) { - logger.error(`[${requestId}] Error listing files:`, error) + logger.error('Error listing files:', error) return NextResponse.json({ error: 'Failed to list files' }, { status: 500 }) } }) /** POST /api/v1/files — Upload a file to a workspace. */ export const POST = withRouteHandler(async (request: NextRequest) => { - const requestId = generateRequestId() - try { const rateLimit = await checkRateLimit(request, 'files') if (!rateLimit.allowed) { @@ -169,7 +164,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { secretProvenance: EXACT_EMPTY_WORKSPACE_FILE_SECRET_PROVENANCE } ) - logger.info(`[${requestId}] Uploaded file: ${file.name} to workspace ${workspaceId}`) + logger.info(`Uploaded file: ${file.name} to workspace ${workspaceId}`) recordAudit({ workspaceId, @@ -219,7 +214,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ error: errorMessage }, { status: 409 }) } - logger.error(`[${requestId}] Error uploading file:`, error) + logger.error('Error uploading file:', error) return NextResponse.json({ error: 'Failed to upload file' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/v1/knowledge/utils.ts b/apps/sim/app/api/v1/knowledge/utils.ts index 4cf6e70777e..abeb501818c 100644 --- a/apps/sim/app/api/v1/knowledge/utils.ts +++ b/apps/sim/app/api/v1/knowledge/utils.ts @@ -122,6 +122,6 @@ export function handleError( } } - logger.error(`[${requestId}] ${defaultMessage}:`, error) + logger.error(`${defaultMessage}:`, error, { requestId }) return NextResponse.json({ error: defaultMessage }, { status: 500 }) } diff --git a/apps/sim/app/api/v1/logs/[id]/route.ts b/apps/sim/app/api/v1/logs/[id]/route.ts index 1b384fc1639..3a33c4139e2 100644 --- a/apps/sim/app/api/v1/logs/[id]/route.ts +++ b/apps/sim/app/api/v1/logs/[id]/route.ts @@ -1,5 +1,4 @@ import { createLogger } from '@sim/logger' -import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { v1GetLogContract } from '@/lib/api/contracts/v1/logs' import { parseRequest } from '@/lib/api/server' @@ -26,8 +25,6 @@ export const revalidate = 0 export const GET = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ id: string }> }) => { - const requestId = generateId().slice(0, 8) - try { const rateLimit = await checkRateLimit(request, 'logs-detail') if (!rateLimit.allowed) { @@ -109,7 +106,7 @@ export const GET = withRouteHandler( return NextResponse.json(apiResponse.body, { headers: apiResponse.headers }) } catch (error: any) { - logger.error(`[${requestId}] Log details fetch error`, { error: error.message }) + logger.error('Log details fetch error', { error: error.message }) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } diff --git a/apps/sim/app/api/v1/logs/route.ts b/apps/sim/app/api/v1/logs/route.ts index d43b44d750f..b3e4b2e2227 100644 --- a/apps/sim/app/api/v1/logs/route.ts +++ b/apps/sim/app/api/v1/logs/route.ts @@ -1,5 +1,4 @@ import { createLogger } from '@sim/logger' -import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { v1ListLogsContract } from '@/lib/api/contracts/v1/logs' import { parseRequest } from '@/lib/api/server' @@ -30,8 +29,6 @@ export const dynamic = 'force-dynamic' export const revalidate = 0 export const GET = withRouteHandler(async (request: NextRequest) => { - const requestId = generateId().slice(0, 8) - try { const rateLimit = await checkRateLimit(request, 'logs') if (!rateLimit.allowed) { @@ -91,7 +88,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { return capabilityRefusalResponse(error.capability) } - logger.info(`[${requestId}] Fetching logs for workspace ${params.workspaceId}`, { + logger.info(`Fetching logs for workspace ${params.workspaceId}`, { userId, filters: { workflowIds: params.workflowIds, @@ -209,7 +206,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { return NextResponse.json(response.body, { headers: response.headers }) } catch (error: any) { - logger.error(`[${requestId}] Logs fetch error`, { error: error.message }) + logger.error('Logs fetch error', { error: error.message }) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/v1/tables/[tableId]/columns/route.ts b/apps/sim/app/api/v1/tables/[tableId]/columns/route.ts index 2037c566048..2fef238d238 100644 --- a/apps/sim/app/api/v1/tables/[tableId]/columns/route.ts +++ b/apps/sim/app/api/v1/tables/[tableId]/columns/route.ts @@ -99,7 +99,7 @@ export const POST = withRouteHandler(async (request: NextRequest, context: Colum const classified = orchestrationErrorResponse(error) if (classified) return classified - logger.error(`[${requestId}] Error adding column to table:`, error) + logger.error('Error adding column to table:', error) return NextResponse.json({ error: 'Failed to add column' }, { status: 500 }) } }) @@ -160,7 +160,7 @@ export const PATCH = withRouteHandler(async (request: NextRequest, context: Colu const validationResponse = v1ValidationErrorResponseFromError(error) if (validationResponse) return validationResponse - logger.error(`[${requestId}] Error updating column in table:`, error) + logger.error('Error updating column in table:', error) return NextResponse.json({ error: 'Failed to update column' }, { status: 500 }) } }) @@ -230,7 +230,7 @@ export const DELETE = withRouteHandler( const classified = orchestrationErrorResponse(error) if (classified) return classified - logger.error(`[${requestId}] Error deleting column from table:`, error) + logger.error('Error deleting column from table:', error) return NextResponse.json({ error: 'Failed to delete column' }, { status: 500 }) } } diff --git a/apps/sim/app/api/v1/tables/[tableId]/route.ts b/apps/sim/app/api/v1/tables/[tableId]/route.ts index c9dcd9daae3..da191b2f71f 100644 --- a/apps/sim/app/api/v1/tables/[tableId]/route.ts +++ b/apps/sim/app/api/v1/tables/[tableId]/route.ts @@ -97,7 +97,7 @@ export const GET = withRouteHandler(async (request: NextRequest, context: TableR }, }) } catch (error) { - logger.error(`[${requestId}] Error getting table:`, error) + logger.error('Error getting table:', error) return NextResponse.json({ error: 'Failed to get table' }, { status: 500 }) } }) @@ -172,7 +172,7 @@ export const DELETE = withRouteHandler(async (request: NextRequest, context: Tab } catch (error) { const lockError = tableLockErrorResponse(error) if (lockError) return lockError - logger.error(`[${requestId}] Error deleting table:`, error) + logger.error('Error deleting table:', error) return NextResponse.json({ error: 'Failed to delete table' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts b/apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts index 40ca9f09ade..e2a7090c016 100644 --- a/apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts +++ b/apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts @@ -111,7 +111,7 @@ export const GET = withRouteHandler(async (request: NextRequest, context: RowRou }, }) } catch (error) { - logger.error(`[${requestId}] Error getting row:`, error) + logger.error('Error getting row:', error) return NextResponse.json({ error: 'Failed to get row' }, { status: 500 }) } }) @@ -204,7 +204,7 @@ export const PATCH = withRouteHandler(async (request: NextRequest, context: RowR const classified = orchestrationErrorResponse(error) if (classified) return classified - logger.error(`[${requestId}] Error updating row:`, error) + logger.error('Error updating row:', error) return NextResponse.json({ error: 'Failed to update row' }, { status: 500 }) } }) @@ -257,7 +257,7 @@ export const DELETE = withRouteHandler(async (request: NextRequest, context: Row if (lockError) return lockError const classified = orchestrationErrorResponse(error) if (classified) return classified - logger.error(`[${requestId}] Error deleting row:`, error) + logger.error('Error deleting row:', error) return NextResponse.json({ error: 'Failed to delete row' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/v1/tables/[tableId]/rows/route.ts b/apps/sim/app/api/v1/tables/[tableId]/rows/route.ts index 3a497fdb39c..b626d602dc3 100644 --- a/apps/sim/app/api/v1/tables/[tableId]/rows/route.ts +++ b/apps/sim/app/api/v1/tables/[tableId]/rows/route.ts @@ -122,7 +122,7 @@ async function handleBatchInsert( const response = orchestrationErrorResponse(error) if (response) return response - logger.error(`[${requestId}] Error batch inserting rows:`, error) + logger.error('Error batch inserting rows:', error, { requestId }) return NextResponse.json({ error: 'Failed to insert rows' }, { status: 500 }) } } @@ -217,7 +217,7 @@ export const GET = withRouteHandler(async (request: NextRequest, context: TableR return NextResponse.json({ error: error.message }, { status: 400 }) } - logger.error(`[${requestId}] Error querying rows:`, error) + logger.error('Error querying rows:', error) return NextResponse.json({ error: 'Failed to query rows' }, { status: 500 }) } }) @@ -318,7 +318,7 @@ export const POST = withRouteHandler( const response = orchestrationErrorResponse(error) if (response) return response - logger.error(`[${requestId}] Error inserting row:`, error) + logger.error('Error inserting row:', error) return NextResponse.json({ error: 'Failed to insert row' }, { status: 500 }) } } @@ -413,7 +413,7 @@ export const PUT = withRouteHandler(async (request: NextRequest, context: TableR const response = orchestrationErrorResponse(error) if (response) return response - logger.error(`[${requestId}] Error updating rows by filter:`, error) + logger.error('Error updating rows by filter:', error) return NextResponse.json({ error: 'Failed to update rows' }, { status: 500 }) } }) @@ -507,7 +507,7 @@ export const DELETE = withRouteHandler( const response = orchestrationErrorResponse(error) if (response) return response - logger.error(`[${requestId}] Error deleting rows:`, error) + logger.error('Error deleting rows:', error) return NextResponse.json({ error: 'Failed to delete rows' }, { status: 500 }) } } diff --git a/apps/sim/app/api/v1/tables/[tableId]/rows/upsert/route.ts b/apps/sim/app/api/v1/tables/[tableId]/rows/upsert/route.ts index 503a072a35a..232f3e3f730 100644 --- a/apps/sim/app/api/v1/tables/[tableId]/rows/upsert/route.ts +++ b/apps/sim/app/api/v1/tables/[tableId]/rows/upsert/route.ts @@ -116,7 +116,7 @@ export const POST = withRouteHandler(async (request: NextRequest, context: Upser const classified = orchestrationErrorResponse(error) if (classified) return classified - logger.error(`[${requestId}] Error upserting row:`, error) + logger.error('Error upserting row:', error) return NextResponse.json({ error: 'Failed to upsert row' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/v1/tables/route.ts b/apps/sim/app/api/v1/tables/route.ts index 5817317f4af..8502268ea97 100644 --- a/apps/sim/app/api/v1/tables/route.ts +++ b/apps/sim/app/api/v1/tables/route.ts @@ -29,8 +29,6 @@ export const revalidate = 0 /** GET /api/v1/tables — List all tables in a workspace. */ export const GET = withRouteHandler(async (request: NextRequest) => { - const requestId = generateRequestId() - try { const rateLimit = await checkRateLimit(request, 'tables') if (!rateLimit.allowed) { @@ -83,7 +81,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { const validationResponse = v1ValidationErrorResponseFromError(error) if (validationResponse) return validationResponse - logger.error(`[${requestId}] Error listing tables:`, error) + logger.error('Error listing tables:', error) return NextResponse.json({ error: 'Failed to list tables' }, { status: 500 }) } }) @@ -186,7 +184,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const classified = orchestrationErrorResponse(error) if (classified) return classified - logger.error(`[${requestId}] Error creating table:`, error) + logger.error('Error creating table:', error) return NextResponse.json({ error: 'Failed to create table' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/v1/workflows/[id]/deploy/route.ts b/apps/sim/app/api/v1/workflows/[id]/deploy/route.ts index a5c1a798391..61cf2671a44 100644 --- a/apps/sim/app/api/v1/workflows/[id]/deploy/route.ts +++ b/apps/sim/app/api/v1/workflows/[id]/deploy/route.ts @@ -58,7 +58,7 @@ export const POST = withRouteHandler( await assertWorkflowMutable(id) - logger.info(`[${requestId}] Deploying workflow ${id} via v1 API`, { userId }) + logger.info(`Deploying workflow ${id} via v1 API`, { userId }) const result = await performFullDeploy({ workflowId: id, @@ -100,7 +100,7 @@ export const POST = withRouteHandler( return NextResponse.json({ error: error.message }, { status: error.status }) } const message = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Workflow deploy error`, { error: message }) + logger.error('Workflow deploy error', { error: message }) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } @@ -135,7 +135,7 @@ export const DELETE = withRouteHandler( await assertWorkflowMutable(id) - logger.info(`[${requestId}] Undeploying workflow ${id} via v1 API`, { userId }) + logger.info(`Undeploying workflow ${id} via v1 API`, { userId }) const result = await performFullUndeploy({ workflowId: id, userId, requestId }) if (!result.success) { @@ -172,7 +172,7 @@ export const DELETE = withRouteHandler( return NextResponse.json({ error: error.message }, { status: error.status }) } const message = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Workflow undeploy error`, { error: message }) + logger.error('Workflow undeploy error', { error: message }) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } diff --git a/apps/sim/app/api/v1/workflows/[id]/export/route.ts b/apps/sim/app/api/v1/workflows/[id]/export/route.ts index af48925e638..f68f5a2a4e3 100644 --- a/apps/sim/app/api/v1/workflows/[id]/export/route.ts +++ b/apps/sim/app/api/v1/workflows/[id]/export/route.ts @@ -2,7 +2,6 @@ import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' import { createLogger } from '@sim/logger' import { getActiveWorkflowRecord } from '@sim/platform-authz/workflow' import { getErrorMessage } from '@sim/utils/errors' -import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { v1ExportWorkflowContract } from '@/lib/api/contracts/v1/workflows' import { parseRequest } from '@/lib/api/server' @@ -33,8 +32,6 @@ export const revalidate = 0 */ export const GET = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ id: string }> }) => { - const requestId = generateId().slice(0, 8) - try { const rateLimit = await checkRateLimit(request, 'workflow-export') if (!rateLimit.allowed) { @@ -50,7 +47,7 @@ export const GET = withRouteHandler( const { id } = parsed.data.params - logger.info(`[${requestId}] Exporting workflow ${id}`, { userId }) + logger.info(`Exporting workflow ${id}`, { userId }) const workflowData = await getActiveWorkflowRecord(id) if (!workflowData?.workspaceId) { @@ -95,7 +92,7 @@ export const GET = withRouteHandler( return NextResponse.json(apiResponse.body, { headers: apiResponse.headers }) } catch (error: unknown) { const message = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Workflow export error`, { error: message }) + logger.error('Workflow export error', { error: message }) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } diff --git a/apps/sim/app/api/v1/workflows/[id]/rollback/route.ts b/apps/sim/app/api/v1/workflows/[id]/rollback/route.ts index e1a2317dafe..030af8c879d 100644 --- a/apps/sim/app/api/v1/workflows/[id]/rollback/route.ts +++ b/apps/sim/app/api/v1/workflows/[id]/rollback/route.ts @@ -75,10 +75,7 @@ export const POST = withRouteHandler( targetVersion = previous.version } - logger.info( - `[${requestId}] Rolling back workflow ${id} to version ${targetVersion} via v1 API`, - { userId } - ) + logger.info(`Rolling back workflow ${id} to version ${targetVersion} via v1 API`, { userId }) const result = await performActivateVersion({ workflowId: id, @@ -117,7 +114,7 @@ export const POST = withRouteHandler( return NextResponse.json({ error: error.message }, { status: error.status }) } const message = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Workflow rollback error`, { error: message }) + logger.error('Workflow rollback error', { error: message }) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } diff --git a/apps/sim/app/api/v1/workflows/[id]/route.ts b/apps/sim/app/api/v1/workflows/[id]/route.ts index 1e01b403a3b..1bf194a5f58 100644 --- a/apps/sim/app/api/v1/workflows/[id]/route.ts +++ b/apps/sim/app/api/v1/workflows/[id]/route.ts @@ -3,7 +3,6 @@ import { workflowBlocks } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { getActiveWorkflowRecord } from '@sim/platform-authz/workflow' import { getErrorMessage } from '@sim/utils/errors' -import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { v1GetWorkflowContract } from '@/lib/api/contracts/v1/workflows' @@ -23,8 +22,6 @@ export const revalidate = 0 export const GET = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ id: string }> }) => { - const requestId = generateId().slice(0, 8) - try { const rateLimit = await checkRateLimit(request, 'workflow-detail') if (!rateLimit.allowed) { @@ -40,7 +37,7 @@ export const GET = withRouteHandler( const { id } = parsed.data.params - logger.info(`[${requestId}] Fetching workflow details for ${id}`, { userId }) + logger.info(`Fetching workflow details for ${id}`, { userId }) const workflowData = await getActiveWorkflowRecord(id) if (!workflowData) { @@ -94,7 +91,7 @@ export const GET = withRouteHandler( return NextResponse.json(apiResponse.body, { headers: apiResponse.headers }) } catch (error: unknown) { const message = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Workflow details fetch error`, { error: message }) + logger.error('Workflow details fetch error', { error: message }) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } diff --git a/apps/sim/app/api/v1/workflows/import/route.ts b/apps/sim/app/api/v1/workflows/import/route.ts index 4401acc57bc..dcfbbace6f7 100644 --- a/apps/sim/app/api/v1/workflows/import/route.ts +++ b/apps/sim/app/api/v1/workflows/import/route.ts @@ -1,12 +1,12 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' -import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { type V1ImportWorkflowData, v1ImportWorkflowContract, } from '@/lib/api/contracts/v1/workflows' import { parseRequest } from '@/lib/api/server' +import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { notifyWorkspaceWorkflowsChanged } from '@/lib/realtime/notify' import { @@ -36,7 +36,7 @@ export const revalidate = 0 * route authenticates and renders the v1 envelope. */ export const POST = withRouteHandler(async (request: NextRequest) => { - const requestId = generateId().slice(0, 8) + const requestId = generateRequestId() try { const rateLimit = await checkRateLimit(request, 'workflow-import') @@ -59,7 +59,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const { workspaceId, folderId, name, description } = parsed.data.body - logger.info(`[${requestId}] Importing workflow into workspace ${workspaceId}`, { + logger.info(`Importing workflow into workspace ${workspaceId}`, { userId, folderId, }) @@ -109,7 +109,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json(apiResponse.body, { status: 201, headers: apiResponse.headers }) } catch (error: unknown) { const message = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Workflow import error`, { error: message }) + logger.error('Workflow import error', { error: message }) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/v1/workflows/route.ts b/apps/sim/app/api/v1/workflows/route.ts index 7814a85ba8a..bc6a0cded4d 100644 --- a/apps/sim/app/api/v1/workflows/route.ts +++ b/apps/sim/app/api/v1/workflows/route.ts @@ -2,7 +2,6 @@ import { db } from '@sim/db' import { workflow } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' -import { generateId } from '@sim/utils/id' import { and, asc, eq, gt, isNull, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { v1ListWorkflowsContract } from '@/lib/api/contracts/v1/workflows' @@ -40,8 +39,6 @@ function decodeCursor(cursor: string): CursorData | null { } export const GET = withRouteHandler(async (request: NextRequest) => { - const requestId = generateId().slice(0, 8) - try { const rateLimit = await checkRateLimit(request, 'workflows') if (!rateLimit.allowed) { @@ -61,7 +58,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { const params = parsed.data.query - logger.info(`[${requestId}] Fetching workflows for workspace ${params.workspaceId}`, { + logger.info(`Fetching workflows for workspace ${params.workspaceId}`, { userId, filters: { folderId: params.folderId, @@ -166,7 +163,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { return NextResponse.json(response.body, { headers: response.headers }) } catch (error: unknown) { const message = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Workflows fetch error`, { error: message }) + logger.error('Workflows fetch error', { error: message }) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/v2/chat/route.ts b/apps/sim/app/api/v2/chat/route.ts index c34498d5636..5c950b4a2fa 100644 --- a/apps/sim/app/api/v2/chat/route.ts +++ b/apps/sim/app/api/v2/chat/route.ts @@ -200,7 +200,7 @@ export const POST = withRouteHandler( const messageId = generateId() const requestId = generateId() - let reqLogger = logger.withMetadata({ messageId, requestId }) + let reqLogger = logger.withMetadata({ messageId, simRequestId: requestId }) try { const workspaceAccess = await assertActiveWorkspaceAccess(workspaceId, userId) @@ -284,7 +284,7 @@ export const POST = withRouteHandler( return v2Error('INTERNAL_ERROR', 'Internal server error') } const chatId = resolvedChat.chatId - reqLogger = logger.withMetadata({ chatId, messageId, requestId }) + reqLogger = logger.withMetadata({ chatId, messageId, simRequestId: requestId }) /** * Write this turn's display copy to the conversation the route just diff --git a/apps/sim/app/api/v2/workflows/[workflowId]/execute/route.ts b/apps/sim/app/api/v2/workflows/[workflowId]/execute/route.ts index 3b8a7d30dbe..24fbbcd3417 100644 --- a/apps/sim/app/api/v2/workflows/[workflowId]/execute/route.ts +++ b/apps/sim/app/api/v2/workflows/[workflowId]/execute/route.ts @@ -265,7 +265,8 @@ function streamPendingRun( send({ type: 'final', data: presentRun(result) }) }) .catch((error) => { - logger.error(`[${requestId}] v2 execute result stream failed`, { + logger.error('v2 execute result stream failed', { + requestId, error: getErrorMessage(error, 'Unknown error'), }) send({ type: 'error', error: 'Internal server error', status: 500 }) @@ -354,7 +355,7 @@ export const POST = withRouteHandler( const incomingCallChain = parseCallChain(req.headers.get(SIM_VIA_HEADER)) const callChainError = validateCallChain(incomingCallChain) if (callChainError) { - logger.warn(`[${requestId}] Call chain rejected`, { workflowId, error: callChainError }) + logger.warn('Call chain rejected', { workflowId, error: callChainError }) return v2Error('CONFLICT', callChainError, { details: { code: 'CALL_CHAIN_DEPTH_EXCEEDED' } }) } const callChain = buildNextCallChain(incomingCallChain, workflowId) @@ -654,7 +655,7 @@ export const POST = withRouteHandler( } catch (error) { const classified = v2WorkflowErrorPolicies.concealWorkflowAuthorization.render(error) if (classified) return classified - logger.error(`[${requestId}] v2 execute failed`, { + logger.error('v2 execute failed', { workflowId, error: getErrorMessage(error, 'Unknown error'), }) diff --git a/apps/sim/app/api/wand/route.ts b/apps/sim/app/api/wand/route.ts index 6d68c132c1b..fbe1e60d148 100644 --- a/apps/sim/app/api/wand/route.ts +++ b/apps/sim/app/api/wand/route.ts @@ -146,17 +146,17 @@ async function updateUserStatsForWand( await checkAndBillPayerOverageThreshold(billingAttribution.billingEntity) } catch (error) { - logger.error(`[${requestId}] Failed to update user stats for wand usage`, error) + logger.error('Failed to update user stats for wand usage', error, { requestId }) } } export const POST = withRouteHandler(async (req: NextRequest) => { const requestId = generateRequestId() - logger.info(`[${requestId}] Received wand generation request`) + logger.info('Received wand generation request') const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized wand generation attempt`) + logger.warn('Unauthorized wand generation attempt') return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 }) } @@ -177,7 +177,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { } = body if (!prompt) { - logger.warn(`[${requestId}] Invalid request: Missing prompt.`) + logger.warn('Invalid request: Missing prompt.') return NextResponse.json( { success: false, error: 'Missing required field: prompt.' }, { status: 400 } @@ -193,7 +193,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { .limit(1) if (!workflowRecord) { - logger.warn(`[${requestId}] Workflow not found: ${workflowId}`) + logger.warn(`Workflow not found: ${workflowId}`) return NextResponse.json({ success: false, error: 'Workflow not found' }, { status: 404 }) } @@ -206,14 +206,12 @@ export const POST = withRouteHandler(async (req: NextRequest) => { ) if (!permission || (permission !== 'admin' && permission !== 'write')) { logger.warn( - `[${requestId}] User ${session.user.id} does not have write access to workspace for workflow ${workflowId}` + `User ${session.user.id} does not have write access to workspace for workflow ${workflowId}` ) return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 403 }) } } else { - logger.warn( - `[${requestId}] Workflow ${workflowId} has no workspaceId; wand request blocked` - ) + logger.warn(`Workflow ${workflowId} has no workspaceId; wand request blocked`) return NextResponse.json( { success: false, @@ -258,12 +256,12 @@ export const POST = withRouteHandler(async (req: NextRequest) => { if (byokResult) { isBYOK = true activeOpenAIKey = byokResult.apiKey - logger.info(`[${requestId}] Using BYOK OpenAI key for wand generation`) + logger.info('Using BYOK OpenAI key for wand generation') } } if (!useWandAzure && !activeOpenAIKey) { - logger.error(`[${requestId}] AI client not initialized. Missing API key.`) + logger.error('AI client not initialized. Missing API key.') return NextResponse.json( { success: false, error: 'Wand generation service is not configured.' }, { status: 503 } @@ -346,9 +344,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { if (stream) { try { - logger.info( - `[${requestId}] About to create stream with model: ${useWandAzure ? wandModelName : 'gpt-4o'}` - ) + logger.info(`About to create stream with model: ${useWandAzure ? wandModelName : 'gpt-4o'}`) const apiUrl = useWandAzure ? `${azureEndpoint?.replace(/\/$/, '')}/openai/v1/responses?api-version=${azureApiVersion}` @@ -379,7 +375,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { if (!response.ok) { const errorText = await response.text() - logger.error(`[${requestId}] API request failed`, { + logger.error('API request failed', { status: response.status, statusText: response.statusText, error: errorText, @@ -387,7 +383,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { throw new Error(`API request failed: ${response.status} ${response.statusText}`) } - logger.info(`[${requestId}] Stream response received, starting processing`) + logger.info('Stream response received, starting processing') const encoder = new TextEncoder() const decoder = new TextDecoder() @@ -421,7 +417,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const { done, value } = await reader.read() if (done) { - logger.info(`[${requestId}] Stream completed. Total chunks: ${chunkCount}`) + logger.info(`Stream completed. Total chunks: ${chunkCount}`) await flushUsage() controller.enqueue(encoder.encode(`data: ${JSON.stringify({ done: true })}\n\n`)) controller.close() @@ -450,7 +446,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const data = trimmed.slice(5).trim() if (data === '[DONE]') { - logger.info(`[${requestId}] Received [DONE] signal`) + logger.info('Received [DONE] signal') await flushUsage() @@ -498,7 +494,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { if (content) { chunkCount++ if (chunkCount === 1) { - logger.info(`[${requestId}] Received first content chunk`) + logger.info('Received first content chunk') } controller.enqueue( @@ -515,15 +511,13 @@ export const POST = withRouteHandler(async (req: NextRequest) => { completion_tokens: usage.completionTokens, total_tokens: usage.totalTokens, } - logger.info( - `[${requestId}] Received usage data: ${JSON.stringify(finalUsage)}` - ) + logger.info(`Received usage data: ${JSON.stringify(finalUsage)}`) } } } } } catch (streamError: any) { - logger.error(`[${requestId}] Streaming error`, { + logger.error('Streaming error', { name: streamError?.name, message: streamError?.message || 'Unknown error', stack: streamError?.stack, @@ -532,7 +526,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { try { await flushUsage() } catch (usageError) { - logger.warn(`[${requestId}] Failed to record usage after stream error`, usageError) + logger.warn('Failed to record usage after stream error', usageError) } const errorData = `data: ${JSON.stringify({ error: 'Streaming failed', done: true })}\n\n` @@ -554,7 +548,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { }, }) } catch (error: any) { - logger.error(`[${requestId}] Failed to create stream`, { + logger.error('Failed to create stream', { name: error?.name, message: error?.message || 'Unknown error', code: error?.code, @@ -612,16 +606,14 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const generatedContent = extractResponseText(completion.output)?.trim() if (!generatedContent) { - logger.error( - `[${requestId}] ${useWandAzure ? 'Azure OpenAI' : 'OpenAI'} response was empty or invalid.` - ) + logger.error(`${useWandAzure ? 'Azure OpenAI' : 'OpenAI'} response was empty or invalid.`) return NextResponse.json( { success: false, error: 'Failed to generate content. AI response was empty.' }, { status: 500 } ) } - logger.info(`[${requestId}] Wand generation successful`) + logger.info('Wand generation successful') const usage = parseResponsesUsage(completion.usage) if (usage) { @@ -639,7 +631,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { return NextResponse.json({ success: true, content: generatedContent }) } catch (error: any) { - logger.error(`[${requestId}] Wand generation failed`, { + logger.error('Wand generation failed', { name: error?.name, message: error?.message || 'Unknown error', code: error?.code, diff --git a/apps/sim/app/api/webhooks/[id]/route.ts b/apps/sim/app/api/webhooks/[id]/route.ts index 5b3b7571f06..41803bcc8f3 100644 --- a/apps/sim/app/api/webhooks/[id]/route.ts +++ b/apps/sim/app/api/webhooks/[id]/route.ts @@ -33,8 +33,6 @@ export const dynamic = 'force-dynamic' // Get a specific webhook export const GET = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ id: string }> }) => { - const requestId = generateRequestId() - try { const parsed = await parseRequest(getWebhookContract, request, context) if (!parsed.success) return parsed.response @@ -42,7 +40,7 @@ export const GET = withRouteHandler( const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!auth.success || !auth.userId) { - logger.warn(`[${requestId}] Unauthorized webhook access attempt`) + logger.warn('Unauthorized webhook access attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const userId = auth.userId @@ -63,7 +61,7 @@ export const GET = withRouteHandler( .limit(1) if (webhooks.length === 0) { - logger.warn(`[${requestId}] Webhook not found: ${id}`) + logger.warn(`Webhook not found: ${id}`) return NextResponse.json({ error: 'Webhook not found' }, { status: 404 }) } @@ -77,14 +75,14 @@ export const GET = withRouteHandler( const hasAccess = authorization.allowed if (!hasAccess) { - logger.warn(`[${requestId}] User ${userId} denied access to webhook: ${id}`) + logger.warn(`User ${userId} denied access to webhook: ${id}`) return NextResponse.json({ error: 'Access denied' }, { status: 403 }) } - logger.info(`[${requestId}] Successfully retrieved webhook: ${id}`) + logger.info(`Successfully retrieved webhook: ${id}`) return NextResponse.json({ webhook: webhooks[0] }, { status: 200 }) } catch (error) { - logger.error(`[${requestId}] Error fetching webhook`, error) + logger.error('Error fetching webhook', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } @@ -92,8 +90,6 @@ export const GET = withRouteHandler( export const PATCH = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ id: string }> }) => { - const requestId = generateRequestId() - try { const parsed = await parseRequest(updateWebhookContract, request, context) if (!parsed.success) return parsed.response @@ -101,7 +97,7 @@ export const PATCH = withRouteHandler( const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!auth.success || !auth.userId) { - logger.warn(`[${requestId}] Unauthorized webhook update attempt`) + logger.warn('Unauthorized webhook update attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const userId = auth.userId @@ -123,7 +119,7 @@ export const PATCH = withRouteHandler( .limit(1) if (webhooks.length === 0) { - logger.warn(`[${requestId}] Webhook not found: ${id}`) + logger.warn(`Webhook not found: ${id}`) return NextResponse.json({ error: 'Webhook not found' }, { status: 404 }) } @@ -136,7 +132,7 @@ export const PATCH = withRouteHandler( const canModify = authorization.allowed if (!canModify) { - logger.warn(`[${requestId}] User ${userId} denied permission to modify webhook: ${id}`) + logger.warn(`User ${userId} denied permission to modify webhook: ${id}`) return NextResponse.json({ error: 'Access denied' }, { status: 403 }) } await assertWorkflowMutable(webhookData.workflow.id) @@ -176,7 +172,7 @@ export const PATCH = withRouteHandler( } if (Object.keys(setClause).length === 0) { - logger.info(`[${requestId}] No-op webhook PATCH (no field changes): ${id}`) + logger.info(`No-op webhook PATCH (no field changes): ${id}`) return NextResponse.json({ webhook: webhooks[0].webhook }, { status: 200 }) } @@ -187,14 +183,14 @@ export const PATCH = withRouteHandler( .where(eq(webhook.id, id)) .returning() - logger.info(`[${requestId}] Successfully updated webhook: ${id}`) + logger.info(`Successfully updated webhook: ${id}`) return NextResponse.json({ webhook: updatedWebhook[0] }, { status: 200 }) } catch (error) { if (error instanceof WorkflowLockedError) { return NextResponse.json({ error: error.message }, { status: error.status }) } - logger.error(`[${requestId}] Error updating webhook`, error) + logger.error('Error updating webhook', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } @@ -212,7 +208,7 @@ export const DELETE = withRouteHandler( const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!auth.success || !auth.userId) { - logger.warn(`[${requestId}] Unauthorized webhook deletion attempt`) + logger.warn('Unauthorized webhook deletion attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const userId = auth.userId @@ -233,7 +229,7 @@ export const DELETE = withRouteHandler( .limit(1) if (webhooks.length === 0) { - logger.warn(`[${requestId}] Webhook not found: ${id}`) + logger.warn(`Webhook not found: ${id}`) return NextResponse.json({ error: 'Webhook not found' }, { status: 404 }) } @@ -247,7 +243,7 @@ export const DELETE = withRouteHandler( const canDelete = authorization.allowed if (!canDelete) { - logger.warn(`[${requestId}] User ${userId} denied permission to delete webhook: ${id}`) + logger.warn(`User ${userId} denied permission to delete webhook: ${id}`) return NextResponse.json({ error: 'Access denied' }, { status: 403 }) } await assertWorkflowMutable(webhookData.workflow.id) @@ -266,7 +262,7 @@ export const DELETE = withRouteHandler( // Telemetry should not fail the operation } - logger.info(`[${requestId}] Successfully deleted webhook: ${id}`) + logger.info(`Successfully deleted webhook: ${id}`) recordAudit({ workspaceId: webhookData.workflow.workspaceId || null, @@ -306,7 +302,7 @@ export const DELETE = withRouteHandler( return NextResponse.json({ error: error.message }, { status: error.status }) } - logger.error(`[${requestId}] Error deleting webhook`, { + logger.error('Error deleting webhook', { error: error.message, stack: error.stack, }) diff --git a/apps/sim/app/api/webhooks/cleanup/idempotency/route.ts b/apps/sim/app/api/webhooks/cleanup/idempotency/route.ts index ba715ab2ee8..5a7346c59c6 100644 --- a/apps/sim/app/api/webhooks/cleanup/idempotency/route.ts +++ b/apps/sim/app/api/webhooks/cleanup/idempotency/route.ts @@ -13,7 +13,7 @@ export const maxDuration = 300 // Allow up to 5 minutes for cleanup export const GET = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() - logger.info(`Idempotency cleanup triggered (${requestId})`) + logger.info('Idempotency cleanup triggered') try { const authError = verifyCronAuth(request, 'Idempotency key cleanup') @@ -45,7 +45,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { }, }) } catch (error) { - logger.error(`Error during idempotency cleanup (${requestId}):`, error) + logger.error('Error during idempotency cleanup:', error) return NextResponse.json( { success: false, diff --git a/apps/sim/app/api/webhooks/poll/[provider]/route.ts b/apps/sim/app/api/webhooks/poll/[provider]/route.ts index 8c09260dbb9..874062e980d 100644 --- a/apps/sim/app/api/webhooks/poll/[provider]/route.ts +++ b/apps/sim/app/api/webhooks/poll/[provider]/route.ts @@ -1,12 +1,12 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' -import { generateShortId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { webhookPollingContract } from '@/lib/api/contracts/webhooks' import { parseRequest } from '@/lib/api/server' import { verifyCronAuth } from '@/lib/auth/internal' import { acquireLock, releaseLock } from '@/lib/core/config/redis' import { runDetached } from '@/lib/core/utils/background' +import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { pollProvider, VALID_POLLING_PROVIDERS } from '@/lib/webhooks/polling' @@ -20,7 +20,7 @@ export const maxDuration = 180 export const GET = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ provider: string }> }) => { - const requestId = generateShortId() + const requestId = generateRequestId() let provider: string | undefined try { @@ -75,7 +75,7 @@ export const GET = withRouteHandler( ) } catch (error) { const providerLabel = provider ?? 'webhook' - logger.error(`Error during ${providerLabel} polling (${requestId}):`, error) + logger.error(`Error during ${providerLabel} polling:`, error) return NextResponse.json( { success: false, diff --git a/apps/sim/app/api/webhooks/quickbooks/[appKey]/route.ts b/apps/sim/app/api/webhooks/quickbooks/[appKey]/route.ts index 518bde8b878..636173abf51 100644 --- a/apps/sim/app/api/webhooks/quickbooks/[appKey]/route.ts +++ b/apps/sim/app/api/webhooks/quickbooks/[appKey]/route.ts @@ -83,7 +83,7 @@ export const POST = withRouteHandler( json.length === 0 || json.length > QUICKBOOKS_WEBHOOK_MAX_EVENTS ) { - logger.warn(`[${requestId}] Invalid QuickBooks webhook envelope`) + logger.warn('Invalid QuickBooks webhook envelope') return NextResponse.json({ error: 'Invalid webhook envelope' }, { status: 400 }) } @@ -95,7 +95,7 @@ export const POST = withRouteHandler( else droppedCount += 1 } if (droppedCount > 0) { - logger.warn(`[${requestId}] Dropped unmodelled QuickBooks webhook events`, { + logger.warn('Dropped unmodelled QuickBooks webhook events', { droppedCount, eventCount: json.length, }) @@ -112,13 +112,13 @@ export const POST = withRouteHandler( receivedAt, } const jobId = await enqueueQuickBooksWebhookIngress(payload) - logger.info(`[${requestId}] Accepted QuickBooks webhook delivery`, { + logger.info('Accepted QuickBooks webhook delivery', { eventCount: events.length, jobId, }) return NextResponse.json({ ok: true }) } catch (error) { - logger.error(`[${requestId}] QuickBooks webhook ingress error`, { + logger.error('QuickBooks webhook ingress error', { error: getErrorMessage(error, 'Unknown error'), }) return NextResponse.json({ error: 'Temporarily unable to accept webhook' }, { status: 503 }) diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index 22468160cb5..dc93062b21f 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -62,7 +62,8 @@ async function revertSavedWebhook( updatedAt: existingWebhook.updatedAt, }) .where(eq(webhook.id, savedWebhook.id)) - logger.info(`[${requestId}] Restored previous webhook configuration after failed re-save`, { + logger.info('Restored previous webhook configuration after failed re-save', { + requestId, webhookId: savedWebhook.id, }) return @@ -73,12 +74,10 @@ async function revertSavedWebhook( // Get all webhooks for the current user export const GET = withRouteHandler(async (request: NextRequest) => { - const requestId = generateRequestId() - try { const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized webhooks access attempt`) + logger.warn('Unauthorized webhooks access attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -96,7 +95,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { .limit(1) if (!wf.length) { - logger.warn(`[${requestId}] Workflow not found: ${workflowId}`) + logger.warn(`Workflow not found: ${workflowId}`) return NextResponse.json({ error: 'Workflow not found' }, { status: 404 }) } @@ -110,7 +109,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { if (!canRead) { logger.warn( - `[${requestId}] User ${session.user.id} denied permission to read webhooks for workflow ${workflowId}` + `User ${session.user.id} denied permission to read webhooks for workflow ${workflowId}` ) return NextResponse.json({ webhooks: [] }, { status: 200 }) } @@ -146,7 +145,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { .orderBy(desc(webhook.updatedAt)) logger.info( - `[${requestId}] Retrieved ${webhooks.length} webhooks for workflow ${workflowId} block ${blockId}` + `Retrieved ${webhooks.length} webhooks for workflow ${workflowId} block ${blockId}` ) return NextResponse.json({ webhooks }, { status: 200 }) } @@ -174,10 +173,10 @@ export const GET = withRouteHandler(async (request: NextRequest) => { .innerJoin(workflow, eq(webhook.workflowId, workflow.id)) .where(and(inArray(workflow.workspaceId, workspaceIds), isNull(webhook.archivedAt))) - logger.info(`[${requestId}] Retrieved ${webhooks.length} workspace-accessible webhooks`) + logger.info(`Retrieved ${webhooks.length} workspace-accessible webhooks`) return NextResponse.json({ webhooks }, { status: 200 }) } catch (error) { - logger.error(`[${requestId}] Error fetching webhooks`, error) + logger.error('Error fetching webhooks', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) @@ -189,7 +188,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const userId = session?.user?.id if (!userId) { - logger.warn(`[${requestId}] Unauthorized webhook creation attempt`) + logger.warn('Unauthorized webhook creation attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -203,7 +202,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { // Validate input if (!workflowId) { - logger.warn(`[${requestId}] Missing required fields for webhook creation`, { + logger.warn('Missing required fields for webhook creation', { hasWorkflowId: !!workflowId, hasPath: !!path, }) @@ -251,19 +250,17 @@ export const POST = withRouteHandler(async (request: NextRequest) => { if (existingForBlock.length > 0) { finalPath = existingForBlock[0].path ?? '' - logger.info( - `[${requestId}] Reusing existing generated path for ${provider} trigger: ${finalPath}` - ) + logger.info(`Reusing existing generated path for ${provider} trigger: ${finalPath}`) } } // If still no path, generate a new dummy path (first-time save) if (!finalPath || finalPath.trim() === '') { finalPath = `${provider}-${generateId()}` - logger.info(`[${requestId}] Generated webhook path for ${provider} trigger: ${finalPath}`) + logger.info(`Generated webhook path for ${provider} trigger: ${finalPath}`) } } else { - logger.warn(`[${requestId}] Missing path for webhook creation`, { + logger.warn('Missing path for webhook creation', { hasWorkflowId: !!workflowId, hasPath: !!path, }) @@ -291,7 +288,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { .limit(1) if (workflowData.length === 0) { - logger.warn(`[${requestId}] Workflow not found: ${workflowId}`) + logger.warn(`Workflow not found: ${workflowId}`) return NextResponse.json({ error: 'Workflow not found' }, { status: 404 }) } @@ -305,9 +302,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const canModify = authorization.allowed if (!canModify) { - logger.warn( - `[${requestId}] User ${userId} denied permission to modify webhook for workflow ${workflowId}` - ) + logger.warn(`User ${userId} denied permission to modify webhook for workflow ${workflowId}`) return NextResponse.json({ error: 'Access denied' }, { status: 403 }) } await assertWorkflowMutable(workflowId) @@ -347,7 +342,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { workflowId, }) if (conflictingOwner) { - logger.warn(`[${requestId}] Webhook path conflict: ${finalPath}`) + logger.warn(`Webhook path conflict: ${finalPath}`) return NextResponse.json( { error: 'Webhook path already exists.', code: 'PATH_EXISTS' }, { status: 409 } @@ -445,7 +440,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { : false if (withheld) { logger.warn( - `[${requestId}] Webhook ${existingWebhook ? 'reactivation' : 'creation'} blocked by permission group`, + `Webhook ${existingWebhook ? 'reactivation' : 'creation'} blocked by permission group`, { userId, workflowId, @@ -500,7 +495,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { credentialId, workflowId } ) if (!credentialAccess.ok) { - logger.warn(`[${requestId}] Webhook credential reference denied`, { + logger.warn('Webhook credential reference denied', { userId, workflowId, credentialId, @@ -576,11 +571,11 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } catch (err) { const isConfigurationError = err instanceof WebhookDeploymentConfigurationError if (isConfigurationError) { - logger.warn(`[${requestId}] Rejected webhook trigger configuration`, { + logger.warn('Rejected webhook trigger configuration', { error: err.message, }) } else { - logger.error(`[${requestId}] Error creating external webhook subscription`, err) + logger.error('Error creating external webhook subscription', err) } return NextResponse.json( { @@ -597,7 +592,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { try { if (targetWebhookId) { - logger.info(`[${requestId}] Updating existing webhook for path: ${finalPath}`, { + logger.info(`Updating existing webhook for path: ${finalPath}`, { webhookId: targetWebhookId, provider, hasCredentialId: !!(configToSave as any)?.credentialId, @@ -618,14 +613,14 @@ export const POST = withRouteHandler(async (request: NextRequest) => { .where(eq(webhook.id, targetWebhookId)) .returning() savedWebhook = updatedResult[0] - logger.info(`[${requestId}] Webhook updated successfully`, { + logger.info('Webhook updated successfully', { webhookId: savedWebhook.id, savedProviderConfig: savedWebhook.providerConfig, }) } else { // Create a new webhook const webhookId = generateShortId() - logger.info(`[${requestId}] Creating new webhook with ID: ${webhookId}`) + logger.info(`Creating new webhook with ID: ${webhookId}`) const newResult = await db .insert(webhook) .values({ @@ -647,7 +642,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } } catch (dbError) { if (externalSubscriptionCreated) { - logger.error(`[${requestId}] DB save failed, cleaning up external subscription`, dbError) + logger.error('DB save failed, cleaning up external subscription', dbError) try { await cleanupExternalWebhook( createTempWebhookData(omit(configToSave, ['previousSubscription'])), @@ -656,7 +651,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } catch (cleanupError) { logger.error( - `[${requestId}] Failed to cleanup external subscription after DB save failure`, + 'Failed to cleanup external subscription after DB save failure', cleanupError ) } @@ -712,7 +707,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { await cleanupExternalWebhook(existingWebhook, workflowRecord, requestId) } catch (cleanupError) { logger.warn( - `[${requestId}] Failed to cleanup previous external webhook subscription ${existingWebhook.id}`, + `Failed to cleanup previous external webhook subscription ${existingWebhook.id}`, cleanupError ) } @@ -721,9 +716,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { if (savedWebhook) { const pollingHandler = getProviderHandler(provider) if (pollingHandler.configurePolling) { - logger.info( - `[${requestId}] ${provider} provider detected. Setting up polling configuration.` - ) + logger.info(`${provider} provider detected. Setting up polling configuration.`) try { const success = await pollingHandler.configurePolling({ webhook: savedWebhook, @@ -735,9 +728,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { }) if (!success) { - logger.error( - `[${requestId}] Failed to configure ${provider} polling, rolling back webhook` - ) + logger.error(`Failed to configure ${provider} polling, rolling back webhook`) await revertSavedWebhook(savedWebhook, existingWebhook, requestId) return NextResponse.json( { @@ -748,10 +739,10 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - logger.info(`[${requestId}] Successfully configured ${provider} polling`) + logger.info(`Successfully configured ${provider} polling`) } catch (err) { logger.error( - `[${requestId}] Error setting up ${provider} webhook configuration, rolling back webhook`, + `Error setting up ${provider} webhook configuration, rolling back webhook`, err ) await revertSavedWebhook(savedWebhook, existingWebhook, requestId) @@ -818,7 +809,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ error: error.message }, { status: error.status }) } - logger.error(`[${requestId}] Error creating/updating webhook`, { + logger.error('Error creating/updating webhook', { message: error.message, stack: error.stack, }) diff --git a/apps/sim/app/api/webhooks/slack/route.ts b/apps/sim/app/api/webhooks/slack/route.ts index 85b87c35b41..c967bb7a369 100644 --- a/apps/sim/app/api/webhooks/slack/route.ts +++ b/apps/sim/app/api/webhooks/slack/route.ts @@ -171,7 +171,7 @@ async function handleSlackAppWebhook(request: NextRequest): Promise { rawBody = await readTikTokBody(request) } catch (bodyError) { if (isPayloadSizeLimitError(bodyError)) { - logger.warn(`[${requestId}] Rejected oversized TikTok webhook body`, { + logger.warn('Rejected oversized TikTok webhook body', { maxBytes: WEBHOOK_MAX_BODY_BYTES, observedBytes: bodyError.observedBytes, }) @@ -74,14 +74,14 @@ export const POST = withRouteHandler(async (request: NextRequest) => { try { parsedJson = rawBody ? JSON.parse(rawBody) : {} } catch { - logger.warn(`[${requestId}] TikTok webhook body is not valid JSON`) + logger.warn('TikTok webhook body is not valid JSON') // Ack to avoid retry storms on malformed payloads after a valid signature. return NextResponse.json({ ok: true }) } const envelopeResult = tiktokWebhookEnvelopeSchema.safeParse(parsedJson) if (!envelopeResult.success) { - logger.warn(`[${requestId}] Invalid TikTok webhook envelope`, { + logger.warn('Invalid TikTok webhook envelope', { issues: envelopeResult.error.issues, }) return NextResponse.json({ ok: true }) @@ -89,7 +89,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const envelope = envelopeResult.data if (!env.TIKTOK_CLIENT_ID || envelope.client_key !== env.TIKTOK_CLIENT_ID) { - logger.warn(`[${requestId}] TikTok webhook client_key does not match configured app`) + logger.warn('TikTok webhook client_key does not match configured app') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -107,7 +107,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { if (result.outcome === 'failed') failed += 1 } - logger.info(`[${requestId}] Processed TikTok webhook delivery`, { + logger.info('Processed TikTok webhook delivery', { dispatched, failed, event: envelope.event, @@ -121,7 +121,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ ok: true }) } catch (error) { - logger.error(`[${requestId}] TikTok webhook processing error`, { + logger.error('TikTok webhook processing error', { error: getErrorMessage(error, 'Unknown error'), }) return NextResponse.json({ error: 'Temporarily unable to accept webhook' }, { status: 503 }) diff --git a/apps/sim/app/api/webhooks/trigger/[path]/route.ts b/apps/sim/app/api/webhooks/trigger/[path]/route.ts index b201908cfc3..2e35e7f11ab 100644 --- a/apps/sim/app/api/webhooks/trigger/[path]/route.ts +++ b/apps/sim/app/api/webhooks/trigger/[path]/route.ts @@ -179,7 +179,7 @@ async function handleWebhookDelivery( ) if (allWebhooksForPath.length > 0 && pathWebhooks.length === 0) { - logger.warn(`[${requestId}] Rejected HTTP delivery to non-path trigger: ${path}`) + logger.warn(`Rejected HTTP delivery to non-path trigger: ${path}`, { requestId }) return notDeliverableResponse(request.method) } @@ -189,7 +189,8 @@ async function handleWebhookDelivery( if (pathWebhooks.length > 0 && webhooksForPath.length === 0) { logger.warn( - `[${requestId}] Rejected ${request.method} delivery to path ${path}: no trigger on this path accepts that method` + `Rejected ${request.method} delivery to path ${path}: no trigger on this path accepts that method`, + { requestId } ) return methodNotAllowedResponse() } @@ -205,7 +206,7 @@ async function handleWebhookDelivery( return verificationResponse } - logger.debug(`[${requestId}] Webhook or workflow not found for path: ${path}`) + logger.debug(`Webhook or workflow not found for path: ${path}`, { requestId }) return notDeliverableResponse(request.method) } @@ -293,9 +294,9 @@ async function handleWebhookDelivery( { status: 500 } ) if (dispatchTargetCount > 1) { - logger.error( - `[${requestId}] Webhook ${foundWebhook.id} has no provider, continuing to next` - ) + logger.error(`Webhook ${foundWebhook.id} has no provider, continuing to next`, { + requestId, + }) continue } return missingProviderResponse @@ -310,7 +311,7 @@ async function handleWebhookDelivery( ) if (authError) { if (dispatchTargetCount > 1) { - logger.warn(`[${requestId}] Auth failed for webhook ${foundWebhook.id}, continuing to next`) + logger.warn(`Auth failed for webhook ${foundWebhook.id}, continuing to next`, { requestId }) continue } return authError @@ -346,10 +347,11 @@ async function handleWebhookDelivery( if (dispatchResult.outcome === 'failed' || dispatchResult.reason === 'block-missing') { if (dispatchTargetCount > 1) { - logger.warn( - `[${requestId}] Webhook dispatch failed for ${foundWebhook.id}, continuing to next`, - { reason: dispatchResult.reason, status: dispatchResult.response.status } - ) + logger.warn(`Webhook dispatch failed for ${foundWebhook.id}, continuing to next`, { + requestId, + reason: dispatchResult.reason, + status: dispatchResult.response.status, + }) if (dispatchResult.outcome === 'failed') { failures.push(dispatchResult.response) } else { @@ -381,7 +383,7 @@ async function handleWebhookDelivery( } // For multiple webhooks, return success if at least one succeeded - logger.info(`[${requestId}] Processed ${responses.length} webhooks for path: ${path}`) + logger.info(`Processed ${responses.length} webhooks for path: ${path}`, { requestId }) return NextResponse.json({ success: true, webhooksProcessed: responses.length, diff --git a/apps/sim/app/api/workflows/[id]/autolayout/route.ts b/apps/sim/app/api/workflows/[id]/autolayout/route.ts index 387047da880..94bc9fea409 100644 --- a/apps/sim/app/api/workflows/[id]/autolayout/route.ts +++ b/apps/sim/app/api/workflows/[id]/autolayout/route.ts @@ -9,7 +9,6 @@ import { type NextRequest, NextResponse } from 'next/server' import { workflowAutoLayoutContract } from '@/lib/api/contracts/workflows' import { parseRequest } from '@/lib/api/server' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { applyAutoLayout } from '@/lib/workflows/autolayout' import { @@ -32,14 +31,13 @@ const logger = createLogger('AutoLayoutAPI') */ export const POST = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ id: string }> }) => { - const requestId = generateRequestId() const startTime = Date.now() const { id: workflowId } = await context.params try { const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!auth.success || !auth.userId) { - logger.warn(`[${requestId}] Unauthorized autolayout attempt for workflow ${workflowId}`) + logger.warn(`Unauthorized autolayout attempt for workflow ${workflowId}`) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -49,7 +47,7 @@ export const POST = withRouteHandler( if (!parsed.success) return parsed.response const layoutOptions = parsed.data.body - logger.info(`[${requestId}] Processing autolayout request for workflow ${workflowId}`, { + logger.info(`Processing autolayout request for workflow ${workflowId}`, { userId, }) @@ -61,16 +59,14 @@ export const POST = withRouteHandler( const workflowData = authorization.workflow if (!workflowData) { - logger.warn(`[${requestId}] Workflow ${workflowId} not found for autolayout`) + logger.warn(`Workflow ${workflowId} not found for autolayout`) return NextResponse.json({ error: 'Workflow not found' }, { status: 404 }) } const canUpdate = authorization.allowed if (!canUpdate) { - logger.warn( - `[${requestId}] User ${userId} denied permission to autolayout workflow ${workflowId}` - ) + logger.warn(`User ${userId} denied permission to autolayout workflow ${workflowId}`) return NextResponse.json( { error: authorization.message || 'Access denied' }, { status: authorization.status || 403 } @@ -82,7 +78,7 @@ export const POST = withRouteHandler( let currentWorkflowData: NormalizedWorkflowData | null if (layoutOptions.blocks && layoutOptions.edges) { - logger.info(`[${requestId}] Using provided blocks with live measurements`) + logger.info('Using provided blocks with live measurements') currentWorkflowData = { blocks: layoutOptions.blocks, edges: layoutOptions.edges, @@ -91,12 +87,12 @@ export const POST = withRouteHandler( isFromNormalizedTables: false, } } else { - logger.info(`[${requestId}] Loading blocks from database`) + logger.info('Loading blocks from database') currentWorkflowData = await loadWorkflowFromNormalizedTables(workflowId) } if (!currentWorkflowData) { - logger.error(`[${requestId}] Could not load workflow ${workflowId} for autolayout`) + logger.error(`Could not load workflow ${workflowId} for autolayout`) return NextResponse.json({ error: 'Could not load workflow data' }, { status: 500 }) } @@ -118,7 +114,7 @@ export const POST = withRouteHandler( ) if (!layoutResult.success || !layoutResult.blocks) { - logger.error(`[${requestId}] Auto layout failed:`, { + logger.error('Auto layout failed:', { error: layoutResult.error, }) return NextResponse.json( @@ -133,7 +129,7 @@ export const POST = withRouteHandler( const elapsed = Date.now() - startTime const blockCount = Object.keys(layoutResult.blocks).length - logger.info(`[${requestId}] Autolayout completed successfully in ${elapsed}ms`, { + logger.info(`Autolayout completed successfully in ${elapsed}ms`, { blockCount, workflowId, }) @@ -154,7 +150,7 @@ export const POST = withRouteHandler( const elapsed = Date.now() - startTime - logger.error(`[${requestId}] Autolayout failed after ${elapsed}ms:`, error) + logger.error(`Autolayout failed after ${elapsed}ms:`, error) return NextResponse.json( { error: 'Autolayout failed', diff --git a/apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts b/apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts index 20bf893ee71..d95612287c8 100644 --- a/apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts +++ b/apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts @@ -88,10 +88,10 @@ export const PATCH = withRouteHandler( }) if (name !== undefined || description !== undefined) { - logger.info( - `[${requestId}] Updated deployment version ${version} metadata during activation`, - { name, description } - ) + logger.info(`Updated deployment version ${version} metadata during activation`, { + name, + description, + }) } return createSuccessResponse({ @@ -111,7 +111,7 @@ export const PATCH = withRouteHandler( request, }) - logger.info(`[${requestId}] Updated deployment version ${version} for workflow ${id}`, { + logger.info(`Updated deployment version ${version} for workflow ${id}`, { name, description, }) @@ -130,7 +130,7 @@ export const PATCH = withRouteHandler( statusForOrchestrationError(orchestrationError.code) ) } - logger.error(`[${requestId}] Error updating deployment version`, { error }) + logger.error('Error updating deployment version', { error }) return createCodedErrorResponse('Failed to update deployment version', 500) } } diff --git a/apps/sim/app/api/workflows/[id]/duplicate/route.ts b/apps/sim/app/api/workflows/[id]/duplicate/route.ts index beba4a3f3ab..bde8f9900bd 100644 --- a/apps/sim/app/api/workflows/[id]/duplicate/route.ts +++ b/apps/sim/app/api/workflows/[id]/duplicate/route.ts @@ -22,9 +22,7 @@ export const POST = withRouteHandler( const auth = await checkSessionOrInternalAuth(req, { requireWorkflowId: false }) if (!auth.success || !auth.userId) { - logger.warn( - `[${requestId}] Unauthorized workflow duplication attempt for ${sourceWorkflowId}` - ) + logger.warn(`Unauthorized workflow duplication attempt for ${sourceWorkflowId}`) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const userId = auth.userId @@ -34,7 +32,7 @@ export const POST = withRouteHandler( if (!parsed.success) return parsed.response const { name, description, workspaceId, folderId, newId } = parsed.data.body - logger.info(`[${requestId}] Duplicating workflow ${sourceWorkflowId} for user ${userId}`) + logger.info(`Duplicating workflow ${sourceWorkflowId} for user ${userId}`) const result = await duplicateWorkflow({ sourceWorkflowId, @@ -70,7 +68,7 @@ export const POST = withRouteHandler( const elapsed = Date.now() - startTime logger.info( - `[${requestId}] Successfully duplicated workflow ${sourceWorkflowId} to ${result.id} in ${elapsed}ms` + `Successfully duplicated workflow ${sourceWorkflowId} to ${result.id} in ${elapsed}ms` ) recordAudit({ @@ -99,20 +97,18 @@ export const POST = withRouteHandler( } if (error.message === 'Source workflow not found') { - logger.warn(`[${requestId}] Source workflow ${sourceWorkflowId} not found`) + logger.warn(`Source workflow ${sourceWorkflowId} not found`) return NextResponse.json({ error: 'Source workflow not found' }, { status: 404 }) } if (error.message === 'Source workflow not found or access denied') { - logger.warn( - `[${requestId}] User ${userId} denied access to source workflow ${sourceWorkflowId}` - ) + logger.warn(`User ${userId} denied access to source workflow ${sourceWorkflowId}`) return NextResponse.json({ error: 'Access denied' }, { status: 403 }) } if (error.message === 'Cross-workspace workflow duplication is not supported') { logger.warn( - `[${requestId}] User ${userId} attempted cross-workspace workflow duplication for ${sourceWorkflowId}` + `User ${userId} attempted cross-workspace workflow duplication for ${sourceWorkflowId}` ) return NextResponse.json({ error: error.message }, { status: 400 }) } @@ -127,10 +123,7 @@ export const POST = withRouteHandler( } const elapsed = Date.now() - startTime - logger.error( - `[${requestId}] Error duplicating workflow ${sourceWorkflowId} after ${elapsed}ms:`, - error - ) + logger.error(`Error duplicating workflow ${sourceWorkflowId} after ${elapsed}ms:`, error) return NextResponse.json({ error: 'Failed to duplicate workflow' }, { status: 500 }) } } diff --git a/apps/sim/app/api/workflows/[id]/restore/route.ts b/apps/sim/app/api/workflows/[id]/restore/route.ts index b42dcdb9ce4..d95dee77ce0 100644 --- a/apps/sim/app/api/workflows/[id]/restore/route.ts +++ b/apps/sim/app/api/workflows/[id]/restore/route.ts @@ -66,7 +66,7 @@ export const POST = withRouteHandler( return NextResponse.json({ error: result.error }, { status }) } - logger.info(`[${requestId}] Restored workflow ${workflowId}`) + logger.info(`Restored workflow ${workflowId}`) captureServerEvent( auth.userId, @@ -81,7 +81,7 @@ export const POST = withRouteHandler( return NextResponse.json({ error: error.message }, { status: error.status }) } - logger.error(`[${requestId}] Error restoring workflow ${workflowId}`, error) + logger.error(`Error restoring workflow ${workflowId}`, error) return NextResponse.json( { error: getErrorMessage(error, 'Internal server error') }, { status: 500 } diff --git a/apps/sim/app/api/workflows/[id]/state/route.ts b/apps/sim/app/api/workflows/[id]/state/route.ts index 27ef1e72c2d..d5b5931b043 100644 --- a/apps/sim/app/api/workflows/[id]/state/route.ts +++ b/apps/sim/app/api/workflows/[id]/state/route.ts @@ -99,7 +99,7 @@ export const PUT = withRouteHandler( try { const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!auth.success || !auth.userId) { - logger.warn(`[${requestId}] Unauthorized state update attempt for workflow ${workflowId}`) + logger.warn(`Unauthorized state update attempt for workflow ${workflowId}`) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const userId = auth.userId @@ -125,15 +125,12 @@ export const PUT = withRouteHandler( } const elapsed = Date.now() - startTime - logger.info(`[${requestId}] Successfully saved workflow ${workflowId} state in ${elapsed}ms`) + logger.info(`Successfully saved workflow ${workflowId} state in ${elapsed}ms`) return NextResponse.json({ success: true, warnings: result.warnings }, { status: 200 }) } catch (error: any) { const elapsed = Date.now() - startTime - logger.error( - `[${requestId}] Error saving workflow ${workflowId} state after ${elapsed}ms`, - error - ) + logger.error(`Error saving workflow ${workflowId} state after ${elapsed}ms`, error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } diff --git a/apps/sim/app/api/workflows/[id]/variables/route.ts b/apps/sim/app/api/workflows/[id]/variables/route.ts index 09f6e6fb694..51171b6c56b 100644 --- a/apps/sim/app/api/workflows/[id]/variables/route.ts +++ b/apps/sim/app/api/workflows/[id]/variables/route.ts @@ -13,7 +13,6 @@ import { type NextRequest, NextResponse } from 'next/server' import { workflowVariablesContract } from '@/lib/api/contracts/workflows' import { parseRequest } from '@/lib/api/server' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import type { Variable } from '@/stores/variables/types' @@ -21,13 +20,12 @@ const logger = createLogger('WorkflowVariablesAPI') export const POST = withRouteHandler( async (req: NextRequest, context: { params: Promise<{ id: string }> }) => { - const requestId = generateRequestId() const workflowId = (await context.params).id try { const auth = await checkSessionOrInternalAuth(req, { requireWorkflowId: false }) if (!auth.success || !auth.userId) { - logger.warn(`[${requestId}] Unauthorized workflow variables update attempt`) + logger.warn('Unauthorized workflow variables update attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const userId = auth.userId @@ -40,14 +38,14 @@ export const POST = withRouteHandler( const workflowData = authorization.workflow if (!workflowData) { - logger.warn(`[${requestId}] Workflow not found: ${workflowId}`) + logger.warn(`Workflow not found: ${workflowId}`) return NextResponse.json({ error: 'Workflow not found' }, { status: 404 }) } const isAuthorized = authorization.allowed if (!isAuthorized) { logger.warn( - `[${requestId}] User ${userId} attempted to update variables for workflow ${workflowId} without permission` + `User ${userId} attempted to update variables for workflow ${workflowId} without permission` ) return NextResponse.json( { error: authorization.message || 'Access denied' }, @@ -99,7 +97,7 @@ export const POST = withRouteHandler( return NextResponse.json({ error: error.message }, { status: error.status }) } - logger.error(`[${requestId}] Error updating workflow variables`, error) + logger.error('Error updating workflow variables', error) return NextResponse.json({ error: 'Failed to update workflow variables' }, { status: 500 }) } } @@ -107,13 +105,12 @@ export const POST = withRouteHandler( export const GET = withRouteHandler( async (req: NextRequest, { params }: { params: Promise<{ id: string }> }) => { - const requestId = generateRequestId() const workflowId = (await params).id try { const auth = await checkSessionOrInternalAuth(req, { requireWorkflowId: false }) if (!auth.success || !auth.userId) { - logger.warn(`[${requestId}] Unauthorized workflow variables access attempt`) + logger.warn('Unauthorized workflow variables access attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const userId = auth.userId @@ -126,14 +123,14 @@ export const GET = withRouteHandler( const workflowData = authorization.workflow if (!workflowData) { - logger.warn(`[${requestId}] Workflow not found: ${workflowId}`) + logger.warn(`Workflow not found: ${workflowId}`) return NextResponse.json({ error: 'Workflow not found' }, { status: 404 }) } const isAuthorized = authorization.allowed if (!isAuthorized) { logger.warn( - `[${requestId}] User ${userId} attempted to access variables for workflow ${workflowId} without permission` + `User ${userId} attempted to access variables for workflow ${workflowId} without permission` ) return NextResponse.json( { error: authorization.message || 'Access denied' }, @@ -162,7 +159,7 @@ export const GET = withRouteHandler( } ) } catch (error) { - logger.error(`[${requestId}] Workflow variables fetch error`, error) + logger.error('Workflow variables fetch error', error) const errorMessage = getErrorMessage(error, 'Unknown error') return NextResponse.json({ error: errorMessage }, { status: 500 }) } diff --git a/apps/sim/app/api/workflows/reorder/route.ts b/apps/sim/app/api/workflows/reorder/route.ts index 45ab9f9addd..9a8a28731a5 100644 --- a/apps/sim/app/api/workflows/reorder/route.ts +++ b/apps/sim/app/api/workflows/reorder/route.ts @@ -14,7 +14,6 @@ import { type NextRequest, NextResponse } from 'next/server' import { reorderWorkflowsContract } from '@/lib/api/contracts/workflows' import { parseRequest } from '@/lib/api/server' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { notifyWorkspaceWorkflowsChanged } from '@/lib/realtime/notify' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' @@ -22,10 +21,9 @@ import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('WorkflowReorderAPI') export const PUT = withRouteHandler(async (req: NextRequest) => { - const requestId = generateRequestId() const auth = await checkSessionOrInternalAuth(req, { requireWorkflowId: false }) if (!auth.success || !auth.userId) { - logger.warn(`[${requestId}] Unauthorized reorder attempt`) + logger.warn('Unauthorized reorder attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const userId = auth.userId @@ -37,9 +35,7 @@ export const PUT = withRouteHandler(async (req: NextRequest) => { const permission = await getUserEntityPermissions(userId, 'workspace', workspaceId) if (!permission || permission === 'read') { - logger.warn( - `[${requestId}] User ${userId} lacks write permission for workspace ${workspaceId}` - ) + logger.warn(`User ${userId} lacks write permission for workspace ${workspaceId}`) return NextResponse.json({ error: 'Write access required' }, { status: 403 }) } @@ -80,9 +76,7 @@ export const PUT = withRouteHandler(async (req: NextRequest) => { } }) - logger.info( - `[${requestId}] Reordered ${validUpdates.length} workflows in workspace ${workspaceId}` - ) + logger.info(`Reordered ${validUpdates.length} workflows in workspace ${workspaceId}`) await notifyWorkspaceWorkflowsChanged(workspaceId) @@ -96,7 +90,7 @@ export const PUT = withRouteHandler(async (req: NextRequest) => { return NextResponse.json({ error: error.message }, { status: error.status }) } - logger.error(`[${requestId}] Error reordering workflows`, error) + logger.error('Error reordering workflows', error) return NextResponse.json({ error: 'Failed to reorder workflows' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/workflows/route.ts b/apps/sim/app/api/workflows/route.ts index 05d94a31b1d..6758f56c55d 100644 --- a/apps/sim/app/api/workflows/route.ts +++ b/apps/sim/app/api/workflows/route.ts @@ -15,7 +15,6 @@ import { verifyWorkspaceMembership } from '@/app/api/workflows/utils' const logger = createLogger('WorkflowAPI') export const GET = withRouteHandler(async (request: NextRequest) => { - const requestId = generateRequestId() const startTime = Date.now() const url = new URL(request.url) const query = workflowListQuerySchema.safeParse(Object.fromEntries(url.searchParams.entries())) @@ -30,7 +29,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { try { const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!auth.success || !auth.userId) { - logger.warn(`[${requestId}] Unauthorized workflow access attempt`) + logger.warn('Unauthorized workflow access attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const userId = auth.userId @@ -39,9 +38,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { const wsExists = await workspaceExists(workspaceId) if (!wsExists) { - logger.warn( - `[${requestId}] Attempt to fetch workflows for non-existent workspace: ${workspaceId}` - ) + logger.warn(`Attempt to fetch workflows for non-existent workspace: ${workspaceId}`) return NextResponse.json( { error: 'Workspace not found', code: 'WORKSPACE_NOT_FOUND' }, { status: 404 } @@ -52,7 +49,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { if (!userRole) { logger.warn( - `[${requestId}] User ${userId} attempted to access workspace ${workspaceId} without membership` + `User ${userId} attempted to access workspace ${workspaceId} without membership` ) return NextResponse.json( { error: 'Access denied to this workspace', code: 'WORKSPACE_ACCESS_DENIED' }, @@ -66,7 +63,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ data: workflows }, { status: 200 }) } catch (error: any) { const elapsed = Date.now() - startTime - logger.error(`[${requestId}] Workflow fetch error after ${elapsed}ms`, error) + logger.error(`Workflow fetch error after ${elapsed}ms`, error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) @@ -76,7 +73,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const requestId = generateRequestId() const auth = await checkSessionOrInternalAuth(req, { requireWorkflowId: false }) if (!auth.success || !auth.userId) { - logger.warn(`[${requestId}] Unauthorized workflow creation attempt`) + logger.warn('Unauthorized workflow creation attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const userId = auth.userId @@ -95,7 +92,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { } = parsed.data.body if (!workspaceId) { - logger.warn(`[${requestId}] Workflow creation blocked: missing workspaceId`) + logger.warn('Workflow creation blocked: missing workspaceId') return NextResponse.json( { error: @@ -109,7 +106,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { if (!workspacePermission || workspacePermission === 'read') { logger.warn( - `[${requestId}] User ${userId} attempted to create workflow in workspace ${workspaceId} without write permissions` + `User ${userId} attempted to create workflow in workspace ${workspaceId} without write permissions` ) return NextResponse.json( { error: 'Write or Admin access required to create workflows in this workspace' }, @@ -152,9 +149,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { // Silently fail }) - logger.info( - `[${requestId}] Successfully created workflow ${createdWorkflow.id} with default blocks` - ) + logger.info(`Successfully created workflow ${createdWorkflow.id} with default blocks`) captureServerEvent( userId, @@ -186,7 +181,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { if (error instanceof FolderLockedError) { return NextResponse.json({ error: error.message }, { status: error.status }) } - logger.error(`[${requestId}] Error creating workflow`, error) + logger.error('Error creating workflow', error) return NextResponse.json({ error: 'Failed to create workflow' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/workspace-events/poll/route.ts b/apps/sim/app/api/workspace-events/poll/route.ts index 7cde86e0d49..a3d63ccbf58 100644 --- a/apps/sim/app/api/workspace-events/poll/route.ts +++ b/apps/sim/app/api/workspace-events/poll/route.ts @@ -1,12 +1,12 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' -import { generateShortId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { noInputSchema } from '@/lib/api/contracts/primitives' import { validationErrorResponse } from '@/lib/api/server' import { verifyCronAuth } from '@/lib/auth/internal' import { acquireLock, releaseLock } from '@/lib/core/config/redis' import { runDetached } from '@/lib/core/utils/background' +import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { pollNoActivityEvents } from '@/lib/workspace-events/no-activity' @@ -18,8 +18,8 @@ const LOCK_KEY = 'workspace-events-no-activity-poll-lock' const LOCK_TTL_SECONDS = 120 export const GET = withRouteHandler(async (request: NextRequest) => { - const requestId = generateShortId() - logger.info(`Workspace events no-activity polling triggered (${requestId})`) + const requestId = generateRequestId() + logger.info('Workspace events no-activity polling triggered') const queryValidation = noInputSchema.safeParse( Object.fromEntries(request.nextUrl.searchParams.entries()) ) @@ -65,7 +65,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { { status: 202 } ) } catch (error) { - logger.error(`Error during workspace events polling (${requestId}):`, error) + logger.error('Error during workspace events polling:', error) return NextResponse.json( { success: false, diff --git a/apps/sim/app/api/workspaces/[id]/api-keys/route.ts b/apps/sim/app/api/workspaces/[id]/api-keys/route.ts index 4ef2865e2f4..2dc2ea4ffdf 100644 --- a/apps/sim/app/api/workspaces/[id]/api-keys/route.ts +++ b/apps/sim/app/api/workspaces/[id]/api-keys/route.ts @@ -24,7 +24,6 @@ import { workspaceApiKeyErrorPolicy } from '@/lib/api-key/management-error-polic import { performCreateWorkspaceApiKey } from '@/lib/api-key/orchestration' import { getSession } from '@/lib/auth' import { PlatformEvents } from '@/lib/core/telemetry' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { capabilityRefusal, @@ -68,13 +67,12 @@ export const GET = defineInternalJsonRoute({ */ export const POST = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ id: string }> }) => { - const requestId = generateRequestId() const workspaceId = (await context.params).id try { const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized workspace API key creation attempt`) + logger.warn('Unauthorized workspace API key creation attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -117,13 +115,13 @@ export const POST = withRouteHandler( } ) - logger.info(`[${requestId}] Created workspace API key: ${name} in workspace ${workspaceId}`) + logger.info(`Created workspace API key: ${name} in workspace ${workspaceId}`) return NextResponse.json({ key: result.key, }) } catch (error: unknown) { - logger.error(`[${requestId}] Workspace API key POST error`, error) + logger.error('Workspace API key POST error', error) return NextResponse.json( { error: getErrorMessage(error, 'Failed to create workspace API key') }, { status: 500 } @@ -134,13 +132,12 @@ export const POST = withRouteHandler( export const DELETE = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ id: string }> }) => { - const requestId = generateRequestId() const workspaceId = (await context.params).id try { const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized workspace API key deletion attempt`) + logger.warn('Unauthorized workspace API key deletion attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -183,9 +180,7 @@ export const DELETE = withRouteHandler( // Telemetry should not fail the operation } - logger.info( - `[${requestId}] Deleted ${deletedCount} workspace API keys from workspace ${workspaceId}` - ) + logger.info(`Deleted ${deletedCount} workspace API keys from workspace ${workspaceId}`) recordAudit({ workspaceId, @@ -201,7 +196,7 @@ export const DELETE = withRouteHandler( return NextResponse.json({ success: true, deletedCount }) } catch (error: unknown) { - logger.error(`[${requestId}] Workspace API key DELETE error`, error) + logger.error('Workspace API key DELETE error', error) return NextResponse.json( { error: getErrorMessage(error, 'Failed to delete workspace API keys') }, { status: 500 } diff --git a/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts b/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts index 96438089796..485c9f4a905 100644 --- a/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts +++ b/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts @@ -28,7 +28,6 @@ import { import { workspaceByokErrorPolicy } from '@/lib/api-key/byok-error-policy' import { getSession } from '@/lib/auth' import { encryptSecret } from '@/lib/core/security/encryption' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { acquireAdvisoryXactLock } from '@/lib/db/advisory-locks' import { captureServerEvent } from '@/lib/posthog/server' @@ -73,13 +72,12 @@ export const GET = defineInternalJsonRoute({ export const POST = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ id: string }> }) => { - const requestId = generateRequestId() const workspaceId = (await context.params).id try { const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized BYOK key creation attempt`) + logger.warn('Unauthorized BYOK key creation attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -127,7 +125,7 @@ export const POST = withRouteHandler( }) .where(eq(workspaceBYOKKeys.id, existingKey.id)) - logger.info(`[${requestId}] Updated BYOK key for ${providerId} in workspace ${workspaceId}`) + logger.info(`Updated BYOK key for ${providerId} in workspace ${workspaceId}`) recordAudit({ workspaceId, @@ -208,7 +206,7 @@ export const POST = withRouteHandler( ) } - logger.info(`[${requestId}] Created BYOK key for ${providerId} in workspace ${workspaceId}`) + logger.info(`Created BYOK key for ${providerId} in workspace ${workspaceId}`) captureServerEvent( userId, @@ -242,7 +240,7 @@ export const POST = withRouteHandler( }, }) } catch (error: unknown) { - logger.error(`[${requestId}] BYOK key POST error`, error) + logger.error('BYOK key POST error', error) return NextResponse.json( { error: getErrorMessage(error, 'Failed to save BYOK key') }, { status: 500 } diff --git a/apps/sim/app/api/workspaces/[id]/environment/route.ts b/apps/sim/app/api/workspaces/[id]/environment/route.ts index 1ed68d7adb2..8b8d51fad1d 100644 --- a/apps/sim/app/api/workspaces/[id]/environment/route.ts +++ b/apps/sim/app/api/workspaces/[id]/environment/route.ts @@ -13,7 +13,6 @@ import { import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { encryptSecret } from '@/lib/core/security/encryption' -import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { lockWorkspaceEnvMap } from '@/lib/credentials/env-locks' import { @@ -125,13 +124,12 @@ async function maskPersonalEnvForViewer({ export const GET = withRouteHandler( async (_request: NextRequest, { params }: { params: Promise<{ id: string }> }) => { - const requestId = generateRequestId() const workspaceId = (await params).id try { const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized workspace env access attempt`) + logger.warn('Unauthorized workspace env access attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -183,7 +181,7 @@ export const GET = withRouteHandler( { status: 200 } ) } catch (error) { - logger.error(`[${requestId}] Workspace env GET error`, error) + logger.error('Workspace env GET error', error) return NextResponse.json( { error: getErrorMessage(error, 'Failed to load environment') }, { status: 500 } @@ -200,13 +198,12 @@ export const GET = withRouteHandler( */ export const PUT = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ id: string }> }) => { - const requestId = generateRequestId() const workspaceId = (await context.params).id try { const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized workspace env update attempt`) + logger.warn('Unauthorized workspace env update attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -236,7 +233,7 @@ export const PUT = withRouteHandler( const isKeyAdmin = (key: string) => permission === 'admin' || adminKeys.has(key) const forbiddenExisting = incomingKeys.filter((k) => knownKeys.has(k) && !isKeyAdmin(k)) if (forbiddenExisting.length > 0) { - logger.warn(`[${requestId}] Workspace env update denied`, { + logger.warn('Workspace env update denied', { workspaceId, userId, reason: 'not-secret-admin', @@ -252,7 +249,7 @@ export const PUT = withRouteHandler( permission !== 'admin' && permission !== 'write' ) { - logger.warn(`[${requestId}] Workspace env update denied`, { + logger.warn('Workspace env update denied', { workspaceId, userId, reason: 'write-access-required', @@ -343,7 +340,7 @@ export const PUT = withRouteHandler( return NextResponse.json({ success: true }) } catch (error) { - logger.error(`[${requestId}] Workspace env PUT error`, error) + logger.error('Workspace env PUT error', error) return NextResponse.json( { error: getErrorMessage(error, 'Failed to update environment') }, { status: 500 } @@ -359,13 +356,12 @@ export const PUT = withRouteHandler( */ export const DELETE = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ id: string }> }) => { - const requestId = generateRequestId() const workspaceId = (await context.params).id try { const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized workspace env delete attempt`) + logger.warn('Unauthorized workspace env delete attempt') return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -391,7 +387,7 @@ export const DELETE = withRouteHandler( const isKeyAdmin = (key: string) => permission === 'admin' || adminKeys.has(key) const forbiddenExisting = keys.filter((k) => knownKeys.has(k) && !isKeyAdmin(k)) if (forbiddenExisting.length > 0) { - logger.warn(`[${requestId}] Workspace env delete denied`, { + logger.warn('Workspace env delete denied', { workspaceId, userId, reason: 'not-secret-admin', @@ -403,7 +399,7 @@ export const DELETE = withRouteHandler( ) } if (keys.some((k) => !knownKeys.has(k)) && permission !== 'admin' && permission !== 'write') { - logger.warn(`[${requestId}] Workspace env delete denied`, { + logger.warn('Workspace env delete denied', { workspaceId, userId, reason: 'write-access-required', @@ -481,7 +477,7 @@ export const DELETE = withRouteHandler( return NextResponse.json({ success: true }) } catch (error) { - logger.error(`[${requestId}] Workspace env DELETE error`, error) + logger.error('Workspace env DELETE error', error) return NextResponse.json( { error: getErrorMessage(error, 'Failed to remove environment keys') }, { status: 500 } diff --git a/apps/sim/background/async-preprocessing-correlation.test.ts b/apps/sim/background/async-preprocessing-correlation.test.ts index 15820a1d6b7..a69f018bc3b 100644 --- a/apps/sim/background/async-preprocessing-correlation.test.ts +++ b/apps/sim/background/async-preprocessing-correlation.test.ts @@ -484,8 +484,12 @@ describe('async preprocessing correlation threading', () => { }) expect(loggingSessionMockFns.mockSafeCompleteWithError).not.toHaveBeenCalled() expect(workflowExecutionLogger.info).toHaveBeenCalledWith( - '[request-deleted] Workflow execution ended: its workspace was deleted', - { workflowId: 'workflow-1', executionId: 'execution-deleted-start' } + 'Workflow execution ended: its workspace was deleted', + { + requestId: 'request-deleted', + workflowId: 'workflow-1', + executionId: 'execution-deleted-start', + } ) }) @@ -627,10 +631,13 @@ describe('async preprocessing correlation threading', () => { }) ) expect(loggingSessionMockFns.mockProjectDiagnosticError).toHaveBeenCalledWith(rawError) - expect(workflowExecutionLogger.error).toHaveBeenCalledWith( - '[request-fault] Workflow execution failed: workflow-1', - { executionId: 'execution-fault', error: projectedError, failureKind: 'internal' } - ) + expect(workflowExecutionLogger.error).toHaveBeenCalledWith('Workflow execution failed', { + requestId: 'request-fault', + workflowId: 'workflow-1', + executionId: 'execution-fault', + error: projectedError, + failureKind: 'internal', + }) const loggerPayload = JSON.stringify(workflowExecutionLogger.error.mock.calls) expect(loggerPayload).not.toContain(secret) expect(loggerPayload).not.toContain('__var_') diff --git a/apps/sim/background/cleanup-file-versions.ts b/apps/sim/background/cleanup-file-versions.ts index 593b3ac42d6..de794024400 100644 --- a/apps/sim/background/cleanup-file-versions.ts +++ b/apps/sim/background/cleanup-file-versions.ts @@ -21,7 +21,7 @@ export async function runCleanupFileVersions(payload: CleanupJobPayload): Promis MAX_VERSIONS_PER_RUN ) const elapsed = ((Date.now() - startTime) / 1000).toFixed(2) - logger.info(`[${payload.label}] File version cleanup: ${deleted} released in ${elapsed}s`) + logger.info(`File version cleanup: ${deleted} released in ${elapsed}s`, { label: payload.label }) } export const cleanupFileVersionsTask = task({ diff --git a/apps/sim/background/cleanup-logs.ts b/apps/sim/background/cleanup-logs.ts index c26b2f2a76d..ce5de57c469 100644 --- a/apps/sim/background/cleanup-logs.ts +++ b/apps/sim/background/cleanup-logs.ts @@ -199,7 +199,8 @@ async function cleanupLargeExecutionValues( } logger.info( - `[${label}/execution_large_values] Complete: ${stats.largeValuesDeleted}/${stats.largeValuesTotal} deleted, ${stats.largeValuesDeleteFailed} failed` + `Complete: ${stats.largeValuesDeleted}/${stats.largeValuesTotal} deleted, ${stats.largeValuesDeleteFailed} failed`, + { label: `${label}/execution_large_values` } ) return stats @@ -356,7 +357,8 @@ async function cleanupLegacyLargeExecutionValues( } logger.info( - `[${label}/legacy_execution_large_values] Complete: ${stats.largeValuesDeleted}/${stats.largeValuesTotal} deleted, ${stats.largeValuesDeleteFailed} failed` + `Complete: ${stats.largeValuesDeleted}/${stats.largeValuesTotal} deleted, ${stats.largeValuesDeleteFailed} failed`, + { label: `${label}/legacy_execution_large_values` } ) return stats @@ -378,11 +380,15 @@ async function cleanupLargeValueMetadata( dbClient: cleanupDb, }) logger.info( - `[${label}/execution_large_value_metadata] Pruned ${result.referencesDeleted} stale references, ${result.dependenciesDeleted} dependencies, ${result.tombstonesDeleted} tombstones` + `Pruned ${result.referencesDeleted} stale references, ${result.dependenciesDeleted} dependencies, ${result.tombstonesDeleted} tombstones`, + { label: `${label}/execution_large_value_metadata` } ) } catch (error) { if (budgets) throw error - logger.error(`[${label}/execution_large_value_metadata] Failed to prune metadata`, { error }) + logger.error('Failed to prune metadata', { + label: `${label}/execution_large_value_metadata`, + error, + }) } } @@ -467,7 +473,7 @@ export async function runCleanupLogs( const retentionDate = new Date(Date.now() - retentionHours * 60 * 60 * 1000) if (workspaceIds.length === 0) { - logger.info(`[${label}] No workspaces to process`) + logger.info('No workspaces to process', { label }) if (runGlobalHousekeeping && plan === 'free') { await cleanupFreePlanOrphanedSnapshots(retentionHours, budgets?.orphanSnapshots) } @@ -475,7 +481,8 @@ export async function runCleanupLogs( } logger.info( - `[${label}] Cleaning ${workspaceIds.length} workspaces, cutoff: ${retentionDate.toISOString()}` + `Cleaning ${workspaceIds.length} workspaces, cutoff: ${retentionDate.toISOString()}`, + { label } ) const workflowResults = await cleanupWorkflowExecutionLogs( @@ -485,7 +492,8 @@ export async function runCleanupLogs( budgets?.workflowLogs ) logger.info( - `[${label}] workflow_execution_logs files: ${workflowResults.filesDeleted}/${workflowResults.filesTotal} deleted, ${workflowResults.filesDeleteFailed} failed` + `workflow_execution_logs files: ${workflowResults.filesDeleted}/${workflowResults.filesTotal} deleted, ${workflowResults.filesDeleteFailed} failed`, + { label } ) const largeValueResults = await cleanupLargeExecutionValues( workspaceIds, @@ -494,7 +502,8 @@ export async function runCleanupLogs( budgets?.largeValues ) logger.info( - `[${label}] execution_large_values: ${largeValueResults.largeValuesDeleted}/${largeValueResults.largeValuesTotal} deleted, ${largeValueResults.largeValuesDeleteFailed} failed` + `execution_large_values: ${largeValueResults.largeValuesDeleted}/${largeValueResults.largeValuesTotal} deleted, ${largeValueResults.largeValuesDeleteFailed} failed`, + { label } ) const legacyLargeValueResults = await cleanupLegacyLargeExecutionValues( workspaceIds, @@ -503,7 +512,8 @@ export async function runCleanupLogs( budgets?.legacyLargeValues ) logger.info( - `[${label}] legacy_execution_large_values: ${legacyLargeValueResults.largeValuesDeleted}/${legacyLargeValueResults.largeValuesTotal} deleted, ${legacyLargeValueResults.largeValuesDeleteFailed} failed` + `legacy_execution_large_values: ${legacyLargeValueResults.largeValuesDeleted}/${legacyLargeValueResults.largeValuesTotal} deleted, ${legacyLargeValueResults.largeValuesDeleteFailed} failed`, + { label } ) await cleanupLargeValueMetadata(workspaceIds, label, budgets) @@ -523,7 +533,7 @@ export async function runCleanupLogs( } const timeElapsed = (Date.now() - startTime) / 1000 - logger.info(`[${label}] Job completed in ${timeElapsed.toFixed(2)}s`) + logger.info(`Job completed in ${timeElapsed.toFixed(2)}s`, { label }) } export const cleanupLogsTask = task({ diff --git a/apps/sim/background/cleanup-soft-deletes.ts b/apps/sim/background/cleanup-soft-deletes.ts index 49e82024b83..4ac9556de5b 100644 --- a/apps/sim/background/cleanup-soft-deletes.ts +++ b/apps/sim/background/cleanup-soft-deletes.ts @@ -168,7 +168,8 @@ async function reRootOne( await preferred() return } catch (error) { - logger.warn(`[${label}] Re-rooting ${subject} under its deduplicated name failed; retrying`, { + logger.warn(`Re-rooting ${subject} under its deduplicated name failed; retrying`, { + label, error, }) } @@ -176,7 +177,7 @@ async function reRootOne( try { await withUniqueName() } catch (error) { - logger.error(`[${label}] Could not re-root ${subject}; its folder delete may stall`, { error }) + logger.error(`Could not re-root ${subject}; its folder delete may stall`, { label, error }) } } @@ -195,7 +196,7 @@ async function reRootActiveFolderChildren( try { await reRootActiveFolderChildrenUnguarded(folderIds, retentionDate, label) } catch (error) { - logger.error(`[${label}] Re-rooting children of purged folders failed`, { error }) + logger.error('Re-rooting children of purged folders failed', { label, error }) } } @@ -300,7 +301,7 @@ async function reRootActiveFolderChildrenUnguarded( } if (workflows.length > 0) { - logger.warn(`[${label}] Re-rooted ${workflows.length} workflow(s) out of folders being purged`) + logger.warn(`Re-rooted ${workflows.length} workflow(s) out of folders being purged`, { label }) } } @@ -408,7 +409,7 @@ async function cleanupOrphanedKnowledgeBaseBindings( const failedKeys = new Set(result.failed.map(({ key }) => key)) deletableKeys = keys.filter((key) => !failedKeys.has(key)) for (const { key, error } of result.failed) { - logger.error(`[${label}] Failed to delete orphan KB object ${key}:`, { error }) + logger.error(`Failed to delete orphan KB object ${key}:`, { label, error }) } } @@ -419,7 +420,7 @@ async function cleanupOrphanedKnowledgeBaseBindings( deletedThisBatch++ } catch (error) { stats.failed++ - logger.error(`[${label}] Failed to delete orphan KB binding ${key}:`, { error }) + logger.error(`Failed to delete orphan KB binding ${key}:`, { label, error }) } } stats.deleted += deletedThisBatch @@ -431,7 +432,8 @@ async function cleanupOrphanedKnowledgeBaseBindings( } logger.info( - `[${label}/kb_orphan_bindings] Complete: ${stats.deleted}/${stats.total} bindings cleaned, ${stats.failed} failed` + `Complete: ${stats.deleted}/${stats.total} bindings cleaned, ${stats.failed} failed`, + { label: `${label}/kb_orphan_bindings` } ) return stats } @@ -445,13 +447,14 @@ export async function runCleanupSoftDeletes( const scope = resolveCleanupOwnerScope(payload) const fileOwners = fileRetentionOwners(payload) if (scope.ids.length === 0 && fileOwners.length === 0) { - logger.info(`[${label}] No resource owners to process`) + logger.info('No resource owners to process', { label }) return } const retentionDate = new Date(Date.now() - retentionHours * 60 * 60 * 1000) logger.info( - `[${label}] Processing ${scope.ids.length} ${scope.kind} owners, cutoff: ${retentionDate.toISOString()}` + `Processing ${scope.ids.length} ${scope.kind} owners, cutoff: ${retentionDate.toISOString()}`, + { label } ) // Select workflows + files + soft-deleted chats once. These sets drive BOTH @@ -544,7 +547,7 @@ export async function runCleanupSoftDeletes( totalDeleted += deleted.length } catch (error) { if (budgets) throw error - logger.error(`[${label}/workflow] Archived workflow delete failed`, { error }) + logger.error('Archived workflow delete failed', { label: `${label}/workflow`, error }) } } @@ -569,7 +572,7 @@ export async function runCleanupSoftDeletes( totalDeleted += deleted.length } catch (error) { if (budgets) throw error - logger.error(`[${label}/copilotChats] Soft-deleted chat delete failed`, { error }) + logger.error('Soft-deleted chat delete failed', { label: `${label}/copilotChats`, error }) } } @@ -610,7 +613,8 @@ export async function runCleanupSoftDeletes( ) logger.info( - `[${label}] Complete: ${totalDeleted} rows deleted, ${fileCleanup.filesDeleted} files cleaned, ${orphanBindingStats.deleted} orphan KB bindings cleaned` + `Complete: ${totalDeleted} rows deleted, ${fileCleanup.filesDeleted} files cleaned, ${orphanBindingStats.deleted} orphan KB bindings cleaned`, + { label } ) // Clean up copilot backend + chat storage files after DB rows are gone @@ -619,7 +623,7 @@ export async function runCleanupSoftDeletes( } const timeElapsed = (Date.now() - startTime) / 1000 - logger.info(`[${label}] Job completed in ${timeElapsed.toFixed(2)}s`) + logger.info(`Job completed in ${timeElapsed.toFixed(2)}s`, { label }) } export const cleanupSoftDeletesTask = task({ diff --git a/apps/sim/background/cleanup-tasks.ts b/apps/sim/background/cleanup-tasks.ts index b77ca2344ba..bd07b751d70 100644 --- a/apps/sim/background/cleanup-tasks.ts +++ b/apps/sim/background/cleanup-tasks.ts @@ -79,13 +79,14 @@ export async function runCleanupTasks(payload: CleanupJobPayload): Promise const scope = resolveCleanupOwnerScope(payload) if (scope.ids.length === 0) { - logger.info(`[${label}] No resource owners to process`) + logger.info('No resource owners to process', { label }) return } const retentionDate = new Date(Date.now() - retentionHours * 60 * 60 * 1000) logger.info( - `[${label}] Processing ${scope.ids.length} ${scope.kind} owners, cutoff: ${retentionDate.toISOString()}` + `Processing ${scope.ids.length} ${scope.kind} owners, cutoff: ${retentionDate.toISOString()}`, + { label } ) const doomedChats = await selectRowsByIdChunks(scope.ids, (chunkIds, chunkLimit) => @@ -109,7 +110,7 @@ export async function runCleanupTasks(payload: CleanupJobPayload): Promise /** Delete run children before their parent runs. Organization chats have no workspace runs. */ const runChildResults = await cleanupRunChildren(workspaceIds, retentionDate, label) for (const r of runChildResults) { - if (r.deleted > 0) logger.info(`[${r.table}] ${r.deleted} deleted`) + if (r.deleted > 0) logger.info(`${r.deleted} deleted`, { table: r.table }) } const runsResult = @@ -146,7 +147,7 @@ export async function runCleanupTasks(payload: CleanupJobPayload): Promise chatsResult.deleted += deleted.length } catch (error) { chatsResult.failed += batch.length - logger.error(`[${label}/copilotChats] Chat retention delete failed`, { error }) + logger.error('Chat retention delete failed', { label: `${label}/copilotChats`, error }) } } @@ -169,7 +170,7 @@ export async function runCleanupTasks(payload: CleanupJobPayload): Promise chatsResult.deleted + inboxResult.deleted - logger.info(`[${label}] Complete: ${totalDeleted} total rows deleted`) + logger.info(`Complete: ${totalDeleted} total rows deleted`, { label }) await chatCleanup.execute() diff --git a/apps/sim/background/knowledge-connector-directory-sync.ts b/apps/sim/background/knowledge-connector-directory-sync.ts index 98fb3364af5..2cd9b514a0e 100644 --- a/apps/sim/background/knowledge-connector-directory-sync.ts +++ b/apps/sim/background/knowledge-connector-directory-sync.ts @@ -14,15 +14,15 @@ const logger = createLogger('TriggerKnowledgeConnectorDirectorySync') export async function executeDirectorySyncJob(payload: unknown) { const { connectorId, requestId } = assertDirectorySyncPayload(payload) - logger.info(`[${requestId}] Starting directory refresh: ${connectorId}`) + logger.info(`Starting directory refresh: ${connectorId}`, { requestId }) try { const outcome = await refreshConnectorDirectory(connectorId, requestId) - logger.info(`[${requestId}] Directory refresh finished`, { connectorId, outcome }) + logger.info('Directory refresh finished', { requestId, connectorId, outcome }) return { outcome } } catch (error) { const diagnostic = getConnectorFailureDiagnostic(error) if (!diagnostic) throw error - logger.error(`[${requestId}] Directory refresh failed`, { connectorId, diagnostic }) + logger.error('Directory refresh failed', { requestId, connectorId, diagnostic }) throw new Error(diagnostic.message) } } diff --git a/apps/sim/background/knowledge-connector-member-sync.ts b/apps/sim/background/knowledge-connector-member-sync.ts index 08b1642783f..1057e92ea51 100644 --- a/apps/sim/background/knowledge-connector-member-sync.ts +++ b/apps/sim/background/knowledge-connector-member-sync.ts @@ -43,7 +43,7 @@ export async function executeMemberSyncJob(payload: unknown) { const { connectorId, requestId, billingAttribution, dispatchToken, forceContentRefresh } = assertMemberSyncPayload(payload) - logger.info(`[${requestId}] Starting member sync: ${connectorId}`) + logger.info(`Starting member sync: ${connectorId}`, { requestId }) try { const result = await executeMemberSync(connectorId, { @@ -53,7 +53,8 @@ export async function executeMemberSyncJob(payload: unknown) { }) const outcome = classifyMemberSyncResult(result) - logger.info(`[${requestId}] Member sync completed`, { + logger.info('Member sync completed', { + requestId, connectorId, outcome, deferred: result.deferred, @@ -86,7 +87,7 @@ export async function executeMemberSyncJob(payload: unknown) { return { success: outcome === 'completed', outcome, connectorId, ...result } } catch (error) { - logger.error(`[${requestId}] Member sync failed: ${connectorId}`, error) + logger.error(`Member sync failed: ${connectorId}`, error, { requestId }) throw error } } diff --git a/apps/sim/background/knowledge-connector-sync.test.ts b/apps/sim/background/knowledge-connector-sync.test.ts index 230edb4535e..2a65a63bb51 100644 --- a/apps/sim/background/knowledge-connector-sync.test.ts +++ b/apps/sim/background/knowledge-connector-sync.test.ts @@ -76,7 +76,8 @@ describe('knowledge connector sync worker', () => { processingDispatch: { failed: 1 }, }) expect(mockWarn).toHaveBeenCalledWith( - expect.stringContaining('1 source failures, 1 dispatch failures') + expect.stringContaining('1 source failures, 1 dispatch failures'), + { requestId: 'request-1' } ) }) diff --git a/apps/sim/background/knowledge-connector-sync.ts b/apps/sim/background/knowledge-connector-sync.ts index 6df8fc4b100..57939b75ce5 100644 --- a/apps/sim/background/knowledge-connector-sync.ts +++ b/apps/sim/background/knowledge-connector-sync.ts @@ -38,7 +38,7 @@ export async function executeConnectorSyncJob(payload: unknown) { dispatchToken, } = assertConnectorSyncPayload(payload) - logger.info(`[${requestId}] Starting connector sync: ${connectorId}`) + logger.info(`Starting connector sync: ${connectorId}`, { requestId }) try { const result = await executeSync(connectorId, { @@ -50,7 +50,8 @@ export async function executeConnectorSyncJob(payload: unknown) { }) const outcome = classifyConnectorSyncResult(result) - logger.info(`[${requestId}] Connector sync completed`, { + logger.info('Connector sync completed', { + requestId, connectorId, outcome, deferred: result.deferred, @@ -78,7 +79,8 @@ export async function executeConnectorSyncJob(payload: unknown) { } if (outcome === 'partial' && (result.docsFailed > 0 || result.processingDispatch.failed > 0)) { logger.warn( - `[${requestId}] Connector sync partially failed for ${connectorId}: ${result.docsFailed} source failures, ${result.processingDispatch.failed} dispatch failures` + `Connector sync partially failed for ${connectorId}: ${result.docsFailed} source failures, ${result.processingDispatch.failed} dispatch failures`, + { requestId } ) } @@ -89,7 +91,7 @@ export async function executeConnectorSyncJob(payload: unknown) { ...result, } } catch (error) { - logger.error(`[${requestId}] Connector sync failed: ${connectorId}`, error) + logger.error(`Connector sync failed: ${connectorId}`, error, { requestId }) throw error } } diff --git a/apps/sim/background/knowledge-processing.ts b/apps/sim/background/knowledge-processing.ts index 1d09c63fa65..a35582316c5 100644 --- a/apps/sim/background/knowledge-processing.ts +++ b/apps/sim/background/knowledge-processing.ts @@ -103,7 +103,7 @@ export async function runDocumentProcessing( payload.providerRetryCount === undefined && payload.processingSliceCount === undefined - logger.info(`[${requestId}] Starting Trigger.dev processing for document: ${docData.filename}`) + logger.info(`Starting Trigger.dev processing for document: ${docData.filename}`, { requestId }) /** Set from the service's callback, so control-flow narrowing cannot see it change. */ let databaseRetryAt = null as Date | null @@ -148,7 +148,7 @@ export async function runDocumentProcessing( } ) - logger.info(`[${requestId}] Document processing finished`, { documentId, ...result }) + logger.info('Document processing finished', { requestId, documentId, ...result }) return { success: result.outcome === 'indexed', @@ -160,7 +160,8 @@ export async function runDocumentProcessing( } catch (error) { if (databaseRetryAt) { const diagnostic = getConnectorFailureDiagnostic(error) - logger.warn(`[${requestId}] Document processing will retry after a database failure`, { + logger.warn('Document processing will retry after a database failure', { + requestId, documentId, diagnostic, attempt: attemptNumber, @@ -178,7 +179,8 @@ export async function runDocumentProcessing( error instanceof ProviderCapacityContinuationExhaustedError ? 'provider_exhausted' : 'provider_deferred' - logger.warn(`[${requestId}] Document processing is waiting for provider recovery`, { + logger.warn('Document processing is waiting for provider recovery', { + requestId, documentId, providerRetryCount: payload.providerRetryCount ?? 0, reason: providerDeferral?.reason, @@ -197,7 +199,8 @@ export async function runDocumentProcessing( } } if (isUsageLimitDocumentProcessingError(error)) { - logger.warn(`[${requestId}] Document processing is blocked by the current usage limit`, { + logger.warn('Document processing is blocked by the current usage limit', { + requestId, filename: docData.filename, }) return { @@ -211,7 +214,8 @@ export async function runDocumentProcessing( } if (isEmbeddingQuotaExhaustion(error)) { const outcome = canScheduleQuotaContinuation ? 'quota_deferred' : 'quota_exhausted' - logger.warn(`[${requestId}] Embedding quota is exhausted`, { + logger.warn('Embedding quota is exhausted', { + requestId, filename: docData.filename, quotaRetryCount: payload.quotaRetryCount ?? 0, continuationLimit: MAX_QUOTA_CONTINUATION_ATTEMPTS, @@ -227,7 +231,8 @@ export async function runDocumentProcessing( } } if (isBYOKEmbeddingCredentialRejection(error)) { - logger.warn(`[${requestId}] Customer-managed embedding credentials were rejected`, { + logger.warn('Customer-managed embedding credentials were rejected', { + requestId, filename: docData.filename, status: error.status, }) @@ -243,7 +248,8 @@ export async function runDocumentProcessing( } const ocrRejection = getOcrRequestRejection(error) if (ocrRejection) { - logger.warn(`[${requestId}] OCR request requires remediation before retrying`, { + logger.warn('OCR request requires remediation before retrying', { + requestId, status: ocrRejection.status, code: ocrRejection.code, }) @@ -258,7 +264,8 @@ export async function runDocumentProcessing( } } if (isPermanentDocumentProcessingError(error)) { - logger.warn(`[${requestId}] Document cannot be processed without changing its content`, { + logger.warn('Document cannot be processed without changing its content', { + requestId, code: error.code, filename: docData.filename, }) @@ -273,10 +280,9 @@ export async function runDocumentProcessing( } } const diagnostic = getConnectorFailureDiagnostic(error) - logger.error( - `[${requestId}] Failed to process document: ${docData.filename}`, - diagnostic ?? error - ) + logger.error(`Failed to process document: ${docData.filename}`, diagnostic ?? error, { + requestId, + }) /** Trigger records the thrown message and stack, never `cause`; Drizzle's message carries SQL. */ if (diagnostic?.category === 'database') throw new Error(diagnostic.message, { cause: error }) throw error diff --git a/apps/sim/background/quickbooks-webhook-ingress.ts b/apps/sim/background/quickbooks-webhook-ingress.ts index 7e7436c49ba..710ef0dec01 100644 --- a/apps/sim/background/quickbooks-webhook-ingress.ts +++ b/apps/sim/background/quickbooks-webhook-ingress.ts @@ -43,7 +43,8 @@ export async function executeQuickBooksWebhookIngress( routingKey = buildQuickBooksWebhookRoutingKey(payload.appKey, event.intuitaccountid) } catch (error) { ignored += 1 - logger.warn(`[${payload.requestId}] QuickBooks webhook event is not routable`, { + logger.warn('QuickBooks webhook event is not routable', { + requestId: payload.requestId, error: getErrorMessage(error, 'Unknown error'), eventId: event.id, eventIndex, @@ -78,7 +79,8 @@ export async function executeQuickBooksWebhookIngress( else failed += 1 } catch (error) { failed += 1 - logger.error(`[${payload.requestId}] QuickBooks webhook target dispatch failed`, { + logger.error('QuickBooks webhook target dispatch failed', { + requestId: payload.requestId, error, eventId: event.id, eventIndex, @@ -87,7 +89,8 @@ export async function executeQuickBooksWebhookIngress( } } - logger.info(`[${payload.requestId}] QuickBooks webhook event completed`, { + logger.info('QuickBooks webhook event completed', { + requestId: payload.requestId, eventId: event.id, eventIndex, ignored, @@ -96,7 +99,8 @@ export async function executeQuickBooksWebhookIngress( }) } catch (error) { failed += 1 - logger.error(`[${payload.requestId}] QuickBooks webhook target lookup failed`, { + logger.error('QuickBooks webhook target lookup failed', { + requestId: payload.requestId, error, eventId: event.id, eventIndex, @@ -104,7 +108,8 @@ export async function executeQuickBooksWebhookIngress( } } - logger.info(`[${payload.requestId}] QuickBooks webhook delivery completed`, { + logger.info('QuickBooks webhook delivery completed', { + requestId: payload.requestId, eventCount: payload.events.length, failed, ignored, diff --git a/apps/sim/background/schedule-execution.ts b/apps/sim/background/schedule-execution.ts index 43465132a3c..533d5eb0063 100644 --- a/apps/sim/background/schedule-execution.ts +++ b/apps/sim/background/schedule-execution.ts @@ -247,7 +247,7 @@ async function applyScheduleUpdate( const row = updatedRows[0] outcome = { updated: Boolean(row), status: row?.status ?? null } } catch (error) { - logger.error(`[${requestId}] ${context}`, error, { cause: describeError(error) }) + logger.error(context, error, { requestId, cause: describeError(error) }) throw error } @@ -386,7 +386,8 @@ async function retryScheduleAfterInfraFailure({ const now = new Date() const retryAttempt = (payload.infraRetryCount || 0) + 1 if (retryAttempt > SCHEDULE_INFRA_RETRY_MAX_ATTEMPTS) { - logger.error(`[${requestId}] Retryable infrastructure failures exhausted for schedule`, { + logger.error('Retryable infrastructure failures exhausted for schedule', { + requestId, scheduleId: payload.scheduleId, workflowId: payload.workflowId, retryAttempt, @@ -410,7 +411,8 @@ async function retryScheduleAfterInfraFailure({ const failureCause = cause ?? describeRetryableInfrastructureError(error) const errorMessage = message ?? (error ? toError(error).message : undefined) - logger.warn(`[${requestId}] Retryable infrastructure failure during scheduled setup`, { + logger.warn('Retryable infrastructure failure during scheduled setup', { + requestId, scheduleId: payload.scheduleId, workflowId: payload.workflowId, retryAttempt, @@ -442,10 +444,9 @@ async function calculateNextRunFromDeployment( const deployedData = await loadDeployedWorkflowState(payload.workflowId) return calculateNextRunTime(payload, deployedData.blocks as Record) } catch (error) { - logger.warn( - `[${requestId}] Unable to calculate nextRunAt for schedule ${payload.scheduleId}`, - error - ) + logger.warn(`Unable to calculate nextRunAt for schedule ${payload.scheduleId}`, error, { + requestId, + }) return null } } @@ -469,7 +470,7 @@ async function determineNextRunAfterError( } } } catch (workflowError) { - logger.error(`[${requestId}] Error retrieving workflow for next run calculation`, workflowError) + logger.error('Error retrieving workflow for next run calculation', workflowError, { requestId }) } return new Date(now.getTime() + 24 * 60 * 60 * 1000) @@ -546,7 +547,8 @@ async function runWorkflowExecution({ const blocks = deployedData.blocks const { deploymentVersionId } = deployedData if (payload.deploymentVersionId && deploymentVersionId !== payload.deploymentVersionId) { - logger.info(`[${requestId}] Loaded deployment no longer matches queued schedule, skipping`, { + logger.info('Loaded deployment no longer matches queued schedule, skipping', { + requestId, scheduleId: payload.scheduleId, workflowId: payload.workflowId, queuedDeploymentVersionId: payload.deploymentVersionId, @@ -558,12 +560,13 @@ async function runWorkflowExecution({ blocks: {} as Record, } } - logger.info(`[${requestId}] Loaded deployed workflow ${payload.workflowId}`) + logger.info(`Loaded deployed workflow ${payload.workflowId}`, { requestId }) if (payload.blockId) { if (!blocks[payload.blockId]) { logger.warn( - `[${requestId}] Schedule trigger block ${payload.blockId} not found in deployed workflow ${payload.workflowId}. Skipping execution.` + `Schedule trigger block ${payload.blockId} not found in deployed workflow ${payload.workflowId}. Skipping execution.`, + { requestId } ) return { @@ -628,7 +631,8 @@ async function runWorkflowExecution({ payload.deploymentVersionId && !(await isScheduleDeploymentVersionActive(payload.workflowId, payload.deploymentVersionId)) ) { - logger.info(`[${requestId}] Schedule deployment changed before execution, skipping`, { + logger.info('Schedule deployment changed before execution, skipping', { + requestId, scheduleId: payload.scheduleId, workflowId: payload.workflowId, deploymentVersionId: payload.deploymentVersionId, @@ -644,7 +648,8 @@ async function runWorkflowExecution({ if ( !(await isScheduleClaimCurrent(payload.scheduleId, claimedAt, payload.deploymentOperationId)) ) { - logger.info(`[${requestId}] Schedule claim changed before workflow core started, skipping`, { + logger.info('Schedule claim changed before workflow core started, skipping', { + requestId, scheduleId: payload.scheduleId, workflowId: payload.workflowId, claimedAt: claimedAt?.toISOString(), @@ -673,9 +678,7 @@ async function runWorkflowExecution({ timeoutMs !== undefined if (timedOut) { const timeoutErrorMessage = getTimeoutErrorMessage(timeoutMs) - logger.info(`[${requestId}] Scheduled workflow execution timed out`, { - timeoutMs, - }) + logger.info('Scheduled workflow execution timed out', { requestId, timeoutMs }) await loggingSession.markAsFailed(timeoutErrorMessage) } else { await handlePostExecutionPauseState({ @@ -699,7 +702,8 @@ async function runWorkflowExecution({ ) .limit(1) - logger.info(`[${requestId}] Workflow execution completed: ${payload.workflowId}`, { + logger.info(`Workflow execution completed: ${payload.workflowId}`, { + requestId, success: executionResult.success, executionTime: executionResult.metadata?.duration, }) @@ -716,7 +720,8 @@ async function runWorkflowExecution({ } catch (error: unknown) { if (!workflowCoreStarted && isRetryableInfrastructureError(error)) { const cause = describeRetryableInfrastructureError(error) - logger.warn(`[${requestId}] Retryable setup failure before scheduled workflow started`, { + logger.warn('Retryable setup failure before scheduled workflow started', { + requestId, scheduleId: payload.scheduleId, workflowId: payload.workflowId, cause, @@ -826,7 +831,8 @@ export async function executeScheduleJob( client: { surface: 'schedule', source: 'trigger' }, } return await runWithRequestContext(requestContext, async () => { - logger.info(`[${requestId}] Starting schedule execution`, { + logger.info('Starting schedule execution', { + requestId, scheduleId: payload.scheduleId, workflowId: payload.workflowId, executionId, @@ -868,7 +874,8 @@ export async function executeScheduleJob( .limit(1) if (!scheduleRecord) { - logger.info(`[${requestId}] Schedule no longer exists, skipping execution`, { + logger.info('Schedule no longer exists, skipping execution', { + requestId, scheduleId: payload.scheduleId, }) return @@ -879,19 +886,18 @@ export async function executeScheduleJob( (!scheduleRecord.lastQueuedAt || scheduleRecord.lastQueuedAt.getTime() !== claimedAt.getTime()) ) { - logger.info( - `[${requestId}] Schedule claim no longer matches payload, skipping execution`, - { - scheduleId: payload.scheduleId, - claimedAt: claimedAt.toISOString(), - currentLastQueuedAt: scheduleRecord.lastQueuedAt?.toISOString(), - } - ) + logger.info('Schedule claim no longer matches payload, skipping execution', { + requestId, + scheduleId: payload.scheduleId, + claimedAt: claimedAt.toISOString(), + currentLastQueuedAt: scheduleRecord.lastQueuedAt?.toISOString(), + }) return } if (scheduleRecord.archivedAt || scheduleRecord.status === 'disabled') { - logger.info(`[${requestId}] Schedule is archived or disabled, skipping execution`, { + logger.info('Schedule is archived or disabled, skipping execution', { + requestId, scheduleId: payload.scheduleId, }) await releaseClaim( @@ -917,14 +923,12 @@ export async function executeScheduleJob( .limit(1) if (!activeDeployment) { - logger.info( - `[${requestId}] Schedule deployment version is no longer active, skipping`, - { - scheduleId: payload.scheduleId, - workflowId: payload.workflowId, - deploymentVersionId: expectedDeploymentVersionId, - } - ) + logger.info('Schedule deployment version is no longer active, skipping', { + requestId, + scheduleId: payload.scheduleId, + workflowId: payload.workflowId, + deploymentVersionId: expectedDeploymentVersionId, + }) await releaseClaim( now, `Failed to release stale deployment schedule ${payload.scheduleId}` @@ -974,9 +978,9 @@ export async function executeScheduleJob( switch (statusCode) { case 401: { - logger.warn( - `[${requestId}] Authentication error during preprocessing, disabling schedule` - ) + logger.warn('Authentication error during preprocessing, disabling schedule', { + requestId, + }) await updateClaimedSchedule( { updatedAt: now, @@ -993,7 +997,8 @@ export async function executeScheduleJob( case 403: { logger.warn( - `[${requestId}] Authorization error during preprocessing, disabling schedule: ${preprocessingError.message}` + `Authorization error during preprocessing, disabling schedule: ${preprocessingError.message}`, + { requestId } ) await updateClaimedSchedule( { @@ -1010,7 +1015,7 @@ export async function executeScheduleJob( } case 404: { - logger.warn(`[${requestId}] Workflow not found, disabling schedule`) + logger.warn('Workflow not found, disabling schedule', { requestId }) await updateClaimedSchedule( { updatedAt: now, @@ -1025,7 +1030,7 @@ export async function executeScheduleJob( } case 429: { - logger.warn(`[${requestId}] Rate limit exceeded, scheduling retry`) + logger.warn('Rate limit exceeded, scheduling retry', { requestId }) const retryDelay = 5 * 60 * 1000 const nextRetryAt = new Date(now.getTime() + retryDelay) @@ -1052,7 +1057,8 @@ export async function executeScheduleJob( const nextRunAt = (await calculateNextRunFromDeployment(payload, requestId)) ?? new Date(now.getTime() + 60 * 60 * 1000) - logger.warn(`[${requestId}] Usage limit exceeded, counting as failed run`, { + logger.warn('Usage limit exceeded, counting as failed run', { + requestId, scheduleId: payload.scheduleId, nextRunAt: nextRunAt.toISOString(), }) @@ -1076,7 +1082,7 @@ export async function executeScheduleJob( return } - logger.error(`[${requestId}] Preprocessing failed: ${preprocessingError.message}`) + logger.error(`Preprocessing failed: ${preprocessingError.message}`, { requestId }) const nextRunAt = await determineNextRunAfterError(payload, now, requestId) await updateClaimedSchedule( @@ -1091,7 +1097,7 @@ export async function executeScheduleJob( const { actorUserId, billingAttribution, workflowRecord } = preprocessResult if (!actorUserId || !billingAttribution || !workflowRecord) { - logger.error(`[${requestId}] Missing required preprocessing data`) + logger.error('Missing required preprocessing data', { requestId }) await releaseClaim( now, `Failed to release schedule ${payload.scheduleId} after missing preprocessing data` @@ -1103,7 +1109,7 @@ export async function executeScheduleJob( throw new Error(`Workflow ${payload.workflowId} has no associated workspace`) } - logger.info(`[${requestId}] Executing scheduled workflow ${payload.workflowId}`) + logger.info(`Executing scheduled workflow ${payload.workflowId}`, { requestId }) try { const executionResult = await runWorkflowExecution({ @@ -1157,7 +1163,7 @@ export async function executeScheduleJob( } if (executionResult.status === 'success') { - logger.info(`[${requestId}] Workflow ${payload.workflowId} executed successfully`) + logger.info(`Workflow ${payload.workflowId} executed successfully`, { requestId }) const nextRunAt = calculateNextRunTime(payload, executionResult.blocks) @@ -1169,7 +1175,7 @@ export async function executeScheduleJob( } if (executionResult.status === 'cancelled') { - logger.info(`[${requestId}] Workflow ${payload.workflowId} execution was cancelled`) + logger.info(`Workflow ${payload.workflowId} execution was cancelled`, { requestId }) const nextRunAt = calculateNextRunTime(payload, executionResult.blocks) await updateClaimedSchedule( @@ -1179,7 +1185,7 @@ export async function executeScheduleJob( return } - logger.warn(`[${requestId}] Workflow ${payload.workflowId} execution failed`) + logger.warn(`Workflow ${payload.workflowId} execution failed`, { requestId }) const nextRunAt = calculateNextRunTime(payload, executionResult.blocks) @@ -1190,8 +1196,9 @@ export async function executeScheduleJob( ) } catch (error: unknown) { logger.error( - `[${requestId}] Error executing scheduled workflow ${payload.workflowId}`, - loggingSession.projectDiagnosticError(error) + `Error executing scheduled workflow ${payload.workflowId}`, + loggingSession.projectDiagnosticError(error), + { requestId } ) const nextRunAt = await determineNextRunAfterError(payload, now, requestId) @@ -1209,7 +1216,8 @@ export async function executeScheduleJob( return } - logger.error(`[${requestId}] Error processing schedule ${payload.scheduleId}`, error, { + logger.error(`Error processing schedule ${payload.scheduleId}`, error, { + requestId, cause: describeError(error), }) await releaseClaim( @@ -1222,8 +1230,9 @@ export async function executeScheduleJob( // claim expires on its TTL and the next tick re-claims the schedule. Record the // exception on the span so it stays visible in traces without faulting the run. logger.error( - `[${requestId}] Failed to recover schedule ${payload.scheduleId} after error`, - recoveryError + `Failed to recover schedule ${payload.scheduleId} after error`, + recoveryError, + { requestId } ) trace.getActiveSpan()?.recordException(toError(recoveryError)) } diff --git a/apps/sim/background/table-run-dispatcher.ts b/apps/sim/background/table-run-dispatcher.ts index bb8593a6973..4e550717565 100644 --- a/apps/sim/background/table-run-dispatcher.ts +++ b/apps/sim/background/table-run-dispatcher.ts @@ -43,7 +43,7 @@ export const tableRunDispatcherTask = task({ try { await runDispatcherToCompletion(dispatchId, concurrency) } catch (err) { - logger.error(`[${dispatchId}] dispatcher loop failed`, { error: toError(err).message }) + logger.error('dispatcher loop failed', { dispatchId, error: toError(err).message }) throw err } }, diff --git a/apps/sim/background/webhook-execution.test.ts b/apps/sim/background/webhook-execution.test.ts index 2ef1a7929cb..ff7a59c56af 100644 --- a/apps/sim/background/webhook-execution.test.ts +++ b/apps/sim/background/webhook-execution.test.ts @@ -495,16 +495,14 @@ describe('executeWebhookJob fault vs error handling', () => { workflowId: 'workflow-1', provider: 'gmail', }) - expect(webhookExecutionLogger.error).toHaveBeenCalledWith( - '[request-1] Webhook execution failed', - { - executionId: 'execution-1', - workflowId: 'workflow-1', - provider: 'gmail', - error: projectedError, - failureKind: 'internal', - } - ) + expect(webhookExecutionLogger.error).toHaveBeenCalledWith('Webhook execution failed', { + requestId: 'request-1', + executionId: 'execution-1', + workflowId: 'workflow-1', + provider: 'gmail', + error: projectedError, + failureKind: 'internal', + }) const loggerPayload = JSON.stringify(webhookExecutionLogger.error.mock.calls) expect(loggerPayload).not.toContain(secret) expect(loggerPayload).not.toContain('__var_') diff --git a/apps/sim/background/webhook-execution.ts b/apps/sim/background/webhook-execution.ts index 7f07734ed8b..660840b0cf2 100644 --- a/apps/sim/background/webhook-execution.ts +++ b/apps/sim/background/webhook-execution.ts @@ -261,8 +261,9 @@ async function processTriggerFileOutputs( processed[key] = processedFile } catch (error) { logger.error( - `[${context.requestId}] Error processing ${currentPath}`, - context.projectDiagnosticError(error, { path: currentPath }) + `Error processing ${currentPath}`, + context.projectDiagnosticError(error, { path: currentPath }), + { requestId: context.requestId } ) processed[key] = value } @@ -409,33 +410,29 @@ async function requeueWebhookExecutionAfterSetupFailure( }, }) - logger.warn( - `[${correlation.requestId}] Requeued webhook execution after retryable setup failure`, - { - workflowId: payload.workflowId, - webhookId: payload.webhookId, - executionId: correlation.executionId, - provider: payload.provider, - retryAttempt, - maxAttempts: WEBHOOK_INFRA_RETRY_MAX_ATTEMPTS, - delayMs, - jobId, - error: error.message, - cause: error.cause, - } - ) + logger.warn('Requeued webhook execution after retryable setup failure', { + requestId: correlation.requestId, + workflowId: payload.workflowId, + webhookId: payload.webhookId, + executionId: correlation.executionId, + provider: payload.provider, + retryAttempt, + maxAttempts: WEBHOOK_INFRA_RETRY_MAX_ATTEMPTS, + delayMs, + jobId, + error: error.message, + cause: error.cause, + }) return true } catch (enqueueError) { - logger.error( - `[${correlation.requestId}] Failed to requeue webhook execution after setup failure`, - { - workflowId: payload.workflowId, - webhookId: payload.webhookId, - executionId: correlation.executionId, - retryAttempt, - error: enqueueError, - } - ) + logger.error('Failed to requeue webhook execution after setup failure', { + requestId: correlation.requestId, + workflowId: payload.workflowId, + webhookId: payload.webhookId, + executionId: correlation.executionId, + retryAttempt, + error: enqueueError, + }) return false } } @@ -475,7 +472,8 @@ async function recordSetupFailureWithoutRequeue( skipCost: true, }) } catch (loggingError) { - logger.error(`[${correlation.requestId}] Failed to record terminal webhook setup failure`, { + logger.error('Failed to record terminal webhook setup failure', { + requestId: correlation.requestId, workflowId: payload.workflowId, executionId: correlation.executionId, error: loggingError, @@ -568,7 +566,8 @@ export async function executeWebhookJob( client: { surface: 'webhook', source: 'trigger' }, } return await runWithRequestContext(requestContext, async () => { - logger.info(`[${requestId}] Starting webhook execution`, { + logger.info('Starting webhook execution', { + requestId, webhookId: authenticatedPayload.webhookId, workflowId: authenticatedPayload.workflowId, provider: authenticatedPayload.provider, @@ -612,7 +611,8 @@ export async function executeWebhookJob( if (execution.outcome === 'in-progress') { // Ingress already acknowledged this delivery and nothing reads this job's result, // so waiting on the live holder would only pin the machine and queue slot. - logger.info(`[${requestId}] Skipping duplicate webhook delivery already in progress`, { + logger.info('Skipping duplicate webhook delivery already in progress', { + requestId, webhookId: authenticatedPayload.webhookId, workflowId: authenticatedPayload.workflowId, provider: authenticatedPayload.provider, @@ -775,7 +775,8 @@ async function handleExecutionResult( ctx.timeoutController.timeoutMs ) { const timeoutErrorMessage = getTimeoutErrorMessage(ctx.timeoutController.timeoutMs) - logger.info(`[${ctx.requestId}] Webhook execution timed out`, { + logger.info('Webhook execution timed out', { + requestId: ctx.requestId, timeoutMs: ctx.timeoutController.timeoutMs, }) await ctx.loggingSession.markAsFailed(timeoutErrorMessage) @@ -844,7 +845,8 @@ async function executeWebhookJobInternal( * condition, not a job fault: acknowledge and skip so workers do not * record a failed job (or burn retries) for work that must never run. */ - logger.info(`[${requestId}] Skipping webhook execution for undeployed workflow`, { + logger.info('Skipping webhook execution for undeployed workflow', { + requestId, workflowId: payload.workflowId, archived: Boolean(workflowRecord.archivedAt), }) @@ -903,9 +905,9 @@ async function executeWebhookJobInternal( const credentialAccountUserId = credentialAccount?.status === 'resolved' ? credentialAccount.userId : undefined if (credentialAccount?.status === 'missing') { - logger.warn( - `[${requestId}] Failed to resolve credential account for credential ${payload.credentialId}` - ) + logger.warn(`Failed to resolve credential account for credential ${payload.credentialId}`, { + requestId, + }) } if (!workflowData) { @@ -967,8 +969,9 @@ async function executeWebhookJobInternal( }) } catch (error) { logger.warn( - `[${requestId}] Failed to build webhook trace secret catalog`, - loggingSession.projectDiagnosticError(error) + 'Failed to build webhook trace secret catalog', + loggingSession.projectDiagnosticError(error), + { requestId } ) resolvedSecretTraceRegistry = createIncompleteResolvedSecretTraceRegistry(secretScope) } @@ -1074,8 +1077,9 @@ async function executeWebhookJobInternal( } } catch (error) { logger.error( - `[${requestId}] Error processing trigger file outputs`, - loggingSession.projectDiagnosticError(error) + 'Error processing trigger file outputs', + loggingSession.projectDiagnosticError(error), + { requestId } ) } } @@ -1094,13 +1098,14 @@ async function executeWebhookJobInternal( }) } catch (error) { logger.error( - `[${requestId}] Error processing provider-specific files`, - loggingSession.projectDiagnosticError(error) + 'Error processing provider-specific files', + loggingSession.projectDiagnosticError(error), + { requestId } ) } } - logger.debug(`[${requestId}] Executing workflow for ${payload.provider} webhook`) + logger.debug(`Executing workflow for ${payload.provider} webhook`, { requestId }) const metadata: ExecutionMetadata = { requestId, @@ -1138,7 +1143,8 @@ async function executeWebhookJobInternal( */ if (payload.webhookReceivedAt !== undefined || payload.triggerTimestampMs !== undefined) { const now = Date.now() - logger.info(`[${requestId}] Webhook dispatch latency`, { + logger.info('Webhook dispatch latency', { + requestId, workflowId: payload.workflowId, provider: payload.provider, dispatchLatencyMs: @@ -1231,7 +1237,8 @@ async function executeWebhookJobInternal( workflowId: payload.workflowId, }) - logger.info(`[${requestId}] Webhook execution completed`, { + logger.info('Webhook execution completed', { + requestId, success: executionResult.success, workflowId: payload.workflowId, provider: payload.provider, @@ -1264,7 +1271,8 @@ async function executeWebhookJobInternal( ? describeRetryableInfrastructureError(error) : undefined if (retryableSetupCause && hasRemainingWebhookInfraRetry(payload)) { - logger.warn(`[${requestId}] Retryable setup failure before webhook workflow started`, { + logger.warn('Retryable setup failure before webhook workflow started', { + requestId, workflowId: payload.workflowId, provider: payload.provider, cause: retryableSetupCause, @@ -1272,12 +1280,14 @@ async function executeWebhookJobInternal( throw new RetryableSetupError(errorMessage, { cause: retryableSetupCause }) } - logFailureOnce(logger, `[${requestId}] Webhook execution failed`, error, { - metadata: () => - loggingSession.projectDiagnosticError(error, { + logFailureOnce(logger, 'Webhook execution failed', error, { + metadata: () => ({ + requestId, + ...loggingSession.projectDiagnosticError(error, { workflowId: payload.workflowId, provider: payload.provider, }), + }), executionId, }) @@ -1290,7 +1300,8 @@ async function executeWebhookJobInternal( provider: payload.provider, }) const completeWithDeletedScope = (scope: ExecutionScope) => { - logger.info(`[${requestId}] Webhook execution ended: its ${scope} was deleted`, { + logger.info(`Webhook execution ended: its ${scope} was deleted`, { + requestId, workflowId: payload.workflowId, executionId, }) @@ -1343,8 +1354,9 @@ async function executeWebhookJobInternal( }) } catch (loggingError) { logger.error( - `[${requestId}] Failed to complete logging session`, - loggingSession.projectDiagnosticError(loggingError) + 'Failed to complete logging session', + loggingSession.projectDiagnosticError(loggingError), + { requestId } ) } diff --git a/apps/sim/background/workflow-execution.ts b/apps/sim/background/workflow-execution.ts index 391d73b168f..0aa2c684e8a 100644 --- a/apps/sim/background/workflow-execution.ts +++ b/apps/sim/background/workflow-execution.ts @@ -220,7 +220,8 @@ export async function executeWorkflowJob( } return await runWithRequestContext({ requestId, ...payload.attribution }, async () => { - logger.info(`[${requestId}] Starting workflow execution job: ${workflowId}`, { + logger.info(`Starting workflow execution job: ${workflowId}`, { + requestId, userId: payload.userId, triggerType: payload.triggerType, executionId, @@ -252,7 +253,8 @@ export async function executeWorkflowJob( }) if (!preprocessResult.success) { - logger.error(`[${requestId}] Preprocessing failed: ${preprocessResult.error?.message}`, { + logger.error(`Preprocessing failed: ${preprocessResult.error?.message}`, { + requestId, workflowId, statusCode: preprocessResult.error?.statusCode, }) @@ -267,7 +269,7 @@ export async function executeWorkflowJob( throw new Error(`Workflow ${workflowId} has no associated workspace`) } - logger.info(`[${requestId}] Preprocessing passed. Using actor: ${actorUserId}`) + logger.info(`Preprocessing passed. Using actor: ${actorUserId}`, { requestId }) const workflow = preprocessResult.workflowRecord! @@ -319,7 +321,8 @@ export async function executeWorkflowJob( timeoutController.timeoutMs ) { const timeoutErrorMessage = getTimeoutErrorMessage(timeoutController.timeoutMs) - logger.info(`[${requestId}] Workflow execution timed out`, { + logger.info('Workflow execution timed out', { + requestId, timeoutMs: timeoutController.timeoutMs, }) await loggingSession.markAsFailed(timeoutErrorMessage) @@ -332,7 +335,8 @@ export async function executeWorkflowJob( if (timeoutError) throw timeoutError - logger.info(`[${requestId}] Workflow execution completed: ${workflowId}`, { + logger.info(`Workflow execution completed: ${workflowId}`, { + requestId, success: result.success, executionTime: result.metadata?.duration, executionId, @@ -347,13 +351,18 @@ export async function executeWorkflowJob( metadata: payload.metadata, } } catch (error: unknown) { - logFailureOnce(logger, `[${requestId}] Workflow execution failed: ${workflowId}`, error, { - metadata: () => loggingSession.projectDiagnosticError(error), + logFailureOnce(logger, 'Workflow execution failed', error, { + metadata: () => ({ + requestId, + workflowId, + ...loggingSession.projectDiagnosticError(error), + }), executionId, }) const completeWithDeletedScope = (scope: ExecutionScope) => { - logger.info(`[${requestId}] Workflow execution ended: its ${scope} was deleted`, { + logger.info(`Workflow execution ended: its ${scope} was deleted`, { + requestId, workflowId, executionId, }) diff --git a/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.ts b/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.ts index 906f919b8f1..f4a92c0718d 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.ts @@ -11,6 +11,7 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { isRecordLike } from '@sim/utils/object' import { and, asc, eq, exists, gt, inArray, isNull, notExists, sql } from 'drizzle-orm' +import { generateRequestId } from '@/lib/core/utils/request' import type { SubBlockRecord } from '@/lib/workflows/persistence/remap-internal-ids' import { invalidateDeployedStateCache } from '@/lib/workflows/persistence/utils' import { @@ -116,7 +117,7 @@ export async function clearFailedForkResourceReferences(params: { deployedTargetWorkflowIds?: string[] requestId?: string }): Promise<{ cleared: number; clearingFailed: boolean }> { - const { childWorkspaceId, failures, requestId = 'unknown' } = params + const { childWorkspaceId, failures, requestId = generateRequestId() } = params if (failures.length === 0) return { cleared: 0, clearingFailed: false } const failedKnowledgeBaseIds = failures.flatMap((failure) => @@ -158,10 +159,11 @@ export async function clearFailedForkResourceReferences(params: { docIds.push(docId) } if (failure.documentChildIds.length > 0) cleanupCount += 1 - logger.warn( - `[${requestId}] Keeping a failed copied knowledge base that contains non-fork documents`, - { childWorkspaceId, childKnowledgeBaseId: failure.childId } - ) + logger.warn('Keeping a failed copied knowledge base that contains non-fork documents', { + requestId, + childWorkspaceId, + childKnowledgeBaseId: failure.childId, + }) continue } markFailed('knowledge-base', failure.childId) @@ -183,7 +185,8 @@ export async function clearFailedForkResourceReferences(params: { affectedWorkflowIds = await clearFailedReferencesInWorkflows(childWorkspaceId, failedByKind) } catch (error) { clearingSucceeded = false - logger.error(`[${requestId}] Failed to clear references for failed fork resources`, { + logger.error('Failed to clear references for failed fork resources', { + requestId, childWorkspaceId, error: getErrorMessage(error), }) @@ -203,7 +206,8 @@ export async function clearFailedForkResourceReferences(params: { await clearFailedReferencesInDeploymentVersions(deployedSweepIds, failedByKind, requestId) } catch (error) { clearingSucceeded = false - logger.error(`[${requestId}] Failed to clear references in fork deployment versions`, { + logger.error('Failed to clear references in fork deployment versions', { + requestId, childWorkspaceId, error: getErrorMessage(error), }) @@ -215,15 +219,13 @@ export async function clearFailedForkResourceReferences(params: { // (now content-less) resource - and ONLY when clearing succeeded, so a clear failure never // turns an empty placeholder into a dangling reference to a deleted row. if (!clearingSucceeded) { - logger.warn( - `[${requestId}] Skipping fork resource placeholder drop after a reference-clear failure`, - { - childWorkspaceId, - tables: tableIds.length, - knowledgeBases: kbIds.length, - documents: docIds.length, - } - ) + logger.warn('Skipping fork resource placeholder drop after a reference-clear failure', { + requestId, + childWorkspaceId, + tables: tableIds.length, + knowledgeBases: kbIds.length, + documents: docIds.length, + }) return { cleared: 0, clearingFailed: true } } try { @@ -239,7 +241,8 @@ export async function clearFailedForkResourceReferences(params: { await db.delete(document).where(inArray(document.id, docIds)) } } catch (error) { - logger.error(`[${requestId}] Failed to drop orphaned fork resource placeholders`, { + logger.error('Failed to drop orphaned fork resource placeholders', { + requestId, childWorkspaceId, error: getErrorMessage(error), }) @@ -407,7 +410,8 @@ export async function clearFailedReferencesInDeploymentVersions( } } catch (error) { failures.push(error) - logger.error(`[${requestId}] Failed to clear references in deployment versions`, { + logger.error('Failed to clear references in deployment versions', { + requestId, workflowId, error: getErrorMessage(error), }) diff --git a/apps/sim/ee/workspace-forking/lib/copy/content-copy-runner.ts b/apps/sim/ee/workspace-forking/lib/copy/content-copy-runner.ts index 0b3551d6126..11e7a264acd 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/content-copy-runner.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/content-copy-runner.ts @@ -3,6 +3,7 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { isTriggerDevEnabled } from '@/lib/core/config/env-flags' import { runDetached } from '@/lib/core/utils/background' +import { generateRequestId } from '@/lib/core/utils/request' import { finishBackgroundWork } from '@/ee/workspace-forking/lib/background-work/store' import { clearFailedForkResourceReferences } from '@/ee/workspace-forking/lib/copy/cleanup-failed' import type { BlobCopyTask } from '@/ee/workspace-forking/lib/copy/copy-files' @@ -219,7 +220,7 @@ export async function scheduleForkContentCopy( payload: ForkContentCopyPayload, options: { detachedLabel: string; requestId?: string } ): Promise { - const { detachedLabel, requestId = 'unknown' } = options + const { detachedLabel, requestId = generateRequestId() } = options try { if (isTriggerDevEnabled) { const [{ forkContentCopyTask }, { tasks }, { resolveTriggerRegion }] = await Promise.all([ @@ -234,7 +235,8 @@ export async function scheduleForkContentCopy( runDetached(detachedLabel, () => runForkContentCopy(payload)) } } catch (error) { - logger.error(`[${requestId}] Failed to schedule fork content copy`, { + logger.error('Failed to schedule fork content copy', { + requestId, detachedLabel, error: getErrorMessage(error), }) diff --git a/apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts index fb716b91749..4a33a1ade82 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts @@ -183,7 +183,8 @@ export async function copyForkChatDeployments(params: { } if (inserts.length > 0) { await tx.insert(chat).values(inserts) - logger.info(`[${requestId ?? 'unknown'}] Carried ${inserts.length} chat deployment(s)`, { + logger.info(`Carried ${inserts.length} chat deployment(s)`, { + requestId, identifiers: inserts.map((row) => row.identifier), }) } diff --git a/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts index 9a17cccd52b..8598d9dda33 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts @@ -9,6 +9,7 @@ import { lockWorkspaceStorageForMutationInTx, resolveStorageBillingContext, } from '@/lib/billing/storage' +import { generateRequestId } from '@/lib/core/utils/request' import type { DbOrTx } from '@/lib/db/types' import { allocateUniqueWorkspaceFileName, @@ -270,7 +271,7 @@ export async function planForkFileCopies(params: { */ export async function executeForkFileBlobCopies( blobTasks: BlobCopyTask[], - requestId = 'unknown', + requestId = generateRequestId(), contentRefMaps?: ForkContentRefMaps, control?: ForkCopyControl ): Promise<{ copied: number; failed: number; failedTargetKeys: string[] }> { @@ -286,7 +287,8 @@ export async function executeForkFileBlobCopies( rethrowForkCopyInterruption(error, control) for (const task of taskPage) { failedTargetKeys.push(task.targetKey) - logger.warn(`[${requestId}] Failed to check copied file replay state`, { + logger.warn('Failed to check copied file replay state', { + requestId, targetKey: task.targetKey, error: getErrorMessage(error), }) @@ -326,13 +328,11 @@ export async function executeForkFileBlobCopies( if (rewritten !== text) body = Buffer.from(rewritten, 'utf8') } catch (error) { rethrowForkCopyInterruption(error, control) - logger.warn( - `[${requestId}] Failed to rewrite markdown blob content; copying raw bytes`, - { - targetKey: task.targetKey, - error: getErrorMessage(error), - } - ) + logger.warn('Failed to rewrite markdown blob content; copying raw bytes', { + requestId, + targetKey: task.targetKey, + error: getErrorMessage(error), + }) } } assertForkCopyActive(control) @@ -447,7 +447,8 @@ export async function executeForkFileBlobCopies( await completeForkCopyResource(control, `file:${task.targetFileId}`) copied += 1 if (targetOriginalName !== task.fileName) { - logger.warn(`[${requestId}] Copied file renamed to avoid a target name collision`, { + logger.warn('Copied file renamed to avoid a target name collision', { + requestId, targetKey: task.targetKey, folderId: task.targetFolderId ?? null, from: task.fileName, @@ -457,7 +458,8 @@ export async function executeForkFileBlobCopies( } catch (error) { rethrowForkCopyInterruption(error, control) failedTargetKeys.push(task.targetKey) - logger.warn(`[${requestId}] Failed to copy file blob during fork`, { + logger.warn('Failed to copy file blob during fork', { + requestId, targetKey: task.targetKey, error: getErrorMessage(error), }) @@ -478,7 +480,8 @@ export async function executeForkFileBlobCopies( await deleteFile({ key: task.targetKey, context: task.context }) } } catch (cleanupError) { - logger.warn(`[${requestId}] Failed to clean up unfinalized copied file blob`, { + logger.warn('Failed to clean up unfinalized copied file blob', { + requestId, targetKey: task.targetKey, error: getErrorMessage(cleanupError), }) diff --git a/apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts index fc5e6eee8ea..584c8b4c92e 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts @@ -42,6 +42,7 @@ import { type StorageBillingContext, } from '@/lib/billing/storage' import { mapWithConcurrency } from '@/lib/core/utils/concurrency' +import { generateRequestId } from '@/lib/core/utils/request' import type { DbOrTx } from '@/lib/db/types' import { type DurableSecretProvenance, @@ -1113,7 +1114,7 @@ export async function copyForkResourceContent(params: { requestId?: string control?: ForkCopyControl }): Promise<{ copied: number; failed: number; failures: ForkFailedResource[] }> { - const { contentPlan, contentRefMaps, control, requestId = 'unknown' } = params + const { contentPlan, contentRefMaps, control, requestId = generateRequestId() } = params const { childWorkspaceId, userId } = contentPlan let copiedResources = 0 @@ -1143,7 +1144,8 @@ export async function copyForkResourceContent(params: { }) } catch (mappingCleanupError) { rethrowForkCopyInterruption(mappingCleanupError, control) - logger.error(`[${requestId}] Failed to clean mapping for a failed copied document`, { + logger.error('Failed to clean mapping for a failed copied document', { + requestId, childDocumentId, error: getErrorMessage(mappingCleanupError), }) @@ -1183,17 +1185,20 @@ export async function copyForkResourceContent(params: { // the same child id again, and the cleanup keys failed ids by kind in a Set. await dropCopiedDocumentMapping(childDocumentId) staleChildIds.push(childDocumentId) - logger.warn( - `[${requestId}] Dropping a fork placeholder planned for a connector-managed document`, - { sourceDocumentId: id, childDocumentId, childKnowledgeBaseId: kb.childId } - ) + logger.warn('Dropping a fork placeholder planned for a connector-managed document', { + requestId, + sourceDocumentId: id, + childDocumentId, + childKnowledgeBaseId: kb.childId, + }) } return staleChildIds } catch (error) { rethrowForkCopyInterruption(error, control) logger.error( - `[${requestId}] Failed to reconcile fork placeholders planned for connector-managed documents`, + 'Failed to reconcile fork placeholders planned for connector-managed documents', { + requestId, sourceKnowledgeBaseId: kb.sourceId, childKnowledgeBaseId: kb.childId, error: getErrorMessage(error), @@ -1223,14 +1228,16 @@ export async function copyForkResourceContent(params: { ) const skipped = Number(row?.total ?? 0) if (skipped === 0) return - logger.info(`[${requestId}] Skipped connector-managed documents in a copied knowledge base`, { + logger.info('Skipped connector-managed documents in a copied knowledge base', { + requestId, sourceKnowledgeBaseId: kb.sourceId, childKnowledgeBaseId: kb.childId, skipped, }) } catch (error) { rethrowForkCopyInterruption(error, control) - logger.warn(`[${requestId}] Failed to count the documents a copied knowledge base skipped`, { + logger.warn('Failed to count the documents a copied knowledge base skipped', { + requestId, sourceKnowledgeBaseId: kb.sourceId, error: getErrorMessage(error), }) @@ -1349,7 +1356,8 @@ export async function copyForkResourceContent(params: { rethrowForkCopyInterruption(error, control) failedResources += 1 failures.push({ kind: 'table', childId: table.childId }) - logger.warn(`[${requestId}] Failed to copy table rows during fork`, { + logger.warn('Failed to copy table rows during fork', { + requestId, sourceTableId: table.sourceId, error: getErrorMessage(error), }) @@ -1469,7 +1477,8 @@ export async function copyForkResourceContent(params: { await rollbackCopiedKbDocuments(kb.childId, childWorkspaceId, control) } catch (rollbackError) { rethrowForkCopyInterruption(rollbackError, control) - logger.error(`[${requestId}] Failed to roll back copied KB storage accounting`, { + logger.error('Failed to roll back copied KB storage accounting', { + requestId, childKnowledgeBaseId: kb.childId, error: getErrorMessage(rollbackError), }) @@ -1487,7 +1496,8 @@ export async function copyForkResourceContent(params: { ) } catch (mappingCleanupError) { rethrowForkCopyInterruption(mappingCleanupError, control) - logger.error(`[${requestId}] Failed to clean mappings for a failed copied KB`, { + logger.error('Failed to clean mappings for a failed copied KB', { + requestId, childKnowledgeBaseId: kb.childId, error: getErrorMessage(mappingCleanupError), }) @@ -1499,7 +1509,8 @@ export async function copyForkResourceContent(params: { childId: kb.childId, documentChildIds: Object.values(kb.documentIdMap), }) - logger.warn(`[${requestId}] Failed to copy knowledge base content during fork`, { + logger.warn('Failed to copy knowledge base content during fork', { + requestId, sourceKnowledgeBaseId: kb.sourceId, error: getErrorMessage(error), }) @@ -1567,7 +1578,8 @@ export async function copyForkResourceContent(params: { await dropCopiedDocumentMapping(docEntry.childDocId) failedResources += 1 failures.push({ kind: 'knowledge-document', childId: docEntry.childDocId }) - logger.warn(`[${requestId}] Failed to copy document into mapped KB during sync`, { + logger.warn('Failed to copy document into mapped KB during sync', { + requestId, sourceDocumentId: docEntry.sourceDocId, error: getErrorMessage(error), }) @@ -1609,13 +1621,11 @@ export async function copyForkResourceContent(params: { await completeForkCopyResource(control, `skill:${row.id}`) } catch (error) { rethrowForkCopyInterruption(error, control) - logger.warn( - `[${requestId}] Failed to rewrite copied skill content; keeping source links`, - { - childSkillId: row.id, - error: getErrorMessage(error), - } - ) + logger.warn('Failed to rewrite copied skill content; keeping source links', { + requestId, + childSkillId: row.id, + error: getErrorMessage(error), + }) } }) afterId = rows[rows.length - 1].id diff --git a/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts index 847f2ec2984..82b4bb95c6f 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts @@ -4,6 +4,7 @@ import { generateId } from '@sim/utils/id' import { isRecordLike } from '@sim/utils/object' import { and, eq, inArray, isNull } from 'drizzle-orm' import type { FolderResourceType } from '@/lib/api/contracts/folders' +import { generateRequestId } from '@/lib/core/utils/request' import type { DbOrTx } from '@/lib/db/types' import { buildFolderPathIndex, ROOT_FOLDER_PATH } from '@/lib/folders/paths' import { assertFolderCollectionHasRoom } from '@/lib/folders/queries' @@ -499,7 +500,7 @@ export async function copyWorkflowStateIntoTarget( nameRegistry, resolveBlockId, triggerPathByBlockId, - requestId = 'unknown', + requestId = generateRequestId(), } = params const targetFolderId = sourceMeta.folderId ? (folderIdMap.get(sourceMeta.folderId) ?? null) : null @@ -651,7 +652,8 @@ export async function copyWorkflowStateIntoTarget( const newSource = blockIdMapping.get(edge.source) const newTarget = blockIdMapping.get(edge.target) if (!newSource || !newTarget) { - logger.warn(`[${requestId}] Skipping edge with unmapped block reference during fork copy`, { + logger.warn('Skipping edge with unmapped block reference during fork copy', { + requestId, edgeId: edge.id, }) return [] diff --git a/apps/sim/ee/workspace-forking/lib/create-fork.ts b/apps/sim/ee/workspace-forking/lib/create-fork.ts index 3944e1b3d0b..9a6df23e071 100644 --- a/apps/sim/ee/workspace-forking/lib/create-fork.ts +++ b/apps/sim/ee/workspace-forking/lib/create-fork.ts @@ -7,6 +7,7 @@ import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import type { Workspace } from '@/lib/api/contracts/workspaces' import { enqueueOutboxEvent } from '@/lib/core/outbox/service' +import { generateRequestId } from '@/lib/core/utils/request' import { requireForkProject } from '@/lib/projects/membership' import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults' import { insertNewWorkflowRow } from '@/lib/workflows/persistence/new-workflow-row' @@ -146,7 +147,7 @@ const FORK_KIND_TO_RESOURCE_TYPE: Partial { - const { source, policy, userId, requestId = 'unknown' } = params + const { source, policy, userId, requestId = generateRequestId() } = params const admission = params.admission if (admission) { const receipt = await findWorkspaceOperationReceipt( @@ -560,7 +561,8 @@ export async function createFork(params: CreateForkParams): Promise { const { edge, sourceWorkspaceId, targetWorkspaceId, direction, userId } = params - const requestId = params.requestId ?? 'unknown' + const requestId = params.requestId ?? generateRequestId() const admission = params.admission const revisionScope = { sourceWorkspaceId, targetWorkspaceId, edge } if (admission) { @@ -701,8 +702,9 @@ export async function promoteFork(params: PromoteForkParams): Promise 0) { logger.info( - `[${requestId}] Promote leaving ${plan.excludedTargets.length} sync-excluded target workflow(s) untouched`, + `Promote leaving ${plan.excludedTargets.length} sync-excluded target workflow(s) untouched`, { + requestId, sourceWorkspaceId, targetWorkspaceId, excludedTargets: plan.excludedTargets.map((target) => target.name), @@ -1235,8 +1237,8 @@ export async function promoteFork(params: PromoteForkParams): Promise ({ id: item.sourceWorkflowId, name: item.sourceMeta.name })) if (skippedItems.length > 0) { logger.warn( - `[${requestId}] Promote skipped ${skippedItems.length} source workflow(s) whose deployment disappeared between plan and apply`, - { sourceWorkspaceId, targetWorkspaceId, skipped: skippedItems.length } + `Promote skipped ${skippedItems.length} source workflow(s) whose deployment disappeared between plan and apply`, + { requestId, sourceWorkspaceId, targetWorkspaceId, skipped: skippedItems.length } ) } @@ -1368,7 +1370,8 @@ export async function promoteFork(params: PromoteForkParams): Promise 0) { - logger.warn(`[${requestId}] Promote wrote state but some targets failed to deploy`, { + logger.warn('Promote wrote state but some targets failed to deploy', { + requestId, sourceWorkspaceId, targetWorkspaceId, deployFailed: deployFailures.length, @@ -1537,21 +1544,24 @@ export async function promoteFork(params: PromoteForkParams): Promise 0) { - logger.warn(`[${requestId}] Promote deploys emitted warnings`, { deployWarnings }) + logger.warn('Promote deploys emitted warnings', { requestId, deployWarnings }) } if (txResult.skippedItems.length > 0) { - logger.warn(`[${requestId}] Promote skipped undeployed source workflows`, { + logger.warn('Promote skipped undeployed source workflows', { + requestId, skipped: txResult.skippedItems.map((item) => item.name), }) } if (txResult.needsConfiguration.length > 0) { - logger.warn(`[${requestId}] Promote left required dependent fields needing configuration`, { + logger.warn('Promote left required dependent fields needing configuration', { + requestId, sourceWorkspaceId, targetWorkspaceId, needsConfiguration: txResult.needsConfiguration.map((n) => n.workflowName), }) } - logger.info(`[${requestId}] Promoted ${sourceWorkspaceId} -> ${targetWorkspaceId}`, { + logger.info(`Promoted ${sourceWorkspaceId} -> ${targetWorkspaceId}`, { + requestId, updated: txResult.updated, created: txResult.created, archived: txResult.archived, diff --git a/apps/sim/ee/workspace-forking/lib/promote/rollback.ts b/apps/sim/ee/workspace-forking/lib/promote/rollback.ts index 281d2956ebc..56d40f157f5 100644 --- a/apps/sim/ee/workspace-forking/lib/promote/rollback.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/rollback.ts @@ -258,10 +258,11 @@ export async function rollbackFork(params: RollbackForkParams): Promise 0) { - logger.warn( - `[${requestId}] Rollback skipped ${skipped.size} workflow(s) no longer in the database`, - { - targetWorkspaceId, - skipped: Array.from(skipped), - } - ) + logger.warn(`Rollback skipped ${skipped.size} workflow(s) no longer in the database`, { + requestId, + targetWorkspaceId, + skipped: Array.from(skipped), + }) } // Notify connected canvases to adopt the restored state (reactivated drafts + the @@ -373,7 +373,8 @@ export async function rollbackFork(params: RollbackForkParams): Promise { const requestId = `${callerLabel}-${Date.now()}` - logger.info(`[${requestId}] Resolving Vertex AI credential: ${credentialId}`) + logger.info(`Resolving Vertex AI credential: ${credentialId}`, { requestId }) if (!actingUserId) { throw new Error('Vertex AI credential use requires an authenticated user') @@ -46,7 +46,8 @@ export async function resolveVertexCredential({ throw new Error(`Vertex AI credential not found: ${credentialId}`) } if (workspaceId && cred.workspaceId !== workspaceId) { - logger.warn(`[${requestId}] Vertex AI credential belongs to a different workspace`, { + logger.warn('Vertex AI credential belongs to a different workspace', { + requestId, credentialId, executingWorkspaceId: workspaceId, }) @@ -69,7 +70,7 @@ export async function resolveVertexCredential({ workflowId, executionId, }) - logger.info(`[${requestId}] Successfully resolved Vertex AI service account credential`) + logger.info('Successfully resolved Vertex AI service account credential', { requestId }) return accessToken } @@ -90,6 +91,6 @@ export async function resolveVertexCredential({ throw new Error('Failed to get Vertex AI access token') } - logger.info(`[${requestId}] Successfully resolved Vertex AI credential`) + logger.info('Successfully resolved Vertex AI credential', { requestId }) return accessToken } diff --git a/apps/sim/hooks/queries/utils/optimistic-mutation.ts b/apps/sim/hooks/queries/utils/optimistic-mutation.ts index 16bab2ffc19..8ef9a80fde4 100644 --- a/apps/sim/hooks/queries/utils/optimistic-mutation.ts +++ b/apps/sim/hooks/queries/utils/optimistic-mutation.ts @@ -45,12 +45,12 @@ export function createOptimisticMutationHandlers( const tempId = generateTempId(variables) const optimisticItem = createOptimisticItem(variables, tempId) applyOptimisticUpdate(tempId, optimisticItem) - logger.info(`[${name}] Added optimistic entry: ${tempId}`) + logger.info(`Added optimistic entry: ${tempId}`, { mutation: name }) return { tempId, previousState } }, onSuccess: (data: TData, variables: TVariables, context: OptimisticMutationContext) => { - logger.info(`[${name}] Success, replacing temp entry ${context.tempId}`) + logger.info(`Success, replacing temp entry ${context.tempId}`, { mutation: name }) replaceOptimisticEntry(context.tempId, data) onSuccessExtra?.(data, variables) }, @@ -60,10 +60,10 @@ export function createOptimisticMutationHandlers( _variables: TVariables, context: OptimisticMutationContext | undefined ) => { - logger.error(`[${name}] Failed:`, error) + logger.error('Failed:', error, { mutation: name }) if (context?.previousState) { rollback(context.previousState, _variables) - logger.info(`[${name}] Rolled back to previous state`) + logger.info('Rolled back to previous state', { mutation: name }) } }, diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index 3273c3a7d86..31ea8978ad5 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -194,12 +194,13 @@ function logUnresolvedStripePlan( priceChanged: boolean ): void { if (isEnterprise(details.dbPlan) || (isLegacyPlan(details.dbPlan) && !priceChanged)) { - logger.info(`[${hook}] Off-catalog Stripe price; keeping the stored plan`, details) + logger.info('Off-catalog Stripe price; keeping the stored plan', details, { hook }) return } logger.error( - `[${hook}] Could not resolve plan from Stripe price; keeping the stored plan — check env var configuration`, - details + 'Could not resolve plan from Stripe price; keeping the stored plan — check env var configuration', + details, + { hook } ) } diff --git a/apps/sim/lib/billing/cleanup-dispatcher.ts b/apps/sim/lib/billing/cleanup-dispatcher.ts index d80757b7a74..9cbe6b56cc0 100644 --- a/apps/sim/lib/billing/cleanup-dispatcher.ts +++ b/apps/sim/lib/billing/cleanup-dispatcher.ts @@ -512,7 +512,8 @@ export async function dispatchCleanupJobs(jobType: CleanupJobType): Promise<{ */ if (!isBillingEnabled && !isDataRetentionEnabled) { logger.info( - `[${jobType}] Skipping cleanup dispatch: billing is disabled and data retention is not enabled` + 'Skipping cleanup dispatch: billing is disabled and data retention is not enabled', + { jobType } ) return { jobIds: [], jobCount: 0, chunkCount: 0, workspaceCount: 0 } } @@ -551,9 +552,9 @@ export async function dispatchCleanupJobs(jobType: CleanupJobType): Promise<{ }) await flushBatch() - logger.info( - `[${jobType}] Trigger cleanup chunks: ${succeeded} dispatched in ${jobIds.length} batch(es)` - ) + logger.info(`Trigger cleanup chunks: ${succeeded} dispatched in ${jobIds.length} batch(es)`, { + jobType, + }) return { jobIds, jobCount: jobIds.length, chunkCount, workspaceCount } } @@ -566,7 +567,8 @@ export async function dispatchCleanupJobs(jobType: CleanupJobType): Promise<{ succeeded++ } catch (error) { failed++ - logger.error(`[${jobType}] Inline cleanup chunk failed:`, { + logger.error('Inline cleanup chunk failed:', { + jobType, plan: payload.plan, label: payload.label, error, @@ -574,7 +576,7 @@ export async function dispatchCleanupJobs(jobType: CleanupJobType): Promise<{ } }) - logger.info(`[${jobType}] Inline cleanup chunks: ${succeeded} succeeded, ${failed} failed`) + logger.info(`Inline cleanup chunks: ${succeeded} succeeded, ${failed} failed`, { jobType }) return { jobIds, jobCount: jobIds.length, chunkCount, workspaceCount } } @@ -588,10 +590,10 @@ export async function dispatchCleanupJobs(jobType: CleanupJobType): Promise<{ succeeded++ } catch (reason) { failed++ - logger.error(`[${jobType}] Failed to enqueue chunk:`, { reason }) + logger.error('Failed to enqueue chunk:', { jobType, reason }) } }) - logger.info(`[${jobType}] Chunk enqueue: ${succeeded} succeeded, ${failed} failed`) + logger.info(`Chunk enqueue: ${succeeded} succeeded, ${failed} failed`, { jobType }) return { jobIds, jobCount: jobIds.length, chunkCount, workspaceCount } } diff --git a/apps/sim/lib/cleanup/batch-delete.ts b/apps/sim/lib/cleanup/batch-delete.ts index cd6506d174c..389825b1477 100644 --- a/apps/sim/lib/cleanup/batch-delete.ts +++ b/apps/sim/lib/cleanup/batch-delete.ts @@ -163,7 +163,7 @@ export async function chunkedBatchDeleteByScope({ const result: TableCleanupResult = { table: tableName, deleted: 0, failed: 0 } if (scopeIds.length === 0) { - logger.info(`[${tableName}] Skipped — no resource owners in scope`) + logger.info('Skipped — no resource owners in scope', { tableName }) return result } @@ -220,8 +220,8 @@ export async function chunkedBatchDeleteByScope({ // Count rows we tried to delete; SELECT-stage errors leave rows=[]. result.failed += rows.length logger.error( - `[${tableName}] Batch failed (chunk ${chunkIdx + 1}/${chunks.length}, ${rows.length} rows):`, - { error } + `Batch failed (chunk ${chunkIdx + 1}/${chunks.length}, ${rows.length} rows):`, + { tableName, error } ) hasMore = false } @@ -229,7 +229,8 @@ export async function chunkedBatchDeleteByScope({ } logger.info( - `[${tableName}] Complete: ${result.deleted} deleted, ${result.failed} failed across ${chunks.length} chunks${stoppedEarly ? ' (row-limit reached, remaining chunks deferred to next run)' : ''}` + `Complete: ${result.deleted} deleted, ${result.failed} failed across ${chunks.length} chunks${stoppedEarly ? ' (row-limit reached, remaining chunks deferred to next run)' : ''}`, + { tableName } ) return result @@ -338,14 +339,15 @@ export async function deleteRowsById( // run picks up whatever's still expired, so this only inflates the metric. result.failed += chunkIds.length logger.error( - `[${tableName}] Delete chunk ${chunkIdx + 1}/${chunks.length} failed (up to ${chunkIds.length} rows):`, - { error } + `Delete chunk ${chunkIdx + 1}/${chunks.length} failed (up to ${chunkIds.length} rows):`, + { tableName, error } ) } } logger.info( - `[${tableName}] Deleted ${result.deleted} rows across ${chunks.length} chunk(s)${result.failed > 0 ? `, ${result.failed} failed` : ''}` + `Deleted ${result.deleted} rows across ${chunks.length} chunk(s)${result.failed > 0 ? `, ${result.failed} failed` : ''}`, + { tableName } ) return result } diff --git a/apps/sim/lib/cleanup/chat-cleanup.ts b/apps/sim/lib/cleanup/chat-cleanup.ts index 8de4de6ed36..110ad576ddf 100644 --- a/apps/sim/lib/cleanup/chat-cleanup.ts +++ b/apps/sim/lib/cleanup/chat-cleanup.ts @@ -110,9 +110,7 @@ export async function deleteStorageFiles( stats.filesDeleted += result.deleted stats.filesFailed += result.failed.length for (const { key, error } of result.failed) { - logger.error(`[${label}] Failed to delete storage file ${key} (context: ${context}):`, { - error, - }) + logger.error(`Failed to delete storage file ${key} (context: ${context}):`, { label, error }) } } @@ -131,7 +129,7 @@ export async function cleanupCopilotBackend( if (chatIds.length === 0 || !env.COPILOT_API_KEY) { if (!env.COPILOT_API_KEY) { - logger.warn(`[${label}] COPILOT_API_KEY not set, skipping copilot backend cleanup`) + logger.warn('COPILOT_API_KEY not set, skipping copilot backend cleanup', { label }) } return stats } @@ -150,7 +148,8 @@ export async function cleanupCopilotBackend( if (!response.ok) { const errorBody = await response.text().catch(() => '') - logger.error(`[${label}] Copilot backend cleanup failed: ${response.status}`, { + logger.error(`Copilot backend cleanup failed: ${response.status}`, { + label, errorBody, chatCount: chunk.length, }) @@ -161,11 +160,12 @@ export async function cleanupCopilotBackend( const result = await response.json() stats.deleted += result.deleted ?? 0 logger.info( - `[${label}] Copilot backend cleanup: ${result.deleted} chats deleted (batch ${Math.floor(i / COPILOT_CLEANUP_BATCH_SIZE) + 1})` + `Copilot backend cleanup: ${result.deleted} chats deleted (batch ${Math.floor(i / COPILOT_CLEANUP_BATCH_SIZE) + 1})`, + { label } ) } catch (error) { stats.failed += chunk.length - logger.error(`[${label}] Copilot backend cleanup request failed:`, { error }) + logger.error('Copilot backend cleanup request failed:', { label, error }) } } @@ -188,7 +188,8 @@ export async function prepareChatCleanup( // Collect file refs BEFORE DB deletion (keys + context are lost after cascade) const files = await collectChatFiles(chatIds) if (files.length > 0) { - logger.info(`[${label}] Collected ${files.length} files for cleanup`, { + logger.info(`Collected ${files.length} files for cleanup`, { + label, files: files.map((f) => ({ key: f.key, context: f.context })), }) } @@ -208,7 +209,8 @@ export async function prepareChatCleanup( } if (survivors.size > 0) { logger.info( - `[${label}] Skipping external cleanup for ${survivors.size} chats whose rows still exist` + `Skipping external cleanup for ${survivors.size} chats whose rows still exist`, + { label } ) } const confirmedChatIds = chatIds.filter((id) => !survivors.has(id)) @@ -220,7 +222,8 @@ export async function prepareChatCleanup( if (confirmedChatIds.length > 0) { const copilotResult = await cleanupCopilotBackend(confirmedChatIds, label) logger.info( - `[${label}] Copilot backend: ${copilotResult.deleted} deleted, ${copilotResult.failed} failed` + `Copilot backend: ${copilotResult.deleted} deleted, ${copilotResult.failed} failed`, + { label } ) await enqueueChatInlineImageCleanup(cleanupDb, confirmedChatIds) } @@ -229,7 +232,8 @@ export async function prepareChatCleanup( if (confirmedFiles.length > 0) { const fileStats = await deleteStorageFiles(confirmedFiles, label) logger.info( - `[${label}] Storage cleanup: ${fileStats.filesDeleted} deleted, ${fileStats.filesFailed} failed` + `Storage cleanup: ${fileStats.filesDeleted} deleted, ${fileStats.filesFailed} failed`, + { label } ) } }, diff --git a/apps/sim/lib/core/async-jobs/backends/database.ts b/apps/sim/lib/core/async-jobs/backends/database.ts index 972a220d5db..8ac12b0140b 100644 --- a/apps/sim/lib/core/async-jobs/backends/database.ts +++ b/apps/sim/lib/core/async-jobs/backends/database.ts @@ -340,10 +340,7 @@ export class DatabaseJobQueue implements JobQueueBackend { } return run().catch((err) => { if (controller.signal.aborted) return - logger.error(`[${type}] Inline run failed`, { - cancelKey, - error: toError(err).message, - }) + logger.error('Inline run failed', { jobType: type, cancelKey, error: toError(err).message }) }) }) try { @@ -628,11 +625,11 @@ export class DatabaseJobQueue implements JobQueueBackend { } catch (err) { if (abortController.signal.aborted) return const message = toError(err).message - logger.error(`[${type}] Inline job ${jobId} failed`, { error: message }) + logger.error(`Inline job ${jobId} failed`, { jobType: type, error: message }) try { await this.markJobFailed(jobId, message) } catch (markErr) { - logger.error(`[${type}] Failed to mark job ${jobId} as failed`, { markErr }) + logger.error(`Failed to mark job ${jobId} as failed`, { jobType: type, markErr }) } } finally { if (inlineAbortControllers.get(jobId) === abortController) { diff --git a/apps/sim/lib/core/security/deployment-auth.ts b/apps/sim/lib/core/security/deployment-auth.ts index d178946c0ba..0c052e038ab 100644 --- a/apps/sim/lib/core/security/deployment-auth.ts +++ b/apps/sim/lib/core/security/deployment-auth.ts @@ -31,7 +31,7 @@ export async function validateDeploymentAuth( sessionPresent = Boolean(session?.user) sessionEmail = session?.user?.email } catch (error) { - logger.error(`[${requestId}] Error validating SSO:`, error) + logger.error('Error validating SSO:', error, { requestId }) return { authorized: false, error: 'SSO authentication error' } } } diff --git a/apps/sim/lib/core/security/deployment-credentials.ts b/apps/sim/lib/core/security/deployment-credentials.ts index 1faa9f9858e..fa4b4fc7ff8 100644 --- a/apps/sim/lib/core/security/deployment-credentials.ts +++ b/apps/sim/lib/core/security/deployment-credentials.ts @@ -120,7 +120,7 @@ export async function validateDeploymentCredentials( } if (!resource.password) { - logger.error(`[${requestId}] No password set for password-protected ${resource.id}`) + logger.error(`No password set for password-protected ${resource.id}`, { requestId }) return { authorized: false, error: 'Authentication configuration error' } } @@ -132,7 +132,8 @@ export async function validateDeploymentCredentials( { failClosed: true } ) if (!ipRateLimit.allowed) { - logger.warn(`[${requestId}] Password attempt IP rate limit exceeded`, { + logger.warn('Password attempt IP rate limit exceeded', { + requestId, resourceId: resource.id, cookiePrefix, ip, @@ -150,7 +151,8 @@ export async function validateDeploymentCredentials( { failClosed: true } ) if (!resourceRateLimit.allowed) { - logger.warn(`[${requestId}] Password attempt resource rate limit exceeded`, { + logger.warn('Password attempt resource rate limit exceeded', { + requestId, resourceId: resource.id, cookiePrefix, }) @@ -167,7 +169,7 @@ export async function validateDeploymentCredentials( return { authorized: true } } catch (error) { - logger.error(`[${requestId}] Error validating password:`, error) + logger.error('Error validating password:', error, { requestId }) return { authorized: false, error: 'Authentication error' } } } @@ -198,7 +200,7 @@ export async function validateDeploymentCredentials( return { authorized: false, error: 'Email not authorized' } } catch (error) { - logger.error(`[${requestId}] Error validating email:`, error) + logger.error('Error validating email:', error, { requestId }) return { authorized: false, error: 'Authentication error' } } } @@ -224,7 +226,7 @@ export async function validateDeploymentCredentials( return { authorized: false, error: 'Your email is not authorized to access this resource' } } catch (error) { - logger.error(`[${requestId}] Error validating SSO:`, error) + logger.error('Error validating SSO:', error, { requestId }) return { authorized: false, error: 'SSO authentication error' } } } diff --git a/apps/sim/lib/core/utils/request.ts b/apps/sim/lib/core/utils/request.ts index a32a7b84e68..683d48fe934 100644 --- a/apps/sim/lib/core/utils/request.ts +++ b/apps/sim/lib/core/utils/request.ts @@ -10,8 +10,8 @@ export const trustedProxies = clientIpResolver.trustedProxies /** * Generate a short request ID for correlation. If called inside a request * context (see `withRouteHandler` and `runWithRequestContext`), returns the - * active request's ID so inline `[${requestId}]` log prefixes align with - * the auto-attached `{requestId=...}` logger metadata. + * active request's ID, the same one the logger stamps on every line, so work + * handed this id correlates with the request that started it. */ export function generateRequestId(): string { return getRequestContext()?.requestId ?? generateId().slice(0, 8) diff --git a/apps/sim/lib/execution/files.ts b/apps/sim/lib/execution/files.ts index 7164ee1fde7..2c99dfd9910 100644 --- a/apps/sim/lib/execution/files.ts +++ b/apps/sim/lib/execution/files.ts @@ -267,7 +267,7 @@ export async function processExecutionFiles( uploadedFiles.push(userFile) } } catch (error) { - logger.error(`[${requestId}] Failed to process workflow file`, error) + logger.error('Failed to process workflow file', error, { requestId }) throw new Error(`Failed to process workflow file: ${getErrorMessage(error, 'Invalid file')}`) } } @@ -384,7 +384,8 @@ export async function processInputFileFields( else processedInput[fileField.name] = uploadedFiles if (uploadedFiles.length > 0) { logger.info( - `[${requestId}] Successfully processed ${uploadedFiles.length} file(s) for field: ${fileField.name}` + `Successfully processed ${uploadedFiles.length} file(s) for field: ${fileField.name}`, + { requestId } ) } } diff --git a/apps/sim/lib/execution/isolated-vm.test.ts b/apps/sim/lib/execution/isolated-vm.test.ts index c1f04e9e47f..af76efff66b 100644 --- a/apps/sim/lib/execution/isolated-vm.test.ts +++ b/apps/sim/lib/execution/isolated-vm.test.ts @@ -1136,7 +1136,8 @@ describe('isolated-vm scheduler', () => { expect(JSON.parse(String(result.result)).error).toContain(secret) expect(JSON.stringify(mockLogger.warn.mock.calls)).not.toContain(secret) - expect(mockLogger.warn).toHaveBeenCalledWith('[req-fetch-secret] Isolated fetch failed', { + expect(mockLogger.warn).toHaveBeenCalledWith('Isolated fetch failed', { + requestId: 'req-fetch-secret', errorName: 'Error', }) }) diff --git a/apps/sim/lib/execution/isolated-vm.ts b/apps/sim/lib/execution/isolated-vm.ts index 66ac6ce06d3..b0e6379e169 100644 --- a/apps/sim/lib/execution/isolated-vm.ts +++ b/apps/sim/lib/execution/isolated-vm.ts @@ -416,9 +416,7 @@ async function secureFetch( }) } catch (error: unknown) { const normalizedError = toError(error) - logger.warn(`[${requestId}] Isolated fetch failed`, { - errorName: normalizedError.name, - }) + logger.warn('Isolated fetch failed', { requestId, errorName: normalizedError.name }) return JSON.stringify({ error: getErrorMessage(error, 'Unknown fetch error') }) } } diff --git a/apps/sim/lib/execution/payloads/materialization.server.ts b/apps/sim/lib/execution/payloads/materialization.server.ts index 1fc17b1f446..b52dbc871ee 100644 --- a/apps/sim/lib/execution/payloads/materialization.server.ts +++ b/apps/sim/lib/execution/payloads/materialization.server.ts @@ -2,6 +2,7 @@ import type { Principal } from '@sim/auth/principal' import { createLogger, type Logger } from '@sim/logger' import { toError } from '@sim/utils/errors' import { OrchestrationError } from '@/lib/core/orchestration/types' +import { generateRequestId } from '@/lib/core/utils/request' import { isPayloadSizeLimitError } from '@/lib/core/utils/stream-limits' import { isUserFileWithMetadata } from '@/lib/core/utils/user-file' import { getExecutionKeyParts } from '@/lib/execution/payloads/access-keys' @@ -398,7 +399,7 @@ export async function readUserFileContentWithContributors( let contributingFiles: readonly WorkspaceFileSecretProvenanceIdentity[] | undefined let renderedContributingFiles: readonly WorkspaceFileSecretProvenanceIdentity[] | undefined const log = getLogger(options) - const requestId = options.requestId ?? 'unknown' + const requestId = options.requestId ?? generateRequestId() try { const servable = await downloadServableFileFromStorage(file, requestId, log, { diff --git a/apps/sim/lib/execution/preprocessing.ts b/apps/sim/lib/execution/preprocessing.ts index 9c20962a2d8..8c1520b097d 100644 --- a/apps/sim/lib/execution/preprocessing.ts +++ b/apps/sim/lib/execution/preprocessing.ts @@ -431,7 +431,8 @@ export async function preprocessExecution( await recordPreprocessingError(record) } - logger.debug(`[${requestId}] Starting execution preprocessing`, { + logger.debug('Starting execution preprocessing', { + requestId, workflowId, userId, triggerType, @@ -439,7 +440,8 @@ export async function preprocessExecution( }) if (prefetchedWorkflowRecord && prefetchedWorkflowRecord.id !== workflowId) { - logger.error(`[${requestId}] Prefetched workflow record ID mismatch`, { + logger.error('Prefetched workflow record ID mismatch', { + requestId, expected: workflowId, received: prefetchedWorkflowRecord.id, }) @@ -466,7 +468,7 @@ export async function preprocessExecution( }) if (!workflowRecord) { - logger.warn(`[${requestId}] Workflow not found: ${workflowId}`) + logger.warn(`Workflow not found: ${workflowId}`, { requestId }) await recordPreprocessingError({ workflowId, @@ -491,7 +493,7 @@ export async function preprocessExecution( } } } catch (error) { - logger.error(`[${requestId}] Error fetching workflow`, { error, workflowId }) + logger.error('Error fetching workflow', { requestId, error, workflowId }) const failure: PreprocessExecutionError = { message: 'Internal error while fetching workflow', @@ -516,7 +518,7 @@ export async function preprocessExecution( return { success: false, error: failure } } } else if (workflowRecord.archivedAt) { - logger.warn(`[${requestId}] Prefetched workflow is archived: ${workflowId}`) + logger.warn(`Prefetched workflow is archived: ${workflowId}`, { requestId }) return { success: false, error: { @@ -530,7 +532,7 @@ export async function preprocessExecution( label: 'getActiveWorkflowRecord', }) if (!activeWorkflow) { - logger.warn(`[${requestId}] Workflow archived before execution started: ${workflowId}`) + logger.warn(`Workflow archived before execution started: ${workflowId}`, { requestId }) return { success: false, error: { @@ -546,7 +548,7 @@ export async function preprocessExecution( const workspaceId = workflowRecord.workspaceId || providedWorkspaceId || '' if (!workspaceId) { - logger.warn(`[${requestId}] Workflow ${workflowId} has no workspaceId; execution blocked`) + logger.warn(`Workflow ${workflowId} has no workspaceId; execution blocked`, { requestId }) return { success: false, error: { @@ -560,7 +562,7 @@ export async function preprocessExecution( /** Undeployed workflows are rejected without creating an execution or cost log. */ if (checkDeployment && !workflowRecord.isDeployed) { - logger.warn(`[${requestId}] Workflow not deployed: ${workflowId}`) + logger.warn(`Workflow not deployed: ${workflowId}`, { requestId }) return { success: false, @@ -589,7 +591,8 @@ export async function preprocessExecution( } actorUserId = validatedAttribution.actorUserId billingAttribution = validatedAttribution - logger.debug(`[${requestId}] Reusing serialized billing attribution`, { + logger.debug('Reusing serialized billing attribution', { + requestId, actorUserId, billingEntity: billingAttribution.billingEntity, }) @@ -597,13 +600,14 @@ export async function preprocessExecution( if (!actorUserId && useAuthenticatedUserAsActor && userId) { actorUserId = userId - logger.debug(`[${requestId}] Using authenticated user as actor: ${actorUserId}`) + logger.debug(`Using authenticated user as actor: ${actorUserId}`, { requestId }) } if (!actorUserId) { billingAttribution = await admission.attributionFor(workspaceId) actorUserId = billingAttribution.actorUserId - logger.debug(`[${requestId}] Using atomically resolved system actor and payer`, { + logger.debug('Using atomically resolved system actor and payer', { + requestId, actorUserId, billingEntity: billingAttribution.billingEntity, }) @@ -611,7 +615,8 @@ export async function preprocessExecution( if (!actorUserId) { const errorLogUserId = userId || 'unknown' - logger.warn(`[${requestId}] ${BILLING_ERROR_MESSAGES.BILLING_REQUIRED}`, { + logger.warn(BILLING_ERROR_MESSAGES.BILLING_REQUIRED, { + requestId, workflowId, workspaceId, }) @@ -641,7 +646,7 @@ export async function preprocessExecution( billingAttribution = await admission.attributionFor(workspaceId) } } catch (error) { - logger.error(`[${requestId}] Error resolving billing attribution`, { error, workflowId }) + logger.error('Error resolving billing attribution', { requestId, error, workflowId }) const errorLogUserId = userId || 'unknown' const failure: PreprocessExecutionError = { message: 'Error resolving billing account', @@ -728,7 +733,7 @@ export async function preprocessExecution( } return null } catch (error) { - logger.error(`[${requestId}] Error checking account ban status`, { error, actorUserId }) + logger.error('Error checking account ban status', { requestId, error, actorUserId }) return { response: { @@ -825,10 +830,7 @@ export async function preprocessExecution( } return { failure: null, snapshot } } catch (error) { - logger.error(`[${requestId}] Error checking usage limits`, { - error, - actorUserId, - }) + logger.error('Error checking usage limits', { requestId, error, actorUserId }) return { failure: { @@ -919,7 +921,7 @@ export async function preprocessExecution( } return null } catch (error) { - logger.error(`[${requestId}] Error checking rate limits`, { error, actorUserId }) + logger.error('Error checking rate limits', { requestId, error, actorUserId }) return { response: { @@ -1052,7 +1054,8 @@ export async function preprocessExecution( return { success: false, error: failure } } } catch (error) { - logger.error(`[${requestId}] Admission reservation infrastructure unavailable`, { + logger.error('Admission reservation infrastructure unavailable', { + requestId, error, actorUserId, }) @@ -1079,7 +1082,8 @@ export async function preprocessExecution( } } - logger.debug(`[${requestId}] All preprocessing checks passed`, { + logger.debug('All preprocessing checks passed', { + requestId, workflowId, actorUserId, triggerType, @@ -1144,7 +1148,8 @@ async function logPreprocessingError(params: { } = params if (!workspaceId) { - logger.warn(`[${requestId}] Cannot log preprocessing error: no workspaceId available`, { + logger.warn('Cannot log preprocessing error: no workspaceId available', { + requestId, workflowId, executionId, errorMessage, @@ -1172,10 +1177,6 @@ async function logPreprocessingError(params: { skipCost: true, }) } catch (error) { - logger.error(`[${requestId}] Failed to log preprocessing error`, { - error, - workflowId, - executionId, - }) + logger.error('Failed to log preprocessing error', { requestId, error, workflowId, executionId }) } } diff --git a/apps/sim/lib/file-retention/legacy-archive.ts b/apps/sim/lib/file-retention/legacy-archive.ts index 28e1b5afa7a..d90316427f4 100644 --- a/apps/sim/lib/file-retention/legacy-archive.ts +++ b/apps/sim/lib/file-retention/legacy-archive.ts @@ -192,7 +192,7 @@ async function deleteExpiredLegacyWorkspaceFileRows( result.failed += batch.length - deleted.length } catch (error) { result.failed += batch.length - logger.error(`[${label}/workspaceFile] Exact-row delete failed`, { error }) + logger.error('Exact-row delete failed', { label: `${label}/workspaceFile`, error }) } } return result @@ -233,7 +233,10 @@ async function deleteExpiredUnbilledWorkspaceFileRows( result.failed += batch.length - deleted.length } catch (error) { result.failed += batch.length - logger.error(`[${label}/workspaceFiles] Exact-row ${context} delete failed`, { error }) + logger.error(`Exact-row ${context} delete failed`, { + label: `${label}/workspaceFiles`, + error, + }) } } } @@ -251,7 +254,8 @@ async function deleteExpiredBillableWorkspaceFileRows( if (!BILLED_FILE_CONTEXTS.includes(row.context)) continue if (!row.workspaceId) { result.failed++ - logger.error(`[${label}/workspaceFiles] Billable row has no workspace attribution`, { + logger.error('Billable row has no workspace attribution', { + label: `${label}/workspaceFiles`, fileId: row.id, }) continue @@ -333,7 +337,8 @@ async function deleteExpiredBillableWorkspaceFileRows( result.failed += batch.length - deletedCount } catch (error) { result.failed += batch.length - logger.error(`[${label}/workspaceFiles] Atomic delete and decrement failed`, { + logger.error('Atomic delete and decrement failed', { + label: `${label}/workspaceFiles`, error, workspaceId, }) diff --git a/apps/sim/lib/function-execution/execute-request.ts b/apps/sim/lib/function-execution/execute-request.ts index 7b50c729753..228060601f3 100644 --- a/apps/sim/lib/function-execution/execute-request.ts +++ b/apps/sim/lib/function-execution/execute-request.ts @@ -2467,7 +2467,8 @@ export async function executeFunctionRequest( const executionParams = { ...params } executionParams._context = undefined - logger.debug(`[${requestId}] Function execution request`, { + logger.debug('Function execution request', { + requestId, hasCode: !!code, paramsCount: Object.keys(executionParams).length, timeout, @@ -2724,7 +2725,8 @@ export async function executeFunctionRequest( // messages already say which file and what to do, so they are the response // rather than a 500 that reads like the platform broke. Matches the // too-many-files refusal above. - logger.warn(`[${requestId}] Could not resolve sandbox file mounts`, { + logger.warn('Could not resolve sandbox file mounts', { + requestId, error: getErrorMessage(error), }) return functionJsonResponse( @@ -2800,9 +2802,7 @@ export async function executeFunctionRequest( : undefined if (mountManifest.length > 0) { - logger.info(`[${requestId}] Mounted files into sandbox`, { - mountCount: mountManifest.length, - }) + logger.info('Mounted files into sandbox', { requestId, mountCount: mountManifest.length }) } if (lang === CodeLanguage.Shell) { @@ -2814,7 +2814,8 @@ export async function executeFunctionRequest( shellEnvs[k] = serializeForShellEnv(v, 'null') } - logger.info(`[${requestId}] E2B shell execution`, { + logger.info('E2B shell execution', { + requestId, enabled: remoteSandboxEnabled, hasApiKey: Boolean(process.env.E2B_API_KEY), envVarCount: Object.keys(shellEnvs).length, @@ -2852,7 +2853,8 @@ export async function executeFunctionRequest( }) const executionTime = Date.now() - execStart - logger.info(`[${requestId}] E2B shell sandbox`, { + logger.info('E2B shell sandbox', { + requestId, sandboxId, succeeded: !shellError, executionTime, @@ -2931,7 +2933,8 @@ export async function executeFunctionRequest( } if (useRemoteSandbox) { - logger.info(`[${requestId}] E2B status`, { + logger.info('E2B status', { + requestId, enabled: remoteSandboxEnabled, hasApiKey: Boolean(process.env.E2B_API_KEY), language: lang, @@ -3011,7 +3014,8 @@ export async function executeFunctionRequest( const executionTime = Date.now() - execStart stdout += e2bStdout - logger.info(`[${requestId}] E2B JS sandbox`, { + logger.info('E2B JS sandbox', { + requestId, sandboxId, succeeded: !e2bError, executionTime, @@ -3143,7 +3147,8 @@ export async function executeFunctionRequest( const executionTime = Date.now() - execStart stdout += e2bStdout - logger.info(`[${requestId}] E2B Py sandbox`, { + logger.info('E2B Py sandbox', { + requestId, sandboxId, succeeded: !e2bError, executionTime, @@ -3315,7 +3320,8 @@ export async function executeFunctionRequest( /** The author's code throwing is logged once by the tool boundary; an isolate failure is ours. */ if (isSystemError) { - logger.error(`[${requestId}] Function execution failed in isolated-vm`, { + logger.error('Function execution failed in isolated-vm', { + requestId, executionTime, hasStack: Boolean(ivmError.stack), line: enhancedError.line, @@ -3345,7 +3351,8 @@ export async function executeFunctionRequest( ) } - logger.debug(`[${requestId}] Function executed successfully using ${executionMethod}`, { + logger.debug(`Function executed successfully using ${executionMethod}`, { + requestId, executionTime, }) @@ -3384,7 +3391,8 @@ export async function executeFunctionRequest( executionTime, }, } - logger.warn(`[${requestId}] Function execution ${timedOut ? 'timed out' : 'was cancelled'}`, { + logger.warn(`Function execution ${timedOut ? 'timed out' : 'was cancelled'}`, { + requestId, executionTime, }) return routeContext @@ -3440,7 +3448,8 @@ export async function executeFunctionRequest( ) } if (isExecutionResourceLimitError(error)) { - logger.warn(`[${requestId}] Function execution exceeded resource limits`, { + logger.warn('Function execution exceeded resource limits', { + requestId, resource: error.resource, attemptedBytes: error.attemptedBytes, limitBytes: error.limitBytes, @@ -3501,7 +3510,8 @@ export async function executeFunctionRequest( (error?.message || String(error)).slice(0, 300), compilerInternalIdentifiers ) - logger.warn(`[${requestId}] Sandbox terminated before completion (likely OOM or timeout)`, { + logger.warn('Sandbox terminated before completion (likely OOM or timeout)', { + requestId, executionTime, }) const killResponse = { @@ -3527,7 +3537,8 @@ export async function executeFunctionRequest( compilerInternalIdentifiers ) - logger.error(`[${requestId}] Function execution failed`, { + logger.error('Function execution failed', { + requestId, executionTime, hasStack: Boolean(error.stack), line: enhancedError.line, diff --git a/apps/sim/lib/guardrails/validate-hallucination.ts b/apps/sim/lib/guardrails/validate-hallucination.ts index cf21d8d8648..d87d1387070 100644 --- a/apps/sim/lib/guardrails/validate-hallucination.ts +++ b/apps/sim/lib/guardrails/validate-hallucination.ts @@ -101,9 +101,7 @@ async function queryKnowledgeBase( } } catch (error) { const message = getErrorMessage(error, 'Unknown Knowledge query error') - logger.error(`[${requestId}] Error querying knowledge base`, { - error: message, - }) + logger.error('Error querying knowledge base', { requestId, error: message }) throw new Error(`Failed to query knowledge base: ${message}`, { cause: error }) } } @@ -153,7 +151,8 @@ ${userInput} Evaluate the consistency and provide your score and reasoning in JSON format.` - logger.info(`[${requestId}] Calling LLM for hallucination scoring`, { + logger.info('Calling LLM for hallucination scoring', { + requestId, model, contextChunks: ragContext.length, }) @@ -228,9 +227,7 @@ Evaluate the consistency and provide your score and reasoning in JSON format.` throw new Error('Invalid score format from LLM') } - logger.info(`[${requestId}] Confidence score: ${result.score}/10`, { - reasoning: result.reasoning, - }) + logger.info(`Confidence score: ${result.score}/10`, { requestId, reasoning: result.reasoning }) return { score: result.score, @@ -243,9 +240,7 @@ Evaluate the consistency and provide your score and reasoning in JSON format.` * `AbortError` name the outer handler classifies on, so it propagates as-is. */ if (isAbortError(error)) throw error - logger.error(`[${requestId}] Error scoring with LLM`, { - error: getErrorMessage(error), - }) + logger.error('Error scoring with LLM', { requestId, error: getErrorMessage(error) }) throw new Error(`Failed to score confidence: ${getErrorMessage(error)}`) } } @@ -351,10 +346,7 @@ export async function validateHallucination( abortSignal ) - logger.info(`[${requestId}] Confidence score: ${score}`, { - reasoning, - threshold, - }) + logger.info(`Confidence score: ${score}`, { requestId, reasoning, threshold }) // Step 3: Check against threshold. Lower scores = less confidence = fail validation const passed = score >= threshold @@ -375,9 +367,7 @@ export async function validateHallucination( * indistinguishable to a consumer from the model actually hallucinating. */ if (isAbortError(error)) throw error - logger.error(`[${requestId}] Hallucination validation error`, { - error: getErrorMessage(error), - }) + logger.error('Hallucination validation error', { requestId, error: getErrorMessage(error) }) return { passed: false, error: `Validation error: ${getErrorMessage(error)}`, diff --git a/apps/sim/lib/guardrails/validate-pii.ts b/apps/sim/lib/guardrails/validate-pii.ts index d2d0cc25472..962870c61e6 100644 --- a/apps/sim/lib/guardrails/validate-pii.ts +++ b/apps/sim/lib/guardrails/validate-pii.ts @@ -425,7 +425,8 @@ async function anonymize( export async function validatePII(input: PIIValidationInput): Promise { const { text, entityTypes, mode, language = 'en', customPatterns, requestId, abortSignal } = input - logger.info(`[${requestId}] Starting PII validation`, { + logger.info('Starting PII validation', { + requestId, textLength: text.length, entityTypes, mode, @@ -448,7 +449,7 @@ export async function validatePII(input: PIIValidationInput): Promise `${count} ${type}`) .join(', ') - logger.info(`[${requestId}] PII validation completed`, { + logger.info('PII validation completed', { + requestId, passed: false, detectedCount: detectedEntities.length, }) @@ -477,7 +479,8 @@ export async function validatePII(input: PIIValidationInput): Promise { } const notReady = docNotReadyResponse(error) if (notReady) return notReady - logger.error(`[${request.requestId}] A2A operation failed`, { + logger.error('A2A operation failed', { + requestId: request.requestId, error: getErrorMessage(error), toolId: request.toolId, }) diff --git a/apps/sim/lib/internal/a2a/operations.ts b/apps/sim/lib/internal/a2a/operations.ts index df31b497e71..1396e465b7b 100644 --- a/apps/sim/lib/internal/a2a/operations.ts +++ b/apps/sim/lib/internal/a2a/operations.ts @@ -124,12 +124,14 @@ export async function sendA2AMessage(input: A2ASendMessageInput, context: A2AOpe context.signal?.throwIfAborted() if (!isTaskResult(result)) { - logger.info(`[${context.requestId}] A2A send returned a direct message`) + logger.info('A2A send returned a direct message', { requestId: context.requestId }) return { success: true as const, output: messageOutput(result) } } const output = taskOutput(result) const errored = taskErrored(result) - logger.info(`[${context.requestId}] A2A send produced task ${result.id} (${output.state})`) + logger.info(`A2A send produced task ${result.id} (${output.state})`, { + requestId: context.requestId, + }) return { success: !errored, ...(errored ? { error: output.content || `Agent task ${output.state}` } : {}), @@ -145,7 +147,7 @@ export async function getA2ATask(input: A2AGetTaskInput, context: A2AOperationCo historyLength: input.historyLength, }) context.signal?.throwIfAborted() - logger.info(`[${context.requestId}] Retrieved A2A task ${task.id}`) + logger.info(`Retrieved A2A task ${task.id}`, { requestId: context.requestId }) return { success: true as const, output: taskOutput(task) } } @@ -154,7 +156,7 @@ export async function cancelA2ATask(input: A2ACancelTaskInput, context: A2AOpera const task = await client.cancelTask({ tenant: '', id: input.taskId, metadata: undefined }) context.signal?.throwIfAborted() const output = taskOutput(task) - logger.info(`[${context.requestId}] Cancel requested for A2A task ${task.id}`) + logger.info(`Cancel requested for A2A task ${task.id}`, { requestId: context.requestId }) return { success: true as const, output: { taskId: output.taskId, state: output.state, canceled: output.state === 'canceled' }, @@ -165,6 +167,6 @@ export async function getA2AAgentCard(input: A2AGetAgentCardInput, context: A2AO const client = await createA2AClient(input.agentUrl, input.apiKey, { signal: context.signal }) const card = await client.getAgentCard() context.signal?.throwIfAborted() - logger.info(`[${context.requestId}] Fetched agent card for ${card.name}`) + logger.info(`Fetched agent card for ${card.name}`, { requestId: context.requestId }) return { success: true as const, output: agentCardOutput(card, input.agentUrl) } } diff --git a/apps/sim/lib/internal/ashby/operations.ts b/apps/sim/lib/internal/ashby/operations.ts index 125f8289097..b0547b52309 100644 --- a/apps/sim/lib/internal/ashby/operations.ts +++ b/apps/sim/lib/internal/ashby/operations.ts @@ -161,7 +161,8 @@ export async function executeAshbyUpload( context.signal?.throwIfAborted() const notReady = docNotReadyResponse(error) if (notReady) return notReady - logger.error(`[${context.requestId}] Ashby candidate file upload failed`, { + logger.error('Ashby candidate file upload failed', { + requestId: context.requestId, error: getErrorMessage(error), }) return failure( diff --git a/apps/sim/lib/internal/buffer/operations.ts b/apps/sim/lib/internal/buffer/operations.ts index f4324b5bee4..8c9aac638cf 100644 --- a/apps/sim/lib/internal/buffer/operations.ts +++ b/apps/sim/lib/internal/buffer/operations.ts @@ -92,7 +92,8 @@ async function resolveMediaKind(args: { } } catch (error) { context.signal?.throwIfAborted() - logger.warn(`[${context.requestId}] Media content-type probe was inconclusive`, { + logger.warn('Media content-type probe was inconclusive', { + requestId: context.requestId, error: getErrorMessage(error, 'probe failed'), }) } @@ -171,7 +172,7 @@ async function executePostMutation(args: { } catch (error) { context.signal?.throwIfAborted() const message = getErrorMessage(error, 'Buffer API request failed') - logger.error(`[${context.requestId}] Buffer post mutation failed`, { error: message }) + logger.error('Buffer post mutation failed', { requestId: context.requestId, error: message }) throw new BufferOperationError(message, 502) } diff --git a/apps/sim/lib/internal/cursor/operations.ts b/apps/sim/lib/internal/cursor/operations.ts index 4ac9ba2cc81..132cb3bdbd1 100644 --- a/apps/sim/lib/internal/cursor/operations.ts +++ b/apps/sim/lib/internal/cursor/operations.ts @@ -111,7 +111,8 @@ export async function downloadCursorArtifact( mimeType: downloadResponse.headers.get('content-type') || 'application/octet-stream', buffer: fileBuffer, } - logger.info(`[${context.requestId}] Cursor artifact downloaded`, { + logger.info('Cursor artifact downloaded', { + requestId: context.requestId, agentId: input.agentId, path: input.path, size: fileBuffer.length, diff --git a/apps/sim/lib/internal/firecrawl/operations.ts b/apps/sim/lib/internal/firecrawl/operations.ts index 55a8a21efe4..3b4faa306e0 100644 --- a/apps/sim/lib/internal/firecrawl/operations.ts +++ b/apps/sim/lib/internal/firecrawl/operations.ts @@ -121,7 +121,8 @@ export async function executeFirecrawlParse( context.signal?.throwIfAborted() const notReady = docNotReadyResponse(error) if (notReady) return notReady - logger.error(`[${context.requestId}] Firecrawl parse failed`, { + logger.error('Firecrawl parse failed', { + requestId: context.requestId, error: getErrorMessage(error), }) return failureResponse( diff --git a/apps/sim/lib/internal/fireflies/operations.ts b/apps/sim/lib/internal/fireflies/operations.ts index 043a3c14a65..8b0dff6a229 100644 --- a/apps/sim/lib/internal/fireflies/operations.ts +++ b/apps/sim/lib/internal/fireflies/operations.ts @@ -111,13 +111,15 @@ export async function executeFirefliesUploadAudio( signal: context.signal, }) - logger.info(`[${context.requestId}] Fireflies upload request completed`, { + logger.info('Fireflies upload request completed', { + requestId: context.requestId, status: response.status, }) return Response.json(data, { status: response.status }) } catch (error) { if (context.signal?.aborted) throw error - logger.error(`[${context.requestId}] Fireflies upload failed`, { + logger.error('Fireflies upload failed', { + requestId: context.requestId, error: getErrorMessage(error, 'Unknown error'), }) return errorResponse('Failed to upload audio', 500) diff --git a/apps/sim/lib/internal/guardrails/execute-tool.ts b/apps/sim/lib/internal/guardrails/execute-tool.ts index 17fd2dfabe2..2d5c2114386 100644 --- a/apps/sim/lib/internal/guardrails/execute-tool.ts +++ b/apps/sim/lib/internal/guardrails/execute-tool.ts @@ -12,7 +12,7 @@ const logger = createLogger('GuardrailsToolExecution') function unexpectedFailure(error: unknown, requestId: string): Response { const message = getErrorMessage(error, 'Validation failed due to unexpected error') - logger.error(`[${requestId}] Guardrails validation failed`, { error: message }) + logger.error('Guardrails validation failed', { requestId, error: message }) return Response.json({ success: true, output: { diff --git a/apps/sim/lib/internal/guardrails/operations.ts b/apps/sim/lib/internal/guardrails/operations.ts index 64606b99e75..dc3b1c3a70b 100644 --- a/apps/sim/lib/internal/guardrails/operations.ts +++ b/apps/sim/lib/internal/guardrails/operations.ts @@ -104,7 +104,8 @@ async function authorizeVertexCredential( callerUserId: context.actorUserId, }) if (!access.ok) { - logger.warn(`[${context.requestId}] Vertex credential access denied`, { + logger.warn('Vertex credential access denied', { + requestId: context.requestId, error: access.error, credentialId: input.vertexCredential, }) @@ -146,7 +147,7 @@ async function prepareHallucinationContext( workspaceId, }) } catch (error) { - logger.error(`[${context.requestId}] Failed to establish billing attribution`, { error }) + logger.error('Failed to establish billing attribution', { requestId: context.requestId, error }) fail(400, 'Invalid billing attribution') } @@ -299,7 +300,7 @@ async function recordHallucinationUsage( }) await checkAndBillPayerOverageThreshold(hallucinationContext.billingAttribution.billingEntity) } catch (error) { - logger.error(`[${context.requestId}] Failed to record guardrail usage`, { error }) + logger.error('Failed to record guardrail usage', { requestId: context.requestId, error }) } } diff --git a/apps/sim/lib/internal/image/operations.ts b/apps/sim/lib/internal/image/operations.ts index 4f3ba6884ce..bc505761e00 100644 --- a/apps/sim/lib/internal/image/operations.ts +++ b/apps/sim/lib/internal/image/operations.ts @@ -81,7 +81,7 @@ export async function executeImageGeneration( ): Promise { const { requestId } = context let undeliveredFalCost: FalAICostMetadata | undefined - logger.info(`[${requestId}] Image generation request started`) + logger.info('Image generation request started', { requestId }) try { context.signal?.throwIfAborted() @@ -95,7 +95,7 @@ export async function executeImageGeneration( return Response.json({ error: getErrorMessage(error) }, { status: 400 }) } - logger.info(`[${requestId}] Generating image with ${provider}, model: ${model || 'default'}`) + logger.info(`Generating image with ${provider}, model: ${model || 'default'}`, { requestId }) let imageResult: GeneratedImageResult if (provider === 'openai') { @@ -120,7 +120,8 @@ export async function executeImageGeneration( const storedImage = await storeGeneratedImage(imageResult, context) - logger.info(`[${requestId}] Image generation completed successfully`, { + logger.info('Image generation completed successfully', { + requestId, provider, model: storedImage.model, contentType: storedImage.contentType, @@ -144,7 +145,7 @@ export async function executeImageGeneration( }) } context.signal?.throwIfAborted() - logger.error(`[${requestId}] Image generation operation error:`, error) + logger.error('Image generation operation error:', error, { requestId }) const errorMessage = getUserFacingErrorMessage(error, 'Unknown error') return Response.json( { error: errorMessage }, @@ -291,7 +292,7 @@ async function generateWithOpenAI( buffer = downloaded.buffer contentType = downloaded.contentType } else { - logger.error(`[${requestId}] OpenAI response missing image payload`) + logger.error('OpenAI response missing image payload', { requestId }) throw new Error('No image data found in OpenAI response') } @@ -353,7 +354,7 @@ async function generateWithGemini( }) if (!isRecordLike(imagePart)) { - logger.error(`[${requestId}] Gemini response missing image part`) + logger.error('Gemini response missing image part', { requestId }) throw new Error('No image data found in Gemini response') } @@ -464,7 +465,7 @@ async function generateWithFalAI( buildFalAIQueueUrl(endpoint, falRequestId, 'response') ) - logger.info(`[${requestId}] Fal.ai image request created: ${falRequestId}`) + logger.info('Fal.ai image request created', { requestId, falRequestId }) const statusValidation = await validateUrlWithDNS(statusUrl, 'statusUrl', 'contentFetch') if (!statusValidation.isValid) { diff --git a/apps/sim/lib/internal/jupyter/execute-tool.ts b/apps/sim/lib/internal/jupyter/execute-tool.ts index d732610cb30..2d29edaaa9b 100644 --- a/apps/sim/lib/internal/jupyter/execute-tool.ts +++ b/apps/sim/lib/internal/jupyter/execute-tool.ts @@ -55,7 +55,7 @@ function unexpectedErrorResponse( ): Response { signal?.throwIfAborted() const logger = scope === 'proxy' ? proxyLogger : uploadLogger - logger.error(`[${requestId}] Unexpected error:`, error) + logger.error('Unexpected error:', error, { requestId }) return Response.json( { success: false, error: getErrorMessage(error, 'Unknown error') }, { status: 500 } diff --git a/apps/sim/lib/internal/jupyter/operations.ts b/apps/sim/lib/internal/jupyter/operations.ts index 9fb6791e083..c3875ce525c 100644 --- a/apps/sim/lib/internal/jupyter/operations.ts +++ b/apps/sim/lib/internal/jupyter/operations.ts @@ -230,10 +230,7 @@ export async function executeJupyterUpload( if (!response.ok) { const errorText = await response.text() signal?.throwIfAborted() - uploadLogger.error(`[${requestId}] Jupyter API error:`, { - status: response.status, - errorText, - }) + uploadLogger.error('Jupyter API error:', { requestId, status: response.status, errorText }) return Response.json( { success: false, error: `Jupyter API error: ${response.status} ${errorText}` }, { status: response.status } @@ -244,7 +241,7 @@ export async function executeJupyterUpload( signal?.throwIfAborted() const uploaded = parseJupyterContentModel(uploadedValue) if (!uploaded) { - uploadLogger.error(`[${requestId}] Jupyter returned an invalid upload response`) + uploadLogger.error('Jupyter returned an invalid upload response', { requestId }) return Response.json( { success: false, error: 'Jupyter returned an invalid upload response' }, { status: 502 } @@ -256,7 +253,7 @@ export async function executeJupyterUpload( const uploadedSize = uploaded.size ?? file.buffer.length const lastModified = uploaded.lastModified ?? null - uploadLogger.info(`[${requestId}] File uploaded to Jupyter: ${uploadedPath}`) + uploadLogger.info(`File uploaded to Jupyter: ${uploadedPath}`, { requestId }) return Response.json({ success: true, output: { diff --git a/apps/sim/lib/internal/knowledge/execute-tool.ts b/apps/sim/lib/internal/knowledge/execute-tool.ts index 535a0e2beb1..2a2e3a6170c 100644 --- a/apps/sim/lib/internal/knowledge/execute-tool.ts +++ b/apps/sim/lib/internal/knowledge/execute-tool.ts @@ -94,7 +94,7 @@ function projectError( signal?.throwIfAborted() const projected = policy.project(error) if (projected && projected.status < 500) return descriptorResponse(projected) - logger.error(`[${requestId}] Knowledge tool execution failed`, { error }) + logger.error('Knowledge tool execution failed', { requestId, error }) if (projected) return descriptorResponse(projected) return descriptorResponse( policy.unhandled?.() ?? { status: 500, body: { error: 'Internal server error' } } diff --git a/apps/sim/lib/internal/llm/credentials.ts b/apps/sim/lib/internal/llm/credentials.ts index 2dbd6407e9a..4c450190600 100644 --- a/apps/sim/lib/internal/llm/credentials.ts +++ b/apps/sim/lib/internal/llm/credentials.ts @@ -15,7 +15,7 @@ export async function resolveVertexAccessToken( requestId: string, credentialId: string ): Promise { - logger.info(`[${requestId}] Resolving Vertex AI credential`, { credentialId }) + logger.info('Resolving Vertex AI credential', { requestId, credentialId }) const resolved = await resolveOAuthAccountId(credentialId) if (!resolved) throw new Error(`Vertex AI credential not found: ${credentialId}`) @@ -24,7 +24,7 @@ export async function resolveVertexAccessToken( const accessToken = await getServiceAccountToken(resolved.credentialId, [ 'https://www.googleapis.com/auth/cloud-platform', ]) - logger.info(`[${requestId}] Resolved Vertex AI service account credential`) + logger.info('Resolved Vertex AI service account credential', { requestId }) return accessToken } @@ -36,6 +36,6 @@ export async function resolveVertexAccessToken( const { accessToken } = await refreshTokenIfNeeded(requestId, credential, resolved.accountId) if (!accessToken) throw new Error('Failed to get Vertex AI access token') - logger.info(`[${requestId}] Resolved Vertex AI credential`) + logger.info('Resolved Vertex AI credential', { requestId }) return accessToken } diff --git a/apps/sim/lib/internal/llm/execute-tool.ts b/apps/sim/lib/internal/llm/execute-tool.ts index 4f42bfad539..17307d0c79b 100644 --- a/apps/sim/lib/internal/llm/execute-tool.ts +++ b/apps/sim/lib/internal/llm/execute-tool.ts @@ -67,7 +67,7 @@ export const executeLlmTool: InternalToolOperationHandler = async (request) => { return Response.json(error.body, { status: error.status }) } const message = getErrorMessage(error) - logger.error(`[${request.requestId}] LLM chat operation failed`, { error: message }) + logger.error('LLM chat operation failed', { requestId: request.requestId, error: message }) return Response.json({ error: message }, { status: 500 }) } } diff --git a/apps/sim/lib/internal/llm/operations.ts b/apps/sim/lib/internal/llm/operations.ts index c0ee22c836a..c34f1e2ec1f 100644 --- a/apps/sim/lib/internal/llm/operations.ts +++ b/apps/sim/lib/internal/llm/operations.ts @@ -63,7 +63,8 @@ async function authorizeVertexCredential( callerUserId: context.actorUserId, }) if (!access.ok) { - logger.warn(`[${context.requestId}] Vertex credential access denied`, { + logger.warn('Vertex credential access denied', { + requestId: context.requestId, error: access.error, credentialId: input.vertexCredential, }) @@ -130,7 +131,8 @@ async function prepareProviderRequest( } } catch (error) { if (error instanceof LlmOperationError) throw error - logger.error(`[${context.requestId}] Failed to resolve Vertex credential`, { + logger.error('Failed to resolve Vertex credential', { + requestId: context.requestId, provider: input.provider, model: input.model, error: toError(error).message, diff --git a/apps/sim/lib/internal/sap-concur/client.ts b/apps/sim/lib/internal/sap-concur/client.ts index 8a67bb75eab..9d3e6959a5e 100644 --- a/apps/sim/lib/internal/sap-concur/client.ts +++ b/apps/sim/lib/internal/sap-concur/client.ts @@ -329,10 +329,11 @@ async function requestAccessToken( const text = redactTokenSecrets(await response.text().catch(() => ''), auth) signal?.throwIfAborted() logger.warn( - `[${requestId}] Concur token fetch failed (${response.status}): ${truncate( + `Concur token fetch failed (${response.status}): ${truncate( text, TOKEN_ERROR_RAW_BODY_MAX_LENGTH - )}` + )}`, + { requestId } ) throw new Error( `Concur token request failed: ${extractSapConcurError(parseMaybeJson(text), response.status, { diff --git a/apps/sim/lib/internal/sap-s4hana/operations.ts b/apps/sim/lib/internal/sap-s4hana/operations.ts index 3b00205095d..1c93e7b1cf1 100644 --- a/apps/sim/lib/internal/sap-s4hana/operations.ts +++ b/apps/sim/lib/internal/sap-s4hana/operations.ts @@ -100,7 +100,7 @@ export async function executeSapS4HanaOperation( let invocation = await callSapOdata(input, accessToken, csrf, signal) if (isWrite && isCsrfRequired(invocation)) { - logger.info(`[${requestId}] CSRF token rejected, refetching once`) + logger.info('CSRF token rejected, refetching once', { requestId }) const refreshed = await fetchSapCsrf(input, accessToken, signal) if (refreshed) { invocation = await callSapOdata(input, accessToken, refreshed, signal) @@ -116,8 +116,8 @@ export async function executeSapS4HanaOperation( } const message = extractOdataError(invocation.body, invocation.status) - logger.warn( - `[${requestId}] SAP API error (${invocation.status}) ${input.service}${input.path}: ${message}` - ) + logger.warn(`SAP API error (${invocation.status}) ${input.service}${input.path}: ${message}`, { + requestId, + }) throw new SapS4HanaProviderError(message, invocation.status) } diff --git a/apps/sim/lib/internal/slack/operations.ts b/apps/sim/lib/internal/slack/operations.ts index 8328b54301d..a275e8da08e 100644 --- a/apps/sim/lib/internal/slack/operations.ts +++ b/apps/sim/lib/internal/slack/operations.ts @@ -320,7 +320,8 @@ async function uploadSlackFiles( const uploadUrl = slackString(data, 'upload_url') const fileId = slackString(data, 'file_id') if (!slackOk(data) || !uploadUrl || !fileId) { - logger.error(`[${context.requestId}] Failed to get Slack upload URL`, { + logger.error('Failed to get Slack upload URL', { + requestId: context.requestId, error: slackString(data, 'error'), }) return @@ -338,7 +339,8 @@ async function uploadSlackFiles( ) context.signal?.throwIfAborted() if (!uploaded.ok) { - logger.error(`[${context.requestId}] Failed to upload Slack file data`, { + logger.error('Failed to upload Slack file data', { + requestId: context.requestId, status: uploaded.status, }) return diff --git a/apps/sim/lib/internal/square/operations.ts b/apps/sim/lib/internal/square/operations.ts index 14dcd670971..13439d0830d 100644 --- a/apps/sim/lib/internal/square/operations.ts +++ b/apps/sim/lib/internal/square/operations.ts @@ -119,7 +119,8 @@ export async function executeSquareCreateCatalogImage( }) } catch (error) { context.signal?.throwIfAborted() - logger.error(`[${context.requestId}] Square catalog image upload failed`, { + logger.error('Square catalog image upload failed', { + requestId: context.requestId, error: getErrorMessage(error), }) return failureResponse(getErrorMessage(error, 'Unknown error'), 500) diff --git a/apps/sim/lib/internal/stt/execute-tool.ts b/apps/sim/lib/internal/stt/execute-tool.ts index 1648acb5c18..f45779fad76 100644 --- a/apps/sim/lib/internal/stt/execute-tool.ts +++ b/apps/sim/lib/internal/stt/execute-tool.ts @@ -49,7 +49,10 @@ export const executeSttTool: InternalToolOperationHandler = async (request) => { const parsed = sttOperationInputSchema.safeParse(request.input) if (!parsed.success) { - logger.warn(`[${request.requestId}] Invalid STT request`, { issues: parsed.error.issues }) + logger.warn('Invalid STT request', { + requestId: request.requestId, + issues: parsed.error.issues, + }) return Response.json( { error: 'Invalid request data', details: parsed.error.issues }, { status: 400 } diff --git a/apps/sim/lib/internal/stt/operations.ts b/apps/sim/lib/internal/stt/operations.ts index e9ffb148a10..a68c15750ea 100644 --- a/apps/sim/lib/internal/stt/operations.ts +++ b/apps/sim/lib/internal/stt/operations.ts @@ -143,7 +143,7 @@ export async function executeSttOperation( ): Promise { const { headers, requestId, signal, userId } = context signal?.throwIfAborted() - logger.info(`[${requestId}] STT transcription request started`) + logger.info('STT transcription request started', { requestId }) try { const modelInputProvenance = validateOpaqueModelInputProvenance({ @@ -181,7 +181,7 @@ export async function executeSttOperation( return Response.json({ error: 'audioFile must be a single file' }, { status: 400 }) } const file = Array.isArray(body.audioFile) ? body.audioFile[0] : body.audioFile - logger.info(`[${requestId}] Processing uploaded audio`) + logger.info('Processing uploaded audio', { requestId }) const deniedAudio = await assertToolFileAccess(file.key, userId, requestId, logger) if (deniedAudio) return deniedAudio @@ -202,7 +202,7 @@ export async function executeSttOperation( const file = Array.isArray(body.audioFileReference) ? body.audioFileReference[0] : body.audioFileReference - logger.info(`[${requestId}] Processing referenced audio`) + logger.info('Processing referenced audio', { requestId }) const deniedRef = await assertToolFileAccess(file.key, userId, requestId, logger) if (deniedRef) return deniedRef @@ -220,7 +220,7 @@ export async function executeSttOperation( } else if (body.audioUrl) { let audioUrl = body.audioUrl.trim() const internalAudioUrl = isInternalFileUrl(audioUrl) - logger.info(`[${requestId}] Downloading audio source`, { internal: internalAudioUrl }) + logger.info('Downloading audio source', { requestId, internal: internalAudioUrl }) if (audioUrl.startsWith('/') && !isInternalFileUrl(audioUrl)) { return Response.json( { @@ -287,7 +287,7 @@ export async function executeSttOperation( } if (isVideoFile(audioMimeType)) { - logger.info(`[${requestId}] Extracting audio from video file`) + logger.info('Extracting audio from video file', { requestId }) try { const extracted = await extractAudioFromVideo(audioBuffer, audioMimeType, { outputFormat: 'mp3', @@ -301,7 +301,7 @@ export async function executeSttOperation( audioFileName = audioFileName.replace(/\.[^.]+$/, '.mp3') } catch (error) { signal?.throwIfAborted() - logger.error(`[${requestId}] Video extraction failed:`, error) + logger.error('Video extraction failed:', error, { requestId }) if (isPayloadSizeLimitError(error)) { return Response.json( { error: 'Extracted audio exceeds the maximum supported size' }, @@ -317,7 +317,7 @@ export async function executeSttOperation( } } - logger.info(`[${requestId}] Transcribing audio`, { provider }) + logger.info('Transcribing audio', { requestId, provider }) let transcript: string let segments: TranscriptSegment[] | undefined @@ -417,12 +417,12 @@ export async function executeSttOperation( } } catch (error) { signal?.throwIfAborted() - logger.error(`[${requestId}] Transcription failed:`, error) + logger.error('Transcription failed:', error, { requestId }) const errorMessage = getErrorMessage(error, 'Transcription failed') return Response.json({ error: errorMessage }, { status: 500 }) } - logger.info(`[${requestId}] Transcription completed successfully`) + logger.info('Transcription completed successfully', { requestId }) const response: SttOutput = { transcript } if (segments !== undefined) response.segments = segments @@ -436,7 +436,7 @@ export async function executeSttOperation( return Response.json(response) } catch (error) { signal?.throwIfAborted() - logger.error(`[${requestId}] STT proxy error:`, error) + logger.error('STT proxy error:', error, { requestId }) const isSizeLimit = isPayloadSizeLimitError(error) const errorMessage = isSizeLimit ? 'Audio file exceeds the maximum supported size' diff --git a/apps/sim/lib/internal/table/execute-tool.ts b/apps/sim/lib/internal/table/execute-tool.ts index d0776a23fde..19d840dc455 100644 --- a/apps/sim/lib/internal/table/execute-tool.ts +++ b/apps/sim/lib/internal/table/execute-tool.ts @@ -113,7 +113,7 @@ function errorResponse( headers: projected.headers, }) } - logger.error(`[${requestId}] ${FAILURE_MESSAGES[toolId] ?? 'Table operation failed'}:`, error) + logger.error(`${FAILURE_MESSAGES[toolId] ?? 'Table operation failed'}:`, error, { requestId }) return Response.json( { error: FAILURE_MESSAGES[toolId] ?? 'Table operation failed' }, { status: 500 } diff --git a/apps/sim/lib/internal/textract/document-input.ts b/apps/sim/lib/internal/textract/document-input.ts index bd871fb5923..0c4cc6d32ec 100644 --- a/apps/sim/lib/internal/textract/document-input.ts +++ b/apps/sim/lib/internal/textract/document-input.ts @@ -153,10 +153,7 @@ export async function resolveDocumentInput( } } } else if (fileUrl.startsWith('/')) { - logger.warn(`[${requestId}] Invalid internal path`, { - userId, - path: fileUrl.substring(0, 50), - }) + logger.warn('Invalid internal path', { requestId, userId, path: fileUrl.substring(0, 50) }) return { ok: false, response: NextResponse.json( @@ -170,7 +167,8 @@ export async function resolveDocumentInput( } else { const urlValidation = await validateUrlWithDNS(fileUrl, 'Document URL', 'contentFetch') if (!urlValidation.isValid) { - logger.warn(`[${requestId}] SSRF attempt blocked`, { + logger.warn('SSRF attempt blocked', { + requestId, userId, url: fileUrl.substring(0, 100), error: urlValidation.error, diff --git a/apps/sim/lib/internal/textract/errors.ts b/apps/sim/lib/internal/textract/errors.ts index 44d19f7898d..b1a0e25e307 100644 --- a/apps/sim/lib/internal/textract/errors.ts +++ b/apps/sim/lib/internal/textract/errors.ts @@ -23,7 +23,7 @@ export function textractErrorResponse( const notReady = docNotReadyResponse(error) if (notReady) return notReady - logger.error(`[${requestId}] Error in Textract request:`, error) + logger.error('Error in Textract request:', error, { requestId }) const status = error instanceof TextractOperationError ? error.status : 500 return NextResponse.json( { success: false, error: getErrorMessage(error, 'Internal server error') }, diff --git a/apps/sim/lib/internal/textract/operations.ts b/apps/sim/lib/internal/textract/operations.ts index 7a021191c60..84b20566792 100644 --- a/apps/sim/lib/internal/textract/operations.ts +++ b/apps/sim/lib/internal/textract/operations.ts @@ -114,7 +114,8 @@ export async function executeTextractParse( } : undefined - parseLogger.info(`[${requestId}] Textract parse request`, { + parseLogger.info('Textract parse request', { + requestId, processingMode, hasFile: Boolean(input.file), hasS3Uri: Boolean(input.s3Uri), @@ -134,10 +135,7 @@ export async function executeTextractParse( } const { bucket, key } = parseS3Uri(input.s3Uri) - parseLogger.info(`[${requestId}] Starting async Textract job`, { - s3Bucket: bucket, - s3Key: key, - }) + parseLogger.info('Starting async Textract job', { requestId, s3Bucket: bucket, s3Key: key }) const client = createTextractClient(input) try { const { JobId: jobId } = useAnalyzeDocument @@ -156,7 +154,7 @@ export async function executeTextractParse( { abortSignal: signal } ) if (!jobId) throw new Error('Failed to start Textract job: No JobId returned') - parseLogger.info(`[${requestId}] Async job started`, { jobId }) + parseLogger.info('Async job started', { requestId, jobId }) const result = await pollTextractJob( requestId, @@ -179,7 +177,8 @@ export async function executeTextractParse( signal ) - parseLogger.info(`[${requestId}] Textract async parse successful`, { + parseLogger.info('Textract async parse successful', { + requestId, pageCount: result.DocumentMetadata?.Pages ?? 0, blockCount: result.Blocks?.length ?? 0, }) @@ -228,7 +227,8 @@ export async function executeTextractParse( throw mapTextractSdkError(error, resolved.document.isPdf) } - parseLogger.info(`[${requestId}] Textract parse successful`, { + parseLogger.info('Textract parse successful', { + requestId, pageCount: result.DocumentMetadata?.Pages ?? 0, blockCount: result.Blocks?.length ?? 0, }) @@ -259,7 +259,8 @@ export async function executeTextractAnalyzeExpense( if (provenanceError) return provenanceError const processingMode = input.processingMode || 'sync' - expenseLogger.info(`[${requestId}] Textract analyze-expense request`, { + expenseLogger.info('Textract analyze-expense request', { + requestId, processingMode, hasFile: Boolean(input.file), hasS3Uri: Boolean(input.s3Uri), @@ -277,7 +278,8 @@ export async function executeTextractAnalyzeExpense( ) } const { bucket, key } = parseS3Uri(input.s3Uri) - expenseLogger.info(`[${requestId}] Starting async Textract expense analysis job`, { + expenseLogger.info('Starting async Textract expense analysis job', { + requestId, s3Bucket: bucket, s3Key: key, }) @@ -292,7 +294,7 @@ export async function executeTextractAnalyzeExpense( if (!jobId) { throw new Error('Failed to start Textract expense analysis job: No JobId returned') } - expenseLogger.info(`[${requestId}] Async expense analysis job started`, { jobId }) + expenseLogger.info('Async expense analysis job started', { requestId, jobId }) const result = await pollTextractJob( requestId, @@ -347,7 +349,8 @@ export async function executeTextractAnalyzeExpense( throw mapTextractSdkError(error, resolved.document.isPdf) } - expenseLogger.info(`[${requestId}] Textract analyze-expense successful`, { + expenseLogger.info('Textract analyze-expense successful', { + requestId, pageCount: result.DocumentMetadata?.Pages ?? 0, expenseDocumentCount: result.ExpenseDocuments?.length ?? 0, }) @@ -376,7 +379,8 @@ export async function executeTextractAnalyzeId( const provenanceError = validateModelInputProvenance(input, headers) if (provenanceError) return provenanceError - identityLogger.info(`[${requestId}] Textract analyze-id request`, { + identityLogger.info('Textract analyze-id request', { + requestId, hasFile: Boolean(input.file), hasBackFile: Boolean(input.fileBack || input.filePathBack), userId, @@ -422,7 +426,8 @@ export async function executeTextractAnalyzeId( throw mapTextractSdkError(error, isPdf, { hasAsyncMode: false }) } - identityLogger.info(`[${requestId}] Textract analyze-id successful`, { + identityLogger.info('Textract analyze-id successful', { + requestId, pageCount: result.DocumentMetadata?.Pages ?? 0, documentCount: result.IdentityDocuments?.length ?? 0, }) diff --git a/apps/sim/lib/internal/textract/poll-job.ts b/apps/sim/lib/internal/textract/poll-job.ts index 06dcc48be84..c3c0fdee0ae 100644 --- a/apps/sim/lib/internal/textract/poll-job.ts +++ b/apps/sim/lib/internal/textract/poll-job.ts @@ -29,9 +29,9 @@ export async function pollTextractJob( if (jobStatus === 'SUCCEEDED' || jobStatus === 'PARTIAL_SUCCESS') { if (jobStatus === 'PARTIAL_SUCCESS') { - logger.warn(`[${requestId}] Job completed with partial success: ${result.StatusMessage}`) + logger.warn(`Job completed with partial success: ${result.StatusMessage}`, { requestId }) } else { - logger.info(`[${requestId}] Async job completed successfully after ${attempt + 1} polls`) + logger.info(`Async job completed successfully after ${attempt + 1} polls`, { requestId }) } let merged = result @@ -52,7 +52,7 @@ export async function pollTextractJob( ) } - logger.info(`[${requestId}] Job status: ${jobStatus}, attempt ${attempt + 1}/${maxAttempts}`) + logger.info(`Job status: ${jobStatus}, attempt ${attempt + 1}/${maxAttempts}`, { requestId }) await sleep(pollIntervalMs) signal?.throwIfAborted() } diff --git a/apps/sim/lib/internal/tiktok/operations.ts b/apps/sim/lib/internal/tiktok/operations.ts index a02fdec061a..0ecdd7eb1a1 100644 --- a/apps/sim/lib/internal/tiktok/operations.ts +++ b/apps/sim/lib/internal/tiktok/operations.ts @@ -136,7 +136,8 @@ export async function executeTikTokUploadVideoDraft( return failureResponse(`Video exceeds the ${maxMb}MB limit for file uploads.`, 413) } if (signal.aborted) return failureResponse('TikTok video upload was cancelled.', 499) - logger.error(`[${context.requestId}] TikTok video draft upload failed`, { + logger.error('TikTok video draft upload failed', { + requestId: context.requestId, error: getErrorMessage(error), }) return failureResponse(getErrorMessage(error, 'Internal server error'), 500) diff --git a/apps/sim/lib/internal/tiktok/upload.ts b/apps/sim/lib/internal/tiktok/upload.ts index 7ec6d5687fc..4fc1c72cb23 100644 --- a/apps/sim/lib/internal/tiktok/upload.ts +++ b/apps/sim/lib/internal/tiktok/upload.ts @@ -256,7 +256,8 @@ async function uploadChunk(options: { const retryable = response.status >= 500 && response.status <= 599 if (retryable && attempt < MAX_UPLOAD_ATTEMPTS) { - logger.warn(`[${options.requestId}] Retrying TikTok video chunk`, { + logger.warn('Retrying TikTok video chunk', { + requestId: options.requestId, chunkIndex: options.chunkIndex, attempt, status: response.status, diff --git a/apps/sim/lib/internal/video/client.ts b/apps/sim/lib/internal/video/client.ts index 28e4de084de..6ef27ce4565 100644 --- a/apps/sim/lib/internal/video/client.ts +++ b/apps/sim/lib/internal/video/client.ts @@ -103,7 +103,8 @@ export async function generateVideo( context: VideoGenerationContext ): Promise { context.signal?.throwIfAborted() - logger.info(`[${context.requestId}] Generating video with ${input.provider}`, { + logger.info(`Generating video with ${input.provider}`, { + requestId: context.requestId, model: input.model || 'default', }) return input.provider === 'falai' @@ -362,7 +363,7 @@ async function generateWithFalAI( const { apiKey, model, duration, aspectRatio, resolution, useHostedCostTracking } = input const { requestId, signal } = context signal?.throwIfAborted() - logger.info(`[${requestId}] Starting Fal.ai generation with model: ${model}`) + logger.info(`Starting Fal.ai generation with model: ${model}`, { requestId }) const { endpoint, body } = buildFalVideoRequest(input) const createResponse = await fetch(`https://queue.fal.run/${endpoint}`, { method: 'POST', @@ -397,7 +398,7 @@ async function generateWithFalAI( ) const responseUrl = falQueueUrl(getStringProperty(createData, 'response_url') || requestUrl) - logger.info(`[${requestId}] Fal.ai request created: ${requestIdFal}`) + logger.info(`Fal.ai request created: ${requestIdFal}`, { requestId }) const maxAttempts = Math.ceil(getMaxExecutionTimeout() / POLL_INTERVAL_MS) let attempts = 0 diff --git a/apps/sim/lib/internal/windchill/execute-tool.ts b/apps/sim/lib/internal/windchill/execute-tool.ts index b49cf727672..a669f7c692b 100644 --- a/apps/sim/lib/internal/windchill/execute-tool.ts +++ b/apps/sim/lib/internal/windchill/execute-tool.ts @@ -105,7 +105,8 @@ export const executeWindchillTool: InternalToolOperationHandler = async (request internalToolIdentityFaultStatus(identityFault) ) } - logger.error(`[${requestId}] Windchill operation failed`, { + logger.error('Windchill operation failed', { + requestId, operation: toolId, error: sanitizeWindchillError(getErrorMessage(error, 'Windchill operation failed')), }) diff --git a/apps/sim/lib/internal/zoom/operations.ts b/apps/sim/lib/internal/zoom/operations.ts index 1c93c350421..98ef6429161 100644 --- a/apps/sim/lib/internal/zoom/operations.ts +++ b/apps/sim/lib/internal/zoom/operations.ts @@ -143,7 +143,8 @@ export async function getZoomMeetingRecordings( 413 ) } - logger.warn(`[${context.requestId}] Failed to download Zoom recording file`, { + logger.warn('Failed to download Zoom recording file', { + requestId: context.requestId, fileId: file.id, }) } diff --git a/apps/sim/lib/knowledge/chunks/service.ts b/apps/sim/lib/knowledge/chunks/service.ts index 3f8b2bcc6f7..6f665851c14 100644 --- a/apps/sim/lib/knowledge/chunks/service.ts +++ b/apps/sim/lib/knowledge/chunks/service.ts @@ -166,7 +166,7 @@ export async function queryChunks( const page = keysetPage(keys, rows as ChunkData[], limit) - logger.info(`[${requestId}] Retrieved ${page.data.length} chunks for document ${documentId}`) + logger.info(`Retrieved ${page.data.length} chunks for document ${documentId}`, { requestId }) return { chunks: page.data, @@ -189,7 +189,7 @@ export async function createChunk( workspaceId?: string | null, secretProvenance?: DurableSecretProvenance ): Promise { - logger.info(`[${requestId}] Generating embedding for manual chunk`) + logger.info('Generating embedding for manual chunk', { requestId }) const kbRow = await db .select({ embeddingModel: knowledgeBase.embeddingModel, @@ -347,7 +347,7 @@ export async function createChunk( } as ChunkData }) - logger.info(`[${requestId}] Created chunk ${chunkId} in document ${documentId}`) + logger.info(`Created chunk ${chunkId} in document ${documentId}`, { requestId }) return newChunk } @@ -362,7 +362,8 @@ export async function batchChunkOperation( requestId: string ): Promise { logger.info( - `[${requestId}] Starting batch ${operation} operation on ${chunkIds.length} chunks for document ${documentId}` + `Starting batch ${operation} operation on ${chunkIds.length} chunks for document ${documentId}`, + { requestId } ) const errors: string[] = [] @@ -435,7 +436,8 @@ export async function batchChunkOperation( } logger.info( - `[${requestId}] Batch ${operation} completed: ${successCount} chunks processed, ${errors.length} errors` + `Batch ${operation} completed: ${successCount} chunks processed, ${errors.length} errors`, + { requestId } ) return { @@ -503,7 +505,7 @@ export async function updateChunk( dimensions: toKbEmbeddingDimensions(kbEmbeddingRow.embeddingDimension), } - logger.info(`[${requestId}] Content changed, regenerating embedding for chunk ${chunkId}`) + logger.info(`Content changed, regenerating embedding for chunk ${chunkId}`, { requestId }) const { embeddings } = await generateEmbeddings([content], chunkEmbedding, workspaceId) regenerated = { vectorValues: embeddingVectorValues(chunkEmbedding.dimensions, embeddings[0]), @@ -604,9 +606,9 @@ export async function updateChunk( .where(eq(embedding.id, chunkId)) .limit(1) - logger.info( - `[${requestId}] Updated chunk: ${chunkId}${regenerated ? ' (regenerated embedding)' : ''}` - ) + logger.info(`Updated chunk: ${chunkId}${regenerated ? ' (regenerated embedding)' : ''}`, { + requestId, + }) return updatedChunk[0] as ChunkData }) @@ -658,7 +660,7 @@ export async function updateChunk( throw new Error(`Chunk ${chunkId} not found`) } - logger.info(`[${requestId}] Updated chunk: ${chunkId}`) + logger.info(`Updated chunk: ${chunkId}`, { requestId }) return updatedChunk[0] as ChunkData } @@ -705,5 +707,5 @@ export async function deleteChunk( .where(eq(document.id, documentId)) }) - logger.info(`[${requestId}] Deleted chunk: ${chunkId}`) + logger.info(`Deleted chunk: ${chunkId}`, { requestId }) } diff --git a/apps/sim/lib/knowledge/connectors/sync-engine.ts b/apps/sim/lib/knowledge/connectors/sync-engine.ts index 76e1fad02eb..6fcf9c9f905 100644 --- a/apps/sim/lib/knowledge/connectors/sync-engine.ts +++ b/apps/sim/lib/knowledge/connectors/sync-engine.ts @@ -820,7 +820,8 @@ async function resolveAccessToken( if (revocationError && connector.credentialId) { throw new ConnectorCredentialRevokedError(connector.credentialId, revocationError) } - logger.error(`[${requestId}] Connector credential resolved no access token`, { + logger.error('Connector credential resolved no access token', { + requestId, credentialId: connector.credentialId, userId, authMode: connectorConfig.auth.mode, diff --git a/apps/sim/lib/knowledge/documents/processing-source.test.ts b/apps/sim/lib/knowledge/documents/processing-source.test.ts index feb358cc76e..d3357cd50aa 100644 --- a/apps/sim/lib/knowledge/documents/processing-source.test.ts +++ b/apps/sim/lib/knowledge/documents/processing-source.test.ts @@ -643,7 +643,8 @@ describe('processDocumentAsync write guards', () => { ) ).rejects.toBe(databaseError) - expect(mockLogError).toHaveBeenCalledWith('[document-1] Failed to insert embedding batch', { + expect(mockLogError).toHaveBeenCalledWith('Failed to insert embedding batch', { + documentId: 'document-1', knowledgeBaseId: 'knowledge-base-1', operation: 'embedding.insert', batchNumber: 1, diff --git a/apps/sim/lib/knowledge/documents/service.ts b/apps/sim/lib/knowledge/documents/service.ts index 799de16ee8f..c7b5f73ca2c 100644 --- a/apps/sim/lib/knowledge/documents/service.ts +++ b/apps/sim/lib/knowledge/documents/service.ts @@ -336,7 +336,8 @@ async function assertKnowledgeBaseFileUrlsOwnership( for (const key of keys) { const binding = bindingByKey.get(key) if (!binding || binding.workspaceId !== kbWorkspaceId) { - logger.warn(`[${requestId}] Rejected document referencing unowned knowledge-base file`, { + logger.warn('Rejected document referencing unowned knowledge-base file', { + requestId, storageKey: key, kbWorkspaceId, bindingWorkspaceId: binding?.workspaceId ?? null, @@ -651,9 +652,7 @@ function resolveDocumentTags( setTagValue(result, targetSlot, stringValue) } - logger.info(`[${requestId}] Set tag ${tagName} (${targetSlot})`, { - fieldType: actualFieldType, - }) + logger.info(`Set tag ${tagName} (${targetSlot})`, { requestId, fieldType: actualFieldType }) } return result @@ -1175,7 +1174,8 @@ async function bestEffortWithdrawDocumentsQueued( try { await clearDocumentsQueued(documentIds, queueToken, queuedAt) } catch (error) { - logger.warn(`[${queueToken}] Failed to withdraw queue ownership after ${reason}`, { + logger.warn(`Failed to withdraw queue ownership after ${reason}`, { + queueToken, error: getErrorMessage(error), }) } @@ -1272,7 +1272,8 @@ export async function processDocumentsWithQueue( } if (queuedDocuments.length === 0) { - logger.info(`[${requestId}] No documents were eligible for a new processing dispatch`, { + logger.info('No documents were eligible for a new processing dispatch', { + requestId, acceptedWithoutDispatch, unresolved, }) @@ -1300,10 +1301,10 @@ export async function processDocumentsWithQueue( }) const useTrigger = isTriggerAvailable() - logger.info( - `[${requestId}] Dispatching background processing for ${jobPayloads.length} documents`, - { backend: useTrigger ? 'trigger-dev' : 'direct' } - ) + logger.info(`Dispatching background processing for ${jobPayloads.length} documents`, { + requestId, + backend: useTrigger ? 'trigger-dev' : 'direct', + }) let dispatchedIds: Set try { @@ -1320,9 +1321,9 @@ export async function processDocumentsWithQueue( throw error } - logger.info( - `[${requestId}] Document dispatch complete: ${dispatchedIds.size}/${jobPayloads.length} accepted` - ) + logger.info(`Document dispatch complete: ${dispatchedIds.size}/${jobPayloads.length} accepted`, { + requestId, + }) /** * Refund every newly owned generation that provably failed before claiming @@ -1392,14 +1393,15 @@ async function dispatchViaBatchTrigger( batchIds.push(result.batchId) for (const payload of chunk) dispatchedIds.add(payload.documentId) } catch (error) { - logger.error(`[${requestId}] Failed to batchTrigger ${chunk.length} document jobs`, { + logger.error(`Failed to batchTrigger ${chunk.length} document jobs`, { + requestId, error: getErrorMessage(error), }) undispatched.push(...chunk) } } if (batchIds.length > 0) { - logger.info(`[${requestId}] Trigger.dev batches dispatched`, { batchIds }) + logger.info('Trigger.dev batches dispatched', { requestId, batchIds }) } /** @@ -1419,13 +1421,14 @@ async function dispatchViaBatchTrigger( const swept = undispatched.length - fallback.length if (swept > 0) { logger.warn( - `[${requestId}] Leaving ${swept} backfill documents for the stuck-document sweep after failed enqueue` + `Leaving ${swept} backfill documents for the stuck-document sweep after failed enqueue`, + { requestId } ) } if (fallback.length > 0) { - logger.warn( - `[${requestId}] Processing ${fallback.length} documents in-process after failed enqueue` - ) + logger.warn(`Processing ${fallback.length} documents in-process after failed enqueue`, { + requestId, + }) const directlyDispatchedIds = await dispatchInProcess(fallback, requestId, executionContext) for (const documentId of directlyDispatchedIds) dispatchedIds.add(documentId) } @@ -1574,13 +1577,15 @@ async function dispatchInProcess( return acceptedByLiveGeneration } catch (error) { if (isPermanentDocumentProcessingError(error) || error instanceof OcrRequestRejectedError) { - logger.warn(`[${requestId}] Document processing reached an expected terminal state`, { + logger.warn('Document processing reached an expected terminal state', { + requestId, code: error.code, }) return true } if (isEmbeddingQuotaExhaustion(error)) { - logger.warn(`[${requestId}] Embedding quota is exhausted; continuation scheduled`, { + logger.warn('Embedding quota is exhausted; continuation scheduled', { + requestId, documentId: p.documentId, quotaRetryCount: p.quotaRetryCount ?? 0, }) @@ -1590,7 +1595,8 @@ async function dispatchInProcess( getProviderCapacityDeferral(error) || error instanceof ProviderCapacityContinuationExhaustedError ) { - logger.warn(`[${requestId}] Provider capacity interrupted document processing`, { + logger.warn('Provider capacity interrupted document processing', { + requestId, documentId: p.documentId, providerRetryCount: p.providerRetryCount ?? 0, outcome: @@ -1601,7 +1607,8 @@ async function dispatchInProcess( return true } if (isBYOKEmbeddingCredentialRejection(error)) { - logger.warn(`[${requestId}] Customer-managed embedding credentials were rejected`, { + logger.warn('Customer-managed embedding credentials were rejected', { + requestId, documentId: p.documentId, status: error.status, }) @@ -1611,7 +1618,8 @@ async function dispatchInProcess( ? 'In-process document processing failed' : 'In-process document dispatch failed before claiming the document' const diagnostic = getConnectorFailureDiagnostic(error) - logger.error(`[${requestId}] ${message}`, { + logger.error(message, { + requestId, documentId: p.documentId, error: diagnostic?.message ?? getErrorMessage(error), ...(diagnostic ? { diagnostic } : {}), @@ -1689,7 +1697,8 @@ export async function processDocumentAsync( let processingFilename = docData.filename try { attemptContext?.signal?.throwIfAborted() - logger.info(`[${documentId}] Starting document processing`, { + logger.info('Starting document processing', { + documentId, knowledgeBaseId, mimeType: docData.mimeType, fileSize: docData.fileSize, @@ -1777,7 +1786,8 @@ export async function processDocumentAsync( } if (contextRows.length === 0) { logger.warn( - `[${documentId}] Skipping document processing: document or knowledge base ${knowledgeBaseId} no longer exists` + `Skipping document processing: document or knowledge base ${knowledgeBaseId} no longer exists`, + { documentId } ) await db .update(document) @@ -1874,14 +1884,15 @@ export async function processDocumentAsync( if (claimed.length === 0) { logger.info( - `[${documentId}] Skipping document processing: superseded, already active, completed, archived, or deleted` + 'Skipping document processing: superseded, already active, completed, archived, or deleted', + { documentId } ) return { outcome: 'skipped', reason: 'not_claimed' } } attemptContext?.onClaimed?.() - logger.info(`[${documentId}] Status updated to 'processing', starting document processor`) + logger.info("Status updated to 'processing', starting document processor", { documentId }) const rawConfig = ctx.chunkingConfig as { maxSize?: number @@ -1922,7 +1933,7 @@ export async function processDocumentAsync( const usageGate = await checkIngestionUsageLimits(billingAttribution) if (usageGate.isExceeded) { - logger.warn(`[${documentId}] Usage limit reached — skipping document indexing`) + logger.warn('Usage limit reached — skipping document indexing', { documentId }) throw new UsageLimitDocumentProcessingError( usageGate.message ?? 'Usage limit exceeded. Please upgrade your plan to continue.' ) @@ -1985,7 +1996,8 @@ export async function processDocumentAsync( outputTokens: visionUsage.outputTokens, }) } catch (billingError) { - logger.error(`[${documentId}] Failed to record image extraction usage`, { + logger.error('Failed to record image extraction usage', { + documentId, error: billingError, }) } @@ -2043,8 +2055,9 @@ export async function processDocumentAsync( logger.info( prepared?.vectors - ? `[${documentId}] Indexing ${processed.chunks.length} chunks with their bundled vectors` - : `[${documentId}] Document parsed successfully, generating embeddings for ${processed.chunks.length} chunks` + ? `Indexing ${processed.chunks.length} chunks with their bundled vectors` + : `Document parsed successfully, generating embeddings for ${processed.chunks.length} chunks`, + { documentId } ) const chunkTexts = processed.chunks.map((chunk) => chunk.text) @@ -2074,16 +2087,16 @@ export async function processDocumentAsync( const batchSize = LARGE_DOC_CONFIG.MAX_EMBEDDING_BATCH const totalBatches = Math.ceil(chunkTexts.length / batchSize) - logger.info(`[${documentId}] Generating embeddings in ${totalBatches} batches`) + logger.info(`Generating embeddings in ${totalBatches} batches`, { documentId }) for (let i = 0; i < chunkTexts.length; i += batchSize) { signal.throwIfAborted() const batch = chunkTexts.slice(i, i + batchSize) const batchNum = Math.floor(i / batchSize) + 1 - logger.info( - `[${documentId}] Processing embedding batch ${batchNum}/${totalBatches}` - ) + logger.info(`Processing embedding batch ${batchNum}/${totalBatches}`, { + documentId, + }) const { embeddings: batchEmbeddings, billableTokens: batchBillableTokens, @@ -2122,9 +2135,9 @@ export async function processDocumentAsync( ) } - logger.info( - `[${documentId}] Embeddings generated, creating embedding records with tags` - ) + logger.info('Embeddings generated, creating embedding records with tags', { + documentId, + }) const chunkProvenances = processed.chunks.map((chunk) => documentSecretContext.tracked @@ -2257,14 +2270,15 @@ export async function processDocumentAsync( batches.push(embeddingRecords.slice(i, i + insertBatchSize)) } - logger.info(`[${documentId}] Inserting ${embeddingRecords.length} embeddings`) + logger.info(`Inserting ${embeddingRecords.length} embeddings`, { documentId }) for (const [batchIndex, batch] of batches.entries()) { signal.throwIfAborted() const insertStartedAt = Date.now() try { await tx.insert(embedding).values(batch) } catch (error) { - logger.error(`[${documentId}] Failed to insert embedding batch`, { + logger.error('Failed to insert embedding batch', { + documentId, knowledgeBaseId, operation: 'embedding.insert', batchNumber: batchIndex + 1, @@ -2353,12 +2367,12 @@ export async function processDocumentAsync( }) if (!processingCommitted) { - logger.info(`[${documentId}] Discarded output from an obsolete processing attempt`) + logger.info('Discarded output from an obsolete processing attempt', { documentId }) return { outcome: 'skipped', reason: 'superseded' } } const processingTime = Date.now() - startTime - logger.info(`[${documentId}] Successfully processed document in ${processingTime}ms`) + logger.info(`Successfully processed document in ${processingTime}ms`, { documentId }) if (billableEmbeddingTokens > 0) { try { @@ -2408,12 +2422,12 @@ export async function processDocumentAsync( }) } else { logger.warn( - `[${documentId}] Embedding model "${embeddingModelName}" has no pricing entry — billing skipped`, - { billableEmbeddingTokens, embeddingModelName } + `Embedding model "${embeddingModelName}" has no pricing entry — billing skipped`, + { documentId, billableEmbeddingTokens, embeddingModelName } ) } } catch (billingError) { - logger.error(`[${documentId}] Failed to record embedding usage`, { error: billingError }) + logger.error('Failed to record embedding usage', { documentId, error: billingError }) } } @@ -2465,6 +2479,7 @@ export async function processDocumentAsync( ? failureDiagnostic.message : getErrorMessage(recordedError, 'Unknown error') const logContext = { + documentId, errorType: toError(recordedError).name, ...(failureDiagnostic ? { diagnostic: failureDiagnostic } : {}), knowledgeBaseId, @@ -2479,8 +2494,8 @@ export async function processDocumentAsync( : {}), } const logMessage = deferredUntil - ? `[${documentId}] Deferred document processing after ${processingTime}ms:` - : `[${documentId}] Failed to process document after ${processingTime}ms:` + ? `Deferred document processing after ${processingTime}ms:` + : `Failed to process document after ${processingTime}ms:` if ( (embeddingQuotaExhausted && !quotaContinuationFailed) || deferredUntil || @@ -2792,7 +2807,7 @@ export async function createDocumentRecords( } } catch (error) { if (error instanceof SyntaxError) { - logger.warn(`[${requestId}] Failed to parse documentTagsData for bulk document:`, error) + logger.warn('Failed to parse documentTagsData for bulk document:', error, { requestId }) } else { throw error } @@ -2882,7 +2897,8 @@ export async function createDocumentRecords( ) } logger.info( - `[${requestId}] Bulk created ${documentRecords.length} document records in knowledge base ${knowledgeBaseId}` + `Bulk created ${documentRecords.length} document records in knowledge base ${knowledgeBaseId}`, + { requestId } ) await tx @@ -3150,7 +3166,8 @@ export async function getDocuments( const hasMore = offset + limit < total logger.info( - `[${requestId}] Retrieved ${documents.length} documents (${offset}-${offset + documents.length} of ${total}) for knowledge base ${knowledgeBaseId}` + `Retrieved ${documents.length} documents (${offset}-${offset + documents.length} of ${total}) for knowledge base ${knowledgeBaseId}`, + { requestId } ) return { @@ -3356,7 +3373,7 @@ export async function createSingleDocument( } } catch (error) { if (error instanceof SyntaxError) { - logger.warn(`[${requestId}] Failed to parse documentTagsData:`, error) + logger.warn('Failed to parse documentTagsData:', error, { requestId }) } else { throw error } @@ -3535,7 +3552,7 @@ export async function createSingleDocument( ) } - logger.info(`[${requestId}] Document created: ${documentId} in knowledge base ${knowledgeBaseId}`) + logger.info(`Document created: ${documentId} in knowledge base ${knowledgeBaseId}`, { requestId }) return newDocument as { id: string @@ -3627,7 +3644,8 @@ export async function bulkDocumentOperation( }> }> { logger.info( - `[${requestId}] Starting bulk ${operation} operation on ${documentIds.length} documents in knowledge base ${knowledgeBaseId}` + `Starting bulk ${operation} operation on ${documentIds.length} documents in knowledge base ${knowledgeBaseId}`, + { requestId } ) const candidateConditions = [ @@ -3662,7 +3680,8 @@ export async function bulkDocumentOperation( if (documentsToUpdate.length !== documentIds.length) { logger.warn( - `[${requestId}] Some documents not found or don't belong to knowledge base. Requested: ${documentIds.length}, Found: ${documentsToUpdate.length}` + `Some documents not found or don't belong to knowledge base. Requested: ${documentIds.length}, Found: ${documentsToUpdate.length}`, + { requestId } ) } @@ -3703,7 +3722,8 @@ export async function bulkDocumentOperation( const successCount = updateResult.length logger.info( - `[${requestId}] Bulk ${operation} operation completed: ${successCount} documents updated in knowledge base ${knowledgeBaseId}` + `Bulk ${operation} operation completed: ${successCount} documents updated in knowledge base ${knowledgeBaseId}`, + { requestId } ) return { @@ -3729,7 +3749,8 @@ export async function bulkDocumentOperationByFilter( }> }> { logger.info( - `[${requestId}] Starting bulk ${operation} operation on all documents (filter: ${enabledFilter || 'all'}) in knowledge base ${knowledgeBaseId}` + `Starting bulk ${operation} operation on all documents (filter: ${enabledFilter || 'all'}) in knowledge base ${knowledgeBaseId}`, + { requestId } ) const whereConditions = [ @@ -3778,7 +3799,8 @@ export async function bulkDocumentOperationByFilter( const successCount = updateResult.length logger.info( - `[${requestId}] Bulk ${operation} by filter completed: ${successCount} documents updated in knowledge base ${knowledgeBaseId}` + `Bulk ${operation} by filter completed: ${successCount} documents updated in knowledge base ${knowledgeBaseId}`, + { requestId } ) return { @@ -3802,10 +3824,11 @@ export async function markDocumentAsFailedTimeout( if (result.success) { logger.info( - `[${requestId}] Marked document ${documentId} as failed due to dead process (processing time: ${Math.round(result.processingDuration / 1000)}s)` + `Marked document ${documentId} as failed due to dead process (processing time: ${Math.round(result.processingDuration / 1000)}s)`, + { requestId } ) } else { - logger.info(`[${requestId}] Did not time out document ${documentId} because its claim changed`) + logger.info(`Did not time out document ${documentId} because its claim changed`, { requestId }) } return result @@ -3932,7 +3955,7 @@ export async function retryDocumentProcessing( status: 'failed', message: 'Source content could not be downloaded. Sync the connector to retry.', } - logger.info(`[${requestId}] Document retry skipped, already queued: ${documentId}`) + logger.info(`Document retry skipped, already queued: ${documentId}`, { requestId }) return { success: true, status: 'pending', @@ -3984,7 +4007,7 @@ export async function retryDocumentProcessing( } } - logger.info(`[${requestId}] Document retry initiated: ${documentId}`) + logger.info(`Document retry initiated: ${documentId}`, { requestId }) return { success: true, @@ -4196,7 +4219,7 @@ export async function updateDocument( throw new Error(`Document ${documentId} not found`) } - logger.info(`[${requestId}] Document updated: ${documentId}`) + logger.info(`Document updated: ${documentId}`, { requestId }) return { id: doc.id, @@ -4255,7 +4278,8 @@ async function excludeConnectorDocuments( .returning({ id: document.id }) if (updated.length > 0) { - logger.info(`[${requestId}] Excluded ${updated.length} connector-backed document(s)`, { + logger.info(`Excluded ${updated.length} connector-backed document(s)`, { + requestId, documentIds: updated.map((doc) => doc.id), }) } @@ -4612,7 +4636,8 @@ async function hardDeleteDocumentBatch( }) }) - logger.info(`[${requestId}] Hard deleted ${deletedDocs.length} documents`, { + logger.info(`Hard deleted ${deletedDocs.length} documents`, { + requestId, documentIds: deletedDocs.map((doc) => doc.id), }) @@ -4676,7 +4701,8 @@ async function excludeConnectorKnowledgeDocuments( ) .returning({ id: document.id }) if (updated.length > 0) { - logger.info(`[${requestId}] Excluded ${updated.length} connector-backed document(s)`, { + logger.info(`Excluded ${updated.length} connector-backed document(s)`, { + requestId, documentIds: updated.map((row) => row.id), knowledgeBaseId, }) diff --git a/apps/sim/lib/knowledge/orchestration/connector-access.ts b/apps/sim/lib/knowledge/orchestration/connector-access.ts index 2a71b798312..0ad4ff6a248 100644 --- a/apps/sim/lib/knowledge/orchestration/connector-access.ts +++ b/apps/sim/lib/knowledge/orchestration/connector-access.ts @@ -280,7 +280,7 @@ export async function performUpdateKnowledgeConnectorAccess( ) .returning() if (!updated) return fail('Connector changed; retry the request', 'conflict') - logger.info(`[${requestId}] Re-enabled member sync on connector ${connectorId}`) + logger.info(`Re-enabled member sync on connector ${connectorId}`, { requestId }) const connector = withoutSecret(updated) if (updated.status !== 'paused') { await dispatchMemberSyncBestEffort(connectorId, params, requestId, now) @@ -382,9 +382,7 @@ export async function performUpdateKnowledgeConnectorAccess( }) const updated = await releaseSwitchLease(connectorId, switchId, previousStatus) if (!updated) throw new SwitchLeaseLostError() - logger.info(`[${requestId}] Switched connector ${connectorId} to members mode`, { - rewritten, - }) + logger.info(`Switched connector ${connectorId} to members mode`, { requestId, rewritten }) const connector = withoutSecret(updated) if (previousStatus !== 'paused') { await dispatchMemberSyncBestEffort(connectorId, params, requestId, flippedAt) @@ -418,7 +416,8 @@ export async function performUpdateKnowledgeConnectorAccess( params.userId ) ).catch((undoError) => { - logger.error(`[${requestId}] Failed to undo the grant of an abandoned switch`, { + logger.error('Failed to undo the grant of an abandoned switch', { + requestId, connectorId, error: getErrorMessage(undoError), }) @@ -500,7 +499,8 @@ export async function performUpdateKnowledgeConnectorAccess( }, params.userId ).catch((error) => { - logger.error(`[${requestId}] Failed to revoke the grant after leaving members mode`, { + logger.error('Failed to revoke the grant after leaving members mode', { + requestId, connectorId, error: getErrorMessage(error), }) @@ -510,7 +510,8 @@ export async function performUpdateKnowledgeConnectorAccess( accessRewritePending: !rewritten, }) if (!updated) throw new SwitchLeaseLostError() - logger.info(`[${requestId}] Switched connector ${connectorId} to ${target.accessMode} mode`, { + logger.info(`Switched connector ${connectorId} to ${target.accessMode} mode`, { + requestId, rewritten, }) const connector = withoutSecret(updated) @@ -525,7 +526,8 @@ export async function performUpdateKnowledgeConnectorAccess( return fail('Connector changed during the switch; retry the request', 'conflict') } await releaseSwitchLease(connectorId, switchId, previousStatus).catch((releaseError) => { - logger.error(`[${requestId}] Failed to release the access switch lease`, { + logger.error('Failed to release the access switch lease', { + requestId, connectorId, error: releaseError, }) @@ -554,10 +556,11 @@ async function dispatchMemberSyncBestEffort( requireRunnable: true, }) if (!dispatch.queued) { - logger.warn(`[${requestId}] Member sync after the switch was not queued: ${dispatch.reason}`) + logger.warn(`Member sync after the switch was not queued: ${dispatch.reason}`, { requestId }) } } catch (error) { - logger.error(`[${requestId}] Failed to dispatch the member sync after the switch`, { + logger.error('Failed to dispatch the member sync after the switch', { + requestId, connectorId, error, }) @@ -579,12 +582,9 @@ async function dispatchContentSyncBestEffort( requireRunnable: true, }) if (!dispatch.queued) { - logger.warn(`[${requestId}] Sync after the switch was not queued: ${dispatch.reason}`) + logger.warn(`Sync after the switch was not queued: ${dispatch.reason}`, { requestId }) } } catch (error) { - logger.error(`[${requestId}] Failed to dispatch the sync after the switch`, { - connectorId, - error, - }) + logger.error('Failed to dispatch the sync after the switch', { requestId, connectorId, error }) } } diff --git a/apps/sim/lib/knowledge/orchestration/connectors.ts b/apps/sim/lib/knowledge/orchestration/connectors.ts index d157987d8a0..058439ab50f 100644 --- a/apps/sim/lib/knowledge/orchestration/connectors.ts +++ b/apps/sim/lib/knowledge/orchestration/connectors.ts @@ -417,7 +417,7 @@ export async function performCreateKnowledgeConnector( newTagSlots = mapping for (const name of skippedTags) { - logger.warn(`[${requestId}] No available slots for "${name}"`) + logger.warn(`No available slots for "${name}"`, { requestId }) } if (skippedTags.length > 0 && Object.keys(tagSlotMapping).length === 0) { @@ -588,7 +588,8 @@ export async function performCreateKnowledgeConnector( { workspaceId, credentialGroupId: membersBinding.credentialGroupId, connectorId }, params.userId ).catch((revokeError) => { - logger.error(`[${requestId}] Failed to revoke the grant of an uncreated connector`, { + logger.error('Failed to revoke the grant of an uncreated connector', { + requestId, connectorId, error: revokeError, }) @@ -606,7 +607,7 @@ export async function performCreateKnowledgeConnector( return { success: true, connector: withoutSecret(created), reused: true } } - logger.info(`[${requestId}] Created connector ${connectorId} for KB ${kb.id}`) + logger.info(`Created connector ${connectorId} for KB ${kb.id}`, { requestId }) if (params.recordProductAnalytics !== false) { captureServerEvent( @@ -669,16 +670,15 @@ export async function performCreateKnowledgeConnector( : await (await loadDispatchSync())(connectorId, { billingAttribution, requestId }) if (!dispatch.queued) { initialSyncQueued = false - logger.warn( - `[${requestId}] Initial sync for connector ${connectorId} was not queued: ${dispatch.reason}` - ) + logger.warn(`Initial sync for connector ${connectorId} was not queued: ${dispatch.reason}`, { + requestId, + }) } } catch (error) { initialSyncQueued = false - logger.error( - `[${requestId}] Failed to dispatch initial sync for connector ${connectorId}`, - error - ) + logger.error(`Failed to dispatch initial sync for connector ${connectorId}`, error, { + requestId, + }) } return { success: true, connector: withoutSecret(created), initialSyncQueued } @@ -1119,13 +1119,11 @@ export async function performUpdateKnowledgeConnector( requireRunnable: true, }) } catch (error) { - logger.error( - `[${requestId}] Saved connector; member sync remains due after dispatch failed`, - { - connectorId, - error, - } - ) + logger.error('Saved connector; member sync remains due after dispatch failed', { + requestId, + connectorId, + error, + }) } } @@ -1138,7 +1136,8 @@ export async function performUpdateKnowledgeConnector( requireRunnable: true, }) } catch (error) { - logger.error(`[${requestId}] Saved connector; sync remains due after dispatch failed`, { + logger.error('Saved connector; sync remains due after dispatch failed', { + requestId, connectorId, error, }) @@ -1356,7 +1355,8 @@ export async function performDeleteKnowledgeConnector( }) } catch (error) { if (['55P03', '57014', '40P01'].includes(getPostgresErrorCode(error) ?? '')) { - logger.warn(`[${requestId}] Connector removal could not acquire or finish its transaction`, { + logger.warn('Connector removal could not acquire or finish its transaction', { + requestId, connectorId, error, }) @@ -1382,7 +1382,8 @@ export async function performDeleteKnowledgeConnector( }, params.userId ).catch((error) => { - logger.error(`[${requestId}] Failed to revoke the detached connector's credential access`, { + logger.error("Failed to revoke the detached connector's credential access", { + requestId, connectorId, error, }) @@ -1390,7 +1391,8 @@ export async function performDeleteKnowledgeConnector( } logger.info( - `[${requestId}] Deleted connector ${connectorId}${deleteDocuments ? ` and ${docCount} documents` : `, kept ${docCount} documents`}` + `Deleted connector ${connectorId}${deleteDocuments ? ` and ${docCount} documents` : `, kept ${docCount} documents`}`, + { requestId } ) if (params.recordProductAnalytics !== false) { @@ -1512,7 +1514,8 @@ export async function performSyncKnowledgeConnector( ) } logger.info( - `[${requestId}] Manual sync${rehydrate ? ' (full rehydrate)' : ''} triggered for connector ${connectorId}` + `Manual sync${rehydrate ? ' (full rehydrate)' : ''} triggered for connector ${connectorId}`, + { requestId } ) /** @@ -1545,16 +1548,15 @@ export async function performSyncKnowledgeConnector( * with an audit and a product event behind it. */ if (!dispatch.queued) { - logger.warn( - `[${requestId}] Manual sync for connector ${connectorId} was not queued: ${dispatch.reason}` - ) + logger.warn(`Manual sync for connector ${connectorId} was not queued: ${dispatch.reason}`, { + requestId, + }) return fail(dispatch.reason ?? 'Sync could not be queued', 'conflict') } } catch (error) { - logger.error( - `[${requestId}] Failed to dispatch manual sync for connector ${connectorId}`, - error - ) + logger.error(`Failed to dispatch manual sync for connector ${connectorId}`, error, { + requestId, + }) return classifyKnowledgeFailure(error, requestId, `Sync connector ${connectorId}`) } diff --git a/apps/sim/lib/knowledge/orchestration/documents.ts b/apps/sim/lib/knowledge/orchestration/documents.ts index 98e9faecb43..eacab19bce2 100644 --- a/apps/sim/lib/knowledge/orchestration/documents.ts +++ b/apps/sim/lib/knowledge/orchestration/documents.ts @@ -339,7 +339,7 @@ export async function performUploadKnowledgeDocuments( ) } - logger.info(`[${requestId}] Starting controlled async processing of ${created.length} documents`) + logger.info(`Starting controlled async processing of ${created.length} documents`, { requestId }) void dispatchDocumentProcessing({ documents: created, @@ -391,9 +391,9 @@ export async function performDeleteKnowledgeDocument( return classifyKnowledgeFailure(error, requestId, `Delete document ${document.id}`) } - logger.info( - `[${requestId}] Deleted document ${document.id} from knowledge base ${knowledgeBase.id}` - ) + logger.info(`Deleted document ${document.id} from knowledge base ${knowledgeBase.id}`, { + requestId, + }) recordAudit({ workspaceId: knowledgeBase.workspaceId, diff --git a/apps/sim/lib/knowledge/orchestration/knowledge-bases.ts b/apps/sim/lib/knowledge/orchestration/knowledge-bases.ts index 985f80e566a..66a07ab65ab 100644 --- a/apps/sim/lib/knowledge/orchestration/knowledge-bases.ts +++ b/apps/sim/lib/knowledge/orchestration/knowledge-bases.ts @@ -82,7 +82,7 @@ export async function performCreateKnowledgeBase( return classifyKnowledgeFailure(error, requestId, `Create knowledge base "${name}"`) } - logger.info(`[${requestId}] Created knowledge base ${created.id} for user ${params.userId}`) + logger.info(`Created knowledge base ${created.id} for user ${params.userId}`, { requestId }) PlatformEvents.knowledgeBaseCreated({ knowledgeBaseId: created.id, @@ -170,7 +170,7 @@ export async function performUpdateKnowledgeBase( return classifyKnowledgeFailure(error, requestId, `Update knowledge base ${knowledgeBaseId}`) } - logger.info(`[${requestId}] Updated knowledge base ${knowledgeBaseId}`) + logger.info(`Updated knowledge base ${knowledgeBaseId}`, { requestId }) recordAudit({ // The destination workspace when this update moved it, so the audit row @@ -229,7 +229,7 @@ export async function performDeleteKnowledgeBase( return classifyKnowledgeFailure(error, requestId, `Delete knowledge base ${knowledgeBase.id}`) } - logger.info(`[${requestId}] Deleted knowledge base ${knowledgeBase.id}`) + logger.info(`Deleted knowledge base ${knowledgeBase.id}`, { requestId }) PlatformEvents.knowledgeBaseDeleted({ knowledgeBaseId: knowledgeBase.id }) diff --git a/apps/sim/lib/knowledge/orchestration/restore.ts b/apps/sim/lib/knowledge/orchestration/restore.ts index be4881743de..b456d0a56ef 100644 --- a/apps/sim/lib/knowledge/orchestration/restore.ts +++ b/apps/sim/lib/knowledge/orchestration/restore.ts @@ -78,7 +78,7 @@ export async function performRestoreKnowledgeBase( return classifyKnowledgeFailure(error, requestId, `Restore knowledge base ${knowledgeBaseId}`) } - logger.info(`[${requestId}] Restored knowledge base ${knowledgeBaseId}`) + logger.info(`Restored knowledge base ${knowledgeBaseId}`, { requestId }) if (params.recordSemanticAudit !== false) { recordAudit({ diff --git a/apps/sim/lib/knowledge/orchestration/shared.ts b/apps/sim/lib/knowledge/orchestration/shared.ts index 2c2581deee7..c8f9a8aa8d0 100644 --- a/apps/sim/lib/knowledge/orchestration/shared.ts +++ b/apps/sim/lib/knowledge/orchestration/shared.ts @@ -70,7 +70,7 @@ export function classifyKnowledgeFailure( if (classified) { return fail(classified.message, classified.code) } - logger.error(`[${requestId}] ${operation} failed`, { error }) + logger.error(`${operation} failed`, { requestId, error }) return fail(toError(error).message, 'internal') } diff --git a/apps/sim/lib/knowledge/service.ts b/apps/sim/lib/knowledge/service.ts index 37e86ebf637..c6b79188264 100644 --- a/apps/sim/lib/knowledge/service.ts +++ b/apps/sim/lib/knowledge/service.ts @@ -489,7 +489,7 @@ export async function createAuthorizedKnowledgeBase( throw error } - logger.info(`[${requestId}] Created knowledge base: ${data.name} (${kbId})`) + logger.info(`Created knowledge base: ${data.name} (${kbId})`, { requestId }) return { id: kbId, @@ -877,7 +877,7 @@ export async function updateKnowledgeBase( throw new KnowledgeBaseNotFoundError(knowledgeBaseId) } - logger.info(`[${requestId}] Updated knowledge base: ${knowledgeBaseId}`) + logger.info(`Updated knowledge base: ${knowledgeBaseId}`, { requestId }) return toActiveKnowledgeBaseReference(updated) } @@ -1065,7 +1065,7 @@ export async function deleteKnowledgeBase( ) }) - logger.info(`[${requestId}] Soft deleted knowledge base: ${knowledgeBaseId}`) + logger.info(`Soft deleted knowledge base: ${knowledgeBaseId}`, { requestId }) } /** @@ -1213,7 +1213,7 @@ export async function restoreKnowledgeBase( } } - logger.info( - `[${requestId}] Restored knowledge base: ${knowledgeBaseId} as "${attemptedRestoreName}"` - ) + logger.info(`Restored knowledge base: ${knowledgeBaseId} as "${attemptedRestoreName}"`, { + requestId, + }) } diff --git a/apps/sim/lib/knowledge/tags/service.ts b/apps/sim/lib/knowledge/tags/service.ts index 03091a79567..523762ca1bc 100644 --- a/apps/sim/lib/knowledge/tags/service.ts +++ b/apps/sim/lib/knowledge/tags/service.ts @@ -504,7 +504,8 @@ export async function createOrUpdateTagDefinitionsBulk( }) logger.info( - `[${requestId}] Bulk tag definitions processed: ${created.length} created, ${updated.length} updated, ${errors.length} errors` + `Bulk tag definitions processed: ${created.length} created, ${updated.length} updated, ${errors.length} errors`, + { requestId } ) return { created, updated, errors } @@ -594,13 +595,13 @@ export async function cleanupUnusedTagDefinitions( .where(eq(knowledgeBaseTagDefinitions.id, def.id)) cleanedUp++ - logger.info( - `[${requestId}] Cleaned up unused tag definition: ${def.displayName} (${def.tagSlot})` - ) + logger.info(`Cleaned up unused tag definition: ${def.displayName} (${def.tagSlot})`, { + requestId, + }) } } - logger.info(`[${requestId}] Cleanup completed: ${cleanedUp} unused tag definitions removed`) + logger.info(`Cleanup completed: ${cleanedUp} unused tag definitions removed`, { requestId }) return cleanedUp } @@ -635,7 +636,7 @@ export async function deleteAllTagDefinitions( return definitions.length }) - logger.info(`[${requestId}] Deleted ${deletedCount} tag definitions for KB: ${knowledgeBaseId}`) + logger.info(`Deleted ${deletedCount} tag definitions for KB: ${knowledgeBaseId}`, { requestId }) return deletedCount } @@ -684,9 +685,9 @@ export async function deleteTagDefinition( const tagSlot = definition.tagSlot as string - logger.info( - `[${requestId}] Deleted tag definition with cleanup: ${definition.displayName} (${tagSlot})` - ) + logger.info(`Deleted tag definition with cleanup: ${definition.displayName} (${tagSlot})`, { + requestId, + }) return { tagSlot, @@ -739,7 +740,8 @@ async function insertTagDefinition( await dbInstance.insert(knowledgeBaseTagDefinitions).values(newDefinition) logger.info( - `[${requestId}] Created tag definition: ${data.displayName} -> ${data.tagSlot} in KB ${data.knowledgeBaseId}` + `Created tag definition: ${data.displayName} -> ${data.tagSlot} in KB ${data.knowledgeBaseId}`, + { requestId } ) return { @@ -810,7 +812,7 @@ export async function updateTagDefinition( } const updated = updatedRows[0] - logger.info(`[${requestId}] Updated tag definition: ${tagDefinitionId}`) + logger.info(`Updated tag definition: ${tagDefinitionId}`, { requestId }) return { ...updated, @@ -883,7 +885,7 @@ export async function getTagUsage( }) } - logger.info(`[${requestId}] Retrieved detailed tag usage for ${usage.length} definitions`) + logger.info(`Retrieved detailed tag usage for ${usage.length} definitions`, { requestId }) return usage } @@ -964,7 +966,7 @@ export async function getTagUsageStats( }) } - logger.info(`[${requestId}] Retrieved tag usage stats for ${stats.length} definitions`) + logger.info(`Retrieved tag usage stats for ${stats.length} definitions`, { requestId }) return stats } diff --git a/apps/sim/lib/logs/execution/logging-session.ts b/apps/sim/lib/logs/execution/logging-session.ts index dc205891752..4824fb92a26 100644 --- a/apps/sim/lib/logs/execution/logging-session.ts +++ b/apps/sim/lib/logs/execution/logging-session.ts @@ -877,7 +877,7 @@ export class LoggingSession { const deleted = skipLogCreation ? null : await this.findDeletedScope(error, workspaceId) if (deleted) throw deleted if (this.requestId) { - logger.error(`[${this.requestId}] Failed to start logging:`, error) + logger.error('Failed to start logging:', error, { requestId: this.requestId }) } throw error } @@ -1134,9 +1134,9 @@ export class LoggingSession { } if (this.requestId) { - logger.debug( - `[${this.requestId}] Completed error logging for execution ${this.executionId}` - ) + logger.debug(`Completed error logging for execution ${this.executionId}`, { + requestId: this.requestId, + }) } } catch (enhancedError) { this.completing = false @@ -1216,9 +1216,9 @@ export class LoggingSession { } if (this.requestId) { - logger.debug( - `[${this.requestId}] Completed cancelled logging for execution ${this.executionId}` - ) + logger.debug(`Completed cancelled logging for execution ${this.executionId}`, { + requestId: this.requestId, + }) } } catch (cancelError) { this.completing = false @@ -1316,9 +1316,9 @@ export class LoggingSession { } catch (_e) {} if (this.requestId) { - logger.debug( - `[${this.requestId}] Completed paused logging for execution ${this.executionId}` - ) + logger.debug(`Completed paused logging for execution ${this.executionId}`, { + requestId: this.requestId, + }) } } catch (pauseError) { this.completing = false @@ -1364,10 +1364,9 @@ export class LoggingSession { return false } if (this.requestId) { - logger.warn( - `[${this.requestId}] Logging start failed - falling back to minimal session:`, - error - ) + logger.warn('Logging start failed - falling back to minimal session:', error, { + requestId: this.requestId, + }) } // Fallback: create a minimal logging session without full workflow state @@ -1416,9 +1415,9 @@ export class LoggingSession { }) if (this.requestId) { - logger.debug( - `[${this.requestId}] Started minimal logging for execution ${this.executionId}` - ) + logger.debug(`Started minimal logging for execution ${this.executionId}`, { + requestId: this.requestId, + }) } return true } catch (fallbackError) { @@ -1428,7 +1427,9 @@ export class LoggingSession { return false } if (this.requestId) { - logger.error(`[${this.requestId}] Minimal logging start also failed:`, fallbackError) + logger.error('Minimal logging start also failed:', fallbackError, { + requestId: this.requestId, + }) } return false } @@ -1521,10 +1522,10 @@ export class LoggingSession { } catch (error) { if (error instanceof ExecutionScopeDeletedError) return this.endDeletedScopeRun(error) const errorMsg = toError(error).message - logger.warn( - `[${this.requestId || 'unknown'}] Complete failed for execution ${this.executionId}, attempting fallback`, - { error: errorMsg } - ) + logger.warn(`Complete failed for execution ${this.executionId}, attempting fallback`, { + requestId: this.requestId, + error: errorMsg, + }) await this.completeWithCostOnlyLog({ traceSpans: params.traceSpans, endedAt: params.endedAt, @@ -1551,8 +1552,8 @@ export class LoggingSession { if (error instanceof ExecutionScopeDeletedError) return this.endDeletedScopeRun(error) const errorMsg = toError(error).message logger.warn( - `[${this.requestId || 'unknown'}] CompleteWithError failed for execution ${this.executionId}, attempting fallback`, - { error: errorMsg } + `CompleteWithError failed for execution ${this.executionId}, attempting fallback`, + { requestId: this.requestId, error: errorMsg } ) await this.completeWithCostOnlyLog({ traceSpans: params?.traceSpans, @@ -1585,8 +1586,8 @@ export class LoggingSession { if (error instanceof ExecutionScopeDeletedError) return this.endDeletedScopeRun(error) const errorMsg = toError(error).message logger.warn( - `[${this.requestId || 'unknown'}] CompleteWithCancellation failed for execution ${this.executionId}, attempting fallback`, - { error: errorMsg } + `CompleteWithCancellation failed for execution ${this.executionId}, attempting fallback`, + { requestId: this.requestId, error: errorMsg } ) await this.completeWithCostOnlyLog({ traceSpans: params?.traceSpans, @@ -1614,8 +1615,8 @@ export class LoggingSession { if (error instanceof ExecutionScopeDeletedError) return this.endDeletedScopeRun(error) const errorMsg = toError(error).message logger.warn( - `[${this.requestId || 'unknown'}] CompleteWithPause failed for execution ${this.executionId}, attempting fallback`, - { error: errorMsg } + `CompleteWithPause failed for execution ${this.executionId}, attempting fallback`, + { requestId: this.requestId, error: errorMsg } ) await this.completeWithCostOnlyLog({ traceSpans: params?.traceSpans, @@ -1716,7 +1717,7 @@ export class LoggingSession { if (markers !== null) void clearProgressMarkers(executionId) - logger.info(`[${requestId || 'unknown'}] Marked execution ${executionId} as failed`) + logger.info(`Marked execution ${executionId} as failed`, { requestId }) } catch (error) { logger.error(`Failed to mark execution ${executionId} as failed:`, { error: toError(error).message, @@ -1741,7 +1742,8 @@ export class LoggingSession { this.completing = true logger.warn( - `[${this.requestId || 'unknown'}] Logging completion failed for execution ${this.executionId} - attempting cost-only fallback` + `Logging completion failed for execution ${this.executionId} - attempting cost-only fallback`, + { requestId: this.requestId } ) try { @@ -1769,23 +1771,21 @@ export class LoggingSession { this.completed = true - logger.info( - `[${this.requestId || 'unknown'}] Cost-only fallback succeeded for execution ${this.executionId}` - ) + logger.info(`Cost-only fallback succeeded for execution ${this.executionId}`, { + requestId: this.requestId, + }) } catch (fallbackError) { this.completing = false if (fallbackError instanceof ExecutionScopeDeletedError) { return this.endDeletedScopeRun(fallbackError) } this.completionAttemptFailed = true - logger.error( - `[${this.requestId || 'unknown'}] Cost-only fallback also failed for execution ${this.executionId}:`, - { - error: toError(fallbackError).message, - cause: describeError(fallbackError), - retryable: isRetryableInfrastructureError(fallbackError), - } - ) + logger.error(`Cost-only fallback also failed for execution ${this.executionId}:`, { + requestId: this.requestId, + error: toError(fallbackError).message, + cause: describeError(fallbackError), + retryable: isRetryableInfrastructureError(fallbackError), + }) } } } diff --git a/apps/sim/lib/mcp/client.ts b/apps/sim/lib/mcp/client.ts index c7cfbaae666..f87b1e8952b 100644 --- a/apps/sim/lib/mcp/client.ts +++ b/apps/sim/lib/mcp/client.ts @@ -234,10 +234,12 @@ export class McpClient { if (this.onToolsChanged) { this.client.setNotificationHandler(ToolListChangedNotificationSchema, async () => { if (!this.isConnected) return - logger.info(`[${this.config.name}] Received tools/list_changed notification`) + logger.info('Received tools/list_changed notification', { serverName: this.config.name }) this.onToolsChanged?.(this.config.id) }) - logger.info(`[${this.config.name}] Registered tools/list_changed notification handler`) + logger.info('Registered tools/list_changed notification handler', { + serverName: this.config.name, + }) } logger.info(`Successfully connected to MCP server: ${this.config.name}`, { @@ -507,16 +509,16 @@ export class McpClient { } try { - logger.info(`[${this.config.name}] Sending ping to server`) + logger.info('Sending ping to server', { serverName: this.config.name }) // Bound the ping so a half-open connection (no FIN/RST, so `onclose` never // fires) is detected quickly instead of stalling on the SDK's 60s default. const response = await this.client.ping( timeoutMs !== undefined ? { timeout: timeoutMs } : undefined ) - logger.info(`[${this.config.name}] Ping successful`) + logger.info('Ping successful', { serverName: this.config.name }) return response } catch (error) { - logger.warn(`[${this.config.name}] Ping failed:`, error) + logger.warn('Ping failed:', error, { serverName: this.config.name }) throw error } } diff --git a/apps/sim/lib/mcp/connection-manager.ts b/apps/sim/lib/mcp/connection-manager.ts index 5c64310dcba..a47d10f3967 100644 --- a/apps/sim/lib/mcp/connection-manager.ts +++ b/apps/sim/lib/mcp/connection-manager.ts @@ -130,7 +130,9 @@ export class McpConnectionManager { const key = connectionKey(config) if (this.connections.has(key) || this.connectingServers.has(key)) { - logger.info(`[${config.name}] Already has a managed connection or is connecting, skipping`) + logger.info('Already has a managed connection or is connecting, skipping', { + serverName: config.name, + }) const state = this.states.get(key) return { supportsListChanged: state?.supportsListChanged ?? false } } @@ -157,7 +159,8 @@ export class McpConnectionManager { }) if (!row.tokens) { logger.info( - `[${config.name}] OAuth server has no workspace tokens — skipping persistent connection until authorized` + 'OAuth server has no workspace tokens — skipping persistent connection until authorized', + { serverName: config.name } ) return { supportsListChanged: false } } @@ -200,16 +203,18 @@ export class McpConnectionManager { try { await withConnectTimeout(client, config.name) } catch (error) { - logger.error(`[${config.name}] Failed to connect for persistent monitoring:`, error) + logger.error('Failed to connect for persistent monitoring:', error, { + serverName: config.name, + }) return { supportsListChanged: false } } const supportsListChanged = client.hasListChangedCapability() if (!supportsListChanged) { - logger.info( - `[${config.name}] Server does not support listChanged — disconnecting (fallback to cache)` - ) + logger.info('Server does not support listChanged — disconnecting (fallback to cache)', { + serverName: config.name, + }) await client.disconnect() return { supportsListChanged: false } } @@ -234,7 +239,9 @@ export class McpConnectionManager { this.ensureIdleCheck() - logger.info(`[${config.name}] Persistent connection established (listChanged supported)`) + logger.info('Persistent connection established (listChanged supported)', { + serverName: config.name, + }) return { supportsListChanged: true } }) } finally { @@ -359,7 +366,7 @@ export class McpConnectionManager { timestamp: Date.now(), } - logger.info(`[${state.serverName}] Tools changed — publishing to pub/sub`) + logger.info('Tools changed — publishing to pub/sub', { serverName: state.serverName }) mcpPubSub?.publishToolsChanged(event) } @@ -373,7 +380,7 @@ export class McpConnectionManager { state.connected = false this.connections.delete(key) - logger.warn(`[${config.name}] Persistent connection lost, scheduling reconnect`) + logger.warn('Persistent connection lost, scheduling reconnect', { serverName: config.name }) this.scheduleReconnect(config, userId, workspaceId) } @@ -385,9 +392,9 @@ export class McpConnectionManager { if (!state || this.disposed) return if (state.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) { - logger.error( - `[${config.name}] Max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) reached — giving up` - ) + logger.error(`Max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) reached — giving up`, { + serverName: config.name, + }) this.states.delete(key) return } @@ -399,7 +406,8 @@ export class McpConnectionManager { }) logger.info( - `[${config.name}] Reconnecting in ${delay}ms (attempt ${state.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})` + `Reconnecting in ${delay}ms (attempt ${state.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`, + { serverName: config.name } ) this.clearReconnectTimer(key) @@ -411,9 +419,9 @@ export class McpConnectionManager { const currentState = this.states.get(key) if (currentState?.connected) { - logger.info( - `[${config.name}] Connection already re-established externally, skipping reconnect` - ) + logger.info('Connection already re-established externally, skipping reconnect', { + serverName: config.name, + }) return } @@ -424,13 +432,13 @@ export class McpConnectionManager { try { const result = await this.connect(config, userId, workspaceId) if (result.supportsListChanged) { - logger.info(`[${config.name}] Reconnected successfully`) + logger.info('Reconnected successfully', { serverName: config.name }) } else { this.restoreReconnectState(config, userId, workspaceId, attempts) this.scheduleReconnect(config, userId, workspaceId) } } catch (error) { - logger.error(`[${config.name}] Reconnect failed:`, error) + logger.error('Reconnect failed:', error, { serverName: config.name }) this.restoreReconnectState(config, userId, workspaceId, attempts) this.scheduleReconnect(config, userId, workspaceId) } @@ -478,9 +486,9 @@ export class McpConnectionManager { const now = Date.now() for (const [key, state] of this.states) { if (now - state.lastActivity > IDLE_TIMEOUT_MS) { - logger.info( - `[${state.serverName}] Idle timeout reached, disconnecting managed connection` - ) + logger.info('Idle timeout reached, disconnecting managed connection', { + serverName: state.serverName, + }) this.disconnectByKey(key) } } diff --git a/apps/sim/lib/mcp/middleware.ts b/apps/sim/lib/mcp/middleware.ts index a3bec4ce0dd..039134808eb 100644 --- a/apps/sim/lib/mcp/middleware.ts +++ b/apps/sim/lib/mcp/middleware.ts @@ -183,7 +183,7 @@ async function validateMcpAuth( try { const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) if (!auth.success || !auth.userId) { - logger.warn(`[${requestId}] Authentication failed: ${auth.error}`) + logger.warn(`Authentication failed: ${auth.error}`, { requestId }) return { success: false, errorResponse: createMcpErrorResponse( @@ -270,7 +270,7 @@ async function validateMcpAuth( }, } } catch (error) { - logger.error(`[${requestId}] Error during MCP auth validation:`, error) + logger.error('Error during MCP auth validation:', error, { requestId }) return { success: false, errorResponse: createMcpErrorResponse( @@ -336,10 +336,9 @@ export function withMcpAuth>( } catch (error) { const bodyErrorResponse = mcpBodyReadErrorResponse(error, request) if (bodyErrorResponse) return bodyErrorResponse - logger.error( - `[${(authResult as AuthResult).context.requestId}] Error in MCP route handler:`, - error - ) + logger.error('Error in MCP route handler:', error, { + requestId: (authResult as AuthResult).context.requestId, + }) return createMcpErrorResponse(toError(error), 'Internal server error', 500) } } diff --git a/apps/sim/lib/mcp/service.ts b/apps/sim/lib/mcp/service.ts index f1c2f53c1d6..093073cbe7d 100644 --- a/apps/sim/lib/mcp/service.ts +++ b/apps/sim/lib/mcp/service.ts @@ -763,7 +763,8 @@ class McpService { options.signal?.throwIfAborted() try { logger.info( - `[${requestId}] Executing MCP tool ${toolCall.name} on server ${serverId} for user ${userId}${attempt > 0 ? ` (attempt ${attempt + 1})` : ''}` + `Executing MCP tool ${toolCall.name} on server ${serverId} for user ${userId}${attempt > 0 ? ` (attempt ${attempt + 1})` : ''}`, + { requestId } ) const config = await this.getServerConfig(serverId, workspaceId) @@ -796,7 +797,7 @@ class McpService { return client.callTool(toolCall, options) } ) - logger.info(`[${requestId}] Successfully executed tool ${toolCall.name}`) + logger.info(`Successfully executed tool ${toolCall.name}`, { requestId }) return result } catch (error) { options.signal?.throwIfAborted() @@ -805,8 +806,9 @@ class McpService { // the request. Timeouts/resets are NOT retried — the tool may have executed. if ((this.isSessionError(error) || isAuthError(error)) && attempt < maxRetries - 1) { logger.warn( - `[${requestId}] Retryable connection error executing tool ${toolCall.name}, retrying (attempt ${attempt + 1}):`, - error + `Retryable connection error executing tool ${toolCall.name}, retrying (attempt ${attempt + 1}):`, + error, + { requestId } ) await interruptibleSleep(100, options.signal) options.signal?.throwIfAborted() @@ -1002,12 +1004,12 @@ class McpService { const discoveryStartedAt = new Date() try { - logger.info(`[${requestId}] Discovering MCP tools for workspace ${workspaceId}`) + logger.info(`Discovering MCP tools for workspace ${workspaceId}`, { requestId }) const servers = await this.getWorkspaceServers(workspaceId) if (servers.length === 0) { - logger.info(`[${requestId}] No servers found for workspace ${workspaceId}`) + logger.info(`No servers found for workspace ${workspaceId}`, { requestId }) return [] } @@ -1021,24 +1023,25 @@ class McpService { if (cached) return { kind: 'cached', tools: cached.tools } } catch (error) { logger.warn( - `[${requestId}] Cache read failed for ${config.name}, proceeding with discovery:`, - error + `Cache read failed for ${config.name}, proceeding with discovery:`, + error, + { requestId } ) } } if (refresh !== 'force' && (await this.isServerUnhealthy(workspaceId, config.id))) { - logger.info( - `[${requestId}] Skipping recently-failed server ${config.name} (negative-cache hit)` - ) + logger.info(`Skipping recently-failed server ${config.name} (negative-cache hit)`, { + requestId, + }) return { kind: 'unhealthy' } } try { const tools = await this.fetchServerTools(config, userId, workspaceId) - logger.debug( - `[${requestId}] Discovered ${tools.length} tools from server ${config.name}` - ) + logger.debug(`Discovered ${tools.length} tools from server ${config.name}`, { + requestId, + }) return { kind: 'fetched', tools } } catch (error) { if (isOauthAuthorizationError(error, config.authType)) { @@ -1082,7 +1085,7 @@ class McpService { this.cacheAdapter .set(serverCacheKey(workspaceId, server.id), outcome.tools, this.cacheTimeout) .catch((err) => - logger.warn(`[${requestId}] Cache write failed for ${server.name}:`, err) + logger.warn(`Cache write failed for ${server.name}:`, err, { requestId }) ) ) deferredSideEffects.push(this.clearServerFailure(workspaceId, server.id)) @@ -1093,7 +1096,7 @@ class McpService { // Mark disconnected so the UI surfaces the re-auth button, and drop the positive // tool cache so a follow-up force-refresh can't serve tools for a server that now // needs re-auth (mirrors the single-server discovery path). - logger.info(`[${requestId}] Skipping server ${server.name}: OAuth authorization pending`) + logger.info(`Skipping server ${server.name}: OAuth authorization pending`, { requestId }) deferredSideEffects.push( this.markServerOauthPending(server.id, workspaceId, discoveryStartedAt).then( async (statusApplied) => { @@ -1101,7 +1104,7 @@ class McpService { await this.cacheAdapter .delete(serverCacheKey(workspaceId, server.id)) .catch((err) => - logger.warn(`[${requestId}] Cache delete failed for ${server.name}:`, err) + logger.warn(`Cache delete failed for ${server.name}:`, err, { requestId }) ) } ) @@ -1114,9 +1117,9 @@ class McpService { return } failedCount++ - logger.warn( - `[${requestId}] Failed to discover tools from server ${server.name}: ${outcome.message}` - ) + logger.warn(`Failed to discover tools from server ${server.name}: ${outcome.message}`, { + requestId, + }) deferredSideEffects.push( this.updateServerStatus(server.id, workspaceId, { outcome: 'failed', @@ -1134,7 +1137,7 @@ class McpService { this.cacheAdapter .delete(serverCacheKey(workspaceId, server.id)) .catch((err) => - logger.warn(`[${requestId}] Cache delete failed for ${server.name}:`, err) + logger.warn(`Cache delete failed for ${server.name}:`, err, { requestId }) ), ]) }) @@ -1163,18 +1166,19 @@ class McpService { ) await manager.connect(resolvedConfig, userId, workspaceId) } catch (err) { - logger.warn(`[${requestId}] Persistent connection failed for ${config.name}:`, err) + logger.warn(`Persistent connection failed for ${config.name}:`, err, { requestId }) } })() } } logger.info( - `[${requestId}] Discovered ${allTools.length} tools from ${servers.length} servers (cached=${cachedCount} fetched=${fetchedCount} failed=${failedCount})` + `Discovered ${allTools.length} tools from ${servers.length} servers (cached=${cachedCount} fetched=${fetchedCount} failed=${failedCount})`, + { requestId } ) return allTools } catch (error) { - logger.error(`[${requestId}] Failed to discover MCP tools for user ${userId}:`, error) + logger.error(`Failed to discover MCP tools for user ${userId}:`, error, { requestId }) throw error } } @@ -1248,16 +1252,16 @@ class McpService { try { const cached = await this.cacheAdapter.get(serverCacheKey(workspaceId, serverId)) if (cached) { - logger.debug(`[${requestId}] Cache hit for server ${serverId}`) + logger.debug(`Cache hit for server ${serverId}`, { requestId }) return cached.tools } } catch (error) { - logger.warn(`[${requestId}] Cache read failed for server ${serverId}:`, error) + logger.warn(`Cache read failed for server ${serverId}:`, error, { requestId }) } } if (refresh !== 'force' && (await this.isServerUnhealthy(workspaceId, serverId))) { - logger.info(`[${requestId}] Skipping recently-failed server ${serverId} (negative-cache)`) + logger.info(`Skipping recently-failed server ${serverId} (negative-cache)`, { requestId }) throw new McpServerCooldownError(serverId) } @@ -1266,7 +1270,8 @@ class McpService { let authType: McpServerConfig['authType'] try { logger.info( - `[${requestId}] Discovering tools from server ${serverId} for user ${userId}${attempt > 0 ? ` (attempt ${attempt + 1})` : ''}` + `Discovering tools from server ${serverId} for user ${userId}${attempt > 0 ? ` (attempt ${attempt + 1})` : ''}`, + { requestId } ) const config = requesterConfig ?? (await this.getServerConfig(serverId, workspaceId)) @@ -1284,10 +1289,10 @@ class McpService { requireComplete, bestEffort ) - logger.info(`[${requestId}] Discovered ${tools.length} tools from server ${config.name}`) + logger.info(`Discovered ${tools.length} tools from server ${config.name}`, { requestId }) const cacheWrite = this.cacheAdapter .set(serverCacheKey(workspaceId, serverId), tools, this.cacheTimeout) - .catch((err) => logger.warn(`[${requestId}] Cache write failed for ${config.name}:`, err)) + .catch((err) => logger.warn(`Cache write failed for ${config.name}:`, err, { requestId })) await Promise.allSettled( bestEffort ? [cacheWrite] @@ -1306,8 +1311,9 @@ class McpService { signal?.throwIfAborted() if (isRetryableDiscoveryError(error) && attempt < maxRetries - 1) { logger.warn( - `[${requestId}] Transient error discovering tools from server ${serverId}, retrying (attempt ${attempt + 1}):`, - error + `Transient error discovering tools from server ${serverId}, retrying (attempt ${attempt + 1}):`, + error, + { requestId } ) await interruptibleSleep( backoffWithJitter(attempt + 1, null, { baseMs: 250, maxMs: 2000 }), @@ -1330,7 +1336,7 @@ class McpService { this.cacheAdapter .delete(serverCacheKey(workspaceId, serverId)) .catch((err) => - logger.warn(`[${requestId}] Cache delete failed for ${serverId}:`, err) + logger.warn(`Cache delete failed for ${serverId}:`, err, { requestId }) ), this.markServerUnhealthy(workspaceId, serverId, error, authType), ]) @@ -1368,7 +1374,7 @@ class McpService { const requestId = generateRequestId() try { - logger.info(`[${requestId}] Getting server summaries for workspace ${workspaceId}`) + logger.info(`Getting server summaries for workspace ${workspaceId}`, { requestId }) const servers = await this.getWorkspaceServers(workspaceId) const summaries: McpServerSummary[] = [] @@ -1416,7 +1422,7 @@ class McpService { return summaries } catch (error) { - logger.error(`[${requestId}] Failed to get server summaries for user ${userId}:`, error) + logger.error(`Failed to get server summaries for user ${userId}:`, error, { requestId }) throw error } } diff --git a/apps/sim/lib/mcp/workflow-mcp-sync.ts b/apps/sim/lib/mcp/workflow-mcp-sync.ts index 827dd5c5a43..100ce90c6a2 100644 --- a/apps/sim/lib/mcp/workflow-mcp-sync.ts +++ b/apps/sim/lib/mcp/workflow-mcp-sync.ts @@ -276,7 +276,8 @@ async function restoreArchivedMcpToolsForWorkflow( MAX_MCP_SERVERS_PER_WORKFLOW - (await countWorkflowLiveMcpServers(tx, workflowId)) if (preLockServerBudget <= 0) { logger.warn( - `[${requestId}] Skipped restoring archived MCP tools for workflow ${workflowId}: it is already exposed on the maximum of ${MAX_MCP_SERVERS_PER_WORKFLOW} MCP servers` + `Skipped restoring archived MCP tools for workflow ${workflowId}: it is already exposed on the maximum of ${MAX_MCP_SERVERS_PER_WORKFLOW} MCP servers`, + { requestId } ) return } @@ -358,7 +359,8 @@ async function restoreArchivedMcpToolsForWorkflow( const skipReason = await getRestoreSkipReason(tx, workflowId, candidate, remainingServerBudget) if (skipReason) { logger.warn( - `[${requestId}] Skipped restoring archived MCP tool "${candidate.toolName}" for workflow ${workflowId} on server ${candidate.serverId}: ${skipReason}` + `Skipped restoring archived MCP tool "${candidate.toolName}" for workflow ${workflowId} on server ${candidate.serverId}: ${skipReason}`, + { requestId } ) continue } @@ -372,9 +374,9 @@ async function restoreArchivedMcpToolsForWorkflow( .set({ archivedAt: null }) .where(inArray(workflowMcpTool.id, restorableIds)) - logger.info( - `[${requestId}] Restored ${restorableIds.length} archived MCP tool(s) for workflow: ${workflowId}` - ) + logger.info(`Restored ${restorableIds.length} archived MCP tool(s) for workflow: ${workflowId}`, { + requestId, + }) } /** @@ -458,8 +460,9 @@ export async function syncMcpToolsForWorkflow( state = await loadDeployedWorkflowState(options.workflowId) } catch (error) { logger.error( - `[${options.requestId}] Error loading deployed state for MCP tool sync (${options.context ?? 'sync'}):`, - error + `Error loading deployed state for MCP tool sync (${options.context ?? 'sync'}):`, + error, + { requestId: options.requestId } ) if (options.throwOnError) throw error return [] @@ -577,13 +580,13 @@ export async function syncMcpToolsForWorkflow( afterToolId = pageTools.at(-1)?.id } - logger.info( - `[${requestId}] Synced ${syncedToolCount} MCP tool(s) for workflow (${context}): ${workflowId}` - ) + logger.info(`Synced ${syncedToolCount} MCP tool(s) for workflow (${context}): ${workflowId}`, { + requestId, + }) return [...affectedServerIds].map((serverId) => ({ serverId })) } catch (error) { - logger.error(`[${requestId}] Error syncing MCP tools (${context}):`, error) + logger.error(`Error syncing MCP tools (${context}):`, error, { requestId }) if (throwOnError) throw error return [] } @@ -631,11 +634,11 @@ export async function removeMcpToolsForWorkflow( .update(workflowMcpTool) .set({ archivedAt: new Date() }) .where(and(eq(workflowMcpTool.workflowId, workflowId), isNull(workflowMcpTool.archivedAt))) - logger.info(`[${requestId}] Archived MCP tools for workflow: ${workflowId}`) + logger.info(`Archived MCP tools for workflow: ${workflowId}`, { requestId }) return tools } catch (error) { - logger.error(`[${requestId}] Error archiving MCP tools:`, error) + logger.error('Error archiving MCP tools:', error, { requestId }) if (throwOnError) throw error return [] } diff --git a/apps/sim/lib/mothership/chat/completion.ts b/apps/sim/lib/mothership/chat/completion.ts index a7a0c051bfd..2f1c943d7d2 100644 --- a/apps/sim/lib/mothership/chat/completion.ts +++ b/apps/sim/lib/mothership/chat/completion.ts @@ -119,7 +119,8 @@ export function buildOnComplete(params: { } } catch (error) { if (error instanceof StreamControllerSupersededError) throw error - logger.error(`[${requestId}] Failed to persist chat messages`, { + logger.error('Failed to persist chat messages', { + requestId, chatId, error: getErrorMessage(error, 'Unknown error'), }) @@ -183,7 +184,8 @@ export function buildOnError(params: { } } catch (error) { if (error instanceof StreamControllerSupersededError) throw error - logger.error(`[${requestId}] Failed to finalize errored chat stream`, { + logger.error('Failed to finalize errored chat stream', { + requestId, chatId, error: getErrorMessage(error, 'Unknown error'), }) diff --git a/apps/sim/lib/mothership/chat/post.ts b/apps/sim/lib/mothership/chat/post.ts index bb68ef96984..dc5fbb0e553 100644 --- a/apps/sim/lib/mothership/chat/post.ts +++ b/apps/sim/lib/mothership/chat/post.ts @@ -425,7 +425,7 @@ async function resolveAgentContexts(params: { requestMode ) } catch (error) { - logger.error(`[${requestId}] Failed to process contexts`, error) + logger.error('Failed to process contexts', error, { requestId }) } } @@ -1410,7 +1410,8 @@ export async function handleUnifiedChatPost(req: NextRequest) { if (isZodError(error)) { // A rejected body otherwise leaves no trace: the client sees a 400 and // its stream reconnect 404s, which reads as the stream dying for no reason. - logger.warn(`[${requestId}] Rejected chat request as invalid`, { + logger.warn('Rejected chat request as invalid', { + requestId, issues: error.issues.map((issue) => ({ path: issue.path.join('.'), message: issue.message, @@ -1442,7 +1443,8 @@ export async function handleUnifiedChatPost(req: NextRequest) { return NextResponse.json({ error: 'Workspace access denied' }, { status: 403 }) } - logger.error(`[${requestId}] Error handling unified chat request`, { + logger.error('Error handling unified chat request', { + requestId, error: getErrorMessage(error, 'Unknown error'), stack: error instanceof Error ? error.stack : undefined, }) diff --git a/apps/sim/lib/mothership/chat/project-file-context.integration.ts b/apps/sim/lib/mothership/chat/project-file-context.integration.ts index bd2b847f1d9..3984333c8e6 100644 --- a/apps/sim/lib/mothership/chat/project-file-context.integration.ts +++ b/apps/sim/lib/mothership/chat/project-file-context.integration.ts @@ -694,7 +694,7 @@ describe('open tabs that no longer resolve', () => { }) expect(contexts[1]?.content).toContain(browserTab.url) expect(logs.calls('info', 'Skipped resource attachment that no longer exists')).toEqual([ - { resourceType: 'file' }, + { requestId: expect.any(String), resourceType: 'file' }, ]) expect(logs.calls('error', 'Failed to resolve resource attachment')).toHaveLength(1) expect( @@ -737,7 +737,7 @@ describe('open tabs that no longer resolve', () => { expect(contexts[0]?.content).toContain(browserTab.url) expect( logs.calls('warn', 'Skipped resource attachment the user can no longer access') - ).toEqual([{ resourceType: 'file' }]) + ).toEqual([{ requestId: expect.any(String), resourceType: 'file' }]) expect(logs.calls('info', 'Skipped resource attachment that no longer exists')).toEqual([]) expect(logs.calls('error', 'Failed to resolve resource attachment')).toEqual([]) } finally { diff --git a/apps/sim/lib/mothership/chat/resource-attachments.ts b/apps/sim/lib/mothership/chat/resource-attachments.ts index 8367a82ce9f..beb7218a496 100644 --- a/apps/sim/lib/mothership/chat/resource-attachments.ts +++ b/apps/sim/lib/mothership/chat/resource-attachments.ts @@ -209,15 +209,17 @@ export async function resolveResourceAttachmentContexts(params: { const resourceType = resourceAttachments[index]?.type const code = asOrchestrationError(result.reason)?.code if (code === 'not_found') { - logger.info(`[${requestId}] Skipped resource attachment that no longer exists`, { + logger.info('Skipped resource attachment that no longer exists', { + requestId, resourceType, }) } else if (code === 'forbidden') { - logger.warn(`[${requestId}] Skipped resource attachment the user can no longer access`, { + logger.warn('Skipped resource attachment the user can no longer access', { + requestId, resourceType, }) } else { - logger.error(`[${requestId}] Failed to resolve resource attachment`, result.reason) + logger.error('Failed to resolve resource attachment', result.reason, { requestId }) } }) } diff --git a/apps/sim/lib/mothership/request/lifecycle/finalize.ts b/apps/sim/lib/mothership/request/lifecycle/finalize.ts index 2bbab2c5167..67dff02c99a 100644 --- a/apps/sim/lib/mothership/request/lifecycle/finalize.ts +++ b/apps/sim/lib/mothership/request/lifecycle/finalize.ts @@ -82,7 +82,8 @@ async function handleAborted( const partialContentLen = result.content?.length ?? 0 const toolCallCount = result.toolCalls?.length ?? 0 const blockCount = result.contentBlocks?.length ?? 0 - logger.info(`[${requestId}] Stream aborted by explicit stop`, { + logger.info('Stream aborted by explicit stop', { + requestId, partialContentLen, toolCallCount, blockCount, @@ -132,9 +133,10 @@ async function handleError( const toolCallCount = result.toolCalls?.length ?? 0 if (publisher.clientDisconnected) { - logger.info(`[${requestId}] Stream failed after client disconnect`, { error: errorMessage }) + logger.info('Stream failed after client disconnect', { requestId, error: errorMessage }) } - logger.error(`[${requestId}] Orchestration returned failure`, { + logger.error('Orchestration returned failure', { + requestId, error: errorMessage, partialContentLen, toolCallCount, @@ -233,7 +235,8 @@ async function loggedRunStatusUpdate( try { await updateRunStatus(runId, status, updates, controllerToken) } catch (error) { - logger.warn(`[${requestId}] Failed to update run status to ${status}`, { + logger.warn(`Failed to update run status to ${status}`, { + requestId, runId, error: toError(error).message, }) diff --git a/apps/sim/lib/mothership/request/lifecycle/start.ts b/apps/sim/lib/mothership/request/lifecycle/start.ts index d499c7b136f..0ae55e90c2d 100644 --- a/apps/sim/lib/mothership/request/lifecycle/start.ts +++ b/apps/sim/lib/mothership/request/lifecycle/start.ts @@ -153,7 +153,8 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS () => false, (error: unknown) => { if (error instanceof StreamControllerSupersededError) return true - logger.warn(`[${requestId}] Chat stream lease is unreadable`, { + logger.warn('Chat stream lease is unreadable', { + requestId, streamId, error: getErrorMessage(error), }) @@ -210,7 +211,8 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS })() // Contract violation: add the new reason to AbortReason / // isExplicitStopReason or extend the classifier. - logger.error(`[${requestId}] Stream cancelled with unknown abort reason`, { + logger.error('Stream cancelled with unknown abort reason', { + requestId, streamId, chatId, reason: serializedReason, @@ -259,7 +261,8 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS failure: StreamTurnFailure, result?: OrchestratorResult ) => { - logger.warn(`[${requestId}] ${failure.message}; ending the turn`, { + logger.warn(`${failure.message}; ending the turn`, { + requestId, streamId, code: failure.code, ...(failure instanceof StreamReplayBudgetExhaustedError @@ -294,7 +297,8 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS ) await requestExplicitStreamAbort({ streamId, userId, chatId }) } catch (error) { - logger.warn(`[${requestId}] Worker stop after a turn failure was not delivered`, { + logger.warn('Worker stop after a turn failure was not delivered', { + requestId, streamId, error: getErrorMessage(error), }) @@ -512,7 +516,8 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS cancelReason = recordCancelled(getErrorMessage(error)) } if (publisher.clientDisconnected) { - logger.info(`[${requestId}] Stream errored after client disconnect`, { + logger.info('Stream errored after client disconnect', { + requestId, error: getErrorMessage(error, 'Stream error'), }) } @@ -520,7 +525,7 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS // cancel — it isn't an "unexpected" failure then, and the // error-level log pollutes alerting on normal Stop presses. const logFn = outcome === RequestTraceV1Outcome.cancelled ? logger.warn : logger.error - logFn.call(logger, `[${requestId}] Orchestration ended with ${outcome}:`, error) + logFn.call(logger, `Orchestration ended with ${outcome}:`, error, { requestId }) const syntheticResult = { success: false as const, @@ -544,7 +549,8 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS try { await publisher.close() } catch (error) { - logger.warn(`[${requestId}] Failed to flush stream persistence during close`, { + logger.warn('Failed to flush stream persistence during close', { + requestId, error: getErrorMessage(error), }) } @@ -622,7 +628,8 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS try { activeOtelRoot.finish(rootOutcome, rootError, cancelReason) } catch (finishError) { - logger.error(`[${requestId}] activeOtelRoot.finish threw; force-ending root span`, { + logger.error('activeOtelRoot.finish threw; force-ending root span', { + requestId, error: getErrorMessage(finishError), }) try { @@ -718,7 +725,7 @@ function fireTitleGeneration(params: { publishChatStatusChanged({ workspaceId, organizationId, userId }, { chatId, type: 'renamed' }) }) .catch((error) => { - logger.error(`[${requestId}] Title generation failed:`, error) + logger.error('Title generation failed:', error, { requestId }) }) } diff --git a/apps/sim/lib/oauth/credential-service.ts b/apps/sim/lib/oauth/credential-service.ts index 12d3bdb5b99..50d45854d68 100644 --- a/apps/sim/lib/oauth/credential-service.ts +++ b/apps/sim/lib/oauth/credential-service.ts @@ -869,7 +869,7 @@ async function getCredentialByAccountId(requestId: string, accountId: string, us .limit(1) if (!credentials.length) { - logger.warn(`[${requestId}] Credential not found`) + logger.warn('Credential not found', { requestId }) return undefined } @@ -885,7 +885,7 @@ async function getCredentialByAccountId(requestId: string, accountId: string, us export async function getCredential(requestId: string, credentialId: string, userId: string) { const resolved = await resolveOAuthAccountId(credentialId) if (!resolved) { - logger.warn(`[${requestId}] Credential is not an OAuth credential`) + logger.warn('Credential is not an OAuth credential', { requestId }) return undefined } return getCredentialByAccountId(requestId, resolved.accountId, userId) @@ -1597,8 +1597,10 @@ async function performCoalescedRefresh({ } /** Starts a refresh ahead of expiry; callers in the window share it through the refresh's own single-flight. */ -function refreshInBackground(options: CoalescedRefreshOptions, logPrefix: string): void { - logger.debug(`${logPrefix}Refreshing ahead of expiry; using the still-valid access token`) +function refreshInBackground(options: CoalescedRefreshOptions): void { + logger.debug('Refreshing ahead of expiry; using the still-valid access token', { + requestId: options.requestId, + }) // The refresh settles and logs its own failures; this catches only a setup error before it starts. performCoalescedRefresh(options).catch((error: unknown) => { logger.error('Background token refresh failed to start', { @@ -1635,9 +1637,8 @@ async function resolveStoredAccessToken( refreshToken: plan.refreshToken, chain: stored, } - const prefix = refresh.requestId ? `[${refresh.requestId}] ` : '' if (plan.kind === 'refresh-ahead') { - refreshInBackground(options, prefix) + refreshInBackground(options) return { kind: 'token', accessToken: stored.accessToken, refreshed: false } } const fresh = await waitWithAbort(performCoalescedRefresh(options), signal) @@ -1645,7 +1646,9 @@ async function resolveStoredAccessToken( return { kind: 'token', accessToken: fresh.accessToken, refreshed: true } } if (plan.kind === 'refresh-or-reuse' && stored.accessToken) { - logger.info(`${prefix}Refresh unavailable; reusing still-valid access token`) + logger.info('Refresh unavailable; reusing still-valid access token', { + requestId: refresh.requestId, + }) return { kind: 'token', accessToken: stored.accessToken, refreshed: false } } return { kind: 'failed', outcome: fresh } @@ -1724,7 +1727,7 @@ export async function resolveCredentialTokenBundle( } if (resolved.credentialType === 'service_account' && resolved.credentialId) { - logger.info(`[${requestId}] Using service account token for credential`) + logger.info('Using service account token for credential', { requestId }) return resolveServiceAccountToken( resolved.credentialId, resolved.providerId, @@ -1761,10 +1764,10 @@ export async function resolveCredentialTokenBundle( if (result.kind === 'failed') return null if (!result.accessToken) { // We have no access token and either no refresh token or not eligible to refresh - logger.error(`[${requestId}] Missing access token for credential`) + logger.error('Missing access token for credential', { requestId }) return null } - if (!result.refreshed) logger.debug(`[${requestId}] Access token is valid for credential`) + if (!result.refreshed) logger.debug('Access token is valid for credential', { requestId }) return { accessToken: result.accessToken } } @@ -1817,7 +1820,7 @@ export async function refreshTokenIfNeeded( throw new Error('OAuth access token expired and cannot be refreshed; reconnect the account') } if (result.kind === 'token') { - if (!result.refreshed) logger.debug(`[${requestId}] Access token is valid`) + if (!result.refreshed) logger.debug('Access token is valid', { requestId }) /** A row storing neither token returns its empty token as before; callers decide how to fail. */ return { accessToken: result.accessToken ?? credential.accessToken, diff --git a/apps/sim/lib/oauth/token-resolution.ts b/apps/sim/lib/oauth/token-resolution.ts index 01b6e005f9b..3844c2f3062 100644 --- a/apps/sim/lib/oauth/token-resolution.ts +++ b/apps/sim/lib/oauth/token-resolution.ts @@ -256,14 +256,15 @@ export async function completeOAuthCredentialToken(params: { return { ok: true, token: buildOAuthTokenPayload(credential, accessToken) } } catch (error) { if (error instanceof CredentialRevokedError) { - logger.warn(`[${requestId}] OAuth credential revoked by provider; reconnect required`, { + logger.warn('OAuth credential revoked by provider; reconnect required', { + requestId, credentialId: resolvedCredentialId, providerId: error.provider?.providerId, errorCode: error.provider?.errorCode, }) return { ok: false, status: 401, code: OAUTH_CREDENTIAL_REVOKED, error: error.message } } - logger.error(`[${requestId}] Failed to refresh access token:`, error) + logger.error('Failed to refresh access token:', error, { requestId }) return { ok: false, status: 401, error: 'Failed to refresh access token' } } } @@ -395,7 +396,7 @@ export async function resolveCredentialToken( }, } } catch (error) { - logger.error(`[${requestId}] Service account token error:`, error) + logger.error('Service account token error:', error, { requestId }) if (error instanceof TokenServiceAccountValidationError) { // Classified provider outages are infra failures, not bad credentials. if (error.code === 'provider_unavailable') { @@ -461,7 +462,7 @@ export async function resolveCredentialToken( auditRequest, }) } catch (error) { - logger.error(`[${requestId}] Error getting access token`, error) + logger.error('Error getting access token', error, { requestId }) return { ok: false, status: 500, error: 'Internal server error' } } } @@ -550,7 +551,7 @@ export async function resolveCredentialAccessToken( const toolMetadata = getToolMetadata(toolId) if (!toolMetadata?.oauth?.required) { - logger.error(`[${requestId}] Tool is not configured for managed OAuth`, { toolId }) + logger.error('Tool is not configured for managed OAuth', { requestId, toolId }) return { ok: false, status: 500, @@ -565,7 +566,8 @@ export async function resolveCredentialAccessToken( toolMetadata.oauth.requiredScopes ?? getDefaultToolScopesForProvider(toolMetadata.oauth.provider) if (requiredScopes.length === 0 && toolMetadata.oauth.requiredScopes === undefined) { - logger.error(`[${requestId}] Tool has no trusted OAuth scope policy`, { + logger.error('Tool has no trusted OAuth scope policy', { + requestId, toolId, providerId: toolMetadata.oauth.provider, }) @@ -614,7 +616,8 @@ export async function resolveCredentialAccessToken( } } catch (error) { if (error instanceof ManagedOAuthCredentialError) { - logger.warn(`[${requestId}] Managed OAuth credential rejected`, { + logger.warn('Managed OAuth credential rejected', { + requestId, credentialId: resolved.credentialId, code: error.code, }) diff --git a/apps/sim/lib/realtime/event-stream-route.ts b/apps/sim/lib/realtime/event-stream-route.ts index ad4fd2e8a82..72fa358bc83 100644 --- a/apps/sim/lib/realtime/event-stream-route.ts +++ b/apps/sim/lib/realtime/event-stream-route.ts @@ -41,7 +41,7 @@ export function createEventStreamResponse( ): NextResponse { const { requestId, streamId, fromEventId, getLatestEventId, readEventsSince, label } = options - logger.info(`[${requestId}] ${label} event stream opened`, { streamId, fromEventId }) + logger.info(`${label} event stream opened`, { requestId, streamId, fromEventId }) const encoder = new TextEncoder() let closed = false @@ -137,7 +137,8 @@ export function createEventStreamResponse( } catch {} } } catch (error) { - logger.error(`[${requestId}] ${label} event stream error`, { + logger.error(`${label} event stream error`, { + requestId, streamId, error: toError(error).message, }) @@ -150,7 +151,7 @@ export function createEventStreamResponse( }, cancel() { closed = true - logger.info(`[${requestId}] Client disconnected from ${label} event stream`, { streamId }) + logger.info(`Client disconnected from ${label} event stream`, { requestId, streamId }) }, }) diff --git a/apps/sim/lib/table/backfill-runner.ts b/apps/sim/lib/table/backfill-runner.ts index ad93dc810f7..9ed5fe0f53d 100644 --- a/apps/sim/lib/table/backfill-runner.ts +++ b/apps/sim/lib/table/backfill-runner.ts @@ -7,6 +7,7 @@ import { getValueAtPath, isRecordLike } from '@sim/utils/object' import { and, asc, count, eq, gt, inArray } from 'drizzle-orm' import { dispatchBackgroundTask } from '@/lib/core/async-jobs/background-task' import { MATERIALIZE_CONCURRENCY, mapWithConcurrency } from '@/lib/core/utils/concurrency' +import { generateRequestId } from '@/lib/core/utils/request' import { type FunctionalExecutionDataSource, getFunctionalBlockOutput, @@ -280,7 +281,7 @@ async function processBackfillPage(opts: { export async function runTableBackfill(payload: TableBackfillPayload): Promise { const { jobId, tableId, groupId, outputs, overwrite, actorUserId, capabilityGovernedUserId } = payload - const requestId = generateId().slice(0, 8) + const requestId = generateRequestId() try { const table = await getTableById(tableId, { includeArchived: true }) @@ -321,16 +322,16 @@ export async function runTableBackfill(payload: TableBackfillPayload): Promise {}) void appendTableEvent({ kind: 'job', @@ -406,7 +407,8 @@ export async function maybeBackfillGroupOutputs(opts: { const claimed = await markTableJobRunning(table.id, jobId, 'backfill', jobPayload) if (!claimed) { logger.warn( - `[${requestId}] Skipping backfill for table ${table.id} group ${groupId}: another job is running` + `Skipping backfill for table ${table.id} group ${groupId}: another job is running`, + { requestId } ) return } @@ -432,9 +434,9 @@ export async function maybeBackfillGroupOutputs(opts: { // the data stays backfillable. const { releaseJobClaim } = await import('@/lib/table/jobs/service') await releaseJobClaim(table.id, jobId).catch(() => {}) - logger.warn( - `[${requestId}] Backfill dispatch failed for table ${table.id} group ${groupId}; skipping`, - { error: getErrorMessage(error) } - ) + logger.warn(`Backfill dispatch failed for table ${table.id} group ${groupId}; skipping`, { + requestId, + error: getErrorMessage(error), + }) } } diff --git a/apps/sim/lib/table/columns/service.ts b/apps/sim/lib/table/columns/service.ts index 6c70903dc6f..a1dc18d5cd5 100644 --- a/apps/sim/lib/table/columns/service.ts +++ b/apps/sim/lib/table/columns/service.ts @@ -239,7 +239,7 @@ export async function addTableColumn( ) ) - logger.info(`[${requestId}] Added column "${column.name}" to table ${tableId}`) + logger.info(`Added column "${column.name}" to table ${tableId}`, { requestId }) return { ...table, @@ -324,7 +324,8 @@ export async function renameColumn( ) logger.info( - `[${requestId}] Renamed column "${actualOldName}" to "${data.newName}" in table ${data.tableId}` + `Renamed column "${actualOldName}" to "${data.newName}" in table ${data.tableId}`, + { requestId } ) return { ...table, schema: updatedSchema, updatedAt: now } }, @@ -389,12 +390,14 @@ function stripColumnDataInBackground( }) }) logger.info( - `[${requestId}] Background-stripped deleted column data [${columnIds.join(', ')}] from table ${tableId}` + `Background-stripped deleted column data [${columnIds.join(', ')}] from table ${tableId}`, + { requestId } ) } catch (err) { logger.error( - `[${requestId}] Background column-data strip failed for table ${tableId} [${columnIds.join(', ')}]:`, - err + `Background column-data strip failed for table ${tableId} [${columnIds.join(', ')}]:`, + err, + { requestId } ) } })() @@ -485,7 +488,7 @@ export async function deleteColumn( }) } - logger.info(`[${requestId}] Deleted column "${actualName}" from table ${data.tableId}`) + logger.info(`Deleted column "${actualName}" from table ${data.tableId}`, { requestId }) return { def: { ...table, schema: updatedSchema, metadata: updatedMetadata, updatedAt: now }, @@ -982,7 +985,8 @@ export async function updateColumnType( ) logger.info( - `[${requestId}] Changed column "${column.name}" type from "${column.type}" to "${data.newType}" in table ${data.tableId}` + `Changed column "${column.name}" type from "${column.type}" to "${data.newType}" in table ${data.tableId}`, + { requestId } ) return { ...table, schema: updatedSchema, updatedAt: now } @@ -1052,9 +1056,9 @@ export async function updateColumnConstraints( ) ) - logger.info( - `[${requestId}] Updated constraints for column "${column.name}" in table ${data.tableId}` - ) + logger.info(`Updated constraints for column "${column.name}" in table ${data.tableId}`, { + requestId, + }) return { ...table, schema: updatedSchema, updatedAt: now } }, @@ -1245,9 +1249,9 @@ export async function updateColumnOptions( const updated = await persistColumns(trx, table, updatedColumns) - logger.info( - `[${requestId}] Updated options for column "${column.name}" in table ${data.tableId}` - ) + logger.info(`Updated options for column "${column.name}" in table ${data.tableId}`, { + requestId, + }) return updated }, @@ -1341,7 +1345,8 @@ export async function updateColumnCurrency( ) logger.info( - `[${requestId}] Set currency for column "${column.name}" to "${updatedColumn.currencyCode}" in table ${data.tableId}` + `Set currency for column "${column.name}" to "${updatedColumn.currencyCode}" in table ${data.tableId}`, + { requestId } ) return { ...table, schema: updatedSchema, updatedAt: now } diff --git a/apps/sim/lib/table/delete-runner.ts b/apps/sim/lib/table/delete-runner.ts index d6823ae6fac..7b164b7fb7d 100644 --- a/apps/sim/lib/table/delete-runner.ts +++ b/apps/sim/lib/table/delete-runner.ts @@ -1,7 +1,7 @@ import { createLogger } from '@sim/logger' import { getErrorMessage, toError } from '@sim/utils/errors' -import { generateId } from '@sim/utils/id' import { truncate } from '@sim/utils/string' +import { generateRequestId } from '@/lib/core/utils/request' import type { Filter, TableDefinition } from '@/lib/table' import { TABLE_LIMITS, USER_TABLE_ROWS_SQL_NAME } from '@/lib/table/constants' import { appendTableEvent, signalTableRowsChanged } from '@/lib/table/events' @@ -63,7 +63,7 @@ export interface TableDeletePayload { */ export async function runTableDelete(payload: TableDeletePayload): Promise { const { jobId, tableId, workspaceId, filter, excludeRowIds, cutoff, maxRows } = payload - const requestId = generateId().slice(0, 8) + const requestId = generateRequestId() const budget = maxRows ?? Number.POSITIVE_INFINITY // Whether any row was actually deleted this run. Signalled in `finally` so open editors refetch the @@ -82,7 +82,8 @@ export async function runTableDelete(payload: TableDeletePayload): Promise // releases the job slot without burning this task's remaining // `maxAttempts` on a deterministic failure. const cancelForLock = async (processedSoFar: number): Promise => { - logger.info(`[${requestId}] Delete job stopped — table is delete-locked`, { + logger.info('Delete job stopped — table is delete-locked', { + requestId, tableId, jobId, processedSoFar, @@ -238,19 +239,17 @@ export async function runTableDelete(payload: TableDeletePayload): Promise status: 'ready', progress: processed, }) - logger.info(`[${requestId}] Delete complete`, { tableId, rows: processed }) + logger.info('Delete complete', { requestId, tableId, rows: processed }) } else { - logger.info( - `[${requestId}] Delete finished but no longer owns the run (canceled/superseded)`, - { - tableId, - jobId, - } - ) + logger.info('Delete finished but no longer owns the run (canceled/superseded)', { + requestId, + tableId, + jobId, + }) } } catch (err) { if (err instanceof JobSupersededError) { - logger.info(`[${requestId}] Delete superseded by a newer run; stopping`, { tableId, jobId }) + logger.info('Delete superseded by a newer run; stopping', { requestId, tableId, jobId }) return } // Rethrow the root cause, not the wrapper: drizzle query errors embed the full SQL + params @@ -259,7 +258,7 @@ export async function runTableDelete(payload: TableDeletePayload): Promise // must already be the thrown error's own `message`. const cause = toError(err).cause const error = cause ? toError(cause) : toError(err) - logger.error(`[${requestId}] Delete failed for table ${tableId}:`, error) + logger.error(`Delete failed for table ${tableId}:`, error, { requestId }) throw error } finally { // Guaranteed final grid refetch on every exit — completion, cancel/supersede, mid-batch lock, or a diff --git a/apps/sim/lib/table/dispatcher.ts b/apps/sim/lib/table/dispatcher.ts index 804725d82e9..f41c57496ab 100644 --- a/apps/sim/lib/table/dispatcher.ts +++ b/apps/sim/lib/table/dispatcher.ts @@ -428,7 +428,7 @@ export async function dispatcherStep( ): Promise { const dispatch = await readDispatch(dispatchId) if (!dispatch) { - logger.warn(`[${dispatchId}] dispatch row missing — aborting`) + logger.warn('dispatch row missing — aborting', { dispatchId }) return 'done' } if (dispatch.status === 'cancelled' || dispatch.status === 'complete') return 'done' @@ -436,7 +436,7 @@ export async function dispatcherStep( const { getTableById } = await import('@/lib/table/service') const table = await getTableById(dispatch.tableId) if (!table) { - logger.warn(`[${dispatchId}] table ${dispatch.tableId} missing — completing dispatch`) + logger.warn(`table ${dispatch.tableId} missing — completing dispatch`, { dispatchId }) await completeDispatchIfActive(dispatchId) return 'done' } @@ -482,7 +482,7 @@ export async function dispatcherStep( * completion path and overwrite `cancelled` with `complete`. */ if (claimed.length === 0) { - logger.info(`[${dispatchId}] dispatch was cancelled before this step claimed it`) + logger.info('dispatch was cancelled before this step claimed it', { dispatchId }) return 'done' } // Announce the dispatch the moment it starts — before the first window's @@ -710,9 +710,7 @@ export async function dispatcherStep( try { await queue.batchEnqueueAndWait('workflow-group-cell', items) } catch (err) { - logger.error(`[${dispatchId}] batch dispatch failed`, { - error: toError(err).message, - }) + logger.error('batch dispatch failed', { dispatchId, error: toError(err).message }) // These rows never actually ran, so they must not consume the row cap — // otherwise a transient failure on the only window of a `max: N` run would // exhaust the budget and complete the dispatch with zero rows started. diff --git a/apps/sim/lib/table/export-runner.ts b/apps/sim/lib/table/export-runner.ts index 7641dff079b..4b41848fa8a 100644 --- a/apps/sim/lib/table/export-runner.ts +++ b/apps/sim/lib/table/export-runner.ts @@ -1,7 +1,7 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' -import { generateId } from '@sim/utils/id' import { neutralizeCsvFormula, toCsvRow } from '@/lib/core/utils/csv' +import { generateRequestId } from '@/lib/core/utils/request' import { namedRowMapper } from '@/lib/table/cell-format' import { getColumnId } from '@/lib/table/column-keys' import { appendTableEvent } from '@/lib/table/events' @@ -47,7 +47,7 @@ export interface TableExportPayload { */ export async function runTableExport(payload: TableExportPayload): Promise { const { jobId, tableId, workspaceId, format } = payload - const requestId = generateId().slice(0, 8) + const requestId = generateRequestId() let handle: MultipartUploadHandle | null = null let uploadedKey: string | null = null @@ -141,14 +141,15 @@ export async function runTableExport(payload: TableExportPayload): Promise status: 'ready', progress: exported, }) - logger.info(`[${requestId}] Export complete`, { tableId, rows: exported, format }) + logger.info('Export complete', { requestId, tableId, rows: exported, format }) } else { // Canceled at the very end — the file is orphaned; remove it (janitor would otherwise // only catch it via the pruned job's resultKey). try { await deleteFile({ key: uploaded.key, context: 'workspace' }) } catch (cleanupError) { - logger.warn(`[${requestId}] Failed to delete superseded export`, { + logger.warn('Failed to delete superseded export', { + requestId, tableId, workspaceId, jobId, @@ -156,7 +157,7 @@ export async function runTableExport(payload: TableExportPayload): Promise error: getErrorMessage(cleanupError, 'Unknown cleanup error'), }) } - logger.info(`[${requestId}] Export finished but no longer owns the run`, { tableId, jobId }) + logger.info('Export finished but no longer owns the run', { requestId, tableId, jobId }) } } catch (err) { // A partial/orphaned upload from this attempt is useless — clean it up best-effort. An @@ -166,7 +167,8 @@ export async function runTableExport(payload: TableExportPayload): Promise try { await deleteFile({ key: uploadedKey, context: 'workspace' }) } catch (cleanupError) { - logger.warn(`[${requestId}] Failed to delete incomplete export`, { + logger.warn('Failed to delete incomplete export', { + requestId, tableId, workspaceId, jobId, @@ -178,7 +180,8 @@ export async function runTableExport(payload: TableExportPayload): Promise try { await handle.abort() } catch (cleanupError) { - logger.warn(`[${requestId}] Failed to abort incomplete export`, { + logger.warn('Failed to abort incomplete export', { + requestId, tableId, workspaceId, jobId, @@ -187,14 +190,15 @@ export async function runTableExport(payload: TableExportPayload): Promise } } if (err instanceof JobSupersededError) { - logger.info(`[${requestId}] Export superseded/canceled; stopping`, { tableId, jobId }) + logger.info('Export superseded/canceled; stopping', { requestId, tableId, jobId }) } else { const message = getErrorMessage(err, 'Export failed') - logger.error(`[${requestId}] Export failed for table ${tableId}:`, err) + logger.error(`Export failed for table ${tableId}:`, err, { requestId }) try { await markJobFailedInWorkspace(tableId, workspaceId, jobId, message) } catch (failureError) { - logger.error(`[${requestId}] Failed to mark export job failed`, { + logger.error('Failed to mark export job failed', { + requestId, tableId, workspaceId, jobId, diff --git a/apps/sim/lib/table/export-stream.ts b/apps/sim/lib/table/export-stream.ts index 307f2f7534d..f6c4f5b44c5 100644 --- a/apps/sim/lib/table/export-stream.ts +++ b/apps/sim/lib/table/export-stream.ts @@ -74,12 +74,9 @@ export function createTableExportStream( if (format === 'json') controller.enqueue(encoder.encode(']')) controller.close() - logger.info(`[${requestId}] Exported table ${table.id}`, { - format, - rowCount: table.rowCount, - }) + logger.info(`Exported table ${table.id}`, { requestId, format, rowCount: table.rowCount }) } catch (err) { - logger.error(`[${requestId}] Export failed for table ${table.id}`, err) + logger.error(`Export failed for table ${table.id}`, err, { requestId }) controller.error(err) } }, diff --git a/apps/sim/lib/table/import-data.ts b/apps/sim/lib/table/import-data.ts index fc5276a6c28..327034e11d6 100644 --- a/apps/sim/lib/table/import-data.ts +++ b/apps/sim/lib/table/import-data.ts @@ -170,7 +170,7 @@ export async function bulkInsertImportBatch( if (inserted !== rowsToInsert.length) { throw new Error('Bulk table import inserted an unexpected row count') } - logger.info(`[${requestId}] Bulk-imported ${inserted} rows into table ${data.tableId}`) + logger.info(`Bulk-imported ${inserted} rows into table ${data.tableId}`, { requestId }) return { inserted, lastOrderKey: orderKeys[orderKeys.length - 1] ?? data.afterOrderKey ?? null, diff --git a/apps/sim/lib/table/import-runner.ts b/apps/sim/lib/table/import-runner.ts index dd4efa4fd2d..0185bc8ed60 100644 --- a/apps/sim/lib/table/import-runner.ts +++ b/apps/sim/lib/table/import-runner.ts @@ -1,9 +1,9 @@ import { type Readable, Transform } from 'node:stream' import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' -import { generateId } from '@sim/utils/id' import { truncate } from '@sim/utils/string' import { OrchestrationError } from '@/lib/core/orchestration/types' +import { generateRequestId } from '@/lib/core/utils/request' import { captureServerEvent } from '@/lib/posthog/server' import { buildAutoMapping, @@ -101,7 +101,7 @@ export interface TableImportPayload { export async function runTableImport(payload: TableImportPayload): Promise { const { importId, tableId, workspaceId, userId, fileKey, fileName, delimiter, mode } = payload const storageContext = payload.storageContext ?? 'workspace' - const requestId = generateId().slice(0, 8) + const requestId = generateRequestId() // Hoisted so `finally` can destroy it on any failure — otherwise the storage HTTP body leaks // open until it times out. let source: Readable | undefined @@ -438,7 +438,7 @@ export async function runTableImport(payload: TableImportPayload): Promise }, { groups: { workspace: workspaceId } } ) - logger.warn(`[${requestId}] Import has no data rows`, { tableId, fileName }) + logger.warn('Import has no data rows', { requestId, tableId, fileName }) return } await resolveSetup() @@ -473,31 +473,27 @@ export async function runTableImport(payload: TableImportPayload): Promise }, { groups: { workspace: workspaceId } } ) - logger.info(`[${requestId}] Import complete`, { tableId, fileName, mode, rows: inserted }) + logger.info('Import complete', { requestId, tableId, fileName, mode, rows: inserted }) } else { - logger.info( - `[${requestId}] Import finished but no longer owns the run (canceled/superseded)`, - { - tableId, - importId, - } - ) + logger.info('Import finished but no longer owns the run (canceled/superseded)', { + requestId, + tableId, + importId, + }) } } catch (err) { if (err instanceof ImportSupersededError) { // A newer import owns the table now — leave its status alone and just stop. - logger.info(`[${requestId}] Import superseded by a newer run; stopping`, { - tableId, - importId, - }) + logger.info('Import superseded by a newer run; stopping', { requestId, tableId, importId }) } else { const message = getErrorMessage(err, 'Import failed') - logger.error(`[${requestId}] Import failed for table ${tableId}:`, err) + logger.error(`Import failed for table ${tableId}:`, err, { requestId }) // Scoped to importId — a no-op if a newer import has taken over. try { await markJobFailedInWorkspace(tableId, workspaceId, importId, message) } catch (failureError) { - logger.error(`[${requestId}] Failed to mark import job failed`, { + logger.error('Failed to mark import job failed', { + requestId, tableId, workspaceId, importId, @@ -541,7 +537,8 @@ export async function runTableImport(payload: TableImportPayload): Promise rejectedSamples: rejections.summary.rejectedSamples, }) } catch (summaryError) { - logger.error(`[${requestId}] Failed to record import rejections`, { + logger.error('Failed to record import rejections', { + requestId, tableId, importId, error: getErrorMessage(summaryError, 'Unknown rejection-summary error'), @@ -553,7 +550,7 @@ export async function runTableImport(payload: TableImportPayload): Promise // persistent workspace files (deleteSourceFile: false). if (payload.deleteSourceFile !== false) { await deleteFile({ key: fileKey, context: storageContext }).catch((err) => { - logger.warn(`[${requestId}] Failed to delete imported file`, { fileKey, err }) + logger.warn('Failed to delete imported file', { requestId, fileKey, err }) }) } } diff --git a/apps/sim/lib/table/orchestration/columns.ts b/apps/sim/lib/table/orchestration/columns.ts index 48f8f5ffd3d..bc35e5beaa9 100644 --- a/apps/sim/lib/table/orchestration/columns.ts +++ b/apps/sim/lib/table/orchestration/columns.ts @@ -247,7 +247,8 @@ export async function performUpdateTableColumn( ) } } catch (error) { - logger.error(`[${requestId}] Failed to update column "${columnName}" on table ${tableId}`, { + logger.error(`Failed to update column "${columnName}" on table ${tableId}`, { + requestId, error, }) return classify(error) diff --git a/apps/sim/lib/table/orchestration/import.ts b/apps/sim/lib/table/orchestration/import.ts index badac316e35..3777657b700 100644 --- a/apps/sim/lib/table/orchestration/import.ts +++ b/apps/sim/lib/table/orchestration/import.ts @@ -81,7 +81,7 @@ function classifyImportFailure(error: unknown, requestId: string, tableId: strin return { ...fail(error.message, 'locked'), lock: error.lock } } if (error instanceof OrchestrationError) return fail(error.message, error.code) - logger.error(`[${requestId}] CSV import failed for table ${tableId}`, { error }) + logger.error(`CSV import failed for table ${tableId}`, { requestId, error }) return fail(toError(error).message, 'internal') } @@ -381,7 +381,8 @@ export async function performTableCsvImport( ) const rejected = rejectionFields(rejections, cellsRejected) if (rejected.rejections) { - logger.warn(`[${requestId}] CSV import lost source data`, { + logger.warn('CSV import lost source data', { + requestId, tableId: table.id, fileName, ...rejected.rejections, @@ -415,7 +416,8 @@ export async function performTableCsvImport( // global db connection and would otherwise see no rows. dispatchAfterBatchInsert(finalTable, inserted, requestId, userId, capabilityGovernedUserId) - logger.info(`[${requestId}] Append CSV imported`, { + logger.info('Append CSV imported', { + requestId, tableId: table.id, fileName, inserted: inserted.length, @@ -433,7 +435,8 @@ export async function performTableCsvImport( requestId ) - logger.info(`[${requestId}] Replace CSV imported`, { + logger.info('Replace CSV imported', { + requestId, tableId: table.id, fileName, deleted: result.deletedCount, @@ -645,7 +648,8 @@ export async function performCreateTableFromCsv( return classifyImportFailure(error, requestId, state?.table.id ?? 'unknown') } - logger.info(`[${requestId}] CSV imported`, { + logger.info('CSV imported', { + requestId, tableId: state.table.id, fileName, columns: state.schema.columns.length, @@ -654,7 +658,8 @@ export async function performCreateTableFromCsv( const rejected = rejectionFields(rejections.summary, cellsRejected) if (rejected.rejections) { - logger.warn(`[${requestId}] CSV import lost source data`, { + logger.warn('CSV import lost source data', { + requestId, tableId: state.table.id, fileName, ...rejected.rejections, diff --git a/apps/sim/lib/table/orchestration/restore.ts b/apps/sim/lib/table/orchestration/restore.ts index 1aff4de5da2..14764d89078 100644 --- a/apps/sim/lib/table/orchestration/restore.ts +++ b/apps/sim/lib/table/orchestration/restore.ts @@ -36,7 +36,7 @@ export async function performRestoreTable( await restoreTable(tableId, requestId) const table = (await getTableById(tableId)) ?? archivedTable - logger.info(`[${requestId}] Restored table ${tableId}`) + logger.info(`Restored table ${tableId}`, { requestId }) recordAudit({ workspaceId: archivedTable.workspaceId, @@ -54,7 +54,7 @@ export async function performRestoreTable( return { success: true, table } } catch (error) { - logger.error(`[${requestId}] Failed to restore table ${tableId}`, { error }) + logger.error(`Failed to restore table ${tableId}`, { requestId, error }) if (error instanceof TableConflictError) { return { success: false, error: error.message, errorCode: 'conflict' } } diff --git a/apps/sim/lib/table/orchestration/tables.ts b/apps/sim/lib/table/orchestration/tables.ts index 3c0abcf6ae0..ce62bf398c5 100644 --- a/apps/sim/lib/table/orchestration/tables.ts +++ b/apps/sim/lib/table/orchestration/tables.ts @@ -62,7 +62,7 @@ export async function performDeleteTable( if (error instanceof OrchestrationError) { return { success: false, error: error.message, errorCode: error.code } } - logger.error(`[${requestId}] Failed to delete table ${table.id}`, { error }) + logger.error(`Failed to delete table ${table.id}`, { requestId, error }) return { success: false, error: toError(error).message, errorCode: 'internal' } } @@ -126,7 +126,7 @@ export async function performDeleteTableRow( if (error instanceof OrchestrationError) { return { success: false, error: error.message, errorCode: error.code } } - logger.error(`[${requestId}] Failed to delete row ${rowId} from table ${table.id}`, { error }) + logger.error(`Failed to delete row ${rowId} from table ${table.id}`, { requestId, error }) return { success: false, error: toError(error).message, errorCode: 'internal' } } } @@ -164,7 +164,7 @@ function classifyTableMutation(error: unknown, requestId: string, tableId: strin if (error instanceof OrchestrationError) { return { success: false as const, error: error.message, errorCode: error.code } } - logger.error(`[${requestId}] Table mutation failed for ${tableId}`, { error }) + logger.error(`Table mutation failed for ${tableId}`, { requestId, error }) return { success: false as const, error: toError(error).message, diff --git a/apps/sim/lib/table/rows/service.ts b/apps/sim/lib/table/rows/service.ts index 5d24925a5a7..0f7a13d12a4 100644 --- a/apps/sim/lib/table/rows/service.ts +++ b/apps/sim/lib/table/rows/service.ts @@ -251,7 +251,7 @@ export async function insertRow( limit: rowLimit, }) - logger.info(`[${requestId}] Inserted row ${rowId} into table ${data.tableId}`) + logger.info(`Inserted row ${rowId} into table ${data.tableId}`, { requestId }) const insertedRow: TableRow = { id: row.id, @@ -282,7 +282,7 @@ export async function insertRow( requestId, triggeredByUserId: data.userId, capabilityGovernedUserId: data.capabilityGovernedUserId, - }).catch((err) => logger.error(`[${requestId}] auto-dispatch (insertRow) failed:`, err)) + }).catch((err) => logger.error('auto-dispatch (insertRow) failed:', err, { requestId })) return insertedRow } @@ -442,7 +442,7 @@ export async function batchInsertRowsWithTx( }, }) - logger.info(`[${requestId}] Batch inserted ${data.rows.length} rows into table ${data.tableId}`) + logger.info(`Batch inserted ${data.rows.length} rows into table ${data.tableId}`, { requestId }) const result: TableRow[] = insertedRows.map((r) => ({ id: r.id, @@ -495,7 +495,7 @@ export function dispatchAfterBatchInsert( requestId, triggeredByUserId: actorUserId, capabilityGovernedUserId, - }).catch((err) => logger.error(`[${requestId}] auto-dispatch (batchInsertRows) failed:`, err)) + }).catch((err) => logger.error('auto-dispatch (batchInsertRows) failed:', err, { requestId })) } /** @@ -691,7 +691,8 @@ export async function replaceTableRowsWithTx( } logger.info( - `[${requestId}] Replaced rows in table ${data.tableId}: deleted ${deletedCount}, inserted ${insertedCount}` + `Replaced rows in table ${data.tableId}: deleted ${deletedCount}, inserted ${insertedCount}`, + { requestId } ) return { deletedCount, insertedCount } @@ -959,9 +960,9 @@ export async function upsertRow( } }) - logger.debug( - `[${requestId}] Upserted (${result.operation}) row ${result.row.id} in table ${data.tableId}` - ) + logger.debug(`Upserted (${result.operation}) row ${result.row.id} in table ${data.tableId}`, { + requestId, + }) if (result.operation === 'insert') { notifyTableRowUsage({ @@ -1002,7 +1003,7 @@ export async function upsertRow( requestId, triggeredByUserId: data.userId, capabilityGovernedUserId: data.capabilityGovernedUserId, - }).catch((err) => logger.error(`[${requestId}] auto-dispatch (upsertRow) failed:`, err)) + }).catch((err) => logger.error('auto-dispatch (upsertRow) failed:', err, { requestId })) return result } @@ -1175,7 +1176,8 @@ export async function findRowMatches( })) logger.info( - `[${requestId}] Find "${options.q}" in table ${table.id}: ${matches.length} match(es)${truncated ? ' (truncated)' : ''}` + `Find "${options.q}" in table ${table.id}: ${matches.length} match(es)${truncated ? ' (truncated)' : ''}`, + { requestId } ) return { matches, truncated } @@ -1331,7 +1333,8 @@ export async function queryRows( : null logger.debug( - `[${requestId}] Queried ${rows.length} rows from table ${table.id} (total: ${totalCount}, bytes: ${fetched.bytes}, more: ${fetched.hasMore})` + `Queried ${rows.length} rows from table ${table.id} (total: ${totalCount}, bytes: ${fetched.bytes}, more: ${fetched.hasMore})`, + { requestId } ) const mappedRows = rows.map((r) => ({ @@ -2111,7 +2114,7 @@ export async function updateRow( throw err } - logger.info(`[${requestId}] Updated row ${data.rowId} in table ${data.tableId}`) + logger.info(`Updated row ${data.rowId} in table ${data.tableId}`, { requestId }) const updatedRow: TableRow = { ...toRowSummary(persistedRow), @@ -2169,7 +2172,7 @@ export async function updateRow( capabilityGovernedUserId: data.capabilityGovernedUserId, }) } catch (err) { - logger.error(`[${requestId}] cancel+rerun for in-flight downstream groups failed:`, err) + logger.error('cancel+rerun for in-flight downstream groups failed:', err, { requestId }) } })() } @@ -2182,7 +2185,7 @@ export async function updateRow( requestId, triggeredByUserId: data.actorUserId, capabilityGovernedUserId: data.capabilityGovernedUserId, - }).catch((err) => logger.error(`[${requestId}] auto-dispatch (updateRow) failed:`, err)) + }).catch((err) => logger.error('auto-dispatch (updateRow) failed:', err, { requestId })) return updatedRow } @@ -2210,7 +2213,7 @@ export async function deleteRow( }) if (!deleted) throw new OrchestrationError('not_found', 'Row not found') - logger.info(`[${requestId}] Deleted row ${rowId} from table ${table.id}`) + logger.info(`Deleted row ${rowId} from table ${table.id}`, { requestId }) void dispatchDeleteTriggers(table, [deleted], requestId) } @@ -2400,8 +2403,8 @@ async function persistBulkUpdateBatch(params: { } if (skippedRowIds.length > 0) { logger.warn( - `[${requestId}] Skipping rows concurrently changed to values that cannot accept the bulk patch`, - { tableId: table.id, rowIds: skippedRowIds } + 'Skipping rows concurrently changed to values that cannot accept the bulk patch', + { requestId, tableId: table.id, rowIds: skippedRowIds } ) } @@ -2474,7 +2477,7 @@ function dispatchBulkUpdateEffects( triggeredByUserId: actorUserId, capabilityGovernedUserId, }).catch((error) => - logger.error(`[${requestId}] auto-dispatch (updateRowsByFilter) failed:`, error) + logger.error('auto-dispatch (updateRowsByFilter) failed:', error, { requestId }) ) } @@ -2593,7 +2596,7 @@ export async function updateRowsByFilter( if (batchRows.length < TABLE_LIMITS.UPDATE_BATCH_SIZE) break } - logger.info(`[${requestId}] Updated ${affectedRowIds.length} rows in table ${table.id}`) + logger.info(`Updated ${affectedRowIds.length} rows in table ${table.id}`, { requestId }) return { affectedCount: affectedRowIds.length, affectedRowIds } } @@ -2636,7 +2639,7 @@ export async function updateRowsByFilter( }) const { affectedRowIds } = persisted - logger.info(`[${requestId}] Updated ${affectedRowIds.length} rows in table ${table.id}`) + logger.info(`Updated ${affectedRowIds.length} rows in table ${table.id}`, { requestId }) dispatchBulkUpdateEffects( persisted.table, persisted.rows, @@ -2917,7 +2920,7 @@ export async function batchUpdateRows( }) }) - logger.info(`[${requestId}] Batch updated ${affectedRowIds.length} rows in table ${data.tableId}`) + logger.info(`Batch updated ${affectedRowIds.length} rows in table ${data.tableId}`, { requestId }) const affectedRowIdSet = new Set(affectedRowIds) const oldRowsForTrigger = new Map( @@ -2975,10 +2978,9 @@ export async function batchUpdateRows( }) } } catch (err) { - logger.error( - `[${requestId}] cancel+rerun for in-flight downstream groups (batch) failed:`, - err - ) + logger.error('cancel+rerun for in-flight downstream groups (batch) failed:', err, { + requestId, + }) } })() } @@ -2992,7 +2994,7 @@ export async function batchUpdateRows( requestId, triggeredByUserId: data.actorUserId, capabilityGovernedUserId: data.capabilityGovernedUserId, - }).catch((err) => logger.error(`[${requestId}] auto-dispatch (batchUpdateRows) failed:`, err)) + }).catch((err) => logger.error('auto-dispatch (batchUpdateRows) failed:', err, { requestId })) } return { @@ -3083,7 +3085,7 @@ export async function deleteRowsByFilter( if (deletedRowIds.length === 0) return { affectedCount: 0, affectedRowIds: [] } - logger.info(`[${requestId}] Deleted ${deletedRowIds.length} rows from table ${table.id}`) + logger.info(`Deleted ${deletedRowIds.length} rows from table ${table.id}`, { requestId }) return { affectedCount: deletedRowIds.length, @@ -3118,7 +3120,7 @@ export async function deleteRowsByIds( const deletedIdSet = new Set(deletedIds) const missingRowIds = uniqueRequestedRowIds.filter((id) => !deletedIdSet.has(id)) - logger.info(`[${requestId}] Deleted ${deletedIds.length} rows by ID from table ${data.tableId}`) + logger.info(`Deleted ${deletedIds.length} rows by ID from table ${data.tableId}`, { requestId }) return { deletedCount: deletedIds.length, deletedRowIds: deletedIds, diff --git a/apps/sim/lib/table/service.ts b/apps/sim/lib/table/service.ts index 7f216babd45..5e1390787d6 100644 --- a/apps/sim/lib/table/service.ts +++ b/apps/sim/lib/table/service.ts @@ -726,7 +726,7 @@ export async function createTable( }) } - logger.info(`[${requestId}] Created table ${tableId} in workspace ${data.workspaceId}`) + logger.info(`Created table ${tableId} in workspace ${data.workspaceId}`, { requestId }) // Live tables list: tell everyone viewing this workspace's tables to refetch. await notifyWorkspaceTablesChanged(data.workspaceId) @@ -842,7 +842,8 @@ export async function addTableColumnsWithTx( ) logger.info( - `[${requestId}] Added ${additions.length} column(s) to table ${table.id}: ${additions.map((c) => c.name).join(', ')}` + `Added ${additions.length} column(s) to table ${table.id}: ${additions.map((c) => c.name).join(', ')}`, + { requestId } ) return { @@ -921,7 +922,7 @@ export async function renameTable( const { workspaceId } = result[0] - logger.info(`[${requestId}] Renamed table ${tableId} to "${newName}"`) + logger.info(`Renamed table ${tableId} to "${newName}"`, { requestId }) // Live tables list: a rename changes the list result, so everyone viewing refetches. if (workspaceId && !options?.skipNotify) await notifyWorkspaceTablesChanged(workspaceId) @@ -958,7 +959,7 @@ export async function updateTableDescription( throw new OrchestrationError('not_found', `Table ${tableId} not found`) } - logger.info(`[${requestId}] Updated description for table ${tableId}`) + logger.info(`Updated description for table ${tableId}`, { requestId }) await notifyWorkspaceTablesChanged(workspaceId) return result[0] } @@ -1020,7 +1021,7 @@ export async function moveTableToFolder( const { name } = result[0] - logger.info(`[${requestId}] Moved table ${tableId} to folder ${folderId ?? 'root'}`) + logger.info(`Moved table ${tableId} to folder ${folderId ?? 'root'}`, { requestId }) // Live tables list: a move changes each table's folder placement in the list result. if (options?.notify ?? true) await notifyWorkspaceTablesChanged(workspaceId) @@ -1056,10 +1057,10 @@ export async function updateTableLocks( }) await appendTableEvent({ kind: 'definition', tableId, reason: 'locks' }).catch((error) => { - logger.warn(`[${requestId}] Failed to emit lock-change event for table ${tableId}`, { error }) + logger.warn(`Failed to emit lock-change event for table ${tableId}`, { requestId, error }) }) - logger.info(`[${requestId}] Updated locks for table ${tableId}`) + logger.info(`Updated locks for table ${tableId}`, { requestId }) return { table: updated, previousLocks } } @@ -1194,7 +1195,7 @@ export async function deleteTable( } // Otherwise the table is missing or already archived — a silent no-op, as before. } - logger.info(`[${requestId}] Archived table ${tableId}`) + logger.info(`Archived table ${tableId}`, { requestId }) // Live tables list: only on a genuine archive (a no-op/already-archived delete changes nothing). // Skipped under a folder cascade — deleteFolder fires one folder-level notify for the whole subtree, // so we don't spam one relay call per archived table. @@ -1302,7 +1303,7 @@ export async function restoreTable( } } - logger.info(`[${requestId}] Restored table ${tableId} as "${attemptedRestoreName}"`) + logger.info(`Restored table ${tableId} as "${attemptedRestoreName}"`, { requestId }) // Live tables list: a restore re-adds the table to the active list. Skipped under a folder cascade — // restoreFolder fires one folder-level notify for the whole subtree (see deleteTable). diff --git a/apps/sim/lib/table/snapshot-cache.ts b/apps/sim/lib/table/snapshot-cache.ts index c4d4b135580..4a7a4183be4 100644 --- a/apps/sim/lib/table/snapshot-cache.ts +++ b/apps/sim/lib/table/snapshot-cache.ts @@ -159,18 +159,19 @@ export async function getOrCreateTableSnapshot( const head = await headObject(key, SNAPSHOT_STORAGE_CONTEXT) if (head) { - logger.info(`[${requestId}] Snapshot hit`, { tableId: table.id, version, size: head.size }) + logger.info('Snapshot hit', { requestId, tableId: table.id, version, size: head.size }) return { key, size: head.size, version } } - logger.info(`[${requestId}] Snapshot miss; materializing`, { tableId: table.id, version }) + logger.info('Snapshot miss; materializing', { requestId, tableId: table.id, version }) const size = await materialize(table, key) const after = await readRowsVersion(table.id) if (after !== version) { // The table mutated mid-scan: the bytes under `key` may be torn. Re-key to the new version and // rebuild once (or reuse if a concurrent writer already stored it); drop the stale object. - logger.info(`[${requestId}] rows_version advanced during materialize; re-keying`, { + logger.info('rows_version advanced during materialize; re-keying', { + requestId, tableId: table.id, from: version, to: after, diff --git a/apps/sim/lib/table/trigger.ts b/apps/sim/lib/table/trigger.ts index d99f1d6da1e..b43b52d2a00 100644 --- a/apps/sim/lib/table/trigger.ts +++ b/apps/sim/lib/table/trigger.ts @@ -97,7 +97,8 @@ export async function fireTableTrigger( const { processPolledWebhookEvent } = await import('@/lib/webhooks/processor') logger.info( - `[${requestId}] Firing ${matching.length} trigger(s) for ${rows.length} ${eventType} event(s) in table ${tableId}` + `Firing ${matching.length} trigger(s) for ${rows.length} ${eventType} event(s) in table ${tableId}`, + { requestId } ) for (const { webhook: webhookData, workflow: workflowData } of matching) { @@ -149,18 +150,21 @@ export async function fireTableTrigger( if (!result.success) { logger.error( - `[${eventRequestId}] Failed to fire table trigger for row ${row.id}:`, + `Failed to fire table trigger for row ${row.id}:`, result.statusCode, - result.error + result.error, + { requestId: eventRequestId } ) } } catch (error) { - logger.error(`[${eventRequestId}] Error firing table trigger for row ${row.id}:`, error) + logger.error(`Error firing table trigger for row ${row.id}:`, error, { + requestId: eventRequestId, + }) } } } } catch (error) { - logger.error(`[${requestId}] Error in fireTableTrigger:`, error) + logger.error('Error in fireTableTrigger:', error, { requestId }) } } diff --git a/apps/sim/lib/table/workflow-columns.ts b/apps/sim/lib/table/workflow-columns.ts index 1b0f2d1de9d..02718652bef 100644 --- a/apps/sim/lib/table/workflow-columns.ts +++ b/apps/sim/lib/table/workflow-columns.ts @@ -916,7 +916,7 @@ export async function assertWorkflowGroupsDeployable( throw new OrchestrationError('validation', describe(undeployed[0])) } for (const group of undeployed) { - logger.warn(`[${options.requestId}] Skipping auto-run: ${describe(group)}`) + logger.warn(`Skipping auto-run: ${describe(group)}`, { requestId: options.requestId }) } const skipped = new Set(undeployed.map((group) => group.id)) return groups.filter((group) => !skipped.has(group.id)) @@ -1109,7 +1109,8 @@ export async function runWorkflowColumn(opts: { try { await cancelDispatchById(dispatchId) } catch (cleanupErr) { - logger.error(`[Cascade] [${requestId}] failed to cancel dispatch after prep failure`, { + logger.error('[Cascade] failed to cancel dispatch after prep failure', { + requestId, dispatchId, error: toError(cleanupErr).message, }) @@ -1126,14 +1127,15 @@ export async function runWorkflowColumn(opts: { // takes its "no dispatch created" path and rolls the optimistic bump back. const current = await readDispatch(dispatchId) if (!current || current.status === 'cancelled' || current.status === 'complete') { - logger.info( - `[Cascade] [${requestId}] dispatch ${dispatchId} cancelled during prep — not firing` - ) + logger.info(`[Cascade] dispatch ${dispatchId} cancelled during prep — not firing`, { + requestId, + }) return { dispatchId: null, shouldSignalRowsChanged } } logger.info( - `[Cascade] [${requestId}] dispatch ${dispatchId} table=${tableId} groups=[${targetGroupIds.join(',')}] rows=${rowIds ? `[${rowIds.join(',')}]` : 'all'} mode=${mode}` + `[Cascade] dispatch ${dispatchId} table=${tableId} groups=[${targetGroupIds.join(',')}] rows=${rowIds ? `[${rowIds.join(',')}]` : 'all'} mode=${mode}`, + { requestId } ) if (isTriggerDevEnabled) { @@ -1154,10 +1156,7 @@ export async function runWorkflowColumn(opts: { // so the HTTP request returns instantly (mirrors the trigger.dev path's // async fan-out). void runDispatcherToCompletion(dispatchId, concurrency).catch((err) => - logger.error(`[${requestId}] dispatcher loop failed`, { - dispatchId, - error: toError(err).message, - }) + logger.error('dispatcher loop failed', { requestId, dispatchId, error: toError(err).message }) ) } diff --git a/apps/sim/lib/table/workflow-groups/service.ts b/apps/sim/lib/table/workflow-groups/service.ts index 1107717c54a..b4d498fb1f2 100644 --- a/apps/sim/lib/table/workflow-groups/service.ts +++ b/apps/sim/lib/table/workflow-groups/service.ts @@ -99,7 +99,8 @@ export async function pruneStaleWorkflowGroupOutputs({ if (filtered.length === group.outputs.length) return group if (filtered.length === 0) { logger.warn( - `[${requestId}] All outputs for workflow group "${group.name ?? group.id}" in table ${t.id} reference deleted blocks; leaving group intact for user reconfiguration.` + `All outputs for workflow group "${group.name ?? group.id}" in table ${t.id} reference deleted blocks; leaving group intact for user reconfiguration.`, + { requestId } ) return group } @@ -119,7 +120,7 @@ export async function pruneStaleWorkflowGroupOutputs({ and(eq(userTableDefinitions.id, t.id), eq(userTableDefinitions.workspaceId, workspaceId)) ) - logger.info(`[${requestId}] Pruned stale workflow=${workflowId} block refs from table ${t.id}`) + logger.info(`Pruned stale workflow=${workflowId} block refs from table ${t.id}`, { requestId }) } } @@ -273,7 +274,8 @@ export async function addWorkflowGroup( ) logger.info( - `[${requestId}] Added workflow group "${data.group.id}" with ${newColumns.length} new and ${attached.size} attached output column(s) to table ${data.tableId}` + `Added workflow group "${data.group.id}" with ${newColumns.length} new and ${attached.size} attached output column(s) to table ${data.tableId}`, + { requestId } ) return { @@ -301,7 +303,7 @@ export async function addWorkflowGroup( requestId, triggeredByUserId: data.actorUserId, capabilityGovernedUserId: data.capabilityGovernedUserId, - }).catch((err) => logger.error(`[${requestId}] auto-dispatch (addWorkflowGroup) failed:`, err)) + }).catch((err) => logger.error('auto-dispatch (addWorkflowGroup) failed:', err, { requestId })) } return updatedTable @@ -580,7 +582,8 @@ export async function updateWorkflowGroup( } logger.info( - `[${requestId}] Updated workflow group "${data.groupId}" in table ${data.tableId} (added=${added.length}, removed=${removed.length}, remapped=${remappedColumnIds.size})` + `Updated workflow group "${data.groupId}" in table ${data.tableId} (added=${added.length}, removed=${removed.length}, remapped=${remappedColumnIds.size})`, + { requestId } ) const updatedTable: TableDefinition = { @@ -624,8 +627,9 @@ export async function updateWorkflowGroup( }) } catch (err) { logger.warn( - `[${requestId}] Backfill from execution logs failed for ${data.tableId} group ${data.groupId}:`, - err + `Backfill from execution logs failed for ${data.tableId} group ${data.groupId}:`, + err, + { requestId } ) } } @@ -643,8 +647,9 @@ export async function updateWorkflowGroup( }) } catch (err) { logger.warn( - `[${requestId}] Remap backfill from execution logs failed for ${data.tableId} group ${data.groupId}:`, - err + `Remap backfill from execution logs failed for ${data.tableId} group ${data.groupId}:`, + err, + { requestId } ) } } @@ -663,7 +668,7 @@ export async function updateWorkflowGroup( triggeredByUserId: data.actorUserId, capabilityGovernedUserId: data.capabilityGovernedUserId, }).catch((err) => - logger.error(`[${requestId}] auto-dispatch (updateWorkflowGroup autoRun=true) failed:`, err) + logger.error('auto-dispatch (updateWorkflowGroup autoRun=true) failed:', err, { requestId }) ) } @@ -737,9 +742,9 @@ export async function deleteWorkflowGroup( expectedWorkspaceId: table.workspaceId, }) - logger.info( - `[${requestId}] Deleted workflow group "${data.groupId}" from table ${data.tableId}` - ) + logger.info(`Deleted workflow group "${data.groupId}" from table ${data.tableId}`, { + requestId, + }) return { ...table, diff --git a/apps/sim/lib/uploads/utils/file.server.ts b/apps/sim/lib/uploads/utils/file.server.ts index ce80eff8c88..a022afb1a05 100644 --- a/apps/sim/lib/uploads/utils/file.server.ts +++ b/apps/sim/lib/uploads/utils/file.server.ts @@ -116,7 +116,8 @@ export async function resolveFileInputToUrl( const hasAccess = await verifyFileAccess(userFile.key, userId, undefined, context) if (!hasAccess) { - logger.warn(`[${requestId}] Unauthorized presigned URL generation attempt`, { + logger.warn('Unauthorized presigned URL generation attempt', { + requestId, userId, key: userFile.key, context, @@ -189,10 +190,7 @@ export async function resolveFileInputToUrl( } } } else if (filePath.startsWith('/')) { - logger.warn(`[${requestId}] Invalid internal path`, { - userId, - path: filePath.substring(0, 50), - }) + logger.warn('Invalid internal path', { requestId, userId, path: filePath.substring(0, 50) }) return { error: { status: 400, @@ -332,7 +330,8 @@ export async function resolveInternalFileUrl( const hasAccess = await verifyFileAccess(storageKey, userId, undefined, context) if (!hasAccess) { - logger.warn(`[${requestId}] Unauthorized presigned URL generation attempt`, { + logger.warn('Unauthorized presigned URL generation attempt', { + requestId, userId, key: storageKey, context, @@ -345,10 +344,10 @@ export async function resolveInternalFileUrl( context, presignExpirySeconds ) - logger.info(`[${requestId}] Generated presigned URL for ${context} file`) + logger.info(`Generated presigned URL for ${context} file`, { requestId }) return { fileUrl } } catch (error) { - logger.error(`[${requestId}] Failed to generate presigned URL:`, error) + logger.error('Failed to generate presigned URL:', error, { requestId }) return { error: { status: 500, message: 'Failed to generate file access URL' } } } } @@ -382,12 +381,12 @@ export async function downloadFileFromStorage( assertKnownSizeWithinLimit(userFile.size, maxBytes, 'storage file download') if (isExecutionFile(userFile)) { - logger.info(`[${requestId}] Downloading from execution storage: ${userFile.key}`) + logger.info(`Downloading from execution storage: ${userFile.key}`, { requestId }) const { downloadExecutionFile } = await import('@/lib/uploads/contexts/execution/file-manager') buffer = await downloadExecutionFile(userFile, { maxBytes, signal }) } else if (userFile.key) { const context = resolveTrustedFileContext(userFile.key, userFile.context) - logger.info(`[${requestId}] Downloading from ${context} storage: ${userFile.key}`) + logger.info(`Downloading from ${context} storage: ${userFile.key}`, { requestId }) const { downloadFile } = await import('@/lib/uploads/core/storage-service') buffer = await downloadFile({ @@ -542,7 +541,7 @@ export async function downloadServableFilesWithinBudget( let spent = 0 for (const userFile of userFiles) { - logger.info(`[${requestId}] Downloading ${userFile.name} (${userFile.size} bytes)`) + logger.info(`Downloading ${userFile.name} (${userFile.size} bytes)`, { requestId }) let servable: ServableFile try { servable = await downloadServableFileFromStorage(userFile, requestId, logger, { diff --git a/apps/sim/lib/uploads/utils/file.ts b/apps/sim/lib/uploads/utils/file.ts index cf7bc616d84..d4470d06f35 100644 --- a/apps/sim/lib/uploads/utils/file.ts +++ b/apps/sim/lib/uploads/utils/file.ts @@ -994,7 +994,7 @@ function convertToUserFile(file: RawFileInput, requestId: string, logger: Logger base64: file.base64, } - logger.info(`[${requestId}] Converted file to UserFile: ${userFile.name} (key: ${userFile.key})`) + logger.info(`Converted file to UserFile: ${userFile.name} (key: ${userFile.key})`, { requestId }) return userFile } @@ -1009,14 +1009,14 @@ export function processSingleFileToUserFile( ): UserFile { if (Array.isArray(file)) { const errorMsg = `Expected a single file but received an array with ${file.length} file(s). Use a file input that accepts multiple files, or select a specific file from the array (e.g., {{block.files[0]}}).` - logger.error(`[${requestId}] ${errorMsg}`) + logger.error(errorMsg, { requestId }) throw new Error(errorMsg) } const userFile = convertToUserFile(file, requestId, logger) if (!userFile) { const errorMsg = `File has no storage key: ${file.name || 'unknown'}` - logger.warn(`[${requestId}] ${errorMsg}`) + logger.warn(errorMsg, { requestId }) throw new Error(errorMsg) } @@ -1036,7 +1036,7 @@ export function processFilesToUserFiles( for (const file of filesArray) { if (Array.isArray(file)) { - logger.warn(`[${requestId}] Skipping nested array in file input`) + logger.warn('Skipping nested array in file input', { requestId }) continue } @@ -1044,7 +1044,7 @@ export function processFilesToUserFiles( if (userFile) { userFiles.push(userFile) } else { - logger.warn(`[${requestId}] Skipping file without storage key: ${file.name || 'unknown'}`) + logger.warn(`Skipping file without storage key: ${file.name || 'unknown'}`, { requestId }) } } diff --git a/apps/sim/lib/uploads/utils/user-file-base64.server.ts b/apps/sim/lib/uploads/utils/user-file-base64.server.ts index 24a1509a4ec..f38a9391a23 100644 --- a/apps/sim/lib/uploads/utils/user-file-base64.server.ts +++ b/apps/sim/lib/uploads/utils/user-file-base64.server.ts @@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger' import { isPlainRecord } from '@sim/utils/object' import { getRedisClient } from '@/lib/core/config/redis' import { getRedisBudgetKeys, getRedisBudgetLimits } from '@/lib/core/redis/byte-budget.server' +import { generateRequestId } from '@/lib/core/utils/request' import { isUserFileWithMetadata } from '@/lib/core/utils/user-file' import { recordMaterializedAccessKeys } from '@/lib/execution/payloads/access-keys' import { @@ -214,10 +215,11 @@ function logSkippedCacheWrite( file: UserFile, error: ExecutionResourceLimitError ): void { - logger.warn( - `[${requestId ?? 'unknown'}] Skipping base64 cache write for ${file.name}: ${error.message}`, - { resource: error.resource, attemptedBytes: error.attemptedBytes } - ) + logger.warn(`Skipping base64 cache write for ${file.name}: ${error.message}`, { + requestId, + resource: error.resource, + attemptedBytes: error.attemptedBytes, + }) } function createBase64Cache(options: Base64HydrationOptions, logger: Logger): Base64Cache { @@ -225,9 +227,9 @@ function createBase64Cache(options: Base64HydrationOptions, logger: Logger): Bas const { executionId } = options if (!redis) { - logger.warn( - `[${options.requestId}] Redis unavailable for base64 cache, using in-memory fallback` - ) + logger.warn('Redis unavailable for base64 cache, using in-memory fallback', { + requestId: options.requestId, + }) return new InMemoryBase64Cache() } @@ -237,7 +239,7 @@ function createBase64Cache(options: Base64HydrationOptions, logger: Logger): Bas const key = getFullCacheKey(executionId, file) return await redis.get(key) } catch (error) { - logger.warn(`[${options.requestId}] Redis get failed, skipping cache`, error) + logger.warn('Redis get failed, skipping cache', error, { requestId: options.requestId }) return null } }, @@ -302,7 +304,7 @@ function createBase64Cache(options: Base64HydrationOptions, logger: Logger): Bas ) } } catch (error) { - logger.warn(`[${options.requestId}] Redis set failed, skipping cache`, error) + logger.warn('Redis set failed, skipping cache', error, { requestId: options.requestId }) } }, } @@ -410,7 +412,8 @@ async function resolveBase64( const base64Bytes = Buffer.byteLength(file.base64, 'base64') if (base64Bytes > maxBytes) { logger.warn( - `[${options.requestId}] Skipping existing base64 for ${file.name} (decoded ${base64Bytes} exceeds ${maxBytes})` + `Skipping existing base64 for ${file.name} (decoded ${base64Bytes} exceeds ${maxBytes})`, + { requestId: options.requestId } ) return { base64: null } } @@ -425,9 +428,9 @@ async function resolveBase64( Number.isFinite(file.size) && file.size > maxBytes ) { - logger.warn( - `[${options.requestId}] Skipping base64 for ${file.name} (size ${file.size} exceeds ${maxBytes})` - ) + logger.warn(`Skipping base64 for ${file.name} (size ${file.size} exceeds ${maxBytes})`, { + requestId: options.requestId, + }) return { base64: null } } @@ -436,11 +439,13 @@ async function resolveBase64( !allowUnknownSize && !hasStableStorageKey ) { - logger.warn(`[${options.requestId}] Skipping base64 for ${file.name} (unknown file size)`) + logger.warn(`Skipping base64 for ${file.name} (unknown file size)`, { + requestId: options.requestId, + }) return { base64: null } } - const requestId = options.requestId ?? 'unknown' + const requestId = options.requestId ?? generateRequestId() try { const result = await readUserFileContentWithContributors(file, { requestId, @@ -463,7 +468,7 @@ async function resolveBase64( if (error instanceof Error && error.name === 'DocCompileUserError') { throw error } - logger.warn(`[${requestId}] Failed to hydrate base64 for ${file.name}`, error) + logger.warn(`Failed to hydrate base64 for ${file.name}`, error, { requestId }) return { base64: null } } } @@ -489,7 +494,7 @@ async function hydrateUserFile( logger, }) } catch (error) { - logger.warn(`[${options.requestId ?? 'unknown'}] Skipping unauthorized file base64`, error) + logger.warn('Skipping unauthorized file base64', error, { requestId: options.requestId }) return stripBase64(file) } } diff --git a/apps/sim/lib/webhooks/attachment-processor.ts b/apps/sim/lib/webhooks/attachment-processor.ts index 0cbbf494ef6..d6895d45d55 100644 --- a/apps/sim/lib/webhooks/attachment-processor.ts +++ b/apps/sim/lib/webhooks/attachment-processor.ts @@ -36,7 +36,8 @@ export class WebhookAttachmentProcessor { } logger.info( - `[${executionContext.requestId}] Processing ${attachments.length} attachments for execution ${executionContext.executionId}` + `Processing ${attachments.length} attachments for execution ${executionContext.executionId}`, + { requestId: executionContext.requestId } ) const processedFiles: UserFile[] = [] @@ -49,16 +50,16 @@ export class WebhookAttachmentProcessor { ) processedFiles.push(userFile) } catch (error) { - logger.error( - `[${executionContext.requestId}] Error processing attachment '${attachment.name}':`, - error - ) + logger.error(`Error processing attachment '${attachment.name}':`, error, { + requestId: executionContext.requestId, + }) // Continue with other attachments rather than failing the entire request } } logger.info( - `[${executionContext.requestId}] Successfully processed ${processedFiles.length}/${attachments.length} attachments` + `Successfully processed ${processedFiles.length}/${attachments.length} attachments`, + { requestId: executionContext.requestId } ) return processedFiles diff --git a/apps/sim/lib/webhooks/deploy.ts b/apps/sim/lib/webhooks/deploy.ts index 0cfb44e3290..275c353cc8f 100644 --- a/apps/sim/lib/webhooks/deploy.ts +++ b/apps/sim/lib/webhooks/deploy.ts @@ -497,10 +497,9 @@ export async function resolveWebhookConfigForBlock(input: { const { userId: botUserId } = await fetchSlackTeamId(botCredential.botToken) if (botUserId) providerConfig.bot_user_id = botUserId } catch (error: unknown) { - logger.error( - `[${input.requestId}] Slack custom bot identity resolution failed for ${input.block.id}`, - error - ) + logger.error(`Slack custom bot identity resolution failed for ${input.block.id}`, error, { + requestId: input.requestId, + }) return { success: false, error: { @@ -618,10 +617,9 @@ export async function resolveWebhookConfigForBlock(input: { routingKey = teamId if (botUserId) providerConfig.bot_user_id = botUserId } catch (error: unknown) { - logger.error( - `[${input.requestId}] Slack team_id resolution failed for ${input.block.id}`, - error - ) + logger.error(`Slack team_id resolution failed for ${input.block.id}`, error, { + requestId: input.requestId, + }) return { success: false, error: { @@ -914,7 +912,8 @@ export async function saveTriggerWebhooksForDeploy({ webhooksByBlockId.set(wh.blockId, existingForBlock) } - logger.info(`[${requestId}] Starting webhook sync`, { + logger.info('Starting webhook sync', { + requestId, workflowId, currentBlockIds: Array.from(currentBlockIds), existingWebhookBlockIds: Array.from(webhooksByBlockId.keys()), @@ -944,7 +943,8 @@ export async function saveTriggerWebhooksForDeploy({ }) if (pathConflict) { logger.warn( - `[${requestId}] Webhook path conflict for "${triggerPath}": already owned by workflow ${pathConflict}` + `Webhook path conflict for "${triggerPath}": already owned by workflow ${pathConflict}`, + { requestId } ) return { success: false, @@ -966,9 +966,9 @@ export async function saveTriggerWebhooksForDeploy({ const [existingWh, ...extraWebhooks] = existingForBlock if (extraWebhooks.length > 0) { webhooksToDelete.push(...extraWebhooks) - logger.info( - `[${requestId}] Found ${extraWebhooks.length} extra webhook(s) for block ${block.id}` - ) + logger.info(`Found ${extraWebhooks.length} extra webhook(s) for block ${block.id}`, { + requestId, + }) } // Check if config changed or if we're forcing recreation (e.g., activating old version) @@ -987,11 +987,11 @@ export async function saveTriggerWebhooksForDeploy({ webhooksToDelete.push(existingWh) blocksNeedingWebhook.push(block) if (forceRecreateSubscriptions) { - logger.info( - `[${requestId}] Forcing webhook recreation for block ${block.id} (reactivating version)` - ) + logger.info(`Forcing webhook recreation for block ${block.id} (reactivating version)`, { + requestId, + }) } else { - logger.info(`[${requestId}] Webhook config changed for block ${block.id}, will recreate`) + logger.info(`Webhook config changed for block ${block.id}, will recreate`, { requestId }) } } if (!needsRecreation && existingConfig.subscriptionActivationPending === true) { @@ -1024,13 +1024,14 @@ export async function saveTriggerWebhooksForDeploy({ for (const wh of existingWebhooks) { if (wh.blockId && !currentBlockIds.has(wh.blockId)) { webhooksToDelete.push(wh) - logger.info(`[${requestId}] Webhook orphaned (block deleted): ${wh.blockId}`) + logger.info(`Webhook orphaned (block deleted): ${wh.blockId}`, { requestId }) } } // 3. Delete webhooks that need deletion if (webhooksToDelete.length > 0) { - logger.info(`[${requestId}] Deleting ${webhooksToDelete.length} webhook(s)`, { + logger.info(`Deleting ${webhooksToDelete.length} webhook(s)`, { + requestId, webhookIds: webhooksToDelete.map((wh) => wh.id), }) @@ -1042,7 +1043,7 @@ export async function saveTriggerWebhooksForDeploy({ }) cleanupSucceeded = true } catch (cleanupError) { - logger.warn(`[${requestId}] Failed to cleanup external webhook ${wh.id}`, cleanupError) + logger.warn(`Failed to cleanup external webhook ${wh.id}`, cleanupError, { requestId }) if (strictExternalCleanup) throw cleanupError } if (!strictExternalCleanup || cleanupSucceeded) { @@ -1106,11 +1107,12 @@ export async function saveTriggerWebhooksForDeploy({ }) } catch (error: unknown) { if (error instanceof WebhookDeploymentConfigurationError) { - logger.warn(`[${requestId}] Rejected webhook trigger configuration for ${block.id}`, { + logger.warn(`Rejected webhook trigger configuration for ${block.id}`, { + requestId, error: error.message, }) } else { - logger.error(`[${requestId}] Failed to create external subscription for ${block.id}`, error) + logger.error(`Failed to create external subscription for ${block.id}`, error, { requestId }) } await pendingVerificationTracker.clearAll() let cleanupFailure: unknown @@ -1131,8 +1133,9 @@ export async function saveTriggerWebhooksForDeploy({ } catch (cleanupError) { cleanupFailure = cleanupError logger.warn( - `[${requestId}] Failed to cleanup external subscription for ${sub.block.id}`, - cleanupError + `Failed to cleanup external subscription for ${sub.block.id}`, + cleanupError, + { requestId } ) await persistCreatedWebhookRecordAfterCleanupFailure({ workflowId, @@ -1215,10 +1218,9 @@ export async function saveTriggerWebhooksForDeploy({ } ) if (pollingError) { - logger.error( - `[${requestId}] Polling configuration failed for ${sub.block.id}`, - pollingError - ) + logger.error(`Polling configuration failed for ${sub.block.id}`, pollingError, { + requestId, + }) const cleanedWebhookIds: string[] = [] for (const otherSub of createdSubscriptions) { if (otherSub.webhookId === sub.webhookId) continue @@ -1238,8 +1240,9 @@ export async function saveTriggerWebhooksForDeploy({ cleanedWebhookIds.push(otherSub.webhookId) } catch (cleanupError) { logger.warn( - `[${requestId}] Failed to cleanup external subscription for ${otherSub.block.id}`, - cleanupError + `Failed to cleanup external subscription for ${otherSub.block.id}`, + cleanupError, + { requestId } ) } } else { @@ -1254,7 +1257,7 @@ export async function saveTriggerWebhooksForDeploy({ } } catch (error: unknown) { await pendingVerificationTracker.clearAll() - logger.error(`[${requestId}] Failed to save or activate webhook records`, error) + logger.error('Failed to save or activate webhook records', error, { requestId }) let cleanupFailure: unknown for (const sub of createdSubscriptions) { if (sub.externalSubscriptionCreated) { @@ -1277,10 +1280,9 @@ export async function saveTriggerWebhooksForDeploy({ if (recordsCommitted) await db.delete(webhook).where(eq(webhook.id, sub.webhookId)) } catch (cleanupError) { cleanupFailure = cleanupError - logger.warn( - `[${requestId}] Failed to cleanup external subscription for ${sub.block.id}`, - cleanupError - ) + logger.warn(`Failed to cleanup external subscription for ${sub.block.id}`, cleanupError, { + requestId, + }) if (!recordsCommitted) await persistCreatedWebhookRecordAfterCleanupFailure({ workflowId, @@ -1341,10 +1343,9 @@ async function persistCreatedWebhookRecordAfterCleanupFailure({ updatedAt: new Date(), }) } catch (persistError) { - logger.error( - `[${requestId}] Failed to persist webhook record after external cleanup failure`, - persistError - ) + logger.error('Failed to persist webhook record after external cleanup failure', persistError, { + requestId, + }) } } @@ -1391,12 +1392,8 @@ export async function cleanupWebhooksForWorkflow( } logger.info( - `[${requestId}] Cleaning up ${existingWebhooks.length} webhook(s) for ${skipExternalCleanup ? 'DB records only' : 'undeploy'}`, - { - workflowId, - deploymentVersionId, - webhookIds: existingWebhooks.map((wh) => wh.id), - } + `Cleaning up ${existingWebhooks.length} webhook(s) for ${skipExternalCleanup ? 'DB records only' : 'undeploy'}`, + { requestId, workflowId, deploymentVersionId, webhookIds: existingWebhooks.map((wh) => wh.id) } ) if (!skipExternalCleanup) { @@ -1420,7 +1417,8 @@ export async function cleanupWebhooksForWorkflow( shouldDeleteWebhook, }) if (!deleted) { - logger.info(`[${requestId}] Stopping webhook DB cleanup because deployment became active`, { + logger.info('Stopping webhook DB cleanup because deployment became active', { + requestId, workflowId, deploymentVersionId, webhookId: wh.id, @@ -1432,8 +1430,9 @@ export async function cleanupWebhooksForWorkflow( logger.info( deploymentVersionId - ? `[${requestId}] Cleaned up webhooks for workflow ${workflowId} deployment ${deploymentVersionId}` - : `[${requestId}] Cleaned up all webhooks for workflow ${workflowId}` + ? `Cleaned up webhooks for workflow ${workflowId} deployment ${deploymentVersionId}` + : `Cleaned up all webhooks for workflow ${workflowId}`, + { requestId } ) } @@ -1455,7 +1454,8 @@ async function cleanupWebhookRow(params: { const { webhook: wh, workflow, requestId, deploymentVersionId, strictExternalCleanup } = params const workflowId = wh.workflowId if (params.shouldDeleteWebhook && !(await params.shouldDeleteWebhook())) { - logger.info(`[${requestId}] Stopping webhook cleanup because deployment became active`, { + logger.info('Stopping webhook cleanup because deployment became active', { + requestId, workflowId, deploymentVersionId, webhookId: wh.id, @@ -1466,7 +1466,7 @@ async function cleanupWebhookRow(params: { try { await cleanupExternalWebhook(wh, workflow, requestId, { throwOnError: strictExternalCleanup }) } catch (cleanupError) { - logger.warn(`[${requestId}] Failed to cleanup external webhook ${wh.id}`, cleanupError) + logger.warn(`Failed to cleanup external webhook ${wh.id}`, cleanupError, { requestId }) if (strictExternalCleanup) throw cleanupError } @@ -1477,7 +1477,8 @@ async function cleanupWebhookRow(params: { shouldDeleteWebhook: params.shouldDeleteWebhook, }) if (!deleted) { - logger.info(`[${requestId}] Stopping webhook DB cleanup because deployment became active`, { + logger.info('Stopping webhook DB cleanup because deployment became active', { + requestId, workflowId, deploymentVersionId, webhookId: wh.id, @@ -1540,13 +1541,11 @@ export async function cleanupInactiveDeploymentWebhooks(params: { const batch = staleWebhooks.slice(0, params.limit) if (batch.length === 0) return { hasMore: false } - logger.info( - `[${requestId}] Cleaning up ${batch.length} webhook(s) owned by inactive deployments`, - { - workflowId, - webhookIds: batch.map((wh) => wh.id), - } - ) + logger.info(`Cleaning up ${batch.length} webhook(s) owned by inactive deployments`, { + requestId, + workflowId, + webhookIds: batch.map((wh) => wh.id), + }) for (const wh of batch) { const deploymentVersionId = wh.deploymentVersionId diff --git a/apps/sim/lib/webhooks/polling/gmail.ts b/apps/sim/lib/webhooks/polling/gmail.ts index 97514ef5646..f17e86dcba9 100644 --- a/apps/sim/lib/webhooks/polling/gmail.ts +++ b/apps/sim/lib/webhooks/polling/gmail.ts @@ -92,11 +92,11 @@ export const gmailPollingHandler: PollingProviderHandler = { if (!emails || !emails.length) { await updateWebhookProviderConfig(webhookId, pollState, logger) await markWebhookSuccess(webhookId, logger) - logger.info(`[${requestId}] No new emails found for webhook ${webhookId}`) + logger.info(`No new emails found for webhook ${webhookId}`, { requestId }) return 'success' } - logger.info(`[${requestId}] Found ${emails.length} new emails for webhook ${webhookId}`) + logger.info(`Found ${emails.length} new emails for webhook ${webhookId}`, { requestId }) const { processedCount, failedCount } = await processEmails( emails, @@ -112,22 +112,23 @@ export const gmailPollingHandler: PollingProviderHandler = { if (failedCount > 0 && processedCount === 0) { await markWebhookFailed(webhookId, logger) - logger.warn( - `[${requestId}] All ${failedCount} emails failed to process for webhook ${webhookId}` - ) + logger.warn(`All ${failedCount} emails failed to process for webhook ${webhookId}`, { + requestId, + }) return 'failure' } await markWebhookSuccess(webhookId, logger) logger.info( - `[${requestId}] Successfully processed ${processedCount} emails for webhook ${webhookId}${failedCount > 0 ? ` (${failedCount} failed)` : ''}` + `Successfully processed ${processedCount} emails for webhook ${webhookId}${failedCount > 0 ? ` (${failedCount} failed)` : ''}`, + { requestId } ) return 'success' } catch (error) { if (error instanceof PollAdmissionRefusedError) { return skipAdmissionRefusedPoll(logger, requestId, webhookId) } - logger.error(`[${requestId}] Error processing Gmail webhook ${webhookId}:`, error) + logger.error(`Error processing Gmail webhook ${webhookId}:`, error, { requestId }) await markWebhookFailed(webhookId, logger) return 'failure' } @@ -165,13 +166,15 @@ async function fetchNewEmails( if (status === 404 || status === 400) { await historyResponse.body?.cancel().catch(() => {}) logger.info( - `[${requestId}] Gmail rejected history cursor ${config.historyId} (${status}); resyncing through search` + `Gmail rejected history cursor ${config.historyId} (${status}); resyncing through search`, + { requestId } ) return resyncAfterExpiredHistory(accessToken, config, requestId, logger) } const errorData = await historyResponse.json().catch(() => ({})) - logger.warn(`[${requestId}] Gmail history API error:`, { + logger.warn('Gmail history API error:', { + requestId, status, statusText: historyResponse.statusText, error: errorData, @@ -183,7 +186,7 @@ async function fetchNewEmails( ) } - logger.info(`[${requestId}] Falling back to search API after history API error ${status}`) + logger.info(`Falling back to search API after history API error ${status}`, { requestId }) return searchEmails(accessToken, config, requestId, logger) } @@ -212,14 +215,14 @@ async function fetchNewEmails( const sortedIds = [...messageIds].sort().reverse() const idsToFetch = sortedIds.slice(0, config.maxEmailsPerPoll || 25) - logger.info(`[${requestId}] Processing ${idsToFetch.length} emails from history API`) + logger.info(`Processing ${idsToFetch.length} emails from history API`, { requestId }) const emailResults = await Promise.allSettled( idsToFetch.map((messageId) => getEmailDetails(accessToken, messageId)) ) const rejected = emailResults.filter((r) => r.status === 'rejected') if (rejected.length > 0) { - logger.warn(`[${requestId}] Failed to fetch ${rejected.length} email details`) + logger.warn(`Failed to fetch ${rejected.length} email details`, { requestId }) } emails = emailResults .filter( @@ -235,7 +238,7 @@ async function fetchNewEmails( return { emails, latestHistoryId } } catch (error) { const errorMessage = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Error fetching new emails:`, errorMessage) + logger.error('Error fetching new emails:', errorMessage, { requestId }) throw error } } @@ -320,7 +323,8 @@ async function searchEmails( if (!searchResponse.ok) { const errorData = await searchResponse.json() - logger.error(`[${requestId}] Gmail search API error:`, { + logger.error('Gmail search API error:', { + requestId, status: searchResponse.status, statusText: searchResponse.statusText, query, @@ -334,7 +338,7 @@ async function searchEmails( const searchData = await searchResponse.json() if (!searchData.messages || !searchData.messages.length) { - logger.info(`[${requestId}] No emails found matching query: ${query}`) + logger.info(`No emails found matching query: ${query}`, { requestId }) return { emails: [], latestHistoryId: config.historyId } } @@ -342,7 +346,8 @@ async function searchEmails( let latestHistoryId = config.historyId logger.info( - `[${requestId}] Processing ${idsToFetch.length} emails from search API (total matches: ${searchData.messages.length})` + `Processing ${idsToFetch.length} emails from search API (total matches: ${searchData.messages.length})`, + { requestId } ) const emailResults = await Promise.allSettled( @@ -350,7 +355,7 @@ async function searchEmails( ) const rejected = emailResults.filter((r) => r.status === 'rejected') if (rejected.length > 0) { - logger.warn(`[${requestId}] Failed to fetch ${rejected.length} email details`) + logger.warn(`Failed to fetch ${rejected.length} email details`, { requestId }) } const emails = emailResults .filter( @@ -365,7 +370,7 @@ async function searchEmails( return { emails, latestHistoryId } } catch (error) { const errorMessage = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Error searching emails:`, errorMessage) + logger.error('Error searching emails:', errorMessage, { requestId }) throw error } } @@ -388,7 +393,7 @@ async function resyncAfterExpiredHistory( : await getGmailProfileHistoryId(accessToken, requestId, logger) const latestHistoryId = candidate && candidate !== config.historyId ? candidate : undefined if (!latestHistoryId) { - logger.info(`[${requestId}] No replacement Gmail history cursor yet; polling through search`) + logger.info('No replacement Gmail history cursor yet; polling through search', { requestId }) } return { emails: searchResult.emails, latestHistoryId } } @@ -403,15 +408,15 @@ async function getGmailProfileHistoryId( headers: { Authorization: `Bearer ${accessToken}` }, }) if (!response.ok) { - logger.warn( - `[${requestId}] Failed to fetch Gmail profile for fresh historyId: ${response.status}` - ) + logger.warn(`Failed to fetch Gmail profile for fresh historyId: ${response.status}`, { + requestId, + }) return null } const profile = await response.json() return (profile.historyId as string | undefined) ?? null } catch (error) { - logger.warn(`[${requestId}] Error fetching Gmail profile:`, error) + logger.warn('Error fetching Gmail profile:', error, { requestId }) return null } } @@ -519,10 +524,9 @@ async function processEmails( const attachmentInfo = extractAttachmentInfo(payload) attachments = await downloadAttachments(email.id, attachmentInfo, accessToken) } catch (error) { - logger.error( - `[${requestId}] Error downloading attachments for email ${email.id}:`, - error - ) + logger.error(`Error downloading attachments for email ${email.id}:`, error, { + requestId, + }) } } @@ -557,9 +561,10 @@ async function processEmails( if (!result.success) { throwIfAdmissionRefused(result) logger.error( - `[${requestId}] Failed to process webhook for email ${email.id}:`, + `Failed to process webhook for email ${email.id}:`, result.statusCode, - result.error + result.error, + { requestId } ) throw new Error(`Webhook processing failed (${result.statusCode}): ${result.error}`) } @@ -572,14 +577,14 @@ async function processEmails( } ) - logger.info( - `[${requestId}] Successfully processed email ${email.id} for webhook ${webhookData.id}` - ) + logger.info(`Successfully processed email ${email.id} for webhook ${webhookData.id}`, { + requestId, + }) processedCount++ } catch (error) { if (error instanceof PollAdmissionRefusedError && processedCount === 0) throw error const errorMessage = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Error processing email ${email.id}:`, errorMessage) + logger.error(`Error processing email ${email.id}:`, errorMessage, { requestId }) failedCount++ } } diff --git a/apps/sim/lib/webhooks/polling/google-calendar.ts b/apps/sim/lib/webhooks/polling/google-calendar.ts index 0c468736386..ec6cf8beae4 100644 --- a/apps/sim/lib/webhooks/polling/google-calendar.ts +++ b/apps/sim/lib/webhooks/polling/google-calendar.ts @@ -119,7 +119,7 @@ export const googleCalendarPollingHandler: PollingProviderHandler = { logger ) await markWebhookSuccess(webhookId, logger) - logger.info(`[${requestId}] First poll for webhook ${webhookId}, seeded timestamp`) + logger.info(`First poll for webhook ${webhookId}, seeded timestamp`, { requestId }) return 'success' } @@ -128,11 +128,11 @@ export const googleCalendarPollingHandler: PollingProviderHandler = { if (!events.length) { await markWebhookSuccess(webhookId, logger) - logger.info(`[${requestId}] No changed events for webhook ${webhookId}`) + logger.info(`No changed events for webhook ${webhookId}`, { requestId }) return 'success' } - logger.info(`[${requestId}] Found ${events.length} changed events for webhook ${webhookId}`) + logger.info(`Found ${events.length} changed events for webhook ${webhookId}`, { requestId }) const { processedCount, failedCount, latestUpdated } = await processEvents( events, @@ -162,22 +162,23 @@ export const googleCalendarPollingHandler: PollingProviderHandler = { if (failedCount > 0 && processedCount === 0) { await markWebhookFailed(webhookId, logger) - logger.warn( - `[${requestId}] All ${failedCount} events failed to process for webhook ${webhookId}` - ) + logger.warn(`All ${failedCount} events failed to process for webhook ${webhookId}`, { + requestId, + }) return 'failure' } await markWebhookSuccess(webhookId, logger) logger.info( - `[${requestId}] Successfully processed ${processedCount} events for webhook ${webhookId}${failedCount > 0 ? ` (${failedCount} failed)` : ''}` + `Successfully processed ${processedCount} events for webhook ${webhookId}${failedCount > 0 ? ` (${failedCount} failed)` : ''}`, + { requestId } ) return 'success' } catch (error) { if (error instanceof PollAdmissionRefusedError) { return skipAdmissionRefusedPoll(logger, requestId, webhookId) } - logger.error(`[${requestId}] Error processing Google Calendar webhook ${webhookId}:`, error) + logger.error(`Error processing Google Calendar webhook ${webhookId}:`, error, { requestId }) await markWebhookFailed(webhookId, logger) return 'failure' } @@ -338,9 +339,10 @@ async function processEvents( if (!result.success) { throwIfAdmissionRefused(result) logger.error( - `[${requestId}] Failed to process webhook for event ${event.id}:`, + `Failed to process webhook for event ${event.id}:`, result.statusCode, - result.error + result.error, + { requestId } ) throw new Error(`Webhook processing failed (${result.statusCode}): ${result.error}`) } @@ -349,14 +351,14 @@ async function processEvents( } ) - logger.info( - `[${requestId}] Successfully processed event ${event.id} for webhook ${webhookData.id}` - ) + logger.info(`Successfully processed event ${event.id} for webhook ${webhookData.id}`, { + requestId, + }) processedCount++ } catch (error) { if (error instanceof PollAdmissionRefusedError && processedCount === 0) throw error const errorMessage = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Error processing event ${event.id}:`, errorMessage) + logger.error(`Error processing event ${event.id}:`, errorMessage, { requestId }) failedCount++ } } diff --git a/apps/sim/lib/webhooks/polling/google-drive.ts b/apps/sim/lib/webhooks/polling/google-drive.ts index c73136273d6..67836766f35 100644 --- a/apps/sim/lib/webhooks/polling/google-drive.ts +++ b/apps/sim/lib/webhooks/polling/google-drive.ts @@ -104,9 +104,9 @@ export const googleDrivePollingHandler: PollingProviderHandler = { logger ) await markWebhookSuccess(webhookId, logger) - logger.info( - `[${requestId}] First poll for webhook ${webhookId}, seeded pageToken: ${startPageToken}` - ) + logger.info(`First poll for webhook ${webhookId}, seeded pageToken: ${startPageToken}`, { + requestId, + }) return 'success' } @@ -115,7 +115,7 @@ export const googleDrivePollingHandler: PollingProviderHandler = { if (!changes.length) { await updateWebhookProviderConfig(webhookId, { pageToken: newStartPageToken }, logger) await markWebhookSuccess(webhookId, logger) - logger.info(`[${requestId}] No changes found for webhook ${webhookId}`) + logger.info(`No changes found for webhook ${webhookId}`, { requestId }) return 'success' } @@ -125,14 +125,15 @@ export const googleDrivePollingHandler: PollingProviderHandler = { await updateWebhookProviderConfig(webhookId, { pageToken: newStartPageToken }, logger) await markWebhookSuccess(webhookId, logger) logger.info( - `[${requestId}] ${changes.length} changes found but none match filters for webhook ${webhookId}` + `${changes.length} changes found but none match filters for webhook ${webhookId}`, + { requestId } ) return 'success' } - logger.info( - `[${requestId}] Found ${filteredChanges.length} matching changes for webhook ${webhookId}` - ) + logger.info(`Found ${filteredChanges.length} matching changes for webhook ${webhookId}`, { + requestId, + }) const { processedCount, failedCount, newKnownFileIds } = await processChanges( filteredChanges, @@ -161,15 +162,16 @@ export const googleDrivePollingHandler: PollingProviderHandler = { if (failedCount > 0 && processedCount === 0) { await markWebhookFailed(webhookId, logger) - logger.warn( - `[${requestId}] All ${failedCount} changes failed to process for webhook ${webhookId}` - ) + logger.warn(`All ${failedCount} changes failed to process for webhook ${webhookId}`, { + requestId, + }) return 'failure' } await markWebhookSuccess(webhookId, logger) logger.info( - `[${requestId}] Successfully processed ${processedCount} changes for webhook ${webhookId}${failedCount > 0 ? ` (${failedCount} failed)` : ''}` + `Successfully processed ${processedCount} changes for webhook ${webhookId}${failedCount > 0 ? ` (${failedCount} failed)` : ''}`, + { requestId } ) return 'success' } catch (error) { @@ -179,19 +181,20 @@ export const googleDrivePollingHandler: PollingProviderHandler = { if (error instanceof Error && error.name === 'DrivePageTokenInvalidError') { await updateWebhookProviderConfig(webhookId, { pageToken: undefined }, logger) await markWebhookSuccess(webhookId, logger) - logger.warn( - `[${requestId}] Drive page token invalid for webhook ${webhookId}, re-seeding on next poll` - ) + logger.warn(`Drive page token invalid for webhook ${webhookId}, re-seeding on next poll`, { + requestId, + }) return 'success' } if (error instanceof Error && error.name === 'DriveRateLimitError') { await markWebhookSuccess(webhookId, logger) logger.warn( - `[${requestId}] Drive API rate limited for webhook ${webhookId}, skipping to retry next poll cycle` + `Drive API rate limited for webhook ${webhookId}, skipping to retry next poll cycle`, + { requestId } ) return 'success' } - logger.error(`[${requestId}] Error processing Google Drive webhook ${webhookId}:`, error) + logger.error(`Error processing Google Drive webhook ${webhookId}:`, error, { requestId }) await markWebhookFailed(webhookId, logger) return 'failure' } @@ -406,9 +409,10 @@ async function processChanges( if (!result.success) { throwIfAdmissionRefused(result) logger.error( - `[${requestId}] Failed to process webhook for file ${change.fileId}:`, + `Failed to process webhook for file ${change.fileId}:`, result.statusCode, - result.error + result.error, + { requestId } ) throw new Error(`Webhook processing failed (${result.statusCode}): ${result.error}`) } @@ -417,16 +421,16 @@ async function processChanges( }) logger.info( - `[${requestId}] Successfully processed change for file ${change.fileId} for webhook ${webhookData.id}` + `Successfully processed change for file ${change.fileId} for webhook ${webhookData.id}`, + { requestId } ) processedCount++ } catch (error) { if (error instanceof PollAdmissionRefusedError && processedCount === 0) throw error const errorMessage = getErrorMessage(error, 'Unknown error') - logger.error( - `[${requestId}] Error processing change for file ${change.fileId}:`, - errorMessage - ) + logger.error(`Error processing change for file ${change.fileId}:`, errorMessage, { + requestId, + }) failedCount++ } } diff --git a/apps/sim/lib/webhooks/polling/google-sheets.ts b/apps/sim/lib/webhooks/polling/google-sheets.ts index fb598c5846d..adbb7e7c649 100644 --- a/apps/sim/lib/webhooks/polling/google-sheets.ts +++ b/apps/sim/lib/webhooks/polling/google-sheets.ts @@ -73,7 +73,7 @@ export const googleSheetsPollingHandler: PollingProviderHandler = { const now = new Date() if (!spreadsheetId || !sheetName) { - logger.error(`[${requestId}] Missing spreadsheetId or sheetName for webhook ${webhookId}`) + logger.error(`Missing spreadsheetId or sheetName for webhook ${webhookId}`, { requestId }) await markWebhookFailed(webhookId, logger) return 'failure' } @@ -93,7 +93,7 @@ export const googleSheetsPollingHandler: PollingProviderHandler = { logger ) await markWebhookSuccess(webhookId, logger) - logger.info(`[${requestId}] Sheet not modified since last poll for webhook ${webhookId}`) + logger.info(`Sheet not modified since last poll for webhook ${webhookId}`, { requestId }) return 'success' } @@ -118,16 +118,17 @@ export const googleSheetsPollingHandler: PollingProviderHandler = { logger ) await markWebhookSuccess(webhookId, logger) - logger.info( - `[${requestId}] First poll for webhook ${webhookId}, seeded row index: ${currentRowCount}` - ) + logger.info(`First poll for webhook ${webhookId}, seeded row index: ${currentRowCount}`, { + requestId, + }) return 'success' } if (currentRowCount <= config.lastIndexChecked) { if (currentRowCount < config.lastIndexChecked) { logger.warn( - `[${requestId}] Row count decreased from ${config.lastIndexChecked} to ${currentRowCount} for webhook ${webhookId}` + `Row count decreased from ${config.lastIndexChecked} to ${currentRowCount} for webhook ${webhookId}`, + { requestId } ) } await updateWebhookProviderConfig( @@ -140,7 +141,7 @@ export const googleSheetsPollingHandler: PollingProviderHandler = { logger ) await markWebhookSuccess(webhookId, logger) - logger.info(`[${requestId}] No new rows for webhook ${webhookId}`) + logger.info(`No new rows for webhook ${webhookId}`, { requestId }) return 'success' } @@ -156,7 +157,8 @@ export const googleSheetsPollingHandler: PollingProviderHandler = { headerRowIndex > 0 ? Math.max(startRow, headerRowIndex + 1) : startRow logger.info( - `[${requestId}] Found ${newRowCount} new rows for webhook ${webhookId}, processing rows ${adjustedStartRow}-${endRow}` + `Found ${newRowCount} new rows for webhook ${webhookId}, processing rows ${adjustedStartRow}-${endRow}`, + { requestId } ) // Entire batch is header/blank rows — advance pointer and skip fetch. @@ -175,7 +177,8 @@ export const googleSheetsPollingHandler: PollingProviderHandler = { ) await markWebhookSuccess(webhookId, logger) logger.info( - `[${requestId}] Batch ${startRow}-${endRow} contained only header/blank rows for webhook ${webhookId}, advancing pointer` + `Batch ${startRow}-${endRow} contained only header/blank rows for webhook ${webhookId}, advancing pointer`, + { requestId } ) return 'success' } @@ -219,22 +222,23 @@ export const googleSheetsPollingHandler: PollingProviderHandler = { if (failedCount > 0 && processedCount === 0) { await markWebhookFailed(webhookId, logger) - logger.warn( - `[${requestId}] All ${failedCount} rows failed to process for webhook ${webhookId}` - ) + logger.warn(`All ${failedCount} rows failed to process for webhook ${webhookId}`, { + requestId, + }) return 'failure' } await markWebhookSuccess(webhookId, logger) logger.info( - `[${requestId}] Successfully processed ${processedCount} rows for webhook ${webhookId}${failedCount > 0 ? ` (${failedCount} failed)` : ''}` + `Successfully processed ${processedCount} rows for webhook ${webhookId}${failedCount > 0 ? ` (${failedCount} failed)` : ''}`, + { requestId } ) return 'success' } catch (error) { if (error instanceof PollAdmissionRefusedError) { return skipAdmissionRefusedPoll(logger, requestId, webhookId) } - logger.error(`[${requestId}] Error processing Google Sheets webhook ${webhookId}:`, error) + logger.error(`Error processing Google Sheets webhook ${webhookId}:`, error, { requestId }) await markWebhookFailed(webhookId, logger) return 'failure' } @@ -255,7 +259,7 @@ async function isDriveFileUnchanged( } return { unchanged: currentModifiedTime === lastModifiedTime, currentModifiedTime } } catch { - logger.warn(`[${requestId}] Drive modifiedTime check failed, proceeding with Sheets API`) + logger.warn('Drive modifiedTime check failed, proceeding with Sheets API', { requestId }) return { unchanged: false } } } @@ -395,7 +399,7 @@ async function processRows( // Skip empty rows — don't fire a workflow run with no data. if (!row || row.length === 0) { - logger.info(`[${requestId}] Skipping empty row ${rowNumber} for webhook ${webhookData.id}`) + logger.info(`Skipping empty row ${rowNumber} for webhook ${webhookData.id}`, { requestId }) continue } @@ -435,9 +439,10 @@ async function processRows( if (!result.success) { throwIfAdmissionRefused(result) logger.error( - `[${requestId}] Failed to process webhook for row ${rowNumber}:`, + `Failed to process webhook for row ${rowNumber}:`, result.statusCode, - result.error + result.error, + { requestId } ) throw new Error(`Webhook processing failed (${result.statusCode}): ${result.error}`) } @@ -446,14 +451,14 @@ async function processRows( } ) - logger.info( - `[${requestId}] Successfully processed row ${rowNumber} for webhook ${webhookData.id}` - ) + logger.info(`Successfully processed row ${rowNumber} for webhook ${webhookData.id}`, { + requestId, + }) processedCount++ } catch (error) { if (error instanceof PollAdmissionRefusedError && processedCount === 0) throw error const errorMessage = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Error processing row ${rowNumber}:`, errorMessage) + logger.error(`Error processing row ${rowNumber}:`, errorMessage, { requestId }) failedCount++ } } diff --git a/apps/sim/lib/webhooks/polling/hubspot.ts b/apps/sim/lib/webhooks/polling/hubspot.ts index 58b6c012797..047d01831a0 100644 --- a/apps/sim/lib/webhooks/polling/hubspot.ts +++ b/apps/sim/lib/webhooks/polling/hubspot.ts @@ -212,7 +212,7 @@ export const hubspotPollingHandler: PollingProviderHandler = { if (error instanceof PollAdmissionRefusedError) { return skipAdmissionRefusedPoll(logger, requestId, webhookId) } - logger.error(`[${requestId}] Error processing HubSpot webhook ${webhookId}:`, error) + logger.error(`Error processing HubSpot webhook ${webhookId}:`, error, { requestId }) await markWebhookFailed(webhookId, logger) return 'failure' } @@ -265,7 +265,8 @@ async function pollSearchBased( ) await markWebhookSuccess(webhookId, logger) logger.info( - `[${requestId}] Seeded HubSpot webhook ${webhookId} watermark to ${nowMs} (${objectType}/${eventType}/${filterProperty})` + `Seeded HubSpot webhook ${webhookId} watermark to ${nowMs} (${objectType}/${eventType}/${filterProperty})`, + { requestId } ) return 'success' } @@ -305,12 +306,13 @@ async function pollSearchBased( logger ) await markWebhookSuccess(webhookId, logger) - logger.info(`[${requestId}] No new HubSpot ${objectType} ${eventType} for webhook ${webhookId}`) + logger.info(`No new HubSpot ${objectType} ${eventType} for webhook ${webhookId}`, { requestId }) return 'success' } logger.info( - `[${requestId}] Found ${records.length} HubSpot ${objectType} ${eventType} candidates for webhook ${webhookId}` + `Found ${records.length} HubSpot ${objectType} ${eventType} candidates for webhook ${webhookId}`, + { requestId } ) const targetProperty = config.targetPropertyName?.trim() || undefined @@ -348,15 +350,16 @@ async function pollSearchBased( if (failedCount > 0 && processedCount === 0) { await markWebhookFailed(webhookId, logger) - logger.warn( - `[${requestId}] All ${failedCount} HubSpot records failed to process for webhook ${webhookId}` - ) + logger.warn(`All ${failedCount} HubSpot records failed to process for webhook ${webhookId}`, { + requestId, + }) return 'failure' } await markWebhookSuccess(webhookId, logger) logger.info( - `[${requestId}] Processed ${processedCount} HubSpot records${skippedCount ? `, skipped ${skippedCount} (no property change)` : ''}${failedCount ? `, ${failedCount} failed` : ''} for webhook ${webhookId}` + `Processed ${processedCount} HubSpot records${skippedCount ? `, skipped ${skippedCount} (no property change)` : ''}${failedCount ? `, ${failedCount} failed` : ''} for webhook ${webhookId}`, + { requestId } ) return 'success' } @@ -403,12 +406,13 @@ async function pollListMembership( } if (result.reachedEnd) { update.membershipSeedComplete = true - logger.info( - `[${requestId}] HubSpot list_membership ${webhookId} seed complete (list=${listId})` - ) + logger.info(`HubSpot list_membership ${webhookId} seed complete (list=${listId})`, { + requestId, + }) } else { logger.info( - `[${requestId}] HubSpot list_membership ${webhookId} seed in progress (list=${listId}, scanned ${result.scanned})` + `HubSpot list_membership ${webhookId} seed in progress (list=${listId}, scanned ${result.scanned})`, + { requestId } ) } await updateWebhookProviderConfig(webhookId, update, logger) @@ -429,9 +433,9 @@ async function pollListMembership( return 'success' } - logger.info( - `[${requestId}] Found ${result.records.length} HubSpot list memberships for webhook ${webhookId}` - ) + logger.info(`Found ${result.records.length} HubSpot list memberships for webhook ${webhookId}`, { + requestId, + }) let processedCount = 0 let failedCount = 0 @@ -470,8 +474,9 @@ async function pollListMembership( if (error instanceof PollAdmissionRefusedError && processedCount === 0) throw error failedCount++ logger.error( - `[${requestId}] Error processing HubSpot list membership ${member.recordId}:`, - getErrorMessage(error, 'Unknown error') + `Error processing HubSpot list membership ${member.recordId}:`, + getErrorMessage(error, 'Unknown error'), + { requestId } ) } } @@ -585,10 +590,10 @@ export function buildUserFilters( } } } catch (error) { - logger?.warn( - `[${requestId ?? ''}] Could not parse user filters as JSON, ignoring:`, - getErrorMessage(error, 'parse error') - ) + logger?.warn('Could not parse user filters as JSON, ignoring:', { + requestId, + error: getErrorMessage(error, 'parse error'), + }) } } @@ -702,7 +707,8 @@ async function fetchHubSpotChanges(args: FetchArgs): Promise '') - logger.error(`[${requestId}] HubSpot search API error:`, { + logger.error('HubSpot search API error:', { + requestId, status: response.status, objectType, error: errorBody, @@ -719,7 +725,8 @@ async function fetchHubSpotChanges(args: FetchArgs): Promise= maxRecords) break if (pages >= MAX_PAGES_PER_POLL) { logger.warn( - `[${requestId}] HubSpot poll hit MAX_PAGES_PER_POLL=${MAX_PAGES_PER_POLL} — remaining records will roll over to next poll` + `HubSpot poll hit MAX_PAGES_PER_POLL=${MAX_PAGES_PER_POLL} — remaining records will roll over to next poll`, + { requestId } ) break } @@ -858,8 +865,9 @@ async function processRecords( failedCount++ cursorFrozen = true logger.error( - `[${requestId}] Error processing HubSpot ${objectType} ${record.id}:`, - getErrorMessage(error, 'Unknown error') + `Error processing HubSpot ${objectType} ${record.id}:`, + getErrorMessage(error, 'Unknown error'), + { requestId } ) } } @@ -943,9 +951,9 @@ async function fetchListMembershipPages( if (!response.ok) { const errorText = await response.text().catch(() => '') - logger.error( - `[${requestId}] HubSpot list memberships fetch failed ${response.status}: ${errorText}` - ) + logger.error(`HubSpot list memberships fetch failed ${response.status}: ${errorText}`, { + requestId, + }) throw new Error( `HubSpot list memberships fetch ${response.status}: ${errorText.slice(0, 500)}` ) diff --git a/apps/sim/lib/webhooks/polling/imap.ts b/apps/sim/lib/webhooks/polling/imap.ts index e3383580e63..942f82475b9 100644 --- a/apps/sim/lib/webhooks/polling/imap.ts +++ b/apps/sim/lib/webhooks/polling/imap.ts @@ -125,7 +125,7 @@ export const imapPollingHandler: PollingProviderHandler = { const config = getProviderConfig(webhookData.providerConfig) if (!config.host || !config.username || !config.password) { - logger.error(`[${requestId}] Missing IMAP credentials for webhook ${webhookId}`) + logger.error(`Missing IMAP credentials for webhook ${webhookId}`, { requestId }) await markWebhookFailed(webhookId, logger) return 'failure' } @@ -157,12 +157,12 @@ export const imapPollingHandler: PollingProviderHandler = { uidValidityByMailbox ) await markWebhookSuccess(webhookId, logger) - logger.info(`[${requestId}] No new emails found for webhook ${webhookId}`) + logger.info(`No new emails found for webhook ${webhookId}`, { requestId }) await client.logout() return 'success' } - logger.info(`[${requestId}] Found ${emails.length} new emails for webhook ${webhookId}`) + logger.info(`Found ${emails.length} new emails for webhook ${webhookId}`, { requestId }) const { processedCount, failedCount } = await processEmails( emails, @@ -187,15 +187,16 @@ export const imapPollingHandler: PollingProviderHandler = { if (failedCount > 0 && processedCount === 0) { await markWebhookFailed(webhookId, logger) - logger.warn( - `[${requestId}] All ${failedCount} emails failed to process for webhook ${webhookId}` - ) + logger.warn(`All ${failedCount} emails failed to process for webhook ${webhookId}`, { + requestId, + }) return 'failure' } await markWebhookSuccess(webhookId, logger) logger.info( - `[${requestId}] Successfully processed ${processedCount} emails for webhook ${webhookId}${failedCount > 0 ? ` (${failedCount} failed)` : ''}` + `Successfully processed ${processedCount} emails for webhook ${webhookId}${failedCount > 0 ? ` (${failedCount} failed)` : ''}`, + { requestId } ) return 'success' } catch (innerError) { @@ -208,7 +209,7 @@ export const imapPollingHandler: PollingProviderHandler = { if (error instanceof PollAdmissionRefusedError) { return skipAdmissionRefusedPoll(logger, requestId, webhookId) } - logger.error(`[${requestId}] Error processing IMAP webhook ${webhookId}`) + logger.error(`Error processing IMAP webhook ${webhookId}`, { requestId }) await markWebhookFailed(webhookId, logger) return 'failure' } @@ -294,7 +295,8 @@ async function fetchNewEmails( if (storedUidValidity && storedUidValidity !== currentUidValidity) { logger.warn( - `[${requestId}] UIDVALIDITY changed for ${mailboxPath} (${storedUidValidity} -> ${currentUidValidity}), discarding stored UID` + `UIDVALIDITY changed for ${mailboxPath} (${storedUidValidity} -> ${currentUidValidity}), discarding stored UID`, + { requestId } ) delete latestUidByMailbox[mailboxPath] } @@ -308,7 +310,7 @@ async function fetchNewEmails( try { searchCriteria = JSON.parse(config.searchCriteria) } catch { - logger.warn(`[${requestId}] Invalid search criteria JSON, using default`) + logger.warn('Invalid search criteria JSON, using default', { requestId }) } } } @@ -360,7 +362,7 @@ async function fetchNewEmails( totalEmailsCollected++ } } catch { - logger.warn(`[${requestId}] Error processing mailbox ${mailboxPath}`) + logger.warn(`Error processing mailbox ${mailboxPath}`, { requestId }) } } @@ -583,9 +585,10 @@ async function processEmails( if (!result.success) { throwIfAdmissionRefused(result) logger.error( - `[${requestId}] Failed to process webhook for email ${email.uid}:`, + `Failed to process webhook for email ${email.uid}:`, result.statusCode, - result.error + result.error, + { requestId } ) throw new Error(`Webhook processing failed (${result.statusCode}): ${result.error}`) } @@ -602,7 +605,7 @@ async function processEmails( } await client.messageFlagsAdd(email.uid, ['\\Seen'], { uid: true }) } catch { - logger.warn(`[${requestId}] Failed to mark message ${email.uid} as read`) + logger.warn(`Failed to mark message ${email.uid} as read`, { requestId }) } } @@ -611,12 +614,13 @@ async function processEmails( ) logger.info( - `[${requestId}] Successfully processed email ${email.uid} from ${email.mailboxPath} for webhook ${webhookData.id}` + `Successfully processed email ${email.uid} from ${email.mailboxPath} for webhook ${webhookData.id}`, + { requestId } ) processedCount++ } catch (error) { if (error instanceof PollAdmissionRefusedError && processedCount === 0) throw error - logger.error(`[${requestId}] Error processing email ${email.uid}`) + logger.error(`Error processing email ${email.uid}`, { requestId }) failedCount++ } } diff --git a/apps/sim/lib/webhooks/polling/orchestrator.ts b/apps/sim/lib/webhooks/polling/orchestrator.ts index 2e60ac387cc..fadfa6f21ef 100644 --- a/apps/sim/lib/webhooks/polling/orchestrator.ts +++ b/apps/sim/lib/webhooks/polling/orchestrator.ts @@ -47,13 +47,12 @@ export async function pollProvider(providerName: string): Promise { return 'skipped' } - const requestId = generateShortId() return withResourceOutboundScope({ workspaceId: entry.workflow.workspaceId }, () => handler.pollWebhook({ webhookData: entry.webhook, workflowData: entry.workflow, - requestId, - logger, + requestId: generateShortId(), + logger: logger.withMetadata({ webhookId: entry.webhook.id }), }) ) }, diff --git a/apps/sim/lib/webhooks/polling/outlook.ts b/apps/sim/lib/webhooks/polling/outlook.ts index 99deb9b7d8b..efa6a0b65a9 100644 --- a/apps/sim/lib/webhooks/polling/outlook.ts +++ b/apps/sim/lib/webhooks/polling/outlook.ts @@ -119,7 +119,7 @@ export const outlookPollingHandler: PollingProviderHandler = { const webhookId = webhookData.id try { - logger.info(`[${requestId}] Processing Outlook webhook: ${webhookId}`) + logger.info(`Processing Outlook webhook: ${webhookId}`, { requestId }) const accessToken = await resolveOAuthCredential(webhookData, 'outlook', requestId) const config = getProviderConfig(webhookData.providerConfig) @@ -134,11 +134,11 @@ export const outlookPollingHandler: PollingProviderHandler = { logger ) await markWebhookSuccess(webhookId, logger) - logger.info(`[${requestId}] No new emails found for webhook ${webhookId}`) + logger.info(`No new emails found for webhook ${webhookId}`, { requestId }) return 'success' } - logger.info(`[${requestId}] Found ${emails.length} emails for webhook ${webhookId}`) + logger.info(`Found ${emails.length} emails for webhook ${webhookId}`, { requestId }) const { processedCount, failedCount } = await processOutlookEmails( emails, @@ -158,22 +158,23 @@ export const outlookPollingHandler: PollingProviderHandler = { if (failedCount > 0 && processedCount === 0) { await markWebhookFailed(webhookId, logger) - logger.warn( - `[${requestId}] All ${failedCount} emails failed to process for webhook ${webhookId}` - ) + logger.warn(`All ${failedCount} emails failed to process for webhook ${webhookId}`, { + requestId, + }) return 'failure' } await markWebhookSuccess(webhookId, logger) logger.info( - `[${requestId}] Successfully processed ${processedCount} emails for webhook ${webhookId}${failedCount > 0 ? ` (${failedCount} failed)` : ''}` + `Successfully processed ${processedCount} emails for webhook ${webhookId}${failedCount > 0 ? ` (${failedCount} failed)` : ''}`, + { requestId } ) return 'success' } catch (error) { if (error instanceof PollAdmissionRefusedError) { return skipAdmissionRefusedPoll(logger, requestId, webhookId) } - logger.error(`[${requestId}] Error processing Outlook webhook ${webhookId}:`, error) + logger.error(`Error processing Outlook webhook ${webhookId}:`, error, { requestId }) await markWebhookFailed(webhookId, logger) return 'failure' } @@ -211,7 +212,7 @@ async function fetchNewOutlookEmails( } const allEmails: OutlookEmail[] = [] let nextUrl: string | undefined = `${apiUrl}?${params.toString()}` - logger.info(`[${requestId}] Fetching emails from: ${nextUrl}`) + logger.info(`Fetching emails from: ${nextUrl}`, { requestId }) while (nextUrl && allEmails.length < maxEmails) { const response = await fetchWithRetry(nextUrl, { @@ -224,7 +225,8 @@ async function fetchNewOutlookEmails( const errorData = await response .json() .catch(() => ({ error: { message: 'Unknown error' } })) - logger.error(`[${requestId}] Microsoft Graph API error:`, { + logger.error('Microsoft Graph API error:', { + requestId, status: response.status, statusText: response.statusText, error: errorData, @@ -245,7 +247,7 @@ async function fetchNewOutlookEmails( if (pageEmails.length === 0) break } - logger.info(`[${requestId}] Fetched ${allEmails.length} emails total`) + logger.info(`Fetched ${allEmails.length} emails total`, { requestId }) const emails = allEmails @@ -262,7 +264,8 @@ async function fetchNewOutlookEmails( ) if (resolvedFolderIds.size < wellKnownFolders.length) { logger.warn( - `[${requestId}] Could not resolve all well-known folders (${resolvedFolderIds.size}/${wellKnownFolders.length}) — skipping folder filter to avoid incorrect results` + `Could not resolve all well-known folders (${resolvedFolderIds.size}/${wellKnownFolders.length}) — skipping folder filter to avoid incorrect results`, + { requestId } ) skipFolderFilter = true } @@ -273,14 +276,14 @@ async function fetchNewOutlookEmails( ? emails : filterEmailsByFolder(emails, config, resolvedFolderIds) - logger.info( - `[${requestId}] Fetched ${emails.length} emails, ${filteredEmails.length} after filtering` - ) + logger.info(`Fetched ${emails.length} emails, ${filteredEmails.length} after filtering`, { + requestId, + }) return { emails: filteredEmails } } catch (error) { const errorMessage = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Error fetching new Outlook emails:`, errorMessage) + logger.error('Error fetching new Outlook emails:', errorMessage, { requestId }) throw error } } @@ -316,16 +319,16 @@ async function resolveWellKnownFolderId( ) if (!response.ok) { - logger.warn( - `[${requestId}] Failed to resolve well-known folder '${folderName}': ${response.status}` - ) + logger.warn(`Failed to resolve well-known folder '${folderName}': ${response.status}`, { + requestId, + }) return null } const folder = await response.json() return folder.id || null } catch (error) { - logger.error(`[${requestId}] Error resolving well-known folder '${folderName}':`, error) + logger.error(`Error resolving well-known folder '${folderName}':`, error, { requestId }) return null } } @@ -353,9 +356,9 @@ async function resolveWellKnownFolderIds( } } - logger.info( - `[${requestId}] Resolved ${resolvedIds.size}/${wellKnownFolders.length} well-known folders` - ) + logger.info(`Resolved ${resolvedIds.size}/${wellKnownFolders.length} well-known folders`, { + requestId, + }) return resolvedIds } @@ -411,10 +414,9 @@ async function processOutlookEmails( logger ) } catch (error) { - logger.error( - `[${requestId}] Error downloading attachments for email ${email.id}:`, - error - ) + logger.error(`Error downloading attachments for email ${email.id}:`, error, { + requestId, + }) } } @@ -452,7 +454,8 @@ async function processOutlookEmails( } logger.info( - `[${requestId}] Processing email: ${email.subject} from ${email.from?.emailAddress?.address}` + `Processing email: ${email.subject} from ${email.from?.emailAddress?.address}`, + { requestId } ) const result = await processPolledWebhookEvent( @@ -465,9 +468,10 @@ async function processOutlookEmails( if (!result.success) { throwIfAdmissionRefused(result) logger.error( - `[${requestId}] Failed to process webhook for email ${email.id}:`, + `Failed to process webhook for email ${email.id}:`, result.statusCode, - result.error + result.error, + { requestId } ) throw new Error(`Webhook processing failed (${result.statusCode}): ${result.error}`) } @@ -480,13 +484,13 @@ async function processOutlookEmails( } ) - logger.info( - `[${requestId}] Successfully processed email ${email.id} for webhook ${webhookData.id}` - ) + logger.info(`Successfully processed email ${email.id} for webhook ${webhookData.id}`, { + requestId, + }) processedCount++ } catch (error) { if (error instanceof PollAdmissionRefusedError && processedCount === 0) throw error - logger.error(`[${requestId}] Error processing email ${email.id}:`, error) + logger.error(`Error processing email ${email.id}:`, error, { requestId }) failedCount++ } } @@ -513,7 +517,7 @@ async function downloadOutlookAttachments( ) if (!response.ok) { - logger.error(`[${requestId}] Failed to fetch attachments for message ${messageId}`) + logger.error(`Failed to fetch attachments for message ${messageId}`, { requestId }) return attachments } @@ -536,17 +540,18 @@ async function downloadOutlookAttachments( } } catch (error) { logger.error( - `[${requestId}] Error processing attachment ${attachment.id} for message ${messageId}:`, - error + `Error processing attachment ${attachment.id} for message ${messageId}:`, + error, + { requestId } ) } } - logger.info( - `[${requestId}] Downloaded ${attachments.length} attachments for message ${messageId}` - ) + logger.info(`Downloaded ${attachments.length} attachments for message ${messageId}`, { + requestId, + }) } catch (error) { - logger.error(`[${requestId}] Error downloading attachments for message ${messageId}:`, error) + logger.error(`Error downloading attachments for message ${messageId}:`, error, { requestId }) } return attachments diff --git a/apps/sim/lib/webhooks/polling/rss.ts b/apps/sim/lib/webhooks/polling/rss.ts index 932ef9da5fb..9da351d8f7b 100644 --- a/apps/sim/lib/webhooks/polling/rss.ts +++ b/apps/sim/lib/webhooks/polling/rss.ts @@ -106,7 +106,7 @@ export const rssPollingHandler: PollingProviderHandler = { const config = getProviderConfig(webhookData.providerConfig) if (!config?.feedUrl) { - logger.error(`[${requestId}] Missing feedUrl for webhook ${webhookId}`) + logger.error(`Missing feedUrl for webhook ${webhookId}`, { requestId }) await markWebhookFailed(webhookId, logger) return 'failure' } @@ -122,11 +122,11 @@ export const rssPollingHandler: PollingProviderHandler = { if (!newItems.length) { await updateRssState(webhookId, now.toISOString(), [], config, logger, etag, lastModified) await markWebhookSuccess(webhookId, logger) - logger.info(`[${requestId}] No new items found for webhook ${webhookId}`) + logger.info(`No new items found for webhook ${webhookId}`, { requestId }) return 'success' } - logger.info(`[${requestId}] Found ${newItems.length} new items for webhook ${webhookId}`) + logger.info(`Found ${newItems.length} new items for webhook ${webhookId}`, { requestId }) const { processedCount, failedCount, admissionRejectedAt } = await processRssItems( newItems, @@ -148,7 +148,8 @@ export const rssPollingHandler: PollingProviderHandler = { await updateRssState(webhookId, now.toISOString(), newGuids, config, logger) } logger.info( - `[${requestId}] Stopped polling webhook ${webhookId}: execution admission refused, ${newItems.length - attemptedItems.length} items left for a later poll` + `Stopped polling webhook ${webhookId}: execution admission refused, ${newItems.length - attemptedItems.length} items left for a later poll`, + { requestId } ) return 'skipped' } @@ -165,15 +166,16 @@ export const rssPollingHandler: PollingProviderHandler = { if (failedCount > 0 && processedCount === 0) { await markWebhookFailed(webhookId, logger) - logger.warn( - `[${requestId}] All ${failedCount} items failed to process for webhook ${webhookId}` - ) + logger.warn(`All ${failedCount} items failed to process for webhook ${webhookId}`, { + requestId, + }) return 'failure' } await markWebhookSuccess(webhookId, logger) logger.info( - `[${requestId}] Successfully processed ${processedCount} items for webhook ${webhookId}${failedCount > 0 ? ` (${failedCount} failed)` : ''}` + `Successfully processed ${processedCount} items for webhook ${webhookId}${failedCount > 0 ? ` (${failedCount} failed)` : ''}`, + { requestId } ) return 'success' } catch (error) { @@ -181,7 +183,7 @@ export const rssPollingHandler: PollingProviderHandler = { webhookData, pollStartedAt, error, - `[${requestId}] Error polling RSS webhook ${webhookId}`, + `Error polling RSS webhook ${webhookId}`, logger ) return 'failure' @@ -250,7 +252,7 @@ async function fetchNewRssItems( }) if (response.status === 304) { - logger.info(`[${requestId}] RSS feed not modified (304) for ${config.feedUrl}`) + logger.info(`RSS feed not modified (304) for ${config.feedUrl}`, { requestId }) return { feed: { items: [] } as RssFeed, items: [], @@ -308,9 +310,9 @@ async function fetchNewRssItems( const limitedItems = newItems.slice(0, 25) - logger.info( - `[${requestId}] Found ${newItems.length} new items (processing ${limitedItems.length})` - ) + logger.info(`Found ${newItems.length} new items (processing ${limitedItems.length})`, { + requestId, + }) return { feed: feed as RssFeed, @@ -336,9 +338,9 @@ async function processRssItems( const itemGuid = getRssItemGuid(item) if (!itemGuid) { - logger.warn( - `[${requestId}] Skipping RSS item with no identifiable GUID for webhook ${webhookData.id}` - ) + logger.warn(`Skipping RSS item with no identifiable GUID for webhook ${webhookData.id}`, { + requestId, + }) continue } @@ -381,9 +383,10 @@ async function processRssItems( if (!result.success) { throwIfAdmissionRefused(result) logger.error( - `[${requestId}] Failed to process webhook for item ${itemGuid}:`, + `Failed to process webhook for item ${itemGuid}:`, result.statusCode, - result.error + result.error, + { requestId } ) throw new Error(`Webhook processing failed (${result.statusCode}): ${result.error}`) } @@ -393,7 +396,8 @@ async function processRssItems( ) logger.info( - `[${requestId}] Successfully processed item ${item.title || itemGuid} for webhook ${webhookData.id}` + `Successfully processed item ${item.title || itemGuid} for webhook ${webhookData.id}`, + { requestId } ) processedCount++ } catch (error) { @@ -401,7 +405,7 @@ async function processRssItems( return { processedCount, failedCount, admissionRejectedAt: index } } const errorMessage = getErrorMessage(error, 'Unknown error') - logger.error(`[${requestId}] Error processing item:`, errorMessage) + logger.error('Error processing item:', errorMessage, { requestId }) failedCount++ } } diff --git a/apps/sim/lib/webhooks/polling/utils.ts b/apps/sim/lib/webhooks/polling/utils.ts index 704739f63df..e1c3b5a1194 100644 --- a/apps/sim/lib/webhooks/polling/utils.ts +++ b/apps/sim/lib/webhooks/polling/utils.ts @@ -68,9 +68,9 @@ export function skipAdmissionRefusedPoll( requestId: string, webhookId: string ): 'skipped' { - logger.info( - `[${requestId}] Execution admission refused webhook ${webhookId}; left its items for a later poll` - ) + logger.info(`Execution admission refused webhook ${webhookId}; left its items for a later poll`, { + requestId, + }) return 'skipped' } diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index dfcd39b765f..6dfd7a55199 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -143,15 +143,14 @@ export async function parseWebhookBody( } } catch (bodyError) { if (isPayloadSizeLimitError(bodyError)) { - logger.warn(`[${requestId}] Rejected oversized webhook body`, { + logger.warn('Rejected oversized webhook body', { + requestId, maxBytes: WEBHOOK_MAX_BODY_BYTES, observedBytes: bodyError.observedBytes, }) return new NextResponse('Request body too large', { status: 413 }) } - logger.error(`[${requestId}] Failed to read request body`, { - error: toError(bodyError).message, - }) + logger.error('Failed to read request body', { requestId, error: toError(bodyError).message }) return new NextResponse('Failed to read request body', { status: 400 }) } @@ -174,7 +173,8 @@ export async function parseWebhookBody( body = JSON.parse(rawBody) } } catch (parseError) { - logger.error(`[${requestId}] Failed to parse webhook body`, { + logger.error('Failed to parse webhook body', { + requestId, error: toError(parseError).message, contentType: request.headers.get('content-type'), bodyPreview: truncate(rawBody ?? '', 100), @@ -237,7 +237,8 @@ export async function handlePreLookupWebhookVerification( } logger.info( - `[${requestId}] Returning 200 for pending ${pendingVerification.provider} webhook verification on path: ${path}` + `Returning 200 for pending ${pendingVerification.provider} webhook verification on path: ${path}`, + { requestId } ) return NextResponse.json({ status: 'ok', message: 'Webhook endpoint verified' }) @@ -350,7 +351,8 @@ export function handlePreDeploymentVerification( ): NextResponse | null { if (requiresPendingWebhookVerification(webhookRecord.provider)) { logger.info( - `[${requestId}] ${webhookRecord.provider} webhook - block not in deployment, returning 200 OK for URL validation` + `${webhookRecord.provider} webhook - block not in deployment, returning 200 OK for URL validation`, + { requestId } ) return NextResponse.json({ status: 'ok', @@ -411,7 +413,9 @@ export async function findAllWebhooksForPath( } if (results.length === 0) { - logger.debug(`[${options.requestId}] No active webhooks found for path: ${options.path}`) + logger.debug(`No active webhooks found for path: ${options.path}`, { + requestId: options.requestId, + }) return results } @@ -434,14 +438,17 @@ export async function findAllWebhooksForPath( const ownerResults = results.filter((result) => result.webhook.workflowId === ownerWorkflowId) logger.error( - `[${options.requestId}] Cross-tenant webhook path collision for path: ${options.path}. Found ${results.length} active webhooks across ${distinctWorkflowIds.size} workflows. Dispatching only to owner workflow ${ownerWorkflowId} (${claimOwnerWorkflowId === ownerWorkflowId ? 'path-claim owner' : 'earliest registration'}) and dropping ${results.length - ownerResults.length} foreign webhook(s).` + `Cross-tenant webhook path collision for path: ${options.path}. Found ${results.length} active webhooks across ${distinctWorkflowIds.size} workflows. Dispatching only to owner workflow ${ownerWorkflowId} (${claimOwnerWorkflowId === ownerWorkflowId ? 'path-claim owner' : 'earliest registration'}) and dropping ${results.length - ownerResults.length} foreign webhook(s).`, + { requestId: options.requestId } ) return ownerResults } if (results.length > 1) { - logger.info(`[${options.requestId}] Found ${results.length} webhooks for path: ${options.path}`) + logger.info(`Found ${results.length} webhooks for path: ${options.path}`, { + requestId: options.requestId, + }) } return results @@ -517,7 +524,7 @@ export async function findWebhooksByRoutingKey( } if (results.length === 0) { - logger.warn(`[${requestId}] No active ${provider} webhooks for routing key`) + logger.warn(`No active ${provider} webhooks for routing key`, { requestId }) } return results @@ -583,9 +590,7 @@ export async function verifyProviderAuth( foundWorkflow.workspaceId ?? undefined ) } catch (error) { - logger.error(`[${requestId}] Failed to fetch environment variables`, { - error, - }) + logger.error('Failed to fetch environment variables', { requestId, error }) } } @@ -648,7 +653,8 @@ export async function checkWebhookPreprocessing( const error = preprocessResult.error const transientAdmissionFailure = classifyTransientAdmissionFailure(error) const admissionRejectionCode = getDeterministicAdmissionRejectionCode(error) - logger.debug(`[${requestId}] Webhook preprocessing failed`, { + logger.debug('Webhook preprocessing failed', { + requestId, provider: foundWebhook.provider, error: error.message, statusCode: error.statusCode, @@ -674,7 +680,7 @@ export async function checkWebhookPreprocessing( executionTimeoutMs: preprocessResult.executionTimeout.async, } } catch (preprocessError) { - logger.error(`[${requestId}] Error during webhook preprocessing:`, preprocessError) + logger.error('Error during webhook preprocessing:', preprocessError, { requestId }) return { error: formatProviderErrorResponse(foundWebhook, 'Internal error during preprocessing', 500), @@ -742,7 +748,9 @@ async function queueWebhookExecutionWithResult( const billingAttribution = options.billingAttribution const workspaceId = foundWorkflow.workspaceId if (!actorUserId || !billingAttribution || !workspaceId) { - logger.error(`[${options.requestId}] Missing billing context for webhook ${foundWebhook.id}`) + logger.error(`Missing billing context for webhook ${foundWebhook.id}`, { + requestId: options.requestId, + }) return { outcome: 'failed', response: NextResponse.json( @@ -820,7 +828,8 @@ async function queueWebhookExecutionWithResult( }) reservationTransferred = true logger.info( - `[${options.requestId}] Queued webhook execution task ${jobId} for ${foundWebhook.provider} webhook via job queue` + `Queued webhook execution task ${jobId} for ${foundWebhook.provider} webhook via job queue`, + { requestId: options.requestId } ) } else { const jobQueue = await getInlineJobQueue() @@ -836,9 +845,9 @@ async function queueWebhookExecutionWithResult( executeWebhookJob(payload, signal), }) reservationTransferred = true - logger.info( - `[${options.requestId}] Queued ${foundWebhook.provider} webhook execution ${jobId} via inline backend` - ) + logger.info(`Queued ${foundWebhook.provider} webhook execution ${jobId} via inline backend`, { + requestId: options.requestId, + }) } const successResponse = handler.formatSuccessResponse?.(providerConfig) ?? null @@ -852,7 +861,7 @@ async function queueWebhookExecutionWithResult( reason: 'queued', } } catch (error: unknown) { - logger.error(`[${options.requestId}] Failed to queue webhook execution:`, error) + logger.error('Failed to queue webhook execution:', error, { requestId: options.requestId }) const errorResponse = handler.formatQueueErrorResponse?.() ?? null if (errorResponse) { @@ -1002,11 +1011,13 @@ export async function processPolledWebhookEvent( return { success: false, error: 'Webhook has no provider', statusCode: 400 } } const provider = foundWebhook.provider + // Concurrent polls in one tick share a request; the webhook id tells their lines apart. + const log = logger.withMetadata({ webhookId: foundWebhook.id }) const ticket = tryAdmit() if (!ticket) { const gateFailure = ADMISSION_ERROR_DESCRIPTOR.GATE_CAPACITY - logger.warn(`[${requestId}] Admission gate rejected polled webhook event`) + log.warn('Admission gate rejected polled webhook event', { requestId }) return { success: false, error: 'Server at capacity', @@ -1023,8 +1034,9 @@ export async function processPolledWebhookEvent( if (foundWebhook.blockId) { const blockExists = await blockExistsInDeployment(foundWorkflow.id, foundWebhook.blockId) if (!blockExists) { - logger.info( - `[${requestId}] Trigger block ${foundWebhook.blockId} not found in deployment for workflow ${foundWorkflow.id}` + log.info( + `Trigger block ${foundWebhook.blockId} not found in deployment for workflow ${foundWorkflow.id}`, + { requestId } ) return { success: false, error: 'Trigger block not found in deployment', statusCode: 404 } } @@ -1042,7 +1054,8 @@ export async function processPolledWebhookEvent( typeof errorBody.error === 'string' ? errorBody.error : 'Preprocessing failed' - logger.warn(`[${requestId}] Polled webhook preprocessing failed`, { + log.warn('Polled webhook preprocessing failed', { + requestId, statusCode, error: errorMessage, }) @@ -1073,7 +1086,7 @@ export async function processPolledWebhookEvent( const billingAttribution = preprocessResult.billingAttribution const workspaceId = foundWorkflow.workspaceId ?? undefined if (!actorUserId || !billingAttribution || !workspaceId) { - logger.error(`[${requestId}] Missing billing context for webhook ${foundWebhook.id}`) + log.error(`Missing billing context for webhook ${foundWebhook.id}`, { requestId }) return { success: false, error: 'Unable to resolve billing account', statusCode: 500 } } @@ -1135,8 +1148,9 @@ export async function processPolledWebhookEvent( maxDurationSeconds, }) reservationTransferred = true - logger.info( - `[${requestId}] Queued polling webhook execution task ${jobId} for ${provider} webhook via job queue` + log.info( + `Queued polling webhook execution task ${jobId} for ${provider} webhook via job queue`, + { requestId } ) } else { const jobQueue = await getInlineJobQueue() @@ -1152,12 +1166,12 @@ export async function processPolledWebhookEvent( executeWebhookJob(payload, signal), }) reservationTransferred = true - logger.info(`[${requestId}] Queued ${provider} webhook execution ${jobId} via inline backend`) + log.info(`Queued ${provider} webhook execution ${jobId} via inline backend`, { requestId }) } return { success: true, executionId } } catch (error: unknown) { - logger.error(`[${requestId}] Failed to process polled webhook event:`, error) + log.error('Failed to process polled webhook event:', error, { requestId }) return { success: false, error: 'Internal server error', statusCode: 500 } } finally { if (!reservationTransferred && reservedExecutionId) { diff --git a/apps/sim/lib/webhooks/provider-subscription-utils.ts b/apps/sim/lib/webhooks/provider-subscription-utils.ts index 71b84f2c9f7..d3290eb56d0 100644 --- a/apps/sim/lib/webhooks/provider-subscription-utils.ts +++ b/apps/sim/lib/webhooks/provider-subscription-utils.ts @@ -31,7 +31,7 @@ export async function getCredentialOwner( ): Promise<{ userId: string; accountId: string } | null> { const resolved = await resolveOAuthAccountId(credentialId) if (!resolved) { - logger.warn(`[${requestId}] Failed to resolve OAuth account for credentialId ${credentialId}`) + logger.warn(`Failed to resolve OAuth account for credentialId ${credentialId}`, { requestId }) return null } const [credentialRecord] = await db @@ -41,7 +41,7 @@ export async function getCredentialOwner( .limit(1) if (!credentialRecord?.userId) { - logger.warn(`[${requestId}] Credential owner not found for credentialId ${credentialId}`) + logger.warn(`Credential owner not found for credentialId ${credentialId}`, { requestId }) return null } diff --git a/apps/sim/lib/webhooks/provider-subscriptions.ts b/apps/sim/lib/webhooks/provider-subscriptions.ts index 82ba65ca77c..61f172c6f18 100644 --- a/apps/sim/lib/webhooks/provider-subscriptions.ts +++ b/apps/sim/lib/webhooks/provider-subscriptions.ts @@ -337,7 +337,8 @@ export async function cleanupExternalWebhook( ) } catch (error) { const projected = projectProviderFailure(error, secrets, resolvedProviderConfig) - logger.warn(`[${requestId}] Error cleaning up external webhook (non-fatal)`, { + logger.warn('Error cleaning up external webhook (non-fatal)', { + requestId, provider, webhookId: webhook.id, error: projected.message, diff --git a/apps/sim/lib/webhooks/providers/airtable.ts b/apps/sim/lib/webhooks/providers/airtable.ts index 0c9a2ccbc03..6d7b88985dc 100644 --- a/apps/sim/lib/webhooks/providers/airtable.ts +++ b/apps/sim/lib/webhooks/providers/airtable.ts @@ -72,7 +72,8 @@ async function fetchAndProcessAirtablePayloads( if (!baseId || !airtableWebhookId) { logger.error( - `[${requestId}] Missing baseId or externalId in providerConfig for webhook ${webhookData.id}. Cannot fetch payloads.` + `Missing baseId or externalId in providerConfig for webhook ${webhookData.id}. Cannot fetch payloads.`, + { requestId } ) return } @@ -80,16 +81,17 @@ async function fetchAndProcessAirtablePayloads( const credentialId = localProviderConfig.credentialId as string | undefined if (!credentialId) { logger.error( - `[${requestId}] Missing credentialId in providerConfig for Airtable webhook ${webhookData.id}.` + `Missing credentialId in providerConfig for Airtable webhook ${webhookData.id}.`, + { requestId } ) return } const resolvedAirtable = await resolveOAuthAccountId(credentialId) if (!resolvedAirtable) { - logger.error( - `[${requestId}] Could not resolve credential ${credentialId} for Airtable webhook` - ) + logger.error(`Could not resolve credential ${credentialId} for Airtable webhook`, { + requestId, + }) return } @@ -107,7 +109,8 @@ async function fetchAndProcessAirtablePayloads( if (!ownerUserId) { logger.error( - `[${requestId}] Could not resolve owner for Airtable credential ${credentialId} on webhook ${webhookData.id}` + `Could not resolve owner for Airtable credential ${credentialId} on webhook ${webhookData.id}`, + { requestId } ) return } @@ -116,7 +119,8 @@ async function fetchAndProcessAirtablePayloads( if (storedCursor === undefined || storedCursor === null) { logger.info( - `[${requestId}] No cursor found in providerConfig for webhook ${webhookData.id}, initializing...` + `No cursor found in providerConfig for webhook ${webhookData.id}, initializing...`, + { requestId } ) localProviderConfig.externalWebhookCursor = null @@ -133,10 +137,11 @@ async function fetchAndProcessAirtablePayloads( .where(eq(webhook.id, webhookData.id as string)) localProviderConfig.externalWebhookCursor = null - logger.info(`[${requestId}] Successfully initialized cursor for webhook ${webhookData.id}`) + logger.info(`Successfully initialized cursor for webhook ${webhookData.id}`, { requestId }) } catch (initError: unknown) { const err = initError as Error - logger.error(`[${requestId}] Failed to initialize cursor in DB`, { + logger.error('Failed to initialize cursor in DB', { + requestId, webhookId: webhookData.id, error: err.message, stack: err.stack, @@ -159,20 +164,19 @@ async function fetchAndProcessAirtablePayloads( ) if (!accessToken) { logger.error( - `[${requestId}] Failed to obtain valid Airtable access token via credential ${credentialId}.` + `Failed to obtain valid Airtable access token via credential ${credentialId}.`, + { requestId } ) throw new Error('Airtable access token not found.') } } catch (tokenError: unknown) { const err = tokenError as Error - logger.error( - `[${requestId}] Failed to get Airtable OAuth token for credential ${credentialId}`, - { - error: err.message, - stack: err.stack, - credentialId, - } - ) + logger.error(`Failed to get Airtable OAuth token for credential ${credentialId}`, { + requestId, + error: err.message, + stack: err.stack, + credentialId, + }) return } @@ -209,14 +213,12 @@ async function fetchAndProcessAirtablePayloads( responseBody.error?.message || responseBody.error || `Airtable API error Status ${response.status}` - logger.error( - `[${requestId}] Airtable API request to /payloads failed (Call ${apiCallCount})`, - { - webhookId: webhookData.id, - status: response.status, - error: errorMessage, - } - ) + logger.error(`Airtable API request to /payloads failed (Call ${apiCallCount})`, { + requestId, + webhookId: webhookData.id, + status: response.status, + error: errorMessage, + }) // Error logging handled by logging session mightHaveMore = false break @@ -322,7 +324,8 @@ async function fetchAndProcessAirtablePayloads( localProviderConfig.externalWebhookCursor = currentCursor // Update local copy too } catch (dbError: unknown) { const err = dbError as Error - logger.error(`[${requestId}] Failed to persist Airtable cursor to DB`, { + logger.error('Failed to persist Airtable cursor to DB', { + requestId, webhookId: webhookData.id, cursor: currentCursor, error: err.message, @@ -332,7 +335,8 @@ async function fetchAndProcessAirtablePayloads( throw new Error('Failed to save Airtable cursor, stopping processing.') // Re-throw to break loop clearly } } else if (!nextCursor || typeof nextCursor !== 'number') { - logger.warn(`[${requestId}] Invalid or missing cursor received, stopping poll`, { + logger.warn('Invalid or missing cursor received, stopping poll', { + requestId, webhookId: webhookData.id, apiCall: apiCallCount, receivedCursor: nextCursor, @@ -343,8 +347,9 @@ async function fetchAndProcessAirtablePayloads( } } catch (fetchError: unknown) { logger.error( - `[${requestId}] Network error calling Airtable GET /payloads (Call ${apiCallCount}) for webhook ${webhookData.id}`, - fetchError + `Network error calling Airtable GET /payloads (Call ${apiCallCount}) for webhook ${webhookData.id}`, + fetchError, + { requestId } ) // Error logging handled by logging session mightHaveMore = false @@ -354,7 +359,8 @@ async function fetchAndProcessAirtablePayloads( // Convert map values to array for final processing const finalConsolidatedChanges = Array.from(consolidatedChangesMap.values()) logger.info( - `[${requestId}] Consolidated ${finalConsolidatedChanges.length} Airtable changes across ${apiCallCount} API calls` + `Consolidated ${finalConsolidatedChanges.length} Airtable changes across ${apiCallCount} API calls`, + { requestId } ) if (finalConsolidatedChanges.length > 0 || allPayloads.length > 0) { @@ -378,8 +384,9 @@ async function fetchAndProcessAirtablePayloads( // CRITICAL EXECUTION TRACE POINT logger.info( - `[${requestId}] CRITICAL_TRACE: Beginning workflow execution with ${finalConsolidatedChanges.length} Airtable changes`, + `CRITICAL_TRACE: Beginning workflow execution with ${finalConsolidatedChanges.length} Airtable changes`, { + requestId, workflowId: workflowData.id, recordCount: finalConsolidatedChanges.length, timestamp: new Date().toISOString(), @@ -388,7 +395,8 @@ async function fetchAndProcessAirtablePayloads( ) // Return the processed input for the trigger.dev task to handle - logger.info(`[${requestId}] CRITICAL_TRACE: Airtable changes processed, returning input`, { + logger.info('CRITICAL_TRACE: Airtable changes processed, returning input', { + requestId, workflowId: workflowData.id, recordCount: finalConsolidatedChanges.length, rawPayloadCount: allPayloads.length, @@ -398,7 +406,8 @@ async function fetchAndProcessAirtablePayloads( return input } catch (processingError: unknown) { const err = processingError as Error - logger.error(`[${requestId}] CRITICAL_TRACE: Error processing Airtable changes`, { + logger.error('CRITICAL_TRACE: Error processing Airtable changes', { + requestId, workflowId: workflowData.id, error: err.message, stack: err.stack, @@ -409,7 +418,8 @@ async function fetchAndProcessAirtablePayloads( } } else { // DEBUG: Log when no changes are found - logger.info(`[${requestId}] TRACE: No Airtable changes to process`, { + logger.info('TRACE: No Airtable changes to process', { + requestId, workflowId: workflowData.id, apiCallCount, webhookId: webhookData.id, @@ -417,14 +427,12 @@ async function fetchAndProcessAirtablePayloads( } } catch (error) { // Catch any unexpected errors during the setup/polling logic itself - logger.error( - `[${requestId}] Unexpected error during asynchronous Airtable payload processing task`, - { - webhookId: webhookData.id, - workflowId: workflowData.id, - error: (error as Error).message, - } - ) + logger.error('Unexpected error during asynchronous Airtable payload processing task', { + requestId, + webhookId: webhookData.id, + workflowId: workflowData.id, + error: (error as Error).message, + }) // Error logging handled by logging session } } @@ -445,7 +453,8 @@ export const airtableHandler: WebhookProviderHandler = { const tableId = toStringOrNull(config.tableId) if (!baseId || !tableId) { - logger.warn(`[${requestId}] Missing baseId or tableId for Airtable webhook creation.`, { + logger.warn('Missing baseId or tableId for Airtable webhook creation.', { + requestId, webhookId: webhookRecord.id, }) throw new WebhookDeploymentConfigurationError( @@ -468,7 +477,8 @@ export const airtableHandler: WebhookProviderHandler = { : await getOAuthToken(userId, 'airtable') if (!accessToken) { logger.warn( - `[${requestId}] Could not retrieve Airtable access token for user ${userId}. Cannot create webhook in Airtable.` + `Could not retrieve Airtable access token for user ${userId}. Cannot create webhook in Airtable.`, + { requestId } ) throw new Error( 'Airtable account connection required. Please connect your Airtable account in the trigger configuration and try again.' @@ -515,8 +525,8 @@ export const airtableHandler: WebhookProviderHandler = { responseBody.error?.message || responseBody.error || 'Unknown Airtable API error' const errorType = responseBody.error?.type logger.error( - `[${requestId}] Failed to create webhook in Airtable for webhook ${webhookRecord.id}. Status: ${airtableResponse.status}`, - { type: errorType, message: errorMessage, response: responseBody } + `Failed to create webhook in Airtable for webhook ${webhookRecord.id}. Status: ${airtableResponse.status}`, + { requestId, type: errorType, message: errorMessage, response: responseBody } ) let userFriendlyMessage = 'Failed to create webhook subscription in Airtable' @@ -529,23 +539,19 @@ export const airtableHandler: WebhookProviderHandler = { throw new Error(userFriendlyMessage) } - logger.info( - `[${requestId}] Successfully created webhook in Airtable for webhook ${webhookRecord.id}.`, - { - airtableWebhookId: responseBody.id, - } - ) + logger.info(`Successfully created webhook in Airtable for webhook ${webhookRecord.id}.`, { + requestId, + airtableWebhookId: responseBody.id, + }) return { providerConfigUpdates: { externalId: responseBody.id } } } catch (error: unknown) { if (error instanceof WebhookDeploymentConfigurationError) throw error const err = error as Error - logger.error( - `[${requestId}] Exception during Airtable webhook creation for webhook ${webhookRecord.id}.`, - { - message: err.message, - stack: err.stack, - } - ) + logger.error(`Exception during Airtable webhook creation for webhook ${webhookRecord.id}.`, { + requestId, + message: err.message, + stack: err.stack, + }) throw error } }, @@ -563,7 +569,8 @@ export const airtableHandler: WebhookProviderHandler = { } if (!baseId) { - logger.warn(`[${requestId}] Missing baseId for Airtable webhook deletion`, { + logger.warn('Missing baseId for Airtable webhook deletion', { + requestId, webhookId: webhookRecord.id, }) if (strict) throw new Error('Missing Airtable baseId for webhook deletion') @@ -572,7 +579,8 @@ export const airtableHandler: WebhookProviderHandler = { const baseIdValidation = validateAirtableId(baseId, 'app', 'baseId') if (!baseIdValidation.isValid) { - logger.warn(`[${requestId}] Invalid Airtable base ID format, skipping deletion`, { + logger.warn('Invalid Airtable base ID format, skipping deletion', { + requestId, webhookId: webhookRecord.id, baseId: baseId.substring(0, 20), }) @@ -582,17 +590,18 @@ export const airtableHandler: WebhookProviderHandler = { const credentialId = config.credentialId as string | undefined if (!credentialId) { - logger.warn( - `[${requestId}] Missing credentialId for Airtable webhook deletion ${webhookRecord.id}` - ) + logger.warn(`Missing credentialId for Airtable webhook deletion ${webhookRecord.id}`, { + requestId, + }) if (strict) throw new Error('Missing Airtable credentialId for webhook deletion') return } const accessToken = await getCredentialAccessToken(credentialId, requestId) if (!accessToken) { - const message = `[${requestId}] Could not retrieve Airtable access token. Cannot delete webhook in Airtable.` - logger.warn(message, { webhookId: webhookRecord.id }) + const message = + 'Could not retrieve Airtable access token. Cannot delete webhook in Airtable.' + logger.warn(message, { requestId, webhookId: webhookRecord.id }) if (strict) throw new Error(message) return } @@ -602,8 +611,8 @@ export const airtableHandler: WebhookProviderHandler = { if (!resolvedExternalId && strict) { logger.warn( - `[${requestId}] Missing Airtable externalId during strict cleanup; skipping unsafe URL-based remote deletion`, - { webhookId: webhookRecord.id, baseId } + 'Missing Airtable externalId during strict cleanup; skipping unsafe URL-based remote deletion', + { requestId, webhookId: webhookRecord.id, baseId } ) throw new Error('Missing Airtable externalId for strict cleanup') } @@ -631,19 +640,22 @@ export const airtableHandler: WebhookProviderHandler = { }) if (match?.id) { resolvedExternalId = match.id as string - logger.info(`[${requestId}] Resolved Airtable externalId by listing webhooks`, { + logger.info('Resolved Airtable externalId by listing webhooks', { + requestId, baseId, externalId: resolvedExternalId, }) } else { - logger.warn(`[${requestId}] Could not resolve Airtable externalId from list`, { + logger.warn('Could not resolve Airtable externalId from list', { + requestId, baseId, expectedNotificationUrl, }) } } else { externalIdLookupFailed = true - logger.warn(`[${requestId}] Failed to list Airtable webhooks to resolve externalId`, { + logger.warn('Failed to list Airtable webhooks to resolve externalId', { + requestId, baseId, status: listResp.status, body: listBody, @@ -651,14 +663,16 @@ export const airtableHandler: WebhookProviderHandler = { } } catch (e: unknown) { externalIdLookupFailed = true - logger.warn(`[${requestId}] Error attempting to resolve Airtable externalId`, { + logger.warn('Error attempting to resolve Airtable externalId', { + requestId, error: (e as Error)?.message, }) } } if (!resolvedExternalId) { - logger.info(`[${requestId}] Airtable externalId not found; skipping remote deletion`, { + logger.info('Airtable externalId not found; skipping remote deletion', { + requestId, baseId, confirmedAbsent: !externalIdLookupFailed, }) @@ -670,7 +684,8 @@ export const airtableHandler: WebhookProviderHandler = { const webhookIdValidation = validateAirtableId(resolvedExternalId, 'ach', 'webhookId') if (!webhookIdValidation.isValid) { - logger.warn(`[${requestId}] Invalid Airtable webhook ID format, skipping deletion`, { + logger.warn('Invalid Airtable webhook ID format, skipping deletion', { + requestId, webhookId: webhookRecord.id, externalId: resolvedExternalId.substring(0, 20), }) @@ -693,12 +708,13 @@ export const airtableHandler: WebhookProviderHandler = { } catch {} logger.warn( - `[${requestId}] Failed to delete Airtable webhook in Airtable. Status: ${airtableResponse.status}`, - { baseId, externalId: resolvedExternalId, response: responseBody } + `Failed to delete Airtable webhook in Airtable. Status: ${airtableResponse.status}`, + { requestId, baseId, externalId: resolvedExternalId, response: responseBody } ) if (strict) throw new Error(`Failed to delete Airtable webhook: ${airtableResponse.status}`) } else { - logger.info(`[${requestId}] Successfully deleted Airtable webhook in Airtable`, { + logger.info('Successfully deleted Airtable webhook in Airtable', { + requestId, baseId, externalId: resolvedExternalId, alreadyDeleted: airtableResponse.status === 404, @@ -706,7 +722,8 @@ export const airtableHandler: WebhookProviderHandler = { } } catch (error: unknown) { const err = error as Error - logger.error(`[${requestId}] Error deleting Airtable webhook`, { + logger.error('Error deleting Airtable webhook', { + requestId, webhookId: webhookRecord.id, error: err.message, stack: err.stack, @@ -724,7 +741,7 @@ export const airtableHandler: WebhookProviderHandler = { }, async formatInput({ webhook, workflow, requestId }: FormatInputContext) { - logger.info(`[${requestId}] Processing Airtable webhook via fetchAndProcessAirtablePayloads`) + logger.info('Processing Airtable webhook via fetchAndProcessAirtablePayloads', { requestId }) const webhookData = { id: webhook.id, @@ -744,11 +761,11 @@ export const airtableHandler: WebhookProviderHandler = { ) if (airtableInput) { - logger.info(`[${requestId}] Executing workflow with Airtable changes`) + logger.info('Executing workflow with Airtable changes', { requestId }) return { input: airtableInput } } - logger.info(`[${requestId}] No Airtable changes to process`) + logger.info('No Airtable changes to process', { requestId }) return { input: null, skip: { message: 'No Airtable changes to process' } } }, } diff --git a/apps/sim/lib/webhooks/providers/ashby.ts b/apps/sim/lib/webhooks/providers/ashby.ts index c42c042414f..10e3a6bb066 100644 --- a/apps/sim/lib/webhooks/providers/ashby.ts +++ b/apps/sim/lib/webhooks/providers/ashby.ts @@ -165,9 +165,9 @@ export const ashbyHandler: WebhookProviderHandler = { verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext): NextResponse | null { const secretToken = (providerConfig.secretToken as string | undefined)?.trim() if (!secretToken) { - logger.warn( - `[${requestId}] Ashby webhook missing secretToken in providerConfig — rejecting request` - ) + logger.warn('Ashby webhook missing secretToken in providerConfig — rejecting request', { + requestId, + }) return new NextResponse( 'Unauthorized - Ashby webhook signing secret is not configured. Re-save the trigger so a webhook can be registered.', { status: 401 } @@ -176,12 +176,13 @@ export const ashbyHandler: WebhookProviderHandler = { const signature = request.headers.get('ashby-signature') if (!signature) { - logger.warn(`[${requestId}] Ashby webhook missing signature header`) + logger.warn('Ashby webhook missing signature header', { requestId }) return new NextResponse('Unauthorized - Missing Ashby signature', { status: 401 }) } if (!validateAshbySignature(secretToken, signature, rawBody)) { - logger.warn(`[${requestId}] Ashby signature verification failed`, { + logger.warn('Ashby signature verification failed', { + requestId, signatureLength: signature.length, secretLength: secretToken.length, }) @@ -202,7 +203,8 @@ export const ashbyHandler: WebhookProviderHandler = { const action = typeof obj?.action === 'string' ? obj.action : '' if (action === 'ping') { - logger.debug(`[${requestId}] Ashby ping event received. Skipping execution.`, { + logger.debug('Ashby ping event received. Skipping execution.', { + requestId, webhookId: webhook.id, triggerId, }) @@ -214,12 +216,8 @@ export const ashbyHandler: WebhookProviderHandler = { const { isAshbyEventMatch } = await import('@/triggers/ashby/utils') if (!isAshbyEventMatch(triggerId, action)) { logger.debug( - `[${requestId}] Ashby event mismatch for trigger ${triggerId}. Action: ${action || '(missing)'}. Skipping execution.`, - { - webhookId: webhook.id, - triggerId, - receivedAction: action, - } + `Ashby event mismatch for trigger ${triggerId}. Action: ${action || '(missing)'}. Skipping execution.`, + { requestId, webhookId: webhook.id, triggerId, receivedAction: action } ) return false } @@ -258,7 +256,8 @@ export const ashbyHandler: WebhookProviderHandler = { const notificationUrl = getNotificationUrl(ctx.webhook) const authString = Buffer.from(`${apiKey}:`).toString('base64') - logger.info(`[${ctx.requestId}] Creating Ashby webhook`, { + logger.info('Creating Ashby webhook', { + requestId: ctx.requestId, triggerId, webhookType, webhookId: ctx.webhook.id, @@ -321,19 +320,18 @@ export const ashbyHandler: WebhookProviderHandler = { } logger.info( - `[${ctx.requestId}] Successfully created Ashby webhook subscription ${externalId} for webhook ${ctx.webhook.id}` + `Successfully created Ashby webhook subscription ${externalId} for webhook ${ctx.webhook.id}`, + { requestId: ctx.requestId } ) return { providerConfigUpdates: { externalId, secretToken } } } catch (error: unknown) { if (error instanceof WebhookDeploymentConfigurationError) throw error const err = error as Error - logger.error( - `[${ctx.requestId}] Exception during Ashby webhook creation for webhook ${ctx.webhook.id}.`, - { - message: err.message, - stack: err.stack, - } - ) + logger.error(`Exception during Ashby webhook creation for webhook ${ctx.webhook.id}.`, { + requestId: ctx.requestId, + message: err.message, + stack: err.stack, + }) throw error } }, @@ -346,7 +344,8 @@ export const ashbyHandler: WebhookProviderHandler = { if (!apiKey) { logger.warn( - `[${ctx.requestId}] Missing apiKey for Ashby webhook deletion ${ctx.webhook.id}, skipping cleanup` + `Missing apiKey for Ashby webhook deletion ${ctx.webhook.id}, skipping cleanup`, + { requestId: ctx.requestId } ) if (ctx.strict) throw new Error('Missing Ashby apiKey for webhook deletion') return @@ -354,7 +353,8 @@ export const ashbyHandler: WebhookProviderHandler = { if (!externalId) { logger.warn( - `[${ctx.requestId}] Missing externalId for Ashby webhook deletion ${ctx.webhook.id}, skipping cleanup` + `Missing externalId for Ashby webhook deletion ${ctx.webhook.id}, skipping cleanup`, + { requestId: ctx.requestId } ) if (ctx.strict) throw new Error('Missing Ashby externalId for webhook deletion') return @@ -393,27 +393,28 @@ export const ashbyHandler: WebhookProviderHandler = { const errorMessage = ashbyErrorMessage(responseBody, `HTTP ${ashbyResponse.status}`) if (!rejected) { - logger.info( - `[${ctx.requestId}] Successfully deleted Ashby webhook subscription ${externalId}` - ) + logger.info(`Successfully deleted Ashby webhook subscription ${externalId}`, { + requestId: ctx.requestId, + }) } else if ( ashbyResponse.status === 404 || isAshbyWebhookNotFound(responseBody, errorMessage) ) { - logger.info( - `[${ctx.requestId}] Ashby webhook ${externalId} not found during deletion (already removed)` - ) + logger.info(`Ashby webhook ${externalId} not found during deletion (already removed)`, { + requestId: ctx.requestId, + }) } else { - logger.warn( - `[${ctx.requestId}] Failed to delete Ashby webhook (non-fatal): ${errorMessage}`, - { status: ashbyResponse.status, response: responseBody } - ) + logger.warn(`Failed to delete Ashby webhook (non-fatal): ${errorMessage}`, { + requestId: ctx.requestId, + status: ashbyResponse.status, + response: responseBody, + }) if (ctx.strict) { throw new Error(`Failed to delete Ashby webhook: ${errorMessage}`) } } } catch (error) { - logger.warn(`[${ctx.requestId}] Error deleting Ashby webhook (non-fatal)`, error) + logger.warn('Error deleting Ashby webhook (non-fatal)', error, { requestId: ctx.requestId }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/attio.ts b/apps/sim/lib/webhooks/providers/attio.ts index 5b3fb473ce8..83f67aa0b54 100644 --- a/apps/sim/lib/webhooks/providers/attio.ts +++ b/apps/sim/lib/webhooks/providers/attio.ts @@ -47,13 +47,14 @@ export const attioHandler: WebhookProviderHandler = { if (!secret) { logger.debug( - `[${requestId}] Attio webhook ${webhook.id as string} has no signing secret, skipping signature verification` + `Attio webhook ${webhook.id as string} has no signing secret, skipping signature verification`, + { requestId } ) } else { const signature = request.headers.get('Attio-Signature') if (!signature) { - logger.warn(`[${requestId}] Attio webhook missing signature header`) + logger.warn('Attio webhook missing signature header', { requestId }) return new NextResponse('Unauthorized - Missing Attio signature', { status: 401, }) @@ -62,7 +63,7 @@ export const attioHandler: WebhookProviderHandler = { const isValidSignature = validateAttioSignature(secret, signature, rawBody) if (!isValidSignature) { - logger.warn(`[${requestId}] Attio signature verification failed`) + logger.warn('Attio signature verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid Attio signature', { status: 401, }) @@ -82,8 +83,9 @@ export const attioHandler: WebhookProviderHandler = { const event = getAttioEvent(obj) const eventType = event?.event_type as string | undefined logger.debug( - `[${requestId}] Attio event mismatch for trigger ${triggerId}. Event: ${eventType}. Skipping execution.`, + `Attio event mismatch for trigger ${triggerId}. Event: ${eventType}. Skipping execution.`, { + requestId, webhookId: webhook.id, workflowId: workflow.id, triggerId, @@ -114,7 +116,8 @@ export const attioHandler: WebhookProviderHandler = { } if (!credentialId) { - logger.warn(`[${requestId}] Missing credentialId for Attio webhook creation.`, { + logger.warn('Missing credentialId for Attio webhook creation.', { + requestId, webhookId: webhookRecord.id, }) throw new WebhookDeploymentConfigurationError( @@ -126,7 +129,8 @@ export const attioHandler: WebhookProviderHandler = { if (!accessToken) { logger.warn( - `[${requestId}] Could not retrieve Attio access token for user ${userId}. Cannot create webhook.` + `Could not retrieve Attio access token for user ${userId}. Cannot create webhook.`, + { requestId } ) throw new Error( 'Attio account connection required. Please connect your Attio account in the trigger configuration and try again.' @@ -149,7 +153,8 @@ export const attioHandler: WebhookProviderHandler = { } else { const events = TRIGGER_EVENT_MAP[triggerId as string] if (!events || events.length === 0) { - logger.warn(`[${requestId}] No event types mapped for trigger ${triggerId}`, { + logger.warn(`No event types mapped for trigger ${triggerId}`, { + requestId, webhookId: webhookRecord.id, }) throw new Error(`Unknown Attio trigger type: ${triggerId}`) @@ -176,8 +181,8 @@ export const attioHandler: WebhookProviderHandler = { if (!attioResponse.ok) { const errorBody = await attioResponse.json().catch(() => ({})) logger.error( - `[${requestId}] Failed to create webhook in Attio for webhook ${webhookRecord.id}. Status: ${attioResponse.status}`, - { response: errorBody } + `Failed to create webhook in Attio for webhook ${webhookRecord.id}. Status: ${attioResponse.status}`, + { requestId, response: errorBody } ) let userFriendlyMessage = 'Failed to create webhook subscription in Attio' @@ -198,37 +203,35 @@ export const attioHandler: WebhookProviderHandler = { if (!webhookId) { logger.error( - `[${requestId}] Attio webhook created but no webhook_id returned for webhook ${webhookRecord.id}`, - { response: responseBody } + `Attio webhook created but no webhook_id returned for webhook ${webhookRecord.id}`, + { requestId, response: responseBody } ) throw new Error('Attio webhook creation succeeded but no webhook ID was returned') } if (!secret) { logger.warn( - `[${requestId}] Attio webhook created but no secret returned for webhook ${webhookRecord.id}. Signature verification will be skipped.`, - { response: responseBody } + `Attio webhook created but no secret returned for webhook ${webhookRecord.id}. Signature verification will be skipped.`, + { requestId, response: responseBody } ) } - logger.info( - `[${requestId}] Successfully created webhook in Attio for webhook ${webhookRecord.id}.`, - { - attioWebhookId: webhookId, - targetUrl: notificationUrl, - subscriptionCount: subscriptions.length, - status: data.status, - } - ) + logger.info(`Successfully created webhook in Attio for webhook ${webhookRecord.id}.`, { + requestId, + attioWebhookId: webhookId, + targetUrl: notificationUrl, + subscriptionCount: subscriptions.length, + status: data.status, + }) return { providerConfigUpdates: { externalId: webhookId, webhookSecret: secret || '' } } } catch (error: unknown) { if (error instanceof WebhookDeploymentConfigurationError) throw error const message = toError(error).message - logger.error( - `[${requestId}] Exception during Attio webhook creation for webhook ${webhookRecord.id}.`, - { message } - ) + logger.error(`Exception during Attio webhook creation for webhook ${webhookRecord.id}.`, { + requestId, + message, + }) throw error } }, @@ -245,7 +248,8 @@ export const attioHandler: WebhookProviderHandler = { if (!externalId) { logger.warn( - `[${requestId}] Missing externalId for Attio webhook deletion ${webhookRecord.id}, skipping cleanup` + `Missing externalId for Attio webhook deletion ${webhookRecord.id}, skipping cleanup`, + { requestId } ) if (strict) throw new Error('Missing Attio externalId for webhook deletion') return @@ -253,7 +257,8 @@ export const attioHandler: WebhookProviderHandler = { if (!credentialId) { logger.warn( - `[${requestId}] Missing credentialId for Attio webhook deletion ${webhookRecord.id}, skipping cleanup` + `Missing credentialId for Attio webhook deletion ${webhookRecord.id}, skipping cleanup`, + { requestId } ) if (strict) throw new Error('Missing Attio credentialId for webhook deletion') return @@ -262,8 +267,8 @@ export const attioHandler: WebhookProviderHandler = { const accessToken = await getCredentialAccessToken(credentialId, requestId) if (!accessToken) { - const message = `[${requestId}] Could not retrieve Attio access token. Cannot delete webhook.` - logger.warn(message, { webhookId: webhookRecord.id }) + const message = 'Could not retrieve Attio access token. Cannot delete webhook.' + logger.warn(message, { requestId, webhookId: webhookRecord.id }) if (strict) throw new Error(message) return } @@ -277,16 +282,16 @@ export const attioHandler: WebhookProviderHandler = { if (!attioResponse.ok && attioResponse.status !== 404) { const responseBody = await attioResponse.json().catch(() => ({})) - logger.warn( - `[${requestId}] Failed to delete Attio webhook (non-fatal): ${attioResponse.status}`, - { response: responseBody } - ) + logger.warn(`Failed to delete Attio webhook (non-fatal): ${attioResponse.status}`, { + requestId, + response: responseBody, + }) if (strict) throw new Error(`Failed to delete Attio webhook: ${attioResponse.status}`) } else { - logger.info(`[${requestId}] Successfully deleted Attio webhook ${externalId}`) + logger.info(`Successfully deleted Attio webhook ${externalId}`, { requestId }) } } catch (error) { - logger.warn(`[${requestId}] Error deleting Attio webhook (non-fatal)`, error) + logger.warn('Error deleting Attio webhook (non-fatal)', error, { requestId }) if (strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/azure-devops.ts b/apps/sim/lib/webhooks/providers/azure-devops.ts index 557443344c2..27daf850714 100644 --- a/apps/sim/lib/webhooks/providers/azure-devops.ts +++ b/apps/sim/lib/webhooks/providers/azure-devops.ts @@ -30,15 +30,13 @@ export const azureDevOpsHandler: WebhookProviderHandler = { if (triggerId && triggerId !== 'azure_devops_webhook') { const { isAzureDevOpsEventMatch } = await import('@/triggers/azure_devops/utils') if (!isAzureDevOpsEventMatch(triggerId, b)) { - logger.debug( - `[${requestId}] Azure DevOps event mismatch for trigger ${triggerId}. Skipping execution.`, - { - webhookId: webhook.id, - workflowId: workflow.id, - triggerId, - eventType: b.eventType, - } - ) + logger.debug(`Azure DevOps event mismatch for trigger ${triggerId}. Skipping execution.`, { + requestId, + webhookId: webhook.id, + workflowId: workflow.id, + triggerId, + eventType: b.eventType, + }) return false } } @@ -77,7 +75,8 @@ export const azureDevOpsHandler: WebhookProviderHandler = { return { input: formatWorkItemCreatedInput(b) } } - logger.warn(`[${requestId}] Azure DevOps: unknown eventType for specialized trigger`, { + logger.warn('Azure DevOps: unknown eventType for specialized trigger', { + requestId, triggerId, eventType, }) diff --git a/apps/sim/lib/webhooks/providers/bitbucket.ts b/apps/sim/lib/webhooks/providers/bitbucket.ts index 3ded1288f57..e0fa832499c 100644 --- a/apps/sim/lib/webhooks/providers/bitbucket.ts +++ b/apps/sim/lib/webhooks/providers/bitbucket.ts @@ -190,7 +190,8 @@ async function findBitbucketCandidateHook( headers: bitbucketHeaders(accessToken), }) } catch (error) { - logger.warn(`[${requestId}] Could not inspect Bitbucket hooks for candidate reconciliation`, { + logger.warn('Could not inspect Bitbucket hooks for candidate reconciliation', { + requestId, error: getErrorMessage(error), }) return { @@ -259,16 +260,17 @@ async function deleteProvenBitbucketCandidate( } ) } catch (error) { - logger.warn(`[${requestId}] Failed to delete proven Bitbucket candidate ${externalId}`, { + logger.warn(`Failed to delete proven Bitbucket candidate ${externalId}`, { + requestId, error: getErrorMessage(error), }) return false } if (!response.ok && response.status !== 404) { - logger.warn( - `[${requestId}] Failed to delete proven Bitbucket candidate ${externalId} (${response.status})` - ) + logger.warn(`Failed to delete proven Bitbucket candidate ${externalId} (${response.status})`, { + requestId, + }) return false } @@ -293,13 +295,11 @@ async function rollbackAmbiguousBitbucketCandidate( ) if (candidate.kind !== 'found') { - logger.warn( - `[${requestId}] Bitbucket candidate cleanup skipped because identity was not unique`, - { - lookup: candidate.kind, - matchCount: candidate.kind === 'ambiguous' ? candidate.matchCount : undefined, - } - ) + logger.warn('Bitbucket candidate cleanup skipped because identity was not unique', { + requestId, + lookup: candidate.kind, + matchCount: candidate.kind === 'ambiguous' ? candidate.matchCount : undefined, + }) return } @@ -455,7 +455,8 @@ export const bitbucketHandler: WebhookProviderHandler = { if (!triggerId || !isBitbucketEventMatch(triggerId, eventKey)) { logger.debug( - `[${requestId}] Bitbucket event '${eventKey}' does not match trigger '${triggerId || 'unknown'}', skipping` + `Bitbucket event '${eventKey}' does not match trigger '${triggerId || 'unknown'}', skipping`, + { requestId } ) return false } @@ -519,9 +520,9 @@ export const bitbucketHandler: WebhookProviderHandler = { existingCandidate.events.length === 1 && existingCandidate.events[0] === eventKey if (candidateMatchesCheckpoint && checkpointedSecret) { - logger.info( - `[${ctx.requestId}] Reusing checkpointed Bitbucket candidate ${existingCandidate.externalId}` - ) + logger.info(`Reusing checkpointed Bitbucket candidate ${existingCandidate.externalId}`, { + requestId: ctx.requestId, + }) return { providerConfigUpdates: { externalId: existingCandidate.externalId, @@ -576,7 +577,8 @@ export const bitbucketHandler: WebhookProviderHandler = { if (!response.ok) { const detail = await readBitbucketErrorDetail(response) - logger.error(`[${ctx.requestId}] Failed to create Bitbucket webhook (${response.status})`, { + logger.error(`Failed to create Bitbucket webhook (${response.status})`, { + requestId: ctx.requestId, detail, workspaceSlug, repoSlug, @@ -612,9 +614,9 @@ export const bitbucketHandler: WebhookProviderHandler = { throw new Error('Bitbucket webhook was created but no hook UUID was returned.') } - logger.info( - `[${ctx.requestId}] Created Bitbucket webhook ${externalId} for ${workspaceSlug}/${repoSlug}` - ) + logger.info(`Created Bitbucket webhook ${externalId} for ${workspaceSlug}/${repoSlug}`, { + requestId: ctx.requestId, + }) return { providerConfigUpdates: { externalId, webhookSecret, eventTypes: [eventKey] }, } @@ -634,7 +636,7 @@ export const bitbucketHandler: WebhookProviderHandler = { const message = 'Missing Bitbucket credential, workspace, repository, or hook UUID for webhook deletion.' if (ctx.strict) throw new Error(message) - logger.warn(`[${ctx.requestId}] ${message}`) + logger.warn(message, { requestId: ctx.requestId }) return } @@ -653,9 +655,11 @@ export const bitbucketHandler: WebhookProviderHandler = { throw createBitbucketApiError(response.status, 'delete', detail) } - logger.info(`[${ctx.requestId}] Deleted Bitbucket webhook ${externalId}`) + logger.info(`Deleted Bitbucket webhook ${externalId}`, { requestId: ctx.requestId }) } catch (error) { - logger.warn(`[${ctx.requestId}] Error deleting Bitbucket webhook (non-fatal)`, error) + logger.warn('Error deleting Bitbucket webhook (non-fatal)', error, { + requestId: ctx.requestId, + }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/calendly.ts b/apps/sim/lib/webhooks/providers/calendly.ts index 83445bf7cfc..9bf401df6b9 100644 --- a/apps/sim/lib/webhooks/providers/calendly.ts +++ b/apps/sim/lib/webhooks/providers/calendly.ts @@ -35,7 +35,8 @@ export const calendlyHandler: WebhookProviderHandler = { } if (!apiKey) { - logger.warn(`[${ctx.requestId}] Missing apiKey for Calendly webhook creation.`, { + logger.warn('Missing apiKey for Calendly webhook creation.', { + requestId: ctx.requestId, webhookId: ctx.webhook.id, }) throw new WebhookDeploymentConfigurationError( @@ -44,7 +45,8 @@ export const calendlyHandler: WebhookProviderHandler = { } if (!organization) { - logger.warn(`[${ctx.requestId}] Missing organization URI for Calendly webhook creation.`, { + logger.warn('Missing organization URI for Calendly webhook creation.', { + requestId: ctx.requestId, webhookId: ctx.webhook.id, }) throw new WebhookDeploymentConfigurationError( @@ -53,7 +55,8 @@ export const calendlyHandler: WebhookProviderHandler = { } if (!triggerId) { - logger.warn(`[${ctx.requestId}] Missing triggerId for Calendly webhook creation.`, { + logger.warn('Missing triggerId for Calendly webhook creation.', { + requestId: ctx.requestId, webhookId: ctx.webhook.id, }) throw new WebhookDeploymentConfigurationError( @@ -101,8 +104,8 @@ export const calendlyHandler: WebhookProviderHandler = { (errorBody as Record).title || 'Unknown Calendly API error' logger.error( - `[${ctx.requestId}] Failed to create webhook in Calendly for webhook ${ctx.webhook.id}. Status: ${calendlyResponse.status}`, - { response: errorBody } + `Failed to create webhook in Calendly for webhook ${ctx.webhook.id}. Status: ${calendlyResponse.status}`, + { requestId: ctx.requestId, response: errorBody } ) let userFriendlyMessage = 'Failed to create webhook subscription in Calendly' @@ -128,8 +131,8 @@ export const calendlyHandler: WebhookProviderHandler = { if (!webhookUri) { logger.error( - `[${ctx.requestId}] Calendly webhook created but no webhook URI returned for webhook ${ctx.webhook.id}`, - { response: responseBody } + `Calendly webhook created but no webhook URI returned for webhook ${ctx.webhook.id}`, + { requestId: ctx.requestId, response: responseBody } ) throw new Error('Calendly webhook creation succeeded but no webhook URI was returned') } @@ -137,33 +140,27 @@ export const calendlyHandler: WebhookProviderHandler = { const webhookId = webhookUri.split('/').pop() if (!webhookId) { - logger.error( - `[${ctx.requestId}] Could not extract webhook ID from Calendly URI: ${webhookUri}`, - { - response: responseBody, - } - ) + logger.error(`Could not extract webhook ID from Calendly URI: ${webhookUri}`, { + requestId: ctx.requestId, + response: responseBody, + }) throw new Error('Failed to extract webhook ID from Calendly response') } - logger.info( - `[${ctx.requestId}] Successfully created webhook in Calendly for webhook ${ctx.webhook.id}.`, - { - calendlyWebhookUri: webhookUri, - calendlyWebhookId: webhookId, - } - ) + logger.info(`Successfully created webhook in Calendly for webhook ${ctx.webhook.id}.`, { + requestId: ctx.requestId, + calendlyWebhookUri: webhookUri, + calendlyWebhookId: webhookId, + }) return { providerConfigUpdates: { externalId: webhookId } } } catch (error: unknown) { if (error instanceof WebhookDeploymentConfigurationError) throw error const err = error as Error - logger.error( - `[${ctx.requestId}] Exception during Calendly webhook creation for webhook ${ctx.webhook.id}.`, - { - message: err.message, - stack: err.stack, - } - ) + logger.error(`Exception during Calendly webhook creation for webhook ${ctx.webhook.id}.`, { + requestId: ctx.requestId, + message: err.message, + stack: err.stack, + }) throw error } }, @@ -176,7 +173,8 @@ export const calendlyHandler: WebhookProviderHandler = { if (!apiKey) { logger.warn( - `[${ctx.requestId}] Missing apiKey for Calendly webhook deletion ${ctx.webhook.id}, skipping cleanup` + `Missing apiKey for Calendly webhook deletion ${ctx.webhook.id}, skipping cleanup`, + { requestId: ctx.requestId } ) if (ctx.strict) throw new Error('Missing Calendly apiKey for webhook deletion') return @@ -184,7 +182,8 @@ export const calendlyHandler: WebhookProviderHandler = { if (!externalId) { logger.warn( - `[${ctx.requestId}] Missing externalId for Calendly webhook deletion ${ctx.webhook.id}, skipping cleanup` + `Missing externalId for Calendly webhook deletion ${ctx.webhook.id}, skipping cleanup`, + { requestId: ctx.requestId } ) if (ctx.strict) throw new Error('Missing Calendly externalId for webhook deletion') return @@ -201,20 +200,22 @@ export const calendlyHandler: WebhookProviderHandler = { if (!calendlyResponse.ok && calendlyResponse.status !== 404) { const responseBody = await calendlyResponse.json().catch(() => ({})) - logger.warn( - `[${ctx.requestId}] Failed to delete Calendly webhook (non-fatal): ${calendlyResponse.status}`, - { response: responseBody } - ) + logger.warn(`Failed to delete Calendly webhook (non-fatal): ${calendlyResponse.status}`, { + requestId: ctx.requestId, + response: responseBody, + }) if (ctx.strict) { throw new Error(`Failed to delete Calendly webhook: ${calendlyResponse.status}`) } } else { - logger.info( - `[${ctx.requestId}] Successfully deleted Calendly webhook subscription ${externalId}` - ) + logger.info(`Successfully deleted Calendly webhook subscription ${externalId}`, { + requestId: ctx.requestId, + }) } } catch (error) { - logger.warn(`[${ctx.requestId}] Error deleting Calendly webhook (non-fatal)`, error) + logger.warn('Error deleting Calendly webhook (non-fatal)', error, { + requestId: ctx.requestId, + }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/checkr.ts b/apps/sim/lib/webhooks/providers/checkr.ts index 2896636da71..a7618573f5c 100644 --- a/apps/sim/lib/webhooks/providers/checkr.ts +++ b/apps/sim/lib/webhooks/providers/checkr.ts @@ -128,7 +128,7 @@ export const checkrHandler: WebhookProviderHandler = { verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext): NextResponse | null { const apiKey = typeof providerConfig.apiKey === 'string' ? providerConfig.apiKey.trim() : '' if (!apiKey) { - logger.warn(`[${requestId}] Checkr webhook has no API key configured — rejecting request`) + logger.warn('Checkr webhook has no API key configured — rejecting request', { requestId }) return new NextResponse( 'Unauthorized - Checkr API key is not configured. Add it to the trigger and redeploy.', { status: 401 } @@ -137,12 +137,12 @@ export const checkrHandler: WebhookProviderHandler = { const signature = request.headers.get('x-checkr-signature') if (!signature) { - logger.warn(`[${requestId}] Checkr webhook missing X-Checkr-Signature header`) + logger.warn('Checkr webhook missing X-Checkr-Signature header', { requestId }) return new NextResponse('Unauthorized - Missing Checkr signature', { status: 401 }) } if (!isValidCheckrSignature(apiKey, signature, rawBody)) { - logger.warn(`[${requestId}] Checkr signature verification failed`) + logger.warn('Checkr signature verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid Checkr signature', { status: 401 }) } @@ -162,8 +162,8 @@ export const checkrHandler: WebhookProviderHandler = { const { isCheckrEventMatch } = await import('@/triggers/checkr/utils') if (!isCheckrEventMatch(triggerId, eventType)) { logger.debug( - `[${requestId}] Checkr event ${eventType || '(missing)'} does not match trigger ${triggerId}. Skipping execution.`, - { webhookId: webhook.id, triggerId } + `Checkr event ${eventType || '(missing)'} does not match trigger ${triggerId}. Skipping execution.`, + { requestId, webhookId: webhook.id, triggerId } ) return false } @@ -207,11 +207,14 @@ export const checkrHandler: WebhookProviderHandler = { const notificationUrl = getNotificationUrl(ctx.webhook) const existingId = await findCheckrWebhookForUrl(apiKey, notificationUrl) if (existingId) { - logger.info(`[${ctx.requestId}] Reusing Checkr webhook ${existingId} for this callback URL`) + logger.info(`Reusing Checkr webhook ${existingId} for this callback URL`, { + requestId: ctx.requestId, + }) return { providerConfigUpdates: { externalId: existingId } } } - logger.info(`[${ctx.requestId}] Creating Checkr webhook`, { + logger.info('Creating Checkr webhook', { + requestId: ctx.requestId, webhookId: ctx.webhook.id, triggerId: providerConfig.triggerId, }) @@ -249,7 +252,7 @@ export const checkrHandler: WebhookProviderHandler = { throw new Error('Checkr created the webhook but did not return its ID.') } - logger.info(`[${ctx.requestId}] Created Checkr webhook ${externalId}`) + logger.info(`Created Checkr webhook ${externalId}`, { requestId: ctx.requestId }) return { providerConfigUpdates: { externalId } } }, @@ -261,7 +264,8 @@ export const checkrHandler: WebhookProviderHandler = { if (!apiKey || !externalId) { logger.warn( - `[${ctx.requestId}] Missing ${apiKey ? 'externalId' : 'apiKey'} for Checkr webhook deletion ${ctx.webhook.id}, skipping cleanup` + `Missing ${apiKey ? 'externalId' : 'apiKey'} for Checkr webhook deletion ${ctx.webhook.id}, skipping cleanup`, + { requestId: ctx.requestId } ) if (ctx.strict) throw new Error('Missing Checkr API key or webhook ID for deletion') return @@ -269,7 +273,8 @@ export const checkrHandler: WebhookProviderHandler = { if (await activeDeploymentUsesCheckrWebhook(ctx.webhook, externalId)) { logger.info( - `[${ctx.requestId}] Keeping Checkr webhook ${externalId} because an active deployment still uses it` + `Keeping Checkr webhook ${externalId} because an active deployment still uses it`, + { requestId: ctx.requestId } ) return } @@ -280,24 +285,27 @@ export const checkrHandler: WebhookProviderHandler = { }) if (response.ok) { - logger.info(`[${ctx.requestId}] Deleted Checkr webhook ${externalId}`) + logger.info(`Deleted Checkr webhook ${externalId}`, { requestId: ctx.requestId }) return } if (response.status === 404) { - logger.info(`[${ctx.requestId}] Checkr webhook ${externalId} was already removed`) + logger.info(`Checkr webhook ${externalId} was already removed`, { + requestId: ctx.requestId, + }) return } const responseBody = await readCheckrResponse(response, 'Checkr webhook deletion response') const message = checkrErrorMessage(responseBody) ?? `HTTP ${response.status}` - logger.warn(`[${ctx.requestId}] Failed to delete Checkr webhook (non-fatal): ${message}`, { + logger.warn(`Failed to delete Checkr webhook (non-fatal): ${message}`, { + requestId: ctx.requestId, status: response.status, }) if (ctx.strict) throw new Error(`Failed to delete Checkr webhook: ${message}`) } catch (error) { - logger.warn( - `[${ctx.requestId}] Error deleting Checkr webhook (non-fatal): ${getErrorMessage(error)}` - ) + logger.warn(`Error deleting Checkr webhook (non-fatal): ${getErrorMessage(error)}`, { + requestId: ctx.requestId, + }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/clerk.ts b/apps/sim/lib/webhooks/providers/clerk.ts index 631d1e03fd5..ed07751e473 100644 --- a/apps/sim/lib/webhooks/providers/clerk.ts +++ b/apps/sim/lib/webhooks/providers/clerk.ts @@ -20,7 +20,7 @@ export const clerkHandler: WebhookProviderHandler = { }: AuthContext): Promise { const signingSecret = providerConfig.signingSecret as string | undefined if (!signingSecret?.trim()) { - logger.warn(`[${requestId}] Clerk webhook missing signing secret in provider configuration`) + logger.warn('Clerk webhook missing signing secret in provider configuration', { requestId }) return new NextResponse('Unauthorized - Clerk signing secret is required', { status: 401 }) } @@ -47,7 +47,8 @@ export const clerkHandler: WebhookProviderHandler = { if (!isClerkEventMatch(triggerId, body as Record)) { const actualType = (body as Record)?.type logger.debug( - `[${requestId}] Clerk event type mismatch for trigger ${triggerId}, got ${String(actualType)}. Skipping.` + `Clerk event type mismatch for trigger ${triggerId}, got ${String(actualType)}. Skipping.`, + { requestId } ) return false } diff --git a/apps/sim/lib/webhooks/providers/clickup.ts b/apps/sim/lib/webhooks/providers/clickup.ts index a411b4be055..44593b59d24 100644 --- a/apps/sim/lib/webhooks/providers/clickup.ts +++ b/apps/sim/lib/webhooks/providers/clickup.ts @@ -110,12 +110,8 @@ export const clickupHandler: WebhookProviderHandler = { const { isClickUpEventMatch, getClickUpEventType } = await import('@/triggers/clickup/utils') if (!isClickUpEventMatch(triggerId, obj)) { logger.debug( - `[${requestId}] ClickUp event mismatch for trigger ${triggerId}. Event: ${getClickUpEventType(obj)}. Skipping execution.`, - { - webhookId: webhook.id, - workflowId: workflow.id, - triggerId, - } + `ClickUp event mismatch for trigger ${triggerId}. Event: ${getClickUpEventType(obj)}. Skipping execution.`, + { requestId, webhookId: webhook.id, workflowId: workflow.id, triggerId } ) return NextResponse.json({ status: 'skipped', @@ -203,8 +199,8 @@ export const clickupHandler: WebhookProviderHandler = { if (!clickupResponse.ok) { const errorBody = (await clickupResponse.json().catch(() => ({}))) as { err?: string } logger.error( - `[${requestId}] Failed to create webhook in ClickUp for webhook ${webhookRecord.id}. Status: ${clickupResponse.status}`, - { response: errorBody } + `Failed to create webhook in ClickUp for webhook ${webhookRecord.id}. Status: ${clickupResponse.status}`, + { requestId, response: errorBody } ) let userFriendlyMessage = 'Failed to create webhook subscription in ClickUp' @@ -239,23 +235,24 @@ export const clickupHandler: WebhookProviderHandler = { if (!externalId) { logger.error( - `[${requestId}] ClickUp webhook created but no webhook id returned for webhook ${webhookRecord.id}`, - { response: redactedResponse } + `ClickUp webhook created but no webhook id returned for webhook ${webhookRecord.id}`, + { requestId, response: redactedResponse } ) throw new Error('ClickUp webhook creation succeeded but no webhook ID was returned') } if (!secret) { logger.error( - `[${requestId}] ClickUp webhook created but no secret returned for webhook ${webhookRecord.id}. Rolling back.`, - { response: redactedResponse } + `ClickUp webhook created but no secret returned for webhook ${webhookRecord.id}. Rolling back.`, + { requestId, response: redactedResponse } ) const rollback = await deleteClickUpWebhook(accessToken, externalId).catch(() => undefined) if (!rollback || (!rollback.ok && rollback.status !== 404)) { - logger.error( - `[${requestId}] Failed to roll back ClickUp webhook ${externalId} after missing secret`, - { clickupWebhookId: externalId, status: rollback?.status } - ) + logger.error(`Failed to roll back ClickUp webhook ${externalId} after missing secret`, { + requestId, + clickupWebhookId: externalId, + status: rollback?.status, + }) throw new Error( `ClickUp webhook creation succeeded but no signing secret was returned, and rolling back the webhook failed. Delete webhook ${externalId} manually in ClickUp before retrying.` ) @@ -263,23 +260,21 @@ export const clickupHandler: WebhookProviderHandler = { throw new Error('ClickUp webhook creation succeeded but no signing secret was returned') } - logger.info( - `[${requestId}] Successfully created webhook in ClickUp for webhook ${webhookRecord.id}.`, - { - clickupWebhookId: externalId, - workspaceId, - events, - } - ) + logger.info(`Successfully created webhook in ClickUp for webhook ${webhookRecord.id}.`, { + requestId, + clickupWebhookId: externalId, + workspaceId, + events, + }) return { providerConfigUpdates: { externalId, webhookSecret: secret } } } catch (error: unknown) { if (error instanceof WebhookDeploymentConfigurationError) throw error const message = toError(error).message - logger.error( - `[${requestId}] Exception during ClickUp webhook creation for webhook ${webhookRecord.id}.`, - { message } - ) + logger.error(`Exception during ClickUp webhook creation for webhook ${webhookRecord.id}.`, { + requestId, + message, + }) throw error } }, @@ -296,7 +291,8 @@ export const clickupHandler: WebhookProviderHandler = { if (!externalId) { logger.warn( - `[${requestId}] Missing externalId for ClickUp webhook deletion ${webhookRecord.id}, skipping cleanup` + `Missing externalId for ClickUp webhook deletion ${webhookRecord.id}, skipping cleanup`, + { requestId } ) if (strict) throw new Error('Missing ClickUp externalId for webhook deletion') return @@ -304,7 +300,8 @@ export const clickupHandler: WebhookProviderHandler = { if (!credentialId) { logger.warn( - `[${requestId}] Missing credentialId for ClickUp webhook deletion ${webhookRecord.id}, skipping cleanup` + `Missing credentialId for ClickUp webhook deletion ${webhookRecord.id}, skipping cleanup`, + { requestId } ) if (strict) throw new Error('Missing ClickUp credentialId for webhook deletion') return @@ -313,8 +310,8 @@ export const clickupHandler: WebhookProviderHandler = { const accessToken = await getCredentialAccessToken(credentialId, requestId) if (!accessToken) { - const message = `[${requestId}] Could not retrieve ClickUp access token. Cannot delete webhook.` - logger.warn(message, { webhookId: webhookRecord.id }) + const message = 'Could not retrieve ClickUp access token. Cannot delete webhook.' + logger.warn(message, { requestId, webhookId: webhookRecord.id }) if (strict) throw new Error(message) return } @@ -323,16 +320,16 @@ export const clickupHandler: WebhookProviderHandler = { if (!clickupResponse.ok && clickupResponse.status !== 404) { const responseBody = await clickupResponse.json().catch(() => ({})) - logger.warn( - `[${requestId}] Failed to delete ClickUp webhook (non-fatal): ${clickupResponse.status}`, - { response: responseBody } - ) + logger.warn(`Failed to delete ClickUp webhook (non-fatal): ${clickupResponse.status}`, { + requestId, + response: responseBody, + }) if (strict) throw new Error(`Failed to delete ClickUp webhook: ${clickupResponse.status}`) } else { - logger.info(`[${requestId}] Successfully deleted ClickUp webhook ${externalId}`) + logger.info(`Successfully deleted ClickUp webhook ${externalId}`, { requestId }) } } catch (error) { - logger.warn(`[${requestId}] Error deleting ClickUp webhook (non-fatal)`, error) + logger.warn('Error deleting ClickUp webhook (non-fatal)', error, { requestId }) if (strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/confluence.ts b/apps/sim/lib/webhooks/providers/confluence.ts index f828555efcd..bfb31066a1b 100644 --- a/apps/sim/lib/webhooks/providers/confluence.ts +++ b/apps/sim/lib/webhooks/providers/confluence.ts @@ -104,15 +104,13 @@ export const confluenceHandler: WebhookProviderHandler = { if (triggerId) { const { isConfluencePayloadMatch } = await import('@/triggers/confluence/utils') if (!isConfluencePayloadMatch(triggerId, obj)) { - logger.debug( - `[${requestId}] Confluence payload mismatch for trigger ${triggerId}. Skipping execution.`, - { - webhookId: webhook.id, - workflowId: workflow.id, - triggerId, - bodyKeys: Object.keys(obj), - } - ) + logger.debug(`Confluence payload mismatch for trigger ${triggerId}. Skipping execution.`, { + requestId, + webhookId: webhook.id, + workflowId: workflow.id, + triggerId, + bodyKeys: Object.keys(obj), + }) return NextResponse.json({ message: 'Payload does not match trigger configuration. Ignoring.', }) diff --git a/apps/sim/lib/webhooks/providers/emailbison.ts b/apps/sim/lib/webhooks/providers/emailbison.ts index cdbcce6dfcd..1969a75c7a2 100644 --- a/apps/sim/lib/webhooks/providers/emailbison.ts +++ b/apps/sim/lib/webhooks/providers/emailbison.ts @@ -27,15 +27,13 @@ export const emailBisonHandler: WebhookProviderHandler = { if (!triggerId) return true if (!isRecordLike(body)) { - logger.warn(`[${requestId}] Email Bison webhook payload was not an object`) + logger.warn('Email Bison webhook payload was not an object', { requestId }) return false } const { isEmailBisonEventMatch } = await import('@/triggers/emailbison/utils') if (!isEmailBisonEventMatch(triggerId, unwrapEmailBisonPayload(body))) { - logger.info(`[${requestId}] Email Bison event did not match trigger`, { - triggerId, - }) + logger.info('Email Bison event did not match trigger', { requestId, triggerId }) return false } @@ -147,7 +145,8 @@ export const emailBisonHandler: WebhookProviderHandler = { const notificationUrl = getNotificationUrl(webhook) - logger.info(`[${requestId}] Creating Email Bison webhook`, { + logger.info('Creating Email Bison webhook', { + requestId, triggerId, eventType, webhookId: webhook.id, @@ -156,7 +155,7 @@ export const emailBisonHandler: WebhookProviderHandler = { const targetUrl = emailBisonUrl('/api/webhook-url', {}, apiBaseUrl) const urlValidation = await validateUrlWithDNS(targetUrl, 'apiBaseUrl', 'configuredEndpoint') if (!urlValidation.isValid) { - logger.warn(`[${requestId}] Invalid Email Bison Instance URL: ${urlValidation.error}`) + logger.warn(`Invalid Email Bison Instance URL: ${urlValidation.error}`, { requestId }) throw new Error('Email Bison Instance URL could not be validated.') } @@ -174,7 +173,8 @@ export const emailBisonHandler: WebhookProviderHandler = { const responseBody = await parseJsonResponse(response) if (!response.ok) { const message = extractEmailBisonError(responseBody) - logger.error(`[${requestId}] Failed to create Email Bison webhook`, { + logger.error('Failed to create Email Bison webhook', { + requestId, status: response.status, message, response: responseBody, @@ -197,7 +197,8 @@ export const emailBisonHandler: WebhookProviderHandler = { throw new Error('Email Bison webhook was created but the API response did not include an ID.') } - logger.info(`[${requestId}] Successfully created Email Bison webhook`, { + logger.info('Successfully created Email Bison webhook', { + requestId, externalId, webhookId: webhook.id, }) @@ -215,7 +216,8 @@ export const emailBisonHandler: WebhookProviderHandler = { const externalId = providerConfig.externalId as string | undefined if (!apiKey?.trim() || !apiBaseUrl?.trim() || !externalId?.trim()) { - logger.warn(`[${requestId}] Missing Email Bison webhook cleanup configuration`, { + logger.warn('Missing Email Bison webhook cleanup configuration', { + requestId, webhookId: webhook.id, hasApiKey: Boolean(apiKey), hasApiBaseUrl: Boolean(apiBaseUrl), @@ -233,7 +235,8 @@ export const emailBisonHandler: WebhookProviderHandler = { ) const urlValidation = await validateUrlWithDNS(targetUrl, 'apiBaseUrl', 'configuredEndpoint') if (!urlValidation.isValid) { - logger.warn(`[${requestId}] Invalid Email Bison Instance URL: ${urlValidation.error}`, { + logger.warn(`Invalid Email Bison Instance URL: ${urlValidation.error}`, { + requestId, webhookId: webhook.id, }) if (ctx.strict) @@ -249,7 +252,8 @@ export const emailBisonHandler: WebhookProviderHandler = { if (!response.ok && response.status !== 404) { const responseBody = await parseJsonResponse(response) - logger.warn(`[${requestId}] Failed to delete Email Bison webhook`, { + logger.warn('Failed to delete Email Bison webhook', { + requestId, status: response.status, response: responseBody, }) @@ -259,13 +263,15 @@ export const emailBisonHandler: WebhookProviderHandler = { } await response.body?.cancel() - logger.info(`[${requestId}] Successfully deleted Email Bison webhook`, { + logger.info('Successfully deleted Email Bison webhook', { + requestId, externalId, webhookId: webhook.id, }) } catch (error) { if (!(error instanceof AlreadyLoggedError)) { - logger.warn(`[${requestId}] Error deleting Email Bison webhook`, { + logger.warn('Error deleting Email Bison webhook', { + requestId, message: toError(error).message, }) } diff --git a/apps/sim/lib/webhooks/providers/fathom.ts b/apps/sim/lib/webhooks/providers/fathom.ts index 5299edadeb2..27d5f6f7534 100644 --- a/apps/sim/lib/webhooks/providers/fathom.ts +++ b/apps/sim/lib/webhooks/providers/fathom.ts @@ -25,7 +25,8 @@ export const fathomHandler: WebhookProviderHandler = { const includeCrmMatches = providerConfig.includeCrmMatches as unknown if (!apiKey) { - logger.warn(`[${requestId}] Missing apiKey for Fathom webhook creation.`, { + logger.warn('Missing apiKey for Fathom webhook creation.', { + requestId, webhookId: webhook.id, }) throw new WebhookDeploymentConfigurationError( @@ -51,7 +52,8 @@ export const fathomHandler: WebhookProviderHandler = { include_crm_matches: toBool(includeCrmMatches, false), } - logger.info(`[${requestId}] Creating Fathom webhook`, { + logger.info('Creating Fathom webhook', { + requestId, triggerId, triggeredFor: triggeredForValue, webhookId: webhook.id, @@ -77,8 +79,8 @@ export const fathomHandler: WebhookProviderHandler = { (responseBody.error as string) || 'Unknown Fathom API error' logger.error( - `[${requestId}] Failed to create webhook in Fathom for webhook ${webhook.id}. Status: ${fathomResponse.status}`, - { message: errorMessage, response: responseBody } + `Failed to create webhook in Fathom for webhook ${webhook.id}. Status: ${fathomResponse.status}`, + { requestId, message: errorMessage, response: responseBody } ) let userFriendlyMessage = 'Failed to create webhook subscription in Fathom' @@ -95,29 +97,26 @@ export const fathomHandler: WebhookProviderHandler = { if (!responseBody.id) { logger.error( - `[${requestId}] Fathom webhook creation returned success but no webhook ID for ${webhook.id}.` + `Fathom webhook creation returned success but no webhook ID for ${webhook.id}.`, + { requestId } ) throw new Error('Fathom webhook created but no ID returned. Please try again.') } - logger.info( - `[${requestId}] Successfully created webhook in Fathom for webhook ${webhook.id}.`, - { - fathomWebhookId: responseBody.id, - } - ) + logger.info(`Successfully created webhook in Fathom for webhook ${webhook.id}.`, { + requestId, + fathomWebhookId: responseBody.id, + }) return { providerConfigUpdates: { externalId: responseBody.id } } } catch (error: unknown) { if (error instanceof WebhookDeploymentConfigurationError) throw error const err = error as Error - logger.error( - `[${requestId}] Exception during Fathom webhook creation for webhook ${webhook.id}.`, - { - message: err.message, - stack: err.stack, - } - ) + logger.error(`Exception during Fathom webhook creation for webhook ${webhook.id}.`, { + requestId, + message: err.message, + stack: err.stack, + }) throw error } }, @@ -130,16 +129,17 @@ export const fathomHandler: WebhookProviderHandler = { const externalId = config.externalId as string | undefined if (!apiKey) { - logger.warn( - `[${requestId}] Missing apiKey for Fathom webhook deletion ${webhook.id}, skipping cleanup` - ) + logger.warn(`Missing apiKey for Fathom webhook deletion ${webhook.id}, skipping cleanup`, { + requestId, + }) if (ctx.strict) throw new Error('Missing Fathom apiKey for webhook deletion') return } if (!externalId) { logger.warn( - `[${requestId}] Missing externalId for Fathom webhook deletion ${webhook.id}, skipping cleanup` + `Missing externalId for Fathom webhook deletion ${webhook.id}, skipping cleanup`, + { requestId } ) if (ctx.strict) throw new Error('Missing Fathom externalId for webhook deletion') return @@ -148,7 +148,8 @@ export const fathomHandler: WebhookProviderHandler = { const idValidation = validateAlphanumericId(externalId, 'Fathom webhook ID', 100) if (!idValidation.isValid) { logger.warn( - `[${requestId}] Invalid externalId format for Fathom webhook deletion ${webhook.id}, skipping cleanup` + `Invalid externalId format for Fathom webhook deletion ${webhook.id}, skipping cleanup`, + { requestId } ) if (ctx.strict) throw new Error('Invalid Fathom externalId for webhook deletion') return @@ -165,15 +166,15 @@ export const fathomHandler: WebhookProviderHandler = { }) if (!fathomResponse.ok && fathomResponse.status !== 404) { - logger.warn( - `[${requestId}] Failed to delete Fathom webhook (non-fatal): ${fathomResponse.status}` - ) + logger.warn(`Failed to delete Fathom webhook (non-fatal): ${fathomResponse.status}`, { + requestId, + }) if (ctx.strict) throw new Error(`Failed to delete Fathom webhook: ${fathomResponse.status}`) } else { - logger.info(`[${requestId}] Successfully deleted Fathom webhook ${externalId}`) + logger.info(`Successfully deleted Fathom webhook ${externalId}`, { requestId }) } } catch (error) { - logger.warn(`[${requestId}] Error deleting Fathom webhook (non-fatal)`, error) + logger.warn('Error deleting Fathom webhook (non-fatal)', error, { requestId }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/fivetran.ts b/apps/sim/lib/webhooks/providers/fivetran.ts index f17ded3337c..c471257bb47 100644 --- a/apps/sim/lib/webhooks/providers/fivetran.ts +++ b/apps/sim/lib/webhooks/providers/fivetran.ts @@ -131,7 +131,7 @@ export const fivetranHandler: WebhookProviderHandler = { const secret = readString(providerConfig, 'webhookSecret') if (!secret) { - logger.warn(`[${requestId}] Fivetran webhook has no signing secret — rejecting request`) + logger.warn('Fivetran webhook has no signing secret — rejecting request', { requestId }) return new NextResponse( 'Unauthorized - Fivetran webhook signing secret is missing. Redeploy the workflow to register the webhook.', { status: 401 } @@ -139,12 +139,12 @@ export const fivetranHandler: WebhookProviderHandler = { } if (!signature) { - logger.warn(`[${requestId}] Fivetran webhook missing X-Fivetran-Signature-256 header`) + logger.warn('Fivetran webhook missing X-Fivetran-Signature-256 header', { requestId }) return new NextResponse('Unauthorized - Missing Fivetran signature', { status: 401 }) } if (!isValidFivetranSignature(secret, signature, rawBody)) { - logger.warn(`[${requestId}] Fivetran signature verification failed`) + logger.warn('Fivetran signature verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid Fivetran signature', { status: 401 }) } @@ -164,7 +164,8 @@ export const fivetranHandler: WebhookProviderHandler = { (!request.headers.get(SIGNATURE_HEADER) || payload.connector_id === FIVETRAN_TEST_CONNECTION_ID) ) { - logger.info(`[${requestId}] Acknowledged Fivetran activation test without running`, { + logger.info('Acknowledged Fivetran activation test without running', { + requestId, webhookId: webhook.id, }) return false @@ -176,8 +177,8 @@ export const fivetranHandler: WebhookProviderHandler = { const { isFivetranEventMatch } = await import('@/triggers/fivetran/utils') if (!isFivetranEventMatch(triggerId, event)) { logger.debug( - `[${requestId}] Fivetran event ${event || '(missing)'} does not match trigger ${triggerId}. Skipping execution.`, - { webhookId: webhook.id, triggerId } + `Fivetran event ${event || '(missing)'} does not match trigger ${triggerId}. Skipping execution.`, + { requestId, webhookId: webhook.id, triggerId } ) return false } @@ -190,7 +191,8 @@ export const fivetranHandler: WebhookProviderHandler = { !event.startsWith('transformation_') && payload.connector_id !== connectionId ) { - logger.debug(`[${requestId}] Fivetran event is for another connection. Skipping execution.`, { + logger.debug('Fivetran event is for another connection. Skipping execution.', { + requestId, webhookId: webhook.id, }) return false @@ -245,7 +247,8 @@ export const fivetranHandler: WebhookProviderHandler = { : `${FIVETRAN_WEBHOOKS_URL}/account` const webhookSecret = generateRandomHex(64) - logger.info(`[${ctx.requestId}] Creating Fivetran ${groupId ? 'group' : 'account'} webhook`, { + logger.info(`Creating Fivetran ${groupId ? 'group' : 'account'} webhook`, { + requestId: ctx.requestId, webhookId: ctx.webhook.id, triggerId, }) @@ -266,7 +269,7 @@ export const fivetranHandler: WebhookProviderHandler = { const externalId = toStringOrNull(toRecord(responseBody.data).id) if (!externalId) throw new Error('Fivetran created the webhook but did not return its ID.') - logger.info(`[${ctx.requestId}] Created inactive Fivetran webhook ${externalId}`) + logger.info(`Created inactive Fivetran webhook ${externalId}`, { requestId: ctx.requestId }) return { providerConfigUpdates: { externalId, webhookSecret, subscriptionActivationPending: true }, } @@ -290,7 +293,7 @@ export const fivetranHandler: WebhookProviderHandler = { ) throw new Error(fivetranFailureMessage('activate', response.status, responseBody)) } - logger.info(`[${ctx.requestId}] Activated Fivetran webhook ${externalId}`) + logger.info(`Activated Fivetran webhook ${externalId}`, { requestId: ctx.requestId }) }, async deleteSubscription(ctx: DeleteSubscriptionContext): Promise { @@ -300,7 +303,8 @@ export const fivetranHandler: WebhookProviderHandler = { const externalId = readString(providerConfig, 'externalId') if (!credentials || !externalId) { logger.warn( - `[${ctx.requestId}] Missing ${credentials ? 'externalId' : 'API credentials'} for Fivetran webhook deletion ${ctx.webhook.id}, skipping cleanup` + `Missing ${credentials ? 'externalId' : 'API credentials'} for Fivetran webhook deletion ${ctx.webhook.id}, skipping cleanup`, + { requestId: ctx.requestId } ) if (ctx.strict) throw new Error('Missing Fivetran API credentials or webhook ID for deletion') @@ -309,7 +313,7 @@ export const fivetranHandler: WebhookProviderHandler = { const response = await fivetranRequest(credentials, webhookUrl(externalId), 'DELETE') if (response.ok || response.status === 404) { - logger.info(`[${ctx.requestId}] Removed Fivetran webhook ${externalId}`) + logger.info(`Removed Fivetran webhook ${externalId}`, { requestId: ctx.requestId }) return } @@ -318,12 +322,12 @@ export const fivetranHandler: WebhookProviderHandler = { 'Fivetran webhook deletion response' ) const message = fivetranFailureMessage('delete', response.status, responseBody) - logger.warn(`[${ctx.requestId}] ${message} (non-fatal)`) + logger.warn(`${message} (non-fatal)`, { requestId: ctx.requestId }) if (ctx.strict) throw new Error(message) } catch (error) { - logger.warn( - `[${ctx.requestId}] Error deleting Fivetran webhook (non-fatal): ${getErrorMessage(error)}` - ) + logger.warn(`Error deleting Fivetran webhook (non-fatal): ${getErrorMessage(error)}`, { + requestId: ctx.requestId, + }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/generic.ts b/apps/sim/lib/webhooks/providers/generic.ts index 807a3930181..30eed47b0c4 100644 --- a/apps/sim/lib/webhooks/providers/generic.ts +++ b/apps/sim/lib/webhooks/providers/generic.ts @@ -117,8 +117,8 @@ function mergeRequestData( if (!isRecordLike(body)) { logger.debug( - `[${requestId}] Dropping webhook request metadata: the body is not an object, so there is no field to merge it into`, - { keys: entries.map(([key]) => key) } + 'Dropping webhook request metadata: the body is not an object, so there is no field to merge it into', + { requestId, keys: entries.map(([key]) => key) } ) return body } @@ -127,9 +127,9 @@ function mergeRequestData( for (const [key, value] of entries) { if (Object.hasOwn(body, key)) { - logger.debug( - `[${requestId}] Dropping webhook ${key}: the body already defines a "${key}" field` - ) + logger.debug(`Dropping webhook ${key}: the body already defines a "${key}" field`, { + requestId, + }) continue } merged[key] = value @@ -168,7 +168,7 @@ export const genericHandler: WebhookProviderHandler = { ) if (!clientIp || !clientIpAllowed) { - logger.warn(`[${requestId}] Forbidden webhook access attempt - IP not allowed: ${clientIp}`) + logger.warn(`Forbidden webhook access attempt - IP not allowed: ${clientIp}`, { requestId }) return new NextResponse('Forbidden - IP not allowed', { status: 403, }) @@ -300,7 +300,8 @@ export const genericHandler: WebhookProviderHandler = { if (uploadedFiles.length > 0) { input[fileField.name] = uploadedFiles logger.info( - `[${requestId}] Successfully processed ${uploadedFiles.length} file(s) for field: ${fileField.name}` + `Successfully processed ${uploadedFiles.length} file(s) for field: ${fileField.name}`, + { requestId } ) } } diff --git a/apps/sim/lib/webhooks/providers/github.ts b/apps/sim/lib/webhooks/providers/github.ts index 0c158b82f0a..0400fef1dbd 100644 --- a/apps/sim/lib/webhooks/providers/github.ts +++ b/apps/sim/lib/webhooks/providers/github.ts @@ -98,12 +98,13 @@ export const githubHandler: WebhookProviderHandler = { const signature = request.headers.get('X-Hub-Signature-256') || request.headers.get('X-Hub-Signature') if (!signature) { - logger.warn(`[${requestId}] GitHub webhook missing signature header`) + logger.warn('GitHub webhook missing signature header', { requestId }) return new NextResponse('Unauthorized - Missing GitHub signature', { status: 401 }) } if (!validateGitHubSignature(secret, signature, rawBody)) { - logger.warn(`[${requestId}] GitHub signature verification failed`, { + logger.warn('GitHub signature verification failed', { + requestId, usingSha256: !!request.headers.get('X-Hub-Signature-256'), }) return new NextResponse('Unauthorized - Invalid GitHub signature', { status: 401 }) @@ -155,8 +156,9 @@ export const githubHandler: WebhookProviderHandler = { const { isGitHubEventMatch } = await import('@/triggers/github/utils') if (!isGitHubEventMatch(triggerId, eventType || '', action, obj)) { logger.debug( - `[${requestId}] GitHub event mismatch for trigger ${triggerId}. Event: ${eventType}, Action: ${action}. Skipping execution.`, + `GitHub event mismatch for trigger ${triggerId}. Event: ${eventType}, Action: ${action}. Skipping execution.`, { + requestId, webhookId: webhook.id, workflowId: workflow.id, triggerId, diff --git a/apps/sim/lib/webhooks/providers/gitlab.ts b/apps/sim/lib/webhooks/providers/gitlab.ts index cbb99aed691..1a5341f66f9 100644 --- a/apps/sim/lib/webhooks/providers/gitlab.ts +++ b/apps/sim/lib/webhooks/providers/gitlab.ts @@ -65,18 +65,18 @@ export const gitlabHandler: WebhookProviderHandler = { verifyAuth({ request, requestId, providerConfig }: AuthContext) { const secret = providerConfig.webhookSecret as string | undefined if (!secret) { - logger.warn(`[${requestId}] GitLab webhook secret not configured`) + logger.warn('GitLab webhook secret not configured', { requestId }) return new NextResponse('Unauthorized - Missing GitLab webhook secret', { status: 401 }) } const token = request.headers.get('X-Gitlab-Token') if (!token) { - logger.warn(`[${requestId}] GitLab webhook missing X-Gitlab-Token header`) + logger.warn('GitLab webhook missing X-Gitlab-Token header', { requestId }) return new NextResponse('Unauthorized - Missing GitLab token', { status: 401 }) } if (!safeCompare(token, secret)) { - logger.warn(`[${requestId}] GitLab token verification failed`) + logger.warn('GitLab token verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid GitLab token', { status: 401 }) } @@ -91,9 +91,9 @@ export const gitlabHandler: WebhookProviderHandler = { const { isGitLabEventMatch } = await import('@/triggers/gitlab/utils') if (!isGitLabEventMatch(triggerId, objectKind || '')) { - logger.debug( - `[${requestId}] GitLab event '${objectKind}' does not match trigger ${triggerId}, skipping` - ) + logger.debug(`GitLab event '${objectKind}' does not match trigger ${triggerId}, skipping`, { + requestId, + }) return false } return true @@ -221,7 +221,10 @@ export const gitlabHandler: WebhookProviderHandler = { if (!res.ok) { const detail = await res.text().catch(() => '') - logger.error(`[${ctx.requestId}] Failed to create GitLab webhook (${res.status})`, { detail }) + logger.error(`Failed to create GitLab webhook (${res.status})`, { + requestId: ctx.requestId, + detail, + }) if (res.status === 401) throw new Error( 'GitLab authentication failed. Verify your Personal Access Token has the api scope.' @@ -240,7 +243,9 @@ export const gitlabHandler: WebhookProviderHandler = { throw new Error('GitLab webhook created but no hook ID was returned.') } - logger.info(`[${ctx.requestId}] Created GitLab webhook ${created.id} for project ${projectId}`) + logger.info(`Created GitLab webhook ${created.id} for project ${projectId}`, { + requestId: ctx.requestId, + }) return { providerConfigUpdates: { externalId: String(created.id), webhookSecret: secretToken } } }, @@ -257,9 +262,9 @@ export const gitlabHandler: WebhookProviderHandler = { if (!accessToken || !projectId || !externalId) { if (ctx.strict) throw new Error('Missing GitLab credentials or hook ID for webhook deletion.') - logger.warn( - `[${ctx.requestId}] Skipping GitLab webhook cleanup — missing token, project, or hook ID` - ) + logger.warn('Skipping GitLab webhook cleanup — missing token, project, or hook ID', { + requestId: ctx.requestId, + }) return } @@ -270,9 +275,9 @@ export const gitlabHandler: WebhookProviderHandler = { if (ctx.strict) { throw new Error('Cannot delete GitLab webhook: the configured host is invalid.') } - logger.warn( - `[${ctx.requestId}] Skipping GitLab webhook cleanup — configured host is invalid` - ) + logger.warn('Skipping GitLab webhook cleanup — configured host is invalid', { + requestId: ctx.requestId, + }) return } throw error @@ -285,11 +290,11 @@ export const gitlabHandler: WebhookProviderHandler = { if (!res.ok && res.status !== 404) { if (ctx.strict) throw new Error(`Failed to delete GitLab webhook: ${res.status}`) - logger.warn( - `[${ctx.requestId}] Failed to delete GitLab webhook ${externalId} (non-fatal): ${res.status}` - ) + logger.warn(`Failed to delete GitLab webhook ${externalId} (non-fatal): ${res.status}`, { + requestId: ctx.requestId, + }) return } - logger.info(`[${ctx.requestId}] Deleted GitLab webhook ${externalId}`) + logger.info(`Deleted GitLab webhook ${externalId}`, { requestId: ctx.requestId }) }, } diff --git a/apps/sim/lib/webhooks/providers/gmail.ts b/apps/sim/lib/webhooks/providers/gmail.ts index 11cbc50e21b..bc70d25b556 100644 --- a/apps/sim/lib/webhooks/providers/gmail.ts +++ b/apps/sim/lib/webhooks/providers/gmail.ts @@ -27,21 +27,22 @@ export const gmailHandler: WebhookProviderHandler = { requestId, persistProviderConfig, }: PollingConfigContext) { - logger.info(`[${requestId}] Setting up Gmail polling for webhook ${webhookData.id}`) + logger.info(`Setting up Gmail polling for webhook ${webhookData.id}`, { requestId }) try { const providerConfig = toRecord(webhookData.providerConfig) const credentialId = providerConfig.credentialId as string | undefined if (!credentialId) { - logger.error(`[${requestId}] Missing credentialId for Gmail webhook ${webhookData.id}`) + logger.error(`Missing credentialId for Gmail webhook ${webhookData.id}`, { requestId }) return false } const resolvedGmail = await resolveOAuthAccountId(credentialId) if (!resolvedGmail) { logger.error( - `[${requestId}] Could not resolve credential ${credentialId} for Gmail webhook ${webhookData.id}` + `Could not resolve credential ${credentialId} for Gmail webhook ${webhookData.id}`, + { requestId } ) return false } @@ -52,9 +53,9 @@ export const gmailHandler: WebhookProviderHandler = { .where(eq(account.id, resolvedGmail.accountId)) .limit(1) if (rows.length === 0) { - logger.error( - `[${requestId}] Credential ${credentialId} not found for Gmail webhook ${webhookData.id}` - ) + logger.error(`Credential ${credentialId} not found for Gmail webhook ${webhookData.id}`, { + requestId, + }) return false } @@ -66,9 +67,9 @@ export const gmailHandler: WebhookProviderHandler = { requestId ) if (!accessToken) { - logger.error( - `[${requestId}] Failed to refresh/access Gmail token for credential ${credentialId}` - ) + logger.error(`Failed to refresh/access Gmail token for credential ${credentialId}`, { + requestId, + }) return false } @@ -106,13 +107,14 @@ export const gmailHandler: WebhookProviderHandler = { .where(eq(webhook.id, webhookData.id as string)) } - logger.info( - `[${requestId}] Successfully configured Gmail polling for webhook ${webhookData.id}` - ) + logger.info(`Successfully configured Gmail polling for webhook ${webhookData.id}`, { + requestId, + }) return true } catch (error: unknown) { const err = error as Error - logger.error(`[${requestId}] Failed to configure Gmail polling`, { + logger.error('Failed to configure Gmail polling', { + requestId, webhookId: webhookData.id, error: err.message, stack: err.stack, diff --git a/apps/sim/lib/webhooks/providers/gong.ts b/apps/sim/lib/webhooks/providers/gong.ts index 609b595be90..ef97d4cbef1 100644 --- a/apps/sim/lib/webhooks/providers/gong.ts +++ b/apps/sim/lib/webhooks/providers/gong.ts @@ -66,7 +66,7 @@ export async function verifyGongJwtAuth(ctx: AuthContext): Promise | null const isVerificationRequest = !obj || Object.keys(obj).length === 0 || !obj.type if (isVerificationRequest) { - logger.info( - `[${requestId}] Grain reachability test detected - returning 200 for webhook verification` - ) + logger.info('Grain reachability test detected - returning 200 for webhook verification', { + requestId, + }) return NextResponse.json({ status: 'ok', message: 'Webhook endpoint verified', @@ -301,7 +300,8 @@ export const grainHandler: WebhookProviderHandler = { const triggerId = providerConfig.triggerId as string | undefined if (!apiKey) { - logger.warn(`[${requestId}] Missing apiKey for Grain webhook creation.`, { + logger.warn('Missing apiKey for Grain webhook creation.', { + requestId, webhookId: webhook.id, }) throw new WebhookDeploymentConfigurationError( @@ -315,7 +315,8 @@ export const grainHandler: WebhookProviderHandler = { GRAIN_V2_TRIGGER_TO_HOOK_TYPES[triggerId as keyof typeof GRAIN_V2_TRIGGER_TO_HOOK_TYPES] if (v2HookTypes) { const hookTypes = [...v2HookTypes] - logger.info(`[${requestId}] Creating Grain v2 hooks`, { + logger.info('Creating Grain v2 hooks', { + requestId, triggerId, hookTypes, webhookId: webhook.id, @@ -330,8 +331,8 @@ export const grainHandler: WebhookProviderHandler = { }) logger.info( - `[${requestId}] Successfully created ${externalIds.length} Grain hook(s) for webhook ${webhook.id}.`, - { externalIds, hookTypes } + `Successfully created ${externalIds.length} Grain hook(s) for webhook ${webhook.id}.`, + { requestId, externalIds, hookTypes } ) return { @@ -355,13 +356,11 @@ export const grainHandler: WebhookProviderHandler = { } catch (error: unknown) { if (error instanceof WebhookDeploymentConfigurationError) throw error const err = error as Error - logger.error( - `[${requestId}] Exception during Grain webhook creation for webhook ${webhook.id}.`, - { - message: err.message, - stack: err.stack, - } - ) + logger.error(`Exception during Grain webhook creation for webhook ${webhook.id}.`, { + requestId, + message: err.message, + stack: err.stack, + }) throw error } }, @@ -383,16 +382,17 @@ export const grainHandler: WebhookProviderHandler = { ) if (!apiKey) { - logger.warn( - `[${requestId}] Missing apiKey for Grain webhook deletion ${webhook.id}, skipping cleanup` - ) + logger.warn(`Missing apiKey for Grain webhook deletion ${webhook.id}, skipping cleanup`, { + requestId, + }) if (ctx.strict) throw new Error('Missing Grain apiKey for webhook deletion') return } if (externalIds.length === 0) { logger.warn( - `[${requestId}] Missing externalId for Grain webhook deletion ${webhook.id}, skipping cleanup` + `Missing externalId for Grain webhook deletion ${webhook.id}, skipping cleanup`, + { requestId } ) if (ctx.strict) throw new Error('Missing Grain externalId for webhook deletion') return @@ -406,9 +406,10 @@ export const grainHandler: WebhookProviderHandler = { } else { await deleteLegacyV1Hook({ apiKey, hookId: externalId, requestId }) } - logger.info(`[${requestId}] Successfully deleted Grain webhook ${externalId}`) + logger.info(`Successfully deleted Grain webhook ${externalId}`, { requestId }) } catch (error) { - logger.warn(`[${requestId}] Failed to delete Grain webhook ${externalId} (non-fatal)`, { + logger.warn(`Failed to delete Grain webhook ${externalId} (non-fatal)`, { + requestId, error, }) failures.push(externalId) @@ -419,7 +420,7 @@ export const grainHandler: WebhookProviderHandler = { throw new Error(`Failed to delete ${failures.length} Grain webhook(s)`) } } catch (error) { - logger.warn(`[${requestId}] Error deleting Grain webhook (non-fatal)`, error) + logger.warn('Error deleting Grain webhook (non-fatal)', error, { requestId }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/granola.ts b/apps/sim/lib/webhooks/providers/granola.ts index 70905343420..d44d0273b69 100644 --- a/apps/sim/lib/webhooks/providers/granola.ts +++ b/apps/sim/lib/webhooks/providers/granola.ts @@ -118,11 +118,12 @@ async function cleanupOrphanedGranolaEndpoint(params: { const { apiKey, endpointId, requestId } = params try { await deleteGranolaEndpoint(apiKey, endpointId) - logger.info(`[${requestId}] Removed orphaned Granola webhook endpoint ${endpointId}`) + logger.info(`Removed orphaned Granola webhook endpoint ${endpointId}`, { requestId }) } catch (error) { logger.error( - `[${requestId}] Failed to remove orphaned Granola webhook endpoint ${endpointId}; it may still be active`, - error + `Failed to remove orphaned Granola webhook endpoint ${endpointId}; it may still be active`, + error, + { requestId } ) } } @@ -141,7 +142,7 @@ export const granolaHandler: WebhookProviderHandler = { }: AuthContext): Promise { const signingSecret = providerConfig.signingSecret as string | undefined if (!signingSecret?.trim()) { - logger.warn(`[${requestId}] Granola webhook missing signing secret in provider configuration`) + logger.warn('Granola webhook missing signing secret in provider configuration', { requestId }) return new NextResponse('Unauthorized - Granola signing secret is required', { status: 401 }) } @@ -166,7 +167,8 @@ export const granolaHandler: WebhookProviderHandler = { if (!isGranolaEventMatch(triggerId, eventType)) { logger.debug( - `[${requestId}] Granola event mismatch for trigger ${triggerId}. event_type: ${eventType}. Skipping.` + `Granola event mismatch for trigger ${triggerId}. event_type: ${eventType}. Skipping.`, + { requestId } ) return false } @@ -213,7 +215,8 @@ export const granolaHandler: WebhookProviderHandler = { const triggerId = providerConfig.triggerId as string | undefined if (!apiKey) { - logger.warn(`[${requestId}] Missing apiKey for Granola webhook creation.`, { + logger.warn('Missing apiKey for Granola webhook creation.', { + requestId, webhookId: webhook.id, }) throw new WebhookDeploymentConfigurationError( @@ -232,7 +235,8 @@ export const granolaHandler: WebhookProviderHandler = { if (events.length > 0) requestBody.events = events if (folderIds.length > 0) requestBody.folder_ids = folderIds - logger.info(`[${requestId}] Creating Granola webhook endpoint`, { + logger.info('Creating Granola webhook endpoint', { + requestId, triggerId, events, scopes: requestBody.scopes, @@ -253,8 +257,8 @@ export const granolaHandler: WebhookProviderHandler = { if (!response.ok) { const errorBody = await response.text().catch(() => '') logger.error( - `[${requestId}] Failed to create Granola webhook endpoint for webhook ${webhook.id}. Status: ${response.status}`, - { response: errorBody } + `Failed to create Granola webhook endpoint for webhook ${webhook.id}. Status: ${response.status}`, + { requestId, response: errorBody } ) throw new Error(granolaUserFacingError(response.status, errorBody)) } @@ -266,7 +270,8 @@ export const granolaHandler: WebhookProviderHandler = { if (!created.id || !created.signing_secret) { logger.error( - `[${requestId}] Granola webhook endpoint response missing id or signing secret for webhook ${webhook.id}.` + `Granola webhook endpoint response missing id or signing secret for webhook ${webhook.id}.`, + { requestId } ) if (created.id) { await cleanupOrphanedGranolaEndpoint({ apiKey, endpointId: created.id, requestId }) @@ -276,10 +281,10 @@ export const granolaHandler: WebhookProviderHandler = { ) } - logger.info( - `[${requestId}] Successfully created Granola webhook endpoint for webhook ${webhook.id}.`, - { externalId: created.id } - ) + logger.info(`Successfully created Granola webhook endpoint for webhook ${webhook.id}.`, { + requestId, + externalId: created.id, + }) return { providerConfigUpdates: { @@ -304,7 +309,8 @@ export const granolaHandler: WebhookProviderHandler = { if (!apiKey || !externalId) { logger.warn( - `[${requestId}] Missing ${!apiKey ? 'apiKey' : 'externalId'} for Granola webhook deletion ${webhook.id}, skipping cleanup` + `Missing ${!apiKey ? 'apiKey' : 'externalId'} for Granola webhook deletion ${webhook.id}, skipping cleanup`, + { requestId } ) if (ctx.strict) { throw new Error( @@ -316,9 +322,9 @@ export const granolaHandler: WebhookProviderHandler = { await deleteGranolaEndpoint(apiKey, externalId) - logger.info(`[${requestId}] Successfully deleted Granola webhook endpoint ${externalId}`) + logger.info(`Successfully deleted Granola webhook endpoint ${externalId}`, { requestId }) } catch (error) { - logger.warn(`[${requestId}] Error deleting Granola webhook endpoint (non-fatal)`, error) + logger.warn('Error deleting Granola webhook endpoint (non-fatal)', error, { requestId }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/greenhouse.ts b/apps/sim/lib/webhooks/providers/greenhouse.ts index f7d2c896fd2..864650782ca 100644 --- a/apps/sim/lib/webhooks/providers/greenhouse.ts +++ b/apps/sim/lib/webhooks/providers/greenhouse.ts @@ -88,8 +88,9 @@ export const greenhouseHandler: WebhookProviderHandler = { const { isGreenhouseEventMatch } = await import('@/triggers/greenhouse/utils') if (!isGreenhouseEventMatch(triggerId, action || '')) { logger.debug( - `[${requestId}] Greenhouse event mismatch for trigger ${triggerId}. Action: ${action}. Skipping execution.`, + `Greenhouse event mismatch for trigger ${triggerId}. Action: ${action}. Skipping execution.`, { + requestId, webhookId: webhook.id, workflowId: workflow.id, triggerId, diff --git a/apps/sim/lib/webhooks/providers/helpscout.ts b/apps/sim/lib/webhooks/providers/helpscout.ts index ef12e368c5f..89fcccd4ca2 100644 --- a/apps/sim/lib/webhooks/providers/helpscout.ts +++ b/apps/sim/lib/webhooks/providers/helpscout.ts @@ -147,7 +147,7 @@ export const helpScoutHandler: WebhookProviderHandler = { }) if (!response.ok) { const detail = await readHelpScoutErrorDetail(response) - logger.warn(`[${requestId}] Help Scout webhook creation failed`, { status: response.status }) + logger.warn('Help Scout webhook creation failed', { requestId, status: response.status }) const message = `Help Scout webhook creation failed (HTTP ${response.status})${detail ? `: ${detail}` : ''}` if (response.status === 429 || response.status >= 500) throw new Error(message) throw new WebhookDeploymentConfigurationError( @@ -162,7 +162,7 @@ export const helpScoutHandler: WebhookProviderHandler = { 'Help Scout created the webhook but returned no webhook ID' ) } - logger.info(`[${requestId}] Created Help Scout webhook`, { externalId, events: events.length }) + logger.info('Created Help Scout webhook', { requestId, externalId, events: events.length }) return { providerConfigUpdates: { externalId, webhookSecret } } }, @@ -176,7 +176,7 @@ export const helpScoutHandler: WebhookProviderHandler = { // Creation always stores the ID, so a trigger without one (set up manually before Sim managed // Help Scout webhooks) has no subscription for Sim to delete. if (!externalId) { - logger.info(`[${requestId}] No managed Help Scout webhook to delete`) + logger.info('No managed Help Scout webhook to delete', { requestId }) return } if (parseIdText(externalId) === null) { @@ -199,7 +199,7 @@ export const helpScoutHandler: WebhookProviderHandler = { }) await response.body?.cancel().catch(() => {}) if (!response.ok && response.status !== 404) { - logger.warn(`[${requestId}] Help Scout webhook deletion failed`, { status: response.status }) + logger.warn('Help Scout webhook deletion failed', { requestId, status: response.status }) if (strict) throw new Error(`Help Scout webhook deletion failed: ${response.status}`) } }, @@ -232,7 +232,7 @@ export const helpScoutHandler: WebhookProviderHandler = { const eventType = request.headers.get(EVENT_HEADER)?.trim() ?? '' if (isHelpScoutEventMatch(triggerId, eventType)) return true - logger.debug(`[${requestId}] Help Scout event ${eventType} does not match ${triggerId}`) + logger.debug(`Help Scout event ${eventType} does not match ${triggerId}`, { requestId }) return false }, diff --git a/apps/sim/lib/webhooks/providers/imap.ts b/apps/sim/lib/webhooks/providers/imap.ts index 722bfa145b4..5ce2006ebcd 100644 --- a/apps/sim/lib/webhooks/providers/imap.ts +++ b/apps/sim/lib/webhooks/providers/imap.ts @@ -51,16 +51,16 @@ export const imapHandler: WebhookProviderHandler = { deploymentVersionId, persistProviderConfig, }: PollingConfigContext) { - logger.info(`[${requestId}] Setting up IMAP polling for webhook ${webhookData.id}`) + logger.info(`Setting up IMAP polling for webhook ${webhookData.id}`, { requestId }) try { const providerConfig = toRecord(webhookData.providerConfig) const now = new Date() if (!providerConfig.host || !providerConfig.username || !providerConfig.password) { - logger.error( - `[${requestId}] Missing required IMAP connection settings for webhook ${webhookData.id}` - ) + logger.error(`Missing required IMAP connection settings for webhook ${webhookData.id}`, { + requestId, + }) return false } @@ -127,14 +127,12 @@ export const imapHandler: WebhookProviderHandler = { .where(eq(webhook.id, webhookData.id as string)) } - logger.info( - `[${requestId}] Successfully configured IMAP polling for webhook ${webhookData.id}` - ) + logger.info(`Successfully configured IMAP polling for webhook ${webhookData.id}`, { + requestId, + }) return true } catch { - logger.error(`[${requestId}] Failed to configure IMAP polling`, { - webhookId: webhookData.id, - }) + logger.error('Failed to configure IMAP polling', { requestId, webhookId: webhookData.id }) return false } }, diff --git a/apps/sim/lib/webhooks/providers/incidentio.ts b/apps/sim/lib/webhooks/providers/incidentio.ts index 1f6acc5294a..afd6a782d12 100644 --- a/apps/sim/lib/webhooks/providers/incidentio.ts +++ b/apps/sim/lib/webhooks/providers/incidentio.ts @@ -44,9 +44,9 @@ export const incidentioHandler: WebhookProviderHandler = { }: AuthContext): Promise { const signingSecret = providerConfig.signingSecret as string | undefined if (!signingSecret?.trim()) { - logger.warn( - `[${requestId}] incident.io webhook missing signing secret in provider configuration` - ) + logger.warn('incident.io webhook missing signing secret in provider configuration', { + requestId, + }) return new NextResponse('Unauthorized - incident.io signing secret is required', { status: 401, }) @@ -75,7 +75,8 @@ export const incidentioHandler: WebhookProviderHandler = { if (!isIncidentioEventMatch(triggerId, eventType || '')) { logger.debug( - `[${requestId}] incident.io event mismatch for trigger ${triggerId}. event_type: ${eventType}. Skipping.` + `incident.io event mismatch for trigger ${triggerId}. event_type: ${eventType}. Skipping.`, + { requestId } ) return false } diff --git a/apps/sim/lib/webhooks/providers/instantly.ts b/apps/sim/lib/webhooks/providers/instantly.ts index 5b62fad2c42..142096bc963 100644 --- a/apps/sim/lib/webhooks/providers/instantly.ts +++ b/apps/sim/lib/webhooks/providers/instantly.ts @@ -26,12 +26,12 @@ export const instantlyHandler: WebhookProviderHandler = { verifyAuth({ request, requestId, providerConfig }: AuthContext): NextResponse | null { const secretToken = providerConfig.secretToken as string | undefined if (!secretToken) { - logger.warn(`[${requestId}] Instantly webhook secret token is missing`) + logger.warn('Instantly webhook secret token is missing', { requestId }) return new NextResponse('Unauthorized', { status: 401 }) } if (!verifyTokenAuth(request, secretToken, SIM_WEBHOOK_TOKEN_HEADER)) { - logger.warn(`[${requestId}] Unauthorized Instantly webhook request`) + logger.warn('Unauthorized Instantly webhook request', { requestId }) return new NextResponse('Unauthorized', { status: 401 }) } @@ -43,13 +43,14 @@ export const instantlyHandler: WebhookProviderHandler = { if (!triggerId) return true if (!isRecordLike(body)) { - logger.warn(`[${requestId}] Instantly webhook payload was not an object`) + logger.warn('Instantly webhook payload was not an object', { requestId }) return false } const { isInstantlyEventMatch } = await import('@/triggers/instantly/utils') if (!isInstantlyEventMatch(triggerId, body)) { - logger.info(`[${requestId}] Instantly event did not match trigger`, { + logger.info('Instantly event did not match trigger', { + requestId, triggerId, eventType: body.event_type, }) @@ -161,7 +162,8 @@ export const instantlyHandler: WebhookProviderHandler = { requestBody.campaign = campaignId } - logger.info(`[${requestId}] Creating Instantly webhook`, { + logger.info('Creating Instantly webhook', { + requestId, triggerId, eventType, hasCampaignId: Boolean(campaignId), @@ -180,7 +182,8 @@ export const instantlyHandler: WebhookProviderHandler = { const responseBody = await parseJsonResponse(response) if (!response.ok) { const message = extractInstantlyError(responseBody) - logger.error(`[${requestId}] Failed to create Instantly webhook`, { + logger.error('Failed to create Instantly webhook', { + requestId, status: response.status, message, response: responseBody, @@ -204,7 +207,8 @@ export const instantlyHandler: WebhookProviderHandler = { throw new Error('Instantly webhook was created but the API response did not include an ID.') } - logger.info(`[${requestId}] Successfully created Instantly webhook`, { + logger.info('Successfully created Instantly webhook', { + requestId, externalId, webhookId: webhook.id, }) @@ -221,7 +225,8 @@ export const instantlyHandler: WebhookProviderHandler = { const externalId = providerConfig.externalId as string | undefined if (!apiKey?.trim() || !externalId?.trim()) { - logger.warn(`[${requestId}] Missing Instantly webhook cleanup configuration`, { + logger.warn('Missing Instantly webhook cleanup configuration', { + requestId, webhookId: webhook.id, hasApiKey: Boolean(apiKey), hasExternalId: Boolean(externalId), @@ -242,7 +247,8 @@ export const instantlyHandler: WebhookProviderHandler = { if (!response.ok && response.status !== 404) { const responseBody = await parseJsonResponse(response) - logger.warn(`[${requestId}] Failed to delete Instantly webhook`, { + logger.warn('Failed to delete Instantly webhook', { + requestId, status: response.status, response: responseBody, }) @@ -251,12 +257,14 @@ export const instantlyHandler: WebhookProviderHandler = { } await response.body?.cancel() - logger.info(`[${requestId}] Successfully deleted Instantly webhook`, { + logger.info('Successfully deleted Instantly webhook', { + requestId, externalId, webhookId: webhook.id, }) } catch (error) { - logger.warn(`[${requestId}] Error deleting Instantly webhook`, { + logger.warn('Error deleting Instantly webhook', { + requestId, message: toError(error).message, }) if (ctx.strict) throw error diff --git a/apps/sim/lib/webhooks/providers/intercom.ts b/apps/sim/lib/webhooks/providers/intercom.ts index 61214df8cd4..ce8005b760b 100644 --- a/apps/sim/lib/webhooks/providers/intercom.ts +++ b/apps/sim/lib/webhooks/providers/intercom.ts @@ -54,12 +54,12 @@ export const intercomHandler: WebhookProviderHandler = { const signature = request.headers.get('X-Hub-Signature') if (!signature) { - logger.warn(`[${requestId}] Intercom webhook missing X-Hub-Signature header`) + logger.warn('Intercom webhook missing X-Hub-Signature header', { requestId }) return new NextResponse('Unauthorized - Missing Intercom signature', { status: 401 }) } if (!validateIntercomSignature(secret, signature, rawBody)) { - logger.warn(`[${requestId}] Intercom signature verification failed`) + logger.warn('Intercom signature verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid Intercom signature', { status: 401 }) } @@ -69,9 +69,9 @@ export const intercomHandler: WebhookProviderHandler = { handleReachabilityTest(body: unknown, requestId: string) { const obj = body as Record | null if (obj?.topic === 'ping') { - logger.info( - `[${requestId}] Intercom ping event detected - returning 200 without triggering workflow` - ) + logger.info('Intercom ping event detected - returning 200 without triggering workflow', { + requestId, + }) return NextResponse.json({ status: 'ok', message: 'Webhook endpoint verified', @@ -93,12 +93,8 @@ export const intercomHandler: WebhookProviderHandler = { const { isIntercomEventMatch } = await import('@/triggers/intercom/utils') if (!isIntercomEventMatch(triggerId, topic || '')) { logger.debug( - `[${requestId}] Intercom event mismatch for trigger ${triggerId}. Topic: ${topic}. Skipping execution.`, - { - webhookId: webhook.id, - triggerId, - receivedTopic: topic, - } + `Intercom event mismatch for trigger ${triggerId}. Topic: ${topic}. Skipping execution.`, + { requestId, webhookId: webhook.id, triggerId, receivedTopic: topic } ) return false } diff --git a/apps/sim/lib/webhooks/providers/jira.ts b/apps/sim/lib/webhooks/providers/jira.ts index fb9675f0d8e..2b115b59aa8 100644 --- a/apps/sim/lib/webhooks/providers/jira.ts +++ b/apps/sim/lib/webhooks/providers/jira.ts @@ -143,8 +143,9 @@ export const jiraHandler: WebhookProviderHandler = { const { isJiraEventMatch } = await import('@/triggers/jira/utils') if (!isJiraEventMatch(triggerId, webhookEvent || '', issueEventTypeName)) { logger.debug( - `[${requestId}] Jira event mismatch for trigger ${triggerId}. Event: ${webhookEvent}. Skipping execution.`, + `Jira event mismatch for trigger ${triggerId}. Event: ${webhookEvent}. Skipping execution.`, { + requestId, webhookId: webhook.id, workflowId: workflow.id, triggerId, @@ -161,8 +162,8 @@ export const jiraHandler: WebhookProviderHandler = { !matchesFieldFilters(obj, fieldFilters) ) { logger.debug( - `[${requestId}] Jira issue_updated field filter did not match for trigger ${triggerId}. Skipping execution.`, - { webhookId: webhook.id, workflowId: workflow.id, fieldFilters } + `Jira issue_updated field filter did not match for trigger ${triggerId}. Skipping execution.`, + { requestId, webhookId: webhook.id, workflowId: workflow.id, fieldFilters } ) return false } diff --git a/apps/sim/lib/webhooks/providers/jotform.ts b/apps/sim/lib/webhooks/providers/jotform.ts index 97ba5d5e1a9..4cb0011cbf3 100644 --- a/apps/sim/lib/webhooks/providers/jotform.ts +++ b/apps/sim/lib/webhooks/providers/jotform.ts @@ -167,7 +167,7 @@ export const jotformHandler: WebhookProviderHandler = { async createSubscription(ctx: SubscriptionContext): Promise { const credentials = readCredentials(ctx.webhook) if ('error' in credentials) { - logger.warn(`[${ctx.requestId}] ${credentials.error}`, { webhookId: ctx.webhook.id }) + logger.warn(credentials.error, { requestId: ctx.requestId, webhookId: ctx.webhook.id }) throw new WebhookDeploymentConfigurationError(credentials.error) } @@ -178,7 +178,8 @@ export const jotformHandler: WebhookProviderHandler = { already holds is not a no-op — it would leave the form delivering every submission twice. Redeploys re-run this, so the existing list decides whether to post. */ if (await listWebhookIdForUrl(credentials, notificationUrl)) { - logger.info(`[${ctx.requestId}] Jotform webhook was already registered`, { + logger.info('Jotform webhook was already registered', { + requestId: ctx.requestId, webhookId: ctx.webhook.id, formId: credentials.formId, }) @@ -206,14 +207,16 @@ export const jotformHandler: WebhookProviderHandler = { ) } - logger.info(`[${ctx.requestId}] Registered Jotform webhook`, { + logger.info('Registered Jotform webhook', { + requestId: ctx.requestId, webhookId: ctx.webhook.id, formId: credentials.formId, }) return {} } catch (error) { - logger.error(`[${ctx.requestId}] Failed to register Jotform webhook`, { + logger.error('Failed to register Jotform webhook', { + requestId: ctx.requestId, webhookId: ctx.webhook.id, error: getErrorMessage(error), }) @@ -224,9 +227,9 @@ export const jotformHandler: WebhookProviderHandler = { async deleteSubscription(ctx: DeleteSubscriptionContext): Promise { const credentials = readCredentials(ctx.webhook) if ('error' in credentials) { - logger.warn( - `[${ctx.requestId}] Missing Jotform credentials, skipping webhook cleanup for ${ctx.webhook.id}` - ) + logger.warn(`Missing Jotform credentials, skipping webhook cleanup for ${ctx.webhook.id}`, { + requestId: ctx.requestId, + }) if (ctx.strict) throw new Error(credentials.error) return } @@ -242,8 +245,8 @@ export const jotformHandler: WebhookProviderHandler = { ) ) { logger.info( - `[${ctx.requestId}] Skipping Jotform webhook deletion because an active deployment is served by the same callback`, - { webhookId: ctx.webhook.id } + 'Skipping Jotform webhook deletion because an active deployment is served by the same callback', + { requestId: ctx.requestId, webhookId: ctx.webhook.id } ) return } @@ -251,9 +254,9 @@ export const jotformHandler: WebhookProviderHandler = { const webhookId = await listWebhookIdForUrl(credentials, notificationUrl) if (!webhookId) { - logger.info( - `[${ctx.requestId}] Jotform webhook was already absent from form ${credentials.formId}` - ) + logger.info(`Jotform webhook was already absent from form ${credentials.formId}`, { + requestId: ctx.requestId, + }) return } @@ -266,9 +269,12 @@ export const jotformHandler: WebhookProviderHandler = { ) await parseJotformResponse(response, 'Jotform Delete Webhook') - logger.info(`[${ctx.requestId}] Deleted Jotform webhook from form ${credentials.formId}`) + logger.info(`Deleted Jotform webhook from form ${credentials.formId}`, { + requestId: ctx.requestId, + }) } catch (error) { - logger.warn(`[${ctx.requestId}] Error deleting Jotform webhook (non-fatal)`, { + logger.warn('Error deleting Jotform webhook (non-fatal)', { + requestId: ctx.requestId, error: getErrorMessage(error), }) if (ctx.strict) throw error diff --git a/apps/sim/lib/webhooks/providers/jsm.ts b/apps/sim/lib/webhooks/providers/jsm.ts index c3cbc651525..7db920543c3 100644 --- a/apps/sim/lib/webhooks/providers/jsm.ts +++ b/apps/sim/lib/webhooks/providers/jsm.ts @@ -68,8 +68,9 @@ export const jsmHandler: WebhookProviderHandler = { const { isJsmEventMatch } = await import('@/triggers/jsm/utils') if (!isJsmEventMatch(triggerId, webhookEvent || '', issueEventTypeName, changelog)) { logger.debug( - `[${requestId}] JSM event mismatch for trigger ${triggerId}. Event: ${webhookEvent}. Skipping execution.`, + `JSM event mismatch for trigger ${triggerId}. Event: ${webhookEvent}. Skipping execution.`, { + requestId, webhookId: webhook.id, workflowId: workflow.id, triggerId, diff --git a/apps/sim/lib/webhooks/providers/lemlist.ts b/apps/sim/lib/webhooks/providers/lemlist.ts index 46fdfab2d54..7c2befa3c85 100644 --- a/apps/sim/lib/webhooks/providers/lemlist.ts +++ b/apps/sim/lib/webhooks/providers/lemlist.ts @@ -21,7 +21,8 @@ export const lemlistHandler: WebhookProviderHandler = { const campaignId = providerConfig.campaignId as string | undefined if (!apiKey) { - logger.warn(`[${requestId}] Missing apiKey for Lemlist webhook creation.`, { + logger.warn('Missing apiKey for Lemlist webhook creation.', { + requestId, webhookId: webhook.id, }) throw new WebhookDeploymentConfigurationError( @@ -45,7 +46,8 @@ export const lemlistHandler: WebhookProviderHandler = { const notificationUrl = getNotificationUrl(webhook) const authString = Buffer.from(`:${apiKey}`).toString('base64') - logger.info(`[${requestId}] Creating Lemlist webhook`, { + logger.info('Creating Lemlist webhook', { + requestId, triggerId, eventType, hasCampaignId: !!campaignId, @@ -83,8 +85,8 @@ export const lemlistHandler: WebhookProviderHandler = { (responseBody.error as string) || 'Unknown Lemlist API error' logger.error( - `[${requestId}] Failed to create webhook in Lemlist for webhook ${webhook.id}. Status: ${lemlistResponse.status}`, - { message: errorMessage, response: responseBody } + `Failed to create webhook in Lemlist for webhook ${webhook.id}. Status: ${lemlistResponse.status}`, + { requestId, message: errorMessage, response: responseBody } ) let userFriendlyMessage = 'Failed to create webhook subscription in Lemlist' @@ -100,24 +102,20 @@ export const lemlistHandler: WebhookProviderHandler = { throw new Error(userFriendlyMessage) } - logger.info( - `[${requestId}] Successfully created webhook in Lemlist for webhook ${webhook.id}.`, - { - lemlistWebhookId: responseBody._id, - } - ) + logger.info(`Successfully created webhook in Lemlist for webhook ${webhook.id}.`, { + requestId, + lemlistWebhookId: responseBody._id, + }) return { providerConfigUpdates: { externalId: responseBody._id } } } catch (error: unknown) { if (error instanceof WebhookDeploymentConfigurationError) throw error const err = error as Error - logger.error( - `[${requestId}] Exception during Lemlist webhook creation for webhook ${webhook.id}.`, - { - message: err.message, - stack: err.stack, - } - ) + logger.error(`Exception during Lemlist webhook creation for webhook ${webhook.id}.`, { + requestId, + message: err.message, + stack: err.stack, + }) throw error } }, @@ -130,9 +128,9 @@ export const lemlistHandler: WebhookProviderHandler = { const externalId = config.externalId as string | undefined if (!apiKey) { - logger.warn( - `[${requestId}] Missing apiKey for Lemlist webhook deletion ${webhook.id}, skipping cleanup` - ) + logger.warn(`Missing apiKey for Lemlist webhook deletion ${webhook.id}, skipping cleanup`, { + requestId, + }) if (ctx.strict) throw new Error('Missing Lemlist apiKey for webhook deletion') return } @@ -142,7 +140,8 @@ export const lemlistHandler: WebhookProviderHandler = { const deleteById = async (id: string) => { const validation = validateAlphanumericId(id, 'Lemlist hook ID', 50) if (!validation.isValid) { - logger.warn(`[${requestId}] Invalid Lemlist hook ID format, skipping deletion`, { + logger.warn('Invalid Lemlist hook ID format, skipping deletion', { + requestId, id: id.substring(0, 30), }) if (ctx.strict) throw new Error('Invalid Lemlist hook ID for deletion') @@ -159,15 +158,15 @@ export const lemlistHandler: WebhookProviderHandler = { if (!lemlistResponse.ok && lemlistResponse.status !== 404) { const responseBody = await lemlistResponse.json().catch(() => ({})) - logger.warn( - `[${requestId}] Failed to delete Lemlist webhook (non-fatal): ${lemlistResponse.status}`, - { response: responseBody } - ) + logger.warn(`Failed to delete Lemlist webhook (non-fatal): ${lemlistResponse.status}`, { + requestId, + response: responseBody, + }) if (ctx.strict) { throw new Error(`Failed to delete Lemlist webhook: ${lemlistResponse.status}`) } } else { - logger.info(`[${requestId}] Successfully deleted Lemlist webhook ${id}`) + logger.info(`Successfully deleted Lemlist webhook ${id}`, { requestId }) } } @@ -178,8 +177,8 @@ export const lemlistHandler: WebhookProviderHandler = { if (ctx.strict) { logger.warn( - `[${requestId}] Missing Lemlist externalId during strict cleanup; skipping unsafe URL-based remote deletion`, - { webhookId: webhook.id } + 'Missing Lemlist externalId during strict cleanup; skipping unsafe URL-based remote deletion', + { requestId, webhookId: webhook.id } ) throw new Error('Missing Lemlist externalId for strict cleanup') } @@ -193,7 +192,8 @@ export const lemlistHandler: WebhookProviderHandler = { }) if (!listResponse.ok) { - logger.warn(`[${requestId}] Failed to list Lemlist webhooks for cleanup ${webhook.id}`, { + logger.warn(`Failed to list Lemlist webhooks for cleanup ${webhook.id}`, { + requestId, status: listResponse.status, }) if (ctx.strict) throw new Error(`Failed to list Lemlist webhooks: ${listResponse.status}`) @@ -215,7 +215,8 @@ export const lemlistHandler: WebhookProviderHandler = { }) if (matches.length === 0) { - logger.info(`[${requestId}] Lemlist webhook not found for cleanup ${webhook.id}`, { + logger.info(`Lemlist webhook not found for cleanup ${webhook.id}`, { + requestId, notificationUrl, }) return @@ -228,7 +229,7 @@ export const lemlistHandler: WebhookProviderHandler = { } } } catch (error) { - logger.warn(`[${requestId}] Error deleting Lemlist webhook (non-fatal)`, error) + logger.warn('Error deleting Lemlist webhook (non-fatal)', error, { requestId }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/linear.ts b/apps/sim/lib/webhooks/providers/linear.ts index 5b5d09a341b..c47e649a2ee 100644 --- a/apps/sim/lib/webhooks/providers/linear.ts +++ b/apps/sim/lib/webhooks/providers/linear.ts @@ -82,7 +82,7 @@ export const linearHandler: WebhookProviderHandler = { const parsed = JSON.parse(rawBody) as Record const ts = parsed.webhookTimestamp if (typeof ts !== 'number' || !Number.isFinite(ts)) { - logger.warn(`[${requestId}] Linear webhookTimestamp missing or invalid`) + logger.warn('Linear webhookTimestamp missing or invalid', { requestId }) return new NextResponse('Unauthorized - Invalid webhook timestamp', { status: 401, }) @@ -90,17 +90,17 @@ export const linearHandler: WebhookProviderHandler = { if (Math.abs(Date.now() - ts) > LINEAR_WEBHOOK_TIMESTAMP_SKEW_MS) { logger.warn( - `[${requestId}] Linear webhookTimestamp outside allowed skew (${LINEAR_WEBHOOK_TIMESTAMP_SKEW_MS}ms)` + `Linear webhookTimestamp outside allowed skew (${LINEAR_WEBHOOK_TIMESTAMP_SKEW_MS}ms)`, + { requestId } ) return new NextResponse('Unauthorized - Webhook timestamp skew too large', { status: 401, }) } } catch (error) { - logger.warn( - `[${requestId}] Linear webhook body parse failed after signature verification`, - error - ) + logger.warn('Linear webhook body parse failed after signature verification', error, { + requestId, + }) return new NextResponse('Unauthorized - Invalid webhook body', { status: 401 }) } @@ -145,7 +145,8 @@ export const linearHandler: WebhookProviderHandler = { const type = typeof obj.type === 'string' ? obj.type : undefined if (!isLinearEventMatch(triggerId, type || '', action)) { logger.debug( - `[${requestId}] Linear event mismatch for trigger ${triggerId}. Type: ${type}, Action: ${action}. Skipping.` + `Linear event mismatch for trigger ${triggerId}. Type: ${type}, Action: ${action}. Skipping.`, + { requestId } ) return false } @@ -183,7 +184,9 @@ export const linearHandler: WebhookProviderHandler = { const apiKey = config.apiKey as string | undefined if (!apiKey) { - logger.warn(`[${ctx.requestId}] Missing API key for Linear webhook ${ctx.webhook.id}`) + logger.warn(`Missing API key for Linear webhook ${ctx.webhook.id}`, { + requestId: ctx.requestId, + }) throw new WebhookDeploymentConfigurationError( 'Linear API key is required. Please provide a valid API key in the trigger configuration.' ) @@ -192,7 +195,7 @@ export const linearHandler: WebhookProviderHandler = { const { LINEAR_RESOURCE_TYPE_MAP } = await import('@/triggers/linear/utils') const resourceTypes = LINEAR_RESOURCE_TYPE_MAP[triggerId] if (!resourceTypes) { - logger.warn(`[${ctx.requestId}] Unknown Linear trigger ID: ${triggerId}`) + logger.warn(`Unknown Linear trigger ID: ${triggerId}`, { requestId: ctx.requestId }) throw new Error(`Unknown Linear trigger type: ${triggerId}`) } @@ -242,7 +245,8 @@ export const linearHandler: WebhookProviderHandler = { if (!result?.success) { const errors = data?.errors?.map((e: { message: string }) => e.message).join(', ') - logger.error(`[${ctx.requestId}] Failed to create Linear webhook`, { + logger.error('Failed to create Linear webhook', { + requestId: ctx.requestId, errors, webhookId: ctx.webhook.id, }) @@ -256,9 +260,9 @@ export const linearHandler: WebhookProviderHandler = { ) } - logger.info( - `[${ctx.requestId}] Created Linear webhook ${externalId} for webhook ${ctx.webhook.id}` - ) + logger.info(`Created Linear webhook ${externalId} for webhook ${ctx.webhook.id}`, { + requestId: ctx.requestId, + }) return { providerConfigUpdates: { @@ -270,7 +274,8 @@ export const linearHandler: WebhookProviderHandler = { if (error instanceof Error && error.message !== 'fetch failed') { throw error } - logger.error(`[${ctx.requestId}] Error creating Linear webhook`, { + logger.error('Error creating Linear webhook', { + requestId: ctx.requestId, error: toError(error).message, }) throw new Error('Failed to create Linear webhook. Please verify your API key and try again.') @@ -309,7 +314,8 @@ export const linearHandler: WebhookProviderHandler = { if (!response.ok) { logger.warn( - `[${ctx.requestId}] Linear API returned HTTP ${response.status} during webhook deletion for ${externalId}` + `Linear API returned HTTP ${response.status} during webhook deletion for ${externalId}`, + { requestId: ctx.requestId } ) if (ctx.strict) throw new Error(`Linear webhook deletion failed: ${response.status}`) return @@ -317,25 +323,26 @@ export const linearHandler: WebhookProviderHandler = { const data = await response.json() if (data?.data?.webhookDelete?.success) { - logger.info( - `[${ctx.requestId}] Deleted Linear webhook ${externalId} for webhook ${ctx.webhook.id}` - ) + logger.info(`Deleted Linear webhook ${externalId} for webhook ${ctx.webhook.id}`, { + requestId: ctx.requestId, + }) } else { const errorMessages = getGraphQLErrorMessages(data) if (errorMessages.some(isAlreadyAbsentWebhookMessage)) { - logger.info( - `[${ctx.requestId}] Linear webhook ${externalId} was already absent during deletion` - ) + logger.info(`Linear webhook ${externalId} was already absent during deletion`, { + requestId: ctx.requestId, + }) return } - logger.warn( - `[${ctx.requestId}] Linear webhook deletion returned unsuccessful for ${externalId}` - ) + logger.warn(`Linear webhook deletion returned unsuccessful for ${externalId}`, { + requestId: ctx.requestId, + }) if (ctx.strict) throw new Error('Linear webhook deletion returned unsuccessful') } } catch (error) { - logger.warn(`[${ctx.requestId}] Error deleting Linear webhook ${externalId} (non-fatal)`, { + logger.warn(`Error deleting Linear webhook ${externalId} (non-fatal)`, { + requestId: ctx.requestId, error: toError(error).message, }) if (ctx.strict) throw error diff --git a/apps/sim/lib/webhooks/providers/linq.ts b/apps/sim/lib/webhooks/providers/linq.ts index 5337dee92d3..6f05fd820d2 100644 --- a/apps/sim/lib/webhooks/providers/linq.ts +++ b/apps/sim/lib/webhooks/providers/linq.ts @@ -36,7 +36,7 @@ export const linqHandler: WebhookProviderHandler = { }: AuthContext): Promise { const signingSecret = providerConfig.signingSecret as string | undefined if (!signingSecret?.trim()) { - logger.warn(`[${requestId}] Linq webhook missing signing secret in provider configuration`) + logger.warn('Linq webhook missing signing secret in provider configuration', { requestId }) return new NextResponse('Unauthorized - Linq signing secret is required', { status: 401 }) } @@ -60,14 +60,15 @@ export const linqHandler: WebhookProviderHandler = { const expectedType = LINQ_TRIGGER_TO_EVENT_TYPE[triggerId] if (!expectedType) { - logger.debug(`[${requestId}] Unknown Linq triggerId ${triggerId}, skipping.`) + logger.debug(`Unknown Linq triggerId ${triggerId}, skipping.`, { requestId }) return false } const actualType = (body as Record)?.event_type as string | undefined if (actualType !== expectedType) { logger.debug( - `[${requestId}] Linq event type mismatch: expected ${expectedType}, got ${actualType}. Skipping.` + `Linq event type mismatch: expected ${expectedType}, got ${actualType}. Skipping.`, + { requestId } ) return false } @@ -100,9 +101,7 @@ export const linqHandler: WebhookProviderHandler = { const triggerId = providerConfig.triggerId as string | undefined if (!apiKey) { - logger.warn(`[${requestId}] Missing apiKey for Linq webhook creation.`, { - webhookId: webhook.id, - }) + logger.warn('Missing apiKey for Linq webhook creation.', { requestId, webhookId: webhook.id }) throw new WebhookDeploymentConfigurationError( 'Linq API Key is required. Please provide your Linq API Key in the trigger configuration.' ) @@ -128,7 +127,8 @@ export const linqHandler: WebhookProviderHandler = { requestBody.phone_numbers = phoneNumbers } - logger.info(`[${requestId}] Creating Linq webhook subscription`, { + logger.info('Creating Linq webhook subscription', { + requestId, triggerId, events, webhookId: webhook.id, @@ -148,8 +148,8 @@ export const linqHandler: WebhookProviderHandler = { (responseBody.message as string) || 'Unknown Linq API error' logger.error( - `[${requestId}] Failed to create Linq webhook subscription for webhook ${webhook.id}. Status: ${response.status}`, - { message: errorMessage } + `Failed to create Linq webhook subscription for webhook ${webhook.id}. Status: ${response.status}`, + { requestId, message: errorMessage } ) if (response.status === 401 || response.status === 403) { @@ -170,7 +170,7 @@ export const linqHandler: WebhookProviderHandler = { ) } - logger.info(`[${requestId}] Successfully created Linq webhook subscription ${externalId}.`) + logger.info(`Successfully created Linq webhook subscription ${externalId}.`, { requestId }) return { providerConfigUpdates: { @@ -189,7 +189,8 @@ export const linqHandler: WebhookProviderHandler = { if (!apiKey || !externalId) { logger.warn( - `[${requestId}] Missing apiKey or externalId for Linq webhook deletion ${webhook.id}, skipping cleanup` + `Missing apiKey or externalId for Linq webhook deletion ${webhook.id}, skipping cleanup`, + { requestId } ) if (ctx.strict) throw new Error('Missing Linq webhook deletion credentials') return @@ -201,17 +202,17 @@ export const linqHandler: WebhookProviderHandler = { }) if (!response.ok && response.status !== 404) { - logger.warn( - `[${requestId}] Failed to delete Linq webhook subscription (non-fatal): ${response.status}` - ) + logger.warn(`Failed to delete Linq webhook subscription (non-fatal): ${response.status}`, { + requestId, + }) if (ctx.strict) { throw new Error(`Failed to delete Linq webhook subscription: ${response.status}`) } } else { - logger.info(`[${requestId}] Successfully deleted Linq webhook subscription ${externalId}`) + logger.info(`Successfully deleted Linq webhook subscription ${externalId}`, { requestId }) } } catch (error) { - logger.warn(`[${requestId}] Error deleting Linq webhook subscription (non-fatal)`, error) + logger.warn('Error deleting Linq webhook subscription (non-fatal)', error, { requestId }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/loops.ts b/apps/sim/lib/webhooks/providers/loops.ts index 756610de6f2..2c2cb44a108 100644 --- a/apps/sim/lib/webhooks/providers/loops.ts +++ b/apps/sim/lib/webhooks/providers/loops.ts @@ -20,7 +20,7 @@ export const loopsHandler: WebhookProviderHandler = { }: AuthContext): Promise { const signingSecret = providerConfig.signingSecret as string | undefined if (!signingSecret?.trim()) { - logger.warn(`[${requestId}] Loops webhook missing signing secret in provider configuration`) + logger.warn('Loops webhook missing signing secret in provider configuration', { requestId }) return new NextResponse('Unauthorized - Loops signing secret is required', { status: 401 }) } @@ -50,7 +50,8 @@ export const loopsHandler: WebhookProviderHandler = { if (!isLoopsEventMatch(triggerId, body as Record)) { const actualEvent = (body as Record)?.eventName logger.debug( - `[${requestId}] Loops event mismatch for trigger ${triggerId}. Got: ${String(actualEvent)}. Skipping.` + `Loops event mismatch for trigger ${triggerId}. Got: ${String(actualEvent)}. Skipping.`, + { requestId } ) return false } diff --git a/apps/sim/lib/webhooks/providers/microsoft-teams.ts b/apps/sim/lib/webhooks/providers/microsoft-teams.ts index 1d441b991b5..71858790543 100644 --- a/apps/sim/lib/webhooks/providers/microsoft-teams.ts +++ b/apps/sim/lib/webhooks/providers/microsoft-teams.ts @@ -97,7 +97,7 @@ async function fetchWithDNSPinning( try { const urlValidation = await validateUrlWithDNS(url, 'contentUrl', 'contentFetch') if (!urlValidation.isValid) { - logger.warn(`[${requestId}] Invalid content URL: ${urlValidation.error}`, { url }) + logger.warn(`Invalid content URL: ${urlValidation.error}`, { requestId, url }) return null } const headers: Record = {} @@ -110,7 +110,8 @@ async function fetchWithDNSPinning( }) return response } catch (error) { - logger.error(`[${requestId}] Error fetching URL with DNS pinning`, { + logger.error('Error fetching URL with DNS pinning', { + requestId, error: toError(error).message, url: sanitizeUrlForLog(url), }) @@ -483,7 +484,7 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { const url = new URL(request.url) const validationToken = url.searchParams.get('validationToken') if (validationToken) { - logger.info(`[${requestId}] Microsoft Graph subscription validation for path: ${path}`) + logger.info(`Microsoft Graph subscription validation for path: ${path}`, { requestId }) return new NextResponse(validationToken, { status: 200, headers: { 'Content-Type': 'text/plain' }, @@ -496,9 +497,9 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { if (providerConfig.triggerId !== 'microsoftteams_chat_subscription') { const hmacSecret = providerConfig.hmacSecret as string | undefined if (!hmacSecret) { - logger.error( - `[${requestId}] Microsoft Teams outgoing webhook missing configured HMAC secret` - ) + logger.error('Microsoft Teams outgoing webhook missing configured HMAC secret', { + requestId, + }) return new NextResponse('Unauthorized - Missing HMAC secret configuration', { status: 401, }) @@ -507,14 +508,14 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { const authHeader = request.headers.get('authorization') if (!authHeader || !authHeader.startsWith('HMAC ')) { - logger.warn( - `[${requestId}] Microsoft Teams outgoing webhook missing HMAC authorization header` - ) + logger.warn('Microsoft Teams outgoing webhook missing HMAC authorization header', { + requestId, + }) return new NextResponse('Unauthorized - Missing HMAC signature', { status: 401 }) } if (!validateMicrosoftTeamsSignature(hmacSecret, authHeader, rawBody)) { - logger.warn(`[${requestId}] Microsoft Teams HMAC signature verification failed`) + logger.warn('Microsoft Teams HMAC signature verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid HMAC signature', { status: 401 }) } } @@ -523,7 +524,8 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { const expectedClientState = String(webhook.id ?? '') if (!expectedClientState) { logger.warn( - `[${requestId}] Microsoft Teams chat subscription webhook missing id for clientState verification` + 'Microsoft Teams chat subscription webhook missing id for clientState verification', + { requestId } ) return new NextResponse('Unauthorized - Invalid clientState', { status: 401 }) } @@ -539,18 +541,18 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { } if (notifications.length === 0) { - logger.warn( - `[${requestId}] Microsoft Teams chat subscription notification missing value array` - ) + logger.warn('Microsoft Teams chat subscription notification missing value array', { + requestId, + }) return new NextResponse('Unauthorized - Invalid notification payload', { status: 401 }) } for (const notification of notifications) { const clientState = (notification as Record)?.clientState if (typeof clientState !== 'string' || !safeCompare(clientState, expectedClientState)) { - logger.warn( - `[${requestId}] Microsoft Teams chat subscription clientState verification failed` - ) + logger.warn('Microsoft Teams chat subscription clientState verification failed', { + requestId, + }) return new NextResponse('Unauthorized - Invalid clientState', { status: 401 }) } } @@ -603,14 +605,14 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { const chatId = config.chatId as string | undefined if (!credentialId) { - logger.warn(`[${requestId}] Missing credentialId for Teams chat subscription ${webhook.id}`) + logger.warn(`Missing credentialId for Teams chat subscription ${webhook.id}`, { requestId }) throw new WebhookDeploymentConfigurationError( 'Microsoft Teams credentials are required. Please connect your Microsoft account in the trigger configuration.' ) } if (!chatId) { - logger.warn(`[${requestId}] Missing chatId for Teams chat subscription ${webhook.id}`) + logger.warn(`Missing chatId for Teams chat subscription ${webhook.id}`, { requestId }) throw new WebhookDeploymentConfigurationError( 'Chat ID is required to create a Teams subscription. Please provide a valid chat ID.' ) @@ -618,7 +620,7 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { const accessToken = await getCredentialAccessToken(credentialId, requestId) if (!accessToken) { - logger.error(`[${requestId}] Failed to get access token for Teams subscription ${webhook.id}`) + logger.error(`Failed to get access token for Teams subscription ${webhook.id}`, { requestId }) throw new Error( 'Failed to authenticate with Microsoft Teams. Please reconnect your Microsoft account and try again.' ) @@ -634,7 +636,8 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { if (checkRes.ok) { const existingPayload = await checkRes.json() logger.info( - `[${requestId}] Teams subscription ${existingSubscriptionId} already exists for webhook ${webhook.id}` + `Teams subscription ${existingSubscriptionId} already exists for webhook ${webhook.id}`, + { requestId } ) return { providerConfigUpdates: { @@ -644,7 +647,7 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { } } } catch { - logger.debug(`[${requestId}] Existing subscription check failed, will create new one`) + logger.debug('Existing subscription check failed, will create new one', { requestId }) } } @@ -678,13 +681,11 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { if (!res.ok) { const errorMessage = payload.error?.message || payload.error?.code || 'Unknown Microsoft Graph API error' - logger.error( - `[${requestId}] Failed to create Teams subscription for webhook ${webhook.id}`, - { - status: res.status, - error: payload.error, - } - ) + logger.error(`Failed to create Teams subscription for webhook ${webhook.id}`, { + requestId, + status: res.status, + error: payload.error, + }) let userFriendlyMessage = 'Failed to create Teams subscription' if (res.status === 401 || res.status === 403) { @@ -701,7 +702,8 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { } logger.info( - `[${requestId}] Successfully created Teams subscription ${payload.id} for webhook ${webhook.id}` + `Successfully created Teams subscription ${payload.id} for webhook ${webhook.id}`, + { requestId } ) return { providerConfigUpdates: { @@ -719,10 +721,9 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { throw error } - logger.error( - `[${requestId}] Error creating Teams subscription for webhook ${webhook.id}`, - error - ) + logger.error(`Error creating Teams subscription for webhook ${webhook.id}`, error, { + requestId, + }) throw new Error( getErrorMessage(error, 'Failed to create Teams subscription. Please try again.') ) @@ -745,7 +746,7 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { const credentialId = config.credentialId as string | undefined if (!externalSubscriptionId || !credentialId) { - logger.info(`[${requestId}] No external subscription to delete for webhook ${webhook.id}`) + logger.info(`No external subscription to delete for webhook ${webhook.id}`, { requestId }) if (strict) throw new Error('Missing Teams subscription cleanup configuration') return } @@ -753,7 +754,8 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { const accessToken = await getCredentialAccessToken(credentialId, requestId) if (!accessToken) { logger.warn( - `[${requestId}] Could not get access token to delete Teams subscription for webhook ${webhook.id}` + `Could not get access token to delete Teams subscription for webhook ${webhook.id}`, + { requestId } ) if (strict) throw new Error('Missing Teams access token for subscription deletion') return @@ -769,21 +771,22 @@ export const microsoftTeamsHandler: WebhookProviderHandler = { if (res.ok || res.status === 404) { logger.info( - `[${requestId}] Successfully deleted Teams subscription ${externalSubscriptionId} for webhook ${webhook.id}` + `Successfully deleted Teams subscription ${externalSubscriptionId} for webhook ${webhook.id}`, + { requestId } ) } else { // Drain the unread body so the connection is released. await res.text() logger.warn( - `[${requestId}] Failed to delete Teams subscription ${externalSubscriptionId} for webhook ${webhook.id}. Status: ${res.status}` + `Failed to delete Teams subscription ${externalSubscriptionId} for webhook ${webhook.id}. Status: ${res.status}`, + { requestId } ) if (strict) throw new Error(`Failed to delete Teams subscription: ${res.status}`) } } catch (error) { - logger.error( - `[${requestId}] Error deleting Teams subscription for webhook ${webhook.id}`, - error - ) + logger.error(`Error deleting Teams subscription for webhook ${webhook.id}`, error, { + requestId, + }) if (strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/monday.ts b/apps/sim/lib/webhooks/providers/monday.ts index 9b10e78d1cf..a70174d3253 100644 --- a/apps/sim/lib/webhooks/providers/monday.ts +++ b/apps/sim/lib/webhooks/providers/monday.ts @@ -73,14 +73,18 @@ export const mondayHandler: WebhookProviderHandler = { const boardId = config.boardId as string | undefined if (!triggerId) { - logger.warn(`[${ctx.requestId}] Missing triggerId for Monday webhook ${ctx.webhook.id}`) + logger.warn(`Missing triggerId for Monday webhook ${ctx.webhook.id}`, { + requestId: ctx.requestId, + }) throw new WebhookDeploymentConfigurationError( 'Trigger type is required for Monday.com webhook creation.' ) } if (!boardId) { - logger.warn(`[${ctx.requestId}] Missing boardId for Monday webhook ${ctx.webhook.id}`) + logger.warn(`Missing boardId for Monday webhook ${ctx.webhook.id}`, { + requestId: ctx.requestId, + }) throw new WebhookDeploymentConfigurationError( 'Board ID is required. Please provide a valid Monday.com board ID in the trigger configuration.' ) @@ -94,7 +98,7 @@ export const mondayHandler: WebhookProviderHandler = { const { MONDAY_EVENT_TYPE_MAP } = await import('@/triggers/monday/utils') const eventType = MONDAY_EVENT_TYPE_MAP[triggerId] if (!eventType) { - logger.warn(`[${ctx.requestId}] Unknown Monday trigger ID: ${triggerId}`) + logger.warn(`Unknown Monday trigger ID: ${triggerId}`, { requestId: ctx.requestId }) throw new Error(`Unknown Monday.com trigger type: ${triggerId}`) } @@ -121,7 +125,8 @@ export const mondayHandler: WebhookProviderHandler = { if (errors && errors.length > 0) { const errorMsg = errors.map((e) => e.message).join(', ') - logger.error(`[${ctx.requestId}] Failed to create Monday webhook`, { + logger.error('Failed to create Monday webhook', { + requestId: ctx.requestId, errors: errorMsg, webhookId: ctx.webhook.id, }) @@ -142,7 +147,8 @@ export const mondayHandler: WebhookProviderHandler = { const externalId = String(result.id) logger.info( - `[${ctx.requestId}] Created Monday webhook ${externalId} for webhook ${ctx.webhook.id} (event: ${eventType}, board: ${boardId})` + `Created Monday webhook ${externalId} for webhook ${ctx.webhook.id} (event: ${eventType}, board: ${boardId})`, + { requestId: ctx.requestId } ) return { @@ -154,7 +160,8 @@ export const mondayHandler: WebhookProviderHandler = { if (error instanceof Error && error.message !== 'fetch failed') { throw error } - logger.error(`[${ctx.requestId}] Error creating Monday webhook`, { + logger.error('Error creating Monday webhook', { + requestId: ctx.requestId, error: toError(error).message, }) throw new Error( @@ -178,9 +185,9 @@ export const mondayHandler: WebhookProviderHandler = { const externalIdValidation = validateMondayNumericId(externalId, 'webhookId') if (!externalIdValidation.isValid) { - logger.warn( - `[${ctx.requestId}] Invalid externalId format for Monday webhook deletion: ${externalId}` - ) + logger.warn(`Invalid externalId format for Monday webhook deletion: ${externalId}`, { + requestId: ctx.requestId, + }) if (ctx.strict) throw new Error('Invalid Monday externalId for webhook deletion') return } @@ -192,16 +199,17 @@ export const mondayHandler: WebhookProviderHandler = { accessToken = await getCredentialAccessToken(credentialId, ctx.requestId) } } catch (error) { - logger.warn( - `[${ctx.requestId}] Could not resolve credentials for Monday webhook deletion (non-fatal)`, - { error: toError(error).message } - ) + logger.warn('Could not resolve credentials for Monday webhook deletion (non-fatal)', { + requestId: ctx.requestId, + error: toError(error).message, + }) if (ctx.strict) throw error } if (!accessToken) { logger.warn( - `[${ctx.requestId}] No access token available for Monday webhook deletion ${externalId} (non-fatal)` + `No access token available for Monday webhook deletion ${externalId} (non-fatal)`, + { requestId: ctx.requestId } ) if (ctx.strict) throw new Error('Missing Monday access token for webhook deletion') return @@ -218,7 +226,8 @@ export const mondayHandler: WebhookProviderHandler = { if (!response.ok) { logger.warn( - `[${ctx.requestId}] Monday API returned HTTP ${response.status} during webhook deletion for ${externalId}` + `Monday API returned HTTP ${response.status} during webhook deletion for ${externalId}`, + { requestId: ctx.requestId } ) if (ctx.strict) throw new Error(`Monday webhook deletion failed: ${response.status}`) return @@ -232,28 +241,31 @@ export const mondayHandler: WebhookProviderHandler = { data.error_message || 'Unknown error' if (isAlreadyAbsentWebhookMessage(errorMsg)) { - logger.info( - `[${ctx.requestId}] Monday webhook ${externalId} was already absent during deletion` - ) + logger.info(`Monday webhook ${externalId} was already absent during deletion`, { + requestId: ctx.requestId, + }) return } - logger.warn( - `[${ctx.requestId}] Monday webhook deletion GraphQL error for ${externalId}: ${errorMsg}` - ) + logger.warn(`Monday webhook deletion GraphQL error for ${externalId}: ${errorMsg}`, { + requestId: ctx.requestId, + }) if (ctx.strict) throw new Error(`Monday webhook deletion failed: ${errorMsg}`) return } if (data.data?.delete_webhook?.id) { - logger.info( - `[${ctx.requestId}] Deleted Monday webhook ${externalId} for webhook ${ctx.webhook.id}` - ) + logger.info(`Deleted Monday webhook ${externalId} for webhook ${ctx.webhook.id}`, { + requestId: ctx.requestId, + }) } else { - logger.warn(`[${ctx.requestId}] Monday webhook deletion returned no data for ${externalId}`) + logger.warn(`Monday webhook deletion returned no data for ${externalId}`, { + requestId: ctx.requestId, + }) if (ctx.strict) throw new Error('Monday webhook deletion returned no data') } } catch (error) { - logger.warn(`[${ctx.requestId}] Error deleting Monday webhook ${externalId} (non-fatal)`, { + logger.warn(`Error deleting Monday webhook ${externalId} (non-fatal)`, { + requestId: ctx.requestId, error: toError(error).message, }) if (ctx.strict) throw error diff --git a/apps/sim/lib/webhooks/providers/notion.ts b/apps/sim/lib/webhooks/providers/notion.ts index 730c1077d35..8b282ae1089 100644 --- a/apps/sim/lib/webhooks/providers/notion.ts +++ b/apps/sim/lib/webhooks/providers/notion.ts @@ -58,7 +58,7 @@ export const notionHandler: WebhookProviderHandler = { const verificationToken = obj?.verification_token if (typeof verificationToken === 'string' && verificationToken.length > 0) { - logger.info(`[${requestId}] Notion verification request detected - returning 200`) + logger.info('Notion verification request detected - returning 200', { requestId }) return NextResponse.json({ status: 'ok', message: 'Webhook endpoint verified', @@ -121,8 +121,9 @@ export const notionHandler: WebhookProviderHandler = { if (!isNotionPayloadMatch(triggerId, obj)) { const eventType = obj.type as string | undefined logger.debug( - `[${requestId}] Notion event mismatch for trigger ${triggerId}. Event: ${eventType}. Skipping execution.`, + `Notion event mismatch for trigger ${triggerId}. Event: ${eventType}. Skipping execution.`, { + requestId, webhookId: webhook.id, workflowId: workflow.id, triggerId, diff --git a/apps/sim/lib/webhooks/providers/otter.ts b/apps/sim/lib/webhooks/providers/otter.ts index 5baa6d19551..98748279a1e 100644 --- a/apps/sim/lib/webhooks/providers/otter.ts +++ b/apps/sim/lib/webhooks/providers/otter.ts @@ -47,7 +47,7 @@ export const otterHandler: WebhookProviderHandler = { const { isOtterEventMatch } = await import('@/triggers/otter/utils') const { event } = readWebhookMeta(body) if (!isOtterEventMatch(triggerId, event)) { - logger.debug(`[${requestId}] Otter event mismatch for trigger ${triggerId}: ${event}`) + logger.debug(`Otter event mismatch for trigger ${triggerId}: ${event}`, { requestId }) return false } return true diff --git a/apps/sim/lib/webhooks/providers/outlook.ts b/apps/sim/lib/webhooks/providers/outlook.ts index 4ac0da014fb..58c6374faa9 100644 --- a/apps/sim/lib/webhooks/providers/outlook.ts +++ b/apps/sim/lib/webhooks/providers/outlook.ts @@ -27,21 +27,22 @@ export const outlookHandler: WebhookProviderHandler = { requestId, persistProviderConfig, }: PollingConfigContext) { - logger.info(`[${requestId}] Setting up Outlook polling for webhook ${webhookData.id}`) + logger.info(`Setting up Outlook polling for webhook ${webhookData.id}`, { requestId }) try { const providerConfig = toRecord(webhookData.providerConfig) const credentialId = providerConfig.credentialId as string | undefined if (!credentialId) { - logger.error(`[${requestId}] Missing credentialId for Outlook webhook ${webhookData.id}`) + logger.error(`Missing credentialId for Outlook webhook ${webhookData.id}`, { requestId }) return false } const resolvedOutlook = await resolveOAuthAccountId(credentialId) if (!resolvedOutlook) { logger.error( - `[${requestId}] Could not resolve credential ${credentialId} for Outlook webhook ${webhookData.id}` + `Could not resolve credential ${credentialId} for Outlook webhook ${webhookData.id}`, + { requestId } ) return false } @@ -52,9 +53,9 @@ export const outlookHandler: WebhookProviderHandler = { .where(eq(account.id, resolvedOutlook.accountId)) .limit(1) if (rows.length === 0) { - logger.error( - `[${requestId}] Credential ${credentialId} not found for Outlook webhook ${webhookData.id}` - ) + logger.error(`Credential ${credentialId} not found for Outlook webhook ${webhookData.id}`, { + requestId, + }) return false } @@ -66,9 +67,9 @@ export const outlookHandler: WebhookProviderHandler = { requestId ) if (!accessToken) { - logger.error( - `[${requestId}] Failed to refresh/access Outlook token for credential ${credentialId}` - ) + logger.error(`Failed to refresh/access Outlook token for credential ${credentialId}`, { + requestId, + }) return false } @@ -102,13 +103,14 @@ export const outlookHandler: WebhookProviderHandler = { .where(eq(webhook.id, webhookData.id as string)) } - logger.info( - `[${requestId}] Successfully configured Outlook polling for webhook ${webhookData.id}` - ) + logger.info(`Successfully configured Outlook polling for webhook ${webhookData.id}`, { + requestId, + }) return true } catch (error: unknown) { const err = error as Error - logger.error(`[${requestId}] Failed to configure Outlook polling`, { + logger.error('Failed to configure Outlook polling', { + requestId, webhookId: webhookData.id, error: err.message, stack: err.stack, diff --git a/apps/sim/lib/webhooks/providers/pagerduty.ts b/apps/sim/lib/webhooks/providers/pagerduty.ts index 9f3d318d4e9..abb9017a387 100644 --- a/apps/sim/lib/webhooks/providers/pagerduty.ts +++ b/apps/sim/lib/webhooks/providers/pagerduty.ts @@ -100,9 +100,9 @@ export const pagerdutyHandler: WebhookProviderHandler = { const { isPagerDutyEventMatch } = await import('@/triggers/pagerduty/utils') if (!isPagerDutyEventMatch(triggerId, eventType || '')) { - logger.debug( - `[${requestId}] PagerDuty event '${eventType}' does not match trigger ${triggerId}, skipping` - ) + logger.debug(`PagerDuty event '${eventType}' does not match trigger ${triggerId}, skipping`, { + requestId, + }) return false } return true @@ -168,7 +168,8 @@ export const pagerdutyHandler: WebhookProviderHandler = { if (!res.ok) { const detail = await res.text().catch(() => '') - logger.error(`[${ctx.requestId}] Failed to create PagerDuty webhook (${res.status})`, { + logger.error(`Failed to create PagerDuty webhook (${res.status})`, { + requestId: ctx.requestId, detail, }) if (res.status === 401) @@ -193,7 +194,9 @@ export const pagerdutyHandler: WebhookProviderHandler = { throw new Error('PagerDuty webhook created but no signing secret was returned on creation.') } - logger.info(`[${ctx.requestId}] Created PagerDuty webhook subscription ${externalId}`) + logger.info(`Created PagerDuty webhook subscription ${externalId}`, { + requestId: ctx.requestId, + }) return { providerConfigUpdates: { externalId, webhookSecret: secret } } }, @@ -204,9 +207,9 @@ export const pagerdutyHandler: WebhookProviderHandler = { if (!apiKey || !externalId) { if (ctx.strict) throw new Error('Missing PagerDuty API key or subscription ID for deletion.') - logger.warn( - `[${ctx.requestId}] Skipping PagerDuty webhook cleanup — missing API key or subscription ID` - ) + logger.warn('Skipping PagerDuty webhook cleanup — missing API key or subscription ID', { + requestId: ctx.requestId, + }) return } @@ -217,11 +220,13 @@ export const pagerdutyHandler: WebhookProviderHandler = { if (!res.ok && res.status !== 404) { if (ctx.strict) throw new Error(`Failed to delete PagerDuty webhook: ${res.status}`) - logger.warn( - `[${ctx.requestId}] Failed to delete PagerDuty webhook ${externalId} (non-fatal): ${res.status}` - ) + logger.warn(`Failed to delete PagerDuty webhook ${externalId} (non-fatal): ${res.status}`, { + requestId: ctx.requestId, + }) return } - logger.info(`[${ctx.requestId}] Deleted PagerDuty webhook subscription ${externalId}`) + logger.info(`Deleted PagerDuty webhook subscription ${externalId}`, { + requestId: ctx.requestId, + }) }, } diff --git a/apps/sim/lib/webhooks/providers/quickbooks.ts b/apps/sim/lib/webhooks/providers/quickbooks.ts index fe48a10ca45..0b948ab3432 100644 --- a/apps/sim/lib/webhooks/providers/quickbooks.ts +++ b/apps/sim/lib/webhooks/providers/quickbooks.ts @@ -33,7 +33,7 @@ export function verifyQuickBooksSignature( } function unauthorized(requestId: string, reason: string): NextResponse { - logger.warn(`[${requestId}] ${reason}`) + logger.warn(reason, { requestId }) return new NextResponse('Unauthorized', { status: 401 }) } diff --git a/apps/sim/lib/webhooks/providers/registry.ts b/apps/sim/lib/webhooks/providers/registry.ts index b3738f5464c..03523e15f40 100644 --- a/apps/sim/lib/webhooks/providers/registry.ts +++ b/apps/sim/lib/webhooks/providers/registry.ts @@ -156,7 +156,7 @@ const defaultHandler: WebhookProviderHandler = { const token = providerConfig.token if (typeof token === 'string') { if (!verifyTokenAuth(request, token)) { - logger.warn(`[${requestId}] Unauthorized webhook access attempt - invalid token`) + logger.warn('Unauthorized webhook access attempt - invalid token', { requestId }) return new NextResponse('Unauthorized', { status: 401 }) } } diff --git a/apps/sim/lib/webhooks/providers/resend.ts b/apps/sim/lib/webhooks/providers/resend.ts index e532540123c..935911c6604 100644 --- a/apps/sim/lib/webhooks/providers/resend.ts +++ b/apps/sim/lib/webhooks/providers/resend.ts @@ -29,7 +29,7 @@ export const resendHandler: WebhookProviderHandler = { }: AuthContext): Promise { const signingSecret = providerConfig.signingSecret as string | undefined if (!signingSecret?.trim()) { - logger.warn(`[${requestId}] Resend webhook missing signing secret in provider configuration`) + logger.warn('Resend webhook missing signing secret in provider configuration', { requestId }) return new NextResponse('Unauthorized - Resend signing secret is required', { status: 401 }) } @@ -54,7 +54,7 @@ export const resendHandler: WebhookProviderHandler = { const expectedType = RESEND_TRIGGER_TO_EVENT_TYPE[triggerId] if (!expectedType) { - logger.debug(`[${requestId}] Unknown Resend triggerId ${triggerId}, skipping.`) + logger.debug(`Unknown Resend triggerId ${triggerId}, skipping.`, { requestId }) return false } @@ -62,7 +62,8 @@ export const resendHandler: WebhookProviderHandler = { if (actualType !== expectedType) { logger.debug( - `[${requestId}] Resend event type mismatch: expected ${expectedType}, got ${actualType}. Skipping.` + `Resend event type mismatch: expected ${expectedType}, got ${actualType}. Skipping.`, + { requestId } ) return false } @@ -115,7 +116,8 @@ export const resendHandler: WebhookProviderHandler = { const triggerId = providerConfig.triggerId as string | undefined if (!apiKey) { - logger.warn(`[${requestId}] Missing apiKey for Resend webhook creation.`, { + logger.warn('Missing apiKey for Resend webhook creation.', { + requestId, webhookId: webhook.id, }) throw new WebhookDeploymentConfigurationError( @@ -136,7 +138,8 @@ export const resendHandler: WebhookProviderHandler = { const notificationUrl = getNotificationUrl(webhook) - logger.info(`[${requestId}] Creating Resend webhook`, { + logger.info('Creating Resend webhook', { + requestId, triggerId, events, webhookId: webhook.id, @@ -162,8 +165,8 @@ export const resendHandler: WebhookProviderHandler = { (responseBody.name as string) || 'Unknown Resend API error' logger.error( - `[${requestId}] Failed to create webhook in Resend for webhook ${webhook.id}. Status: ${resendResponse.status}`, - { message: errorMessage, response: responseBody } + `Failed to create webhook in Resend for webhook ${webhook.id}. Status: ${resendResponse.status}`, + { requestId, message: errorMessage, response: responseBody } ) let userFriendlyMessage = 'Failed to create webhook subscription in Resend' @@ -190,12 +193,10 @@ export const resendHandler: WebhookProviderHandler = { ) } - logger.info( - `[${requestId}] Successfully created webhook in Resend for webhook ${webhook.id}.`, - { - resendWebhookId: externalId, - } - ) + logger.info(`Successfully created webhook in Resend for webhook ${webhook.id}.`, { + requestId, + resendWebhookId: externalId, + }) return { providerConfigUpdates: { @@ -206,13 +207,11 @@ export const resendHandler: WebhookProviderHandler = { } catch (error: unknown) { if (error instanceof WebhookDeploymentConfigurationError) throw error const err = error as Error - logger.error( - `[${requestId}] Exception during Resend webhook creation for webhook ${webhook.id}.`, - { - message: err.message, - stack: err.stack, - } - ) + logger.error(`Exception during Resend webhook creation for webhook ${webhook.id}.`, { + requestId, + message: err.message, + stack: err.stack, + }) throw error } }, @@ -226,7 +225,8 @@ export const resendHandler: WebhookProviderHandler = { if (!apiKey || !externalId) { logger.warn( - `[${requestId}] Missing apiKey or externalId for Resend webhook deletion ${webhook.id}, skipping cleanup` + `Missing apiKey or externalId for Resend webhook deletion ${webhook.id}, skipping cleanup`, + { requestId } ) if (ctx.strict) throw new Error('Missing Resend webhook deletion credentials') return @@ -241,16 +241,16 @@ export const resendHandler: WebhookProviderHandler = { if (!resendResponse.ok && resendResponse.status !== 404) { const responseBody = await resendResponse.json().catch(() => ({})) - logger.warn( - `[${requestId}] Failed to delete Resend webhook (non-fatal): ${resendResponse.status}`, - { response: responseBody } - ) + logger.warn(`Failed to delete Resend webhook (non-fatal): ${resendResponse.status}`, { + requestId, + response: responseBody, + }) if (ctx.strict) throw new Error(`Failed to delete Resend webhook: ${resendResponse.status}`) } else { - logger.info(`[${requestId}] Successfully deleted Resend webhook ${externalId}`) + logger.info(`Successfully deleted Resend webhook ${externalId}`, { requestId }) } } catch (error) { - logger.warn(`[${requestId}] Error deleting Resend webhook (non-fatal)`, error) + logger.warn('Error deleting Resend webhook (non-fatal)', error, { requestId }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/revenuecat.ts b/apps/sim/lib/webhooks/providers/revenuecat.ts index 14ff48b7010..ffbca9b15ec 100644 --- a/apps/sim/lib/webhooks/providers/revenuecat.ts +++ b/apps/sim/lib/webhooks/providers/revenuecat.ts @@ -36,9 +36,9 @@ export const revenueCatHandler: WebhookProviderHandler = { const secret = providerConfig.authHeaderSecret as string | undefined if (!secret) { - logger.warn( - `[${requestId}] RevenueCat webhook missing Authorization secret in provider configuration` - ) + logger.warn('RevenueCat webhook missing Authorization secret in provider configuration', { + requestId, + }) return new NextResponse('Unauthorized - RevenueCat Authorization secret is required', { status: 401, }) @@ -46,14 +46,14 @@ export const revenueCatHandler: WebhookProviderHandler = { const authHeader = request.headers.get('authorization') if (!authHeader) { - logger.warn(`[${requestId}] RevenueCat webhook missing Authorization header`) + logger.warn('RevenueCat webhook missing Authorization header', { requestId }) return new NextResponse('Unauthorized - Missing RevenueCat Authorization header', { status: 401, }) } if (!safeCompare(authHeader, secret)) { - logger.warn(`[${requestId}] RevenueCat Authorization header verification failed`) + logger.warn('RevenueCat Authorization header verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid RevenueCat Authorization header', { status: 401, }) @@ -124,8 +124,8 @@ export const revenueCatHandler: WebhookProviderHandler = { if (!response.ok) { const errorBody = (await response.json().catch(() => ({}))) as Record logger.error( - `[${ctx.requestId}] Failed to create RevenueCat webhook for webhook ${ctx.webhook.id}. Status: ${response.status}`, - { response: errorBody } + `Failed to create RevenueCat webhook for webhook ${ctx.webhook.id}. Status: ${response.status}`, + { requestId: ctx.requestId, response: errorBody } ) let message = 'Failed to create webhook integration in RevenueCat' @@ -147,14 +147,15 @@ export const revenueCatHandler: WebhookProviderHandler = { if (!integrationId) { logger.error( - `[${ctx.requestId}] RevenueCat webhook created but no integration id was returned for webhook ${ctx.webhook.id}`, - { response: responseBody } + `RevenueCat webhook created but no integration id was returned for webhook ${ctx.webhook.id}`, + { requestId: ctx.requestId, response: responseBody } ) throw new Error('RevenueCat webhook creation succeeded but no integration id was returned') } logger.info( - `[${ctx.requestId}] Created RevenueCat webhook integration ${integrationId} for webhook ${ctx.webhook.id}` + `Created RevenueCat webhook integration ${integrationId} for webhook ${ctx.webhook.id}`, + { requestId: ctx.requestId } ) return { providerConfigUpdates: { externalId: integrationId, authHeaderSecret } } @@ -179,7 +180,8 @@ export const revenueCatHandler: WebhookProviderHandler = { if (!apiKey || !projectId || !externalId) { logger.warn( - `[${ctx.requestId}] Missing apiKey/projectId/externalId for RevenueCat webhook deletion ${ctx.webhook.id}, skipping cleanup` + `Missing apiKey/projectId/externalId for RevenueCat webhook deletion ${ctx.webhook.id}, skipping cleanup`, + { requestId: ctx.requestId } ) if (ctx.strict) throw new Error('Missing RevenueCat credentials for webhook deletion') return @@ -195,18 +197,22 @@ export const revenueCatHandler: WebhookProviderHandler = { if (!response.ok && response.status !== 404) { const errorBody = await response.json().catch(() => ({})) - logger.warn( - `[${ctx.requestId}] Failed to delete RevenueCat webhook (non-fatal): ${response.status}`, - { response: errorBody } - ) + logger.warn(`Failed to delete RevenueCat webhook (non-fatal): ${response.status}`, { + requestId: ctx.requestId, + response: errorBody, + }) if (ctx.strict) { throw new Error(`Failed to delete RevenueCat webhook: ${response.status}`) } } else { - logger.info(`[${ctx.requestId}] Deleted RevenueCat webhook integration ${externalId}`) + logger.info(`Deleted RevenueCat webhook integration ${externalId}`, { + requestId: ctx.requestId, + }) } } catch (error) { - logger.warn(`[${ctx.requestId}] Error deleting RevenueCat webhook (non-fatal)`, error) + logger.warn('Error deleting RevenueCat webhook (non-fatal)', error, { + requestId: ctx.requestId, + }) if (ctx.strict) throw error } }, @@ -221,7 +227,8 @@ export const revenueCatHandler: WebhookProviderHandler = { if (!isRevenueCatEventMatch(triggerId, (body as Record) || {})) { const event = (body as Record)?.event as Record | undefined logger.debug( - `[${requestId}] RevenueCat event type '${event?.type as string | undefined}' does not match trigger ${triggerId}, skipping` + `RevenueCat event type '${event?.type as string | undefined}' does not match trigger ${triggerId}, skipping`, + { requestId } ) return false } diff --git a/apps/sim/lib/webhooks/providers/rootly.ts b/apps/sim/lib/webhooks/providers/rootly.ts index 79bbc026f8a..cd1ea420d80 100644 --- a/apps/sim/lib/webhooks/providers/rootly.ts +++ b/apps/sim/lib/webhooks/providers/rootly.ts @@ -68,34 +68,34 @@ export const rootlyHandler: WebhookProviderHandler = { }: AuthContext): Promise { const secret = providerConfig.webhookSecret as string | undefined if (!secret) { - logger.warn(`[${requestId}] Rootly webhook missing signing secret in provider configuration`) + logger.warn('Rootly webhook missing signing secret in provider configuration', { requestId }) return new NextResponse('Unauthorized - Rootly signing secret is required', { status: 401 }) } const header = request.headers.get('X-Rootly-Signature') if (!header) { - logger.warn(`[${requestId}] Rootly webhook missing signature header`) + logger.warn('Rootly webhook missing signature header', { requestId }) return new NextResponse('Unauthorized - Missing Rootly signature', { status: 401 }) } const parsed = parseRootlySignatureHeader(header) if (!parsed) { - logger.warn(`[${requestId}] Rootly signature header malformed`) + logger.warn('Rootly signature header malformed', { requestId }) return new NextResponse('Unauthorized - Malformed Rootly signature', { status: 401 }) } if (!validateRootlySignature(secret, parsed.timestamp, parsed.signature, rawBody)) { - logger.warn(`[${requestId}] Rootly signature verification failed`) + logger.warn('Rootly signature verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid Rootly signature', { status: 401 }) } const tsSeconds = Number(parsed.timestamp) if (!Number.isFinite(tsSeconds)) { - logger.warn(`[${requestId}] Rootly signature timestamp invalid`) + logger.warn('Rootly signature timestamp invalid', { requestId }) return new NextResponse('Unauthorized - Invalid Rootly timestamp', { status: 401 }) } if (Math.abs(Date.now() - tsSeconds * 1000) > ROOTLY_WEBHOOK_TIMESTAMP_SKEW_MS) { - logger.warn(`[${requestId}] Rootly signature timestamp outside allowed skew`) + logger.warn('Rootly signature timestamp outside allowed skew', { requestId }) return new NextResponse('Unauthorized - Rootly timestamp skew too large', { status: 401 }) } @@ -110,7 +110,8 @@ export const rootlyHandler: WebhookProviderHandler = { const eventType = typeof event?.type === 'string' ? event.type : '' if (!isRootlyEventMatch(triggerId, eventType)) { logger.debug( - `[${requestId}] Rootly event mismatch for trigger ${triggerId}. Type: ${eventType}. Skipping.` + `Rootly event mismatch for trigger ${triggerId}. Type: ${eventType}. Skipping.`, + { requestId } ) return false } @@ -156,7 +157,8 @@ export const rootlyHandler: WebhookProviderHandler = { const signingSecret = generateId() - logger.info(`[${ctx.requestId}] Creating Rootly webhook endpoint`, { + logger.info('Creating Rootly webhook endpoint', { + requestId: ctx.requestId, triggerId, eventTypes, webhookId: ctx.webhook.id, @@ -205,8 +207,8 @@ export const rootlyHandler: WebhookProviderHandler = { } logger.error( - `[${ctx.requestId}] Failed to create Rootly webhook endpoint for webhook ${ctx.webhook.id}. Status: ${response.status}`, - { response: responseBody } + `Failed to create Rootly webhook endpoint for webhook ${ctx.webhook.id}. Status: ${response.status}`, + { requestId: ctx.requestId, response: responseBody } ) throw new Error(userFriendlyMessage) } @@ -218,19 +220,18 @@ export const rootlyHandler: WebhookProviderHandler = { } logger.info( - `[${ctx.requestId}] Successfully created Rootly webhook endpoint ${externalId} for webhook ${ctx.webhook.id}` + `Successfully created Rootly webhook endpoint ${externalId} for webhook ${ctx.webhook.id}`, + { requestId: ctx.requestId } ) return { providerConfigUpdates: { externalId, webhookSecret: signingSecret } } } catch (error: unknown) { if (error instanceof WebhookDeploymentConfigurationError) throw error const err = error as Error - logger.error( - `[${ctx.requestId}] Exception during Rootly webhook creation for webhook ${ctx.webhook.id}.`, - { - message: err.message, - stack: err.stack, - } - ) + logger.error(`Exception during Rootly webhook creation for webhook ${ctx.webhook.id}.`, { + requestId: ctx.requestId, + message: err.message, + stack: err.stack, + }) throw error } }, @@ -243,7 +244,8 @@ export const rootlyHandler: WebhookProviderHandler = { if (!apiKey) { logger.warn( - `[${ctx.requestId}] Missing apiKey for Rootly webhook deletion ${ctx.webhook.id}, skipping cleanup` + `Missing apiKey for Rootly webhook deletion ${ctx.webhook.id}, skipping cleanup`, + { requestId: ctx.requestId } ) if (ctx.strict) throw new Error('Missing Rootly apiKey for webhook deletion') return @@ -251,7 +253,8 @@ export const rootlyHandler: WebhookProviderHandler = { if (!externalId) { logger.warn( - `[${ctx.requestId}] Missing externalId for Rootly webhook deletion ${ctx.webhook.id}, skipping cleanup` + `Missing externalId for Rootly webhook deletion ${ctx.webhook.id}, skipping cleanup`, + { requestId: ctx.requestId } ) if (ctx.strict) throw new Error('Missing Rootly externalId for webhook deletion') return @@ -267,21 +270,21 @@ export const rootlyHandler: WebhookProviderHandler = { if (response.ok || response.status === 404) { await response.body?.cancel() - logger.info( - `[${ctx.requestId}] Deleted Rootly webhook endpoint ${externalId} (status ${response.status})` - ) + logger.info(`Deleted Rootly webhook endpoint ${externalId} (status ${response.status})`, { + requestId: ctx.requestId, + }) } else { const responseBody = await response.json().catch(() => ({})) - logger.warn( - `[${ctx.requestId}] Failed to delete Rootly webhook endpoint (non-fatal): ${response.status}`, - { response: responseBody } - ) + logger.warn(`Failed to delete Rootly webhook endpoint (non-fatal): ${response.status}`, { + requestId: ctx.requestId, + response: responseBody, + }) if (ctx.strict) { throw new Error(`Failed to delete Rootly webhook endpoint: ${response.status}`) } } } catch (error) { - logger.warn(`[${ctx.requestId}] Error deleting Rootly webhook (non-fatal)`, error) + logger.warn('Error deleting Rootly webhook (non-fatal)', error, { requestId: ctx.requestId }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/rss.ts b/apps/sim/lib/webhooks/providers/rss.ts index f89720bd871..00c40925264 100644 --- a/apps/sim/lib/webhooks/providers/rss.ts +++ b/apps/sim/lib/webhooks/providers/rss.ts @@ -35,7 +35,7 @@ export const rssHandler: WebhookProviderHandler = { requestId, persistProviderConfig, }: PollingConfigContext) { - logger.info(`[${requestId}] Setting up RSS polling for webhook ${webhookData.id}`) + logger.info(`Setting up RSS polling for webhook ${webhookData.id}`, { requestId }) try { const providerConfig = toRecord(webhookData.providerConfig) @@ -56,13 +56,14 @@ export const rssHandler: WebhookProviderHandler = { .where(eq(webhook.id, webhookData.id as string)) } - logger.info( - `[${requestId}] Successfully configured RSS polling for webhook ${webhookData.id}` - ) + logger.info(`Successfully configured RSS polling for webhook ${webhookData.id}`, { + requestId, + }) return true } catch (error: unknown) { const err = error as Error - logger.error(`[${requestId}] Failed to configure RSS polling`, { + logger.error('Failed to configure RSS polling', { + requestId, webhookId: webhookData.id, error: err.message, }) diff --git a/apps/sim/lib/webhooks/providers/salesforce.ts b/apps/sim/lib/webhooks/providers/salesforce.ts index 0331da2d260..5fccb69c480 100644 --- a/apps/sim/lib/webhooks/providers/salesforce.ts +++ b/apps/sim/lib/webhooks/providers/salesforce.ts @@ -80,12 +80,12 @@ export const salesforceHandler: WebhookProviderHandler = { verifyAuth({ request, requestId, providerConfig }: AuthContext): NextResponse | null { const secret = providerConfig.webhookSecret as string | undefined if (!secret?.trim()) { - logger.warn(`[${requestId}] Salesforce webhook missing webhookSecret — rejecting`) + logger.warn('Salesforce webhook missing webhookSecret — rejecting', { requestId }) return new NextResponse('Unauthorized - Webhook secret not configured', { status: 401 }) } if (!verifySalesforceSharedSecret(request, secret.trim())) { - logger.warn(`[${requestId}] Salesforce webhook secret verification failed`) + logger.warn('Salesforce webhook secret verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid webhook secret', { status: 401 }) } @@ -103,10 +103,12 @@ export const salesforceHandler: WebhookProviderHandler = { const obj = toRecord(body) if (!isSalesforceEventMatch(triggerId, obj, configuredObjectType)) { - logger.debug( - `[${requestId}] Salesforce event mismatch for trigger ${triggerId}. Skipping execution.`, - { webhookId: webhook.id, workflowId: workflow.id, triggerId } - ) + logger.debug(`Salesforce event mismatch for trigger ${triggerId}. Skipping execution.`, { + requestId, + webhookId: webhook.id, + workflowId: workflow.id, + triggerId, + }) return false } diff --git a/apps/sim/lib/webhooks/providers/sendblue.ts b/apps/sim/lib/webhooks/providers/sendblue.ts index 63ca68e52e6..3b7f7269677 100644 --- a/apps/sim/lib/webhooks/providers/sendblue.ts +++ b/apps/sim/lib/webhooks/providers/sendblue.ts @@ -41,14 +41,14 @@ export const sendblueHandler: WebhookProviderHandler = { if (!triggerId || !(triggerId in SENDBLUE_TRIGGER_IS_OUTBOUND)) return true if (!isRecordLike(body)) { - logger.warn(`[${requestId}] Sendblue webhook payload was not an object`) + logger.warn('Sendblue webhook payload was not an object', { requestId }) return false } const expected = SENDBLUE_TRIGGER_IS_OUTBOUND[triggerId] const isOutbound = body.is_outbound === true if (isOutbound !== expected) { - logger.info(`[${requestId}] Sendblue event did not match trigger`, { triggerId, isOutbound }) + logger.info('Sendblue event did not match trigger', { requestId, triggerId, isOutbound }) return false } diff --git a/apps/sim/lib/webhooks/providers/sentry.ts b/apps/sim/lib/webhooks/providers/sentry.ts index ab08586967e..769f4b1e9c7 100644 --- a/apps/sim/lib/webhooks/providers/sentry.ts +++ b/apps/sim/lib/webhooks/providers/sentry.ts @@ -64,7 +64,8 @@ export const sentryHandler: WebhookProviderHandler = { const { isSentryEventMatch } = await import('@/triggers/sentry/utils') if (!isSentryEventMatch(triggerId, resource, action)) { logger.debug( - `[${requestId}] Sentry event mismatch for trigger ${triggerId}. Resource: ${resource}, Action: ${action}. Skipping.` + `Sentry event mismatch for trigger ${triggerId}. Resource: ${resource}, Action: ${action}. Skipping.`, + { requestId } ) return false } diff --git a/apps/sim/lib/webhooks/providers/servicenow.ts b/apps/sim/lib/webhooks/providers/servicenow.ts index 43c79e4e159..83381812c24 100644 --- a/apps/sim/lib/webhooks/providers/servicenow.ts +++ b/apps/sim/lib/webhooks/providers/servicenow.ts @@ -14,7 +14,7 @@ export const servicenowHandler: WebhookProviderHandler = { verifyAuth({ request, requestId, providerConfig }: AuthContext): NextResponse | null { const secret = providerConfig.webhookSecret as string | undefined if (!secret?.trim()) { - logger.warn(`[${requestId}] ServiceNow webhook missing webhookSecret — rejecting`) + logger.warn('ServiceNow webhook missing webhookSecret — rejecting', { requestId }) return new NextResponse('Unauthorized - Webhook secret not configured', { status: 401 }) } @@ -22,7 +22,7 @@ export const servicenowHandler: WebhookProviderHandler = { !verifyTokenAuth(request, secret.trim(), 'x-sim-webhook-secret') && !verifyTokenAuth(request, secret.trim()) ) { - logger.warn(`[${requestId}] ServiceNow webhook secret verification failed`) + logger.warn('ServiceNow webhook secret verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid webhook secret', { status: 401 }) } @@ -40,10 +40,12 @@ export const servicenowHandler: WebhookProviderHandler = { const obj = toRecord(body) if (!isServiceNowEventMatch(triggerId, obj, configuredTableName)) { - logger.debug( - `[${requestId}] ServiceNow event mismatch for trigger ${triggerId}. Skipping execution.`, - { webhookId: webhook.id, workflowId: workflow.id, triggerId } - ) + logger.debug(`ServiceNow event mismatch for trigger ${triggerId}. Skipping execution.`, { + requestId, + webhookId: webhook.id, + workflowId: workflow.id, + triggerId, + }) return false } diff --git a/apps/sim/lib/webhooks/providers/slack.ts b/apps/sim/lib/webhooks/providers/slack.ts index 90b6db22a74..b103879edc0 100644 --- a/apps/sim/lib/webhooks/providers/slack.ts +++ b/apps/sim/lib/webhooks/providers/slack.ts @@ -591,24 +591,24 @@ export function verifySlackRequestSignature( const timestamp = request.headers.get('x-slack-request-timestamp') if (!signature || !timestamp) { - logger.warn(`[${requestId}] Slack webhook missing signature or timestamp header`) + logger.warn('Slack webhook missing signature or timestamp header', { requestId }) return new NextResponse('Unauthorized - Missing Slack signature', { status: 401 }) } const now = Math.floor(Date.now() / 1000) const parsedTimestamp = Number(timestamp) if (Number.isNaN(parsedTimestamp)) { - logger.warn(`[${requestId}] Slack webhook timestamp is not a valid number`, { timestamp }) + logger.warn('Slack webhook timestamp is not a valid number', { requestId, timestamp }) return new NextResponse('Unauthorized - Invalid timestamp', { status: 401 }) } const skew = Math.abs(now - parsedTimestamp) if (skew > SLACK_TIMESTAMP_MAX_SKEW) { - logger.warn(`[${requestId}] Slack webhook timestamp too old`, { timestamp, now, skew }) + logger.warn('Slack webhook timestamp too old', { requestId, timestamp, now, skew }) return new NextResponse('Unauthorized - Request timestamp too old', { status: 401 }) } if (!validateSlackSignature(signingSecret, signature, timestamp, rawBody)) { - logger.warn(`[${requestId}] Slack signature verification failed`) + logger.warn('Slack signature verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid Slack signature', { status: 401 }) } @@ -880,7 +880,7 @@ export const slackHandler: WebhookProviderHandler = { verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext) { const signingSecret = providerConfig.signingSecret as string | undefined if (!signingSecret) { - logger.warn(`[${requestId}] Slack webhook signing secret not configured`) + logger.warn('Slack webhook signing secret not configured', { requestId }) return new NextResponse('Unauthorized - Missing Slack signing secret', { status: 401 }) } return verifySlackRequestSignature(signingSecret, request, rawBody, requestId) diff --git a/apps/sim/lib/webhooks/providers/stripe.ts b/apps/sim/lib/webhooks/providers/stripe.ts index fb488a23999..49a4f9b57fa 100644 --- a/apps/sim/lib/webhooks/providers/stripe.ts +++ b/apps/sim/lib/webhooks/providers/stripe.ts @@ -18,22 +18,23 @@ export const stripeHandler: WebhookProviderHandler = { verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext) { const secret = providerConfig.webhookSecret as string | undefined if (!secret) { - logger.warn( - `[${requestId}] Stripe webhook missing webhookSecret in providerConfig — rejecting request` - ) + logger.warn('Stripe webhook missing webhookSecret in providerConfig — rejecting request', { + requestId, + }) return new NextResponse('Unauthorized - Webhook secret not configured', { status: 401 }) } const signature = request.headers.get('stripe-signature') if (!signature) { - logger.warn(`[${requestId}] Stripe webhook missing Stripe-Signature header`) + logger.warn('Stripe webhook missing Stripe-Signature header', { requestId }) return new NextResponse('Unauthorized - Missing Stripe signature', { status: 401 }) } try { Stripe.webhooks.constructEvent(rawBody, signature, secret) } catch (error) { - logger.warn(`[${requestId}] Stripe signature verification failed`, { + logger.warn('Stripe signature verification failed', { + requestId, error: getErrorMessage(error), }) return new NextResponse('Unauthorized - Invalid Stripe signature', { status: 401 }) diff --git a/apps/sim/lib/webhooks/providers/telegram.ts b/apps/sim/lib/webhooks/providers/telegram.ts index afd30d9fa93..778ec28330f 100644 --- a/apps/sim/lib/webhooks/providers/telegram.ts +++ b/apps/sim/lib/webhooks/providers/telegram.ts @@ -49,7 +49,7 @@ export const telegramHandler: WebhookProviderHandler = { if (!secretToken) return null if (!verifyTokenAuth(request, secretToken, TELEGRAM_SECRET_TOKEN_HEADER)) { - logger.warn(`[${requestId}] Rejected Telegram webhook request with invalid secret token`) + logger.warn('Rejected Telegram webhook request with invalid secret token', { requestId }) return new NextResponse('Unauthorized', { status: 401 }) } @@ -147,7 +147,9 @@ export const telegramHandler: WebhookProviderHandler = { const botToken = config.botToken as string | undefined if (!botToken) { - logger.warn(`[${ctx.requestId}] Missing botToken for Telegram webhook ${ctx.webhook.id}`) + logger.warn(`Missing botToken for Telegram webhook ${ctx.webhook.id}`, { + requestId: ctx.requestId, + }) throw new WebhookDeploymentConfigurationError( 'Bot token is required to create a Telegram webhook. Please provide a valid Telegram bot token.' ) @@ -172,7 +174,7 @@ export const telegramHandler: WebhookProviderHandler = { const errorMessage = responseBody.description || `Failed to create Telegram webhook. Status: ${telegramResponse.status}` - logger.error(`[${ctx.requestId}] ${errorMessage}`, { response: responseBody }) + logger.error(errorMessage, { requestId: ctx.requestId, response: responseBody }) let userFriendlyMessage = 'Failed to create Telegram webhook' if (telegramResponse.status === 401) { @@ -185,9 +187,9 @@ export const telegramHandler: WebhookProviderHandler = { throw new Error(userFriendlyMessage) } - logger.info( - `[${ctx.requestId}] Successfully created Telegram webhook for webhook ${ctx.webhook.id}` - ) + logger.info(`Successfully created Telegram webhook for webhook ${ctx.webhook.id}`, { + requestId: ctx.requestId, + }) return { providerConfigUpdates: { secretToken } } } catch (error: unknown) { if ( @@ -197,10 +199,9 @@ export const telegramHandler: WebhookProviderHandler = { throw error } - logger.error( - `[${ctx.requestId}] Error creating Telegram webhook for webhook ${ctx.webhook.id}`, - error - ) + logger.error(`Error creating Telegram webhook for webhook ${ctx.webhook.id}`, error, { + requestId: ctx.requestId, + }) throw new Error( getErrorMessage(error, 'Failed to create Telegram webhook. Please try again.') ) @@ -213,9 +214,9 @@ export const telegramHandler: WebhookProviderHandler = { const botToken = config.botToken as string | undefined if (!botToken) { - logger.warn( - `[${ctx.requestId}] Missing botToken for Telegram webhook deletion ${ctx.webhook.id}` - ) + logger.warn(`Missing botToken for Telegram webhook deletion ${ctx.webhook.id}`, { + requestId: ctx.requestId, + }) if (ctx.strict) throw new Error('Missing Telegram botToken for webhook deletion') return } @@ -228,8 +229,8 @@ export const telegramHandler: WebhookProviderHandler = { ) if (activeConfigs.length > 0) { logger.info( - `[${ctx.requestId}] Skipping Telegram webhook deletion because an active deployment uses the same bot token`, - { webhookId: ctx.webhook.id } + 'Skipping Telegram webhook deletion because an active deployment uses the same bot token', + { requestId: ctx.requestId, webhookId: ctx.webhook.id } ) return } @@ -245,18 +246,17 @@ export const telegramHandler: WebhookProviderHandler = { const errorMessage = responseBody.description || `Failed to delete Telegram webhook. Status: ${telegramResponse.status}` - logger.error(`[${ctx.requestId}] ${errorMessage}`, { response: responseBody }) + logger.error(errorMessage, { requestId: ctx.requestId, response: responseBody }) if (ctx.strict) throw new Error(errorMessage) } else { - logger.info( - `[${ctx.requestId}] Successfully deleted Telegram webhook for webhook ${ctx.webhook.id}` - ) + logger.info(`Successfully deleted Telegram webhook for webhook ${ctx.webhook.id}`, { + requestId: ctx.requestId, + }) } } catch (error) { - logger.error( - `[${ctx.requestId}] Error deleting Telegram webhook for webhook ${ctx.webhook.id}`, - error - ) + logger.error(`Error deleting Telegram webhook for webhook ${ctx.webhook.id}`, error, { + requestId: ctx.requestId, + }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/tiktok.ts b/apps/sim/lib/webhooks/providers/tiktok.ts index a3aed74ab05..a5ac7d354e7 100644 --- a/apps/sim/lib/webhooks/providers/tiktok.ts +++ b/apps/sim/lib/webhooks/providers/tiktok.ts @@ -57,24 +57,25 @@ export function verifyTikTokSignature( nowSeconds: number = Math.floor(Date.now() / 1000) ): NextResponse | null { if (!clientSecret) { - logger.warn(`[${requestId}] TikTok webhook missing TIKTOK_CLIENT_SECRET`) + logger.warn('TikTok webhook missing TIKTOK_CLIENT_SECRET', { requestId }) return new NextResponse('Unauthorized - TikTok client secret not configured', { status: 401 }) } const parts = parseTikTokSignatureHeader(signatureHeader) if (!parts) { - logger.warn(`[${requestId}] TikTok webhook missing or malformed TikTok-Signature header`) + logger.warn('TikTok webhook missing or malformed TikTok-Signature header', { requestId }) return new NextResponse('Unauthorized - Missing TikTok signature', { status: 401 }) } const timestampSeconds = Number(parts.timestamp) if (!Number.isFinite(timestampSeconds)) { - logger.warn(`[${requestId}] TikTok webhook signature timestamp is not a number`) + logger.warn('TikTok webhook signature timestamp is not a number', { requestId }) return new NextResponse('Unauthorized - Invalid TikTok signature timestamp', { status: 401 }) } if (Math.abs(nowSeconds - timestampSeconds) > TIKTOK_WEBHOOK_TIMESTAMP_SKEW_SECONDS) { - logger.warn(`[${requestId}] TikTok webhook signature timestamp outside allowed skew`, { + logger.warn('TikTok webhook signature timestamp outside allowed skew', { + requestId, skewSeconds: TIKTOK_WEBHOOK_TIMESTAMP_SKEW_SECONDS, timestampSeconds, nowSeconds, @@ -87,7 +88,7 @@ export function verifyTikTokSignature( const signedPayload = `${parts.timestamp}.${rawBody}` const computed = hmacSha256Hex(signedPayload, clientSecret) if (!safeCompare(computed, parts.signature)) { - logger.warn(`[${requestId}] TikTok signature verification failed`) + logger.warn('TikTok signature verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid TikTok signature', { status: 401 }) } @@ -139,9 +140,9 @@ export const tiktokHandler: WebhookProviderHandler = { const { isTikTokEventMatch } = await import('@/triggers/tiktok/utils') const event = stringField(toRecord(body), 'event') if (!isTikTokEventMatch(triggerId, event)) { - logger.debug( - `[${requestId}] TikTok event mismatch for trigger ${triggerId}. Event: ${event}. Skipping.` - ) + logger.debug(`TikTok event mismatch for trigger ${triggerId}. Event: ${event}. Skipping.`, { + requestId, + }) return false } return true diff --git a/apps/sim/lib/webhooks/providers/twilio-signature.ts b/apps/sim/lib/webhooks/providers/twilio-signature.ts index a8f74d25e41..547ed23ce9e 100644 --- a/apps/sim/lib/webhooks/providers/twilio-signature.ts +++ b/apps/sim/lib/webhooks/providers/twilio-signature.ts @@ -79,14 +79,15 @@ export async function verifyTwilioAuth( if (!authToken) { logger.warn( - `[${requestId}] ${providerLabel} webhook has no auth token configured — accepting request without signature verification. Configure an auth token to require signed requests.` + `${providerLabel} webhook has no auth token configured — accepting request without signature verification. Configure an auth token to require signed requests.`, + { requestId } ) return null } const signature = request.headers.get('x-twilio-signature') if (!signature) { - logger.warn(`[${requestId}] ${providerLabel} webhook missing signature header`) + logger.warn(`${providerLabel} webhook missing signature header`, { requestId }) return new NextResponse('Unauthorized - Missing Twilio signature', { status: 401 }) } @@ -97,10 +98,9 @@ export async function verifyTwilioAuth( params = Object.fromEntries(urlParams.entries()) } } catch (error) { - logger.error( - `[${requestId}] Error parsing ${providerLabel} webhook body for signature validation:`, - error - ) + logger.error(`Error parsing ${providerLabel} webhook body for signature validation:`, error, { + requestId, + }) return new NextResponse('Bad Request - Invalid body format', { status: 400 }) } @@ -108,7 +108,8 @@ export async function verifyTwilioAuth( const isValidSignature = await validateTwilioSignature(authToken, signature, fullUrl, params) if (!isValidSignature) { - logger.warn(`[${requestId}] ${providerLabel} signature verification failed`, { + logger.warn(`${providerLabel} signature verification failed`, { + requestId, url: fullUrl, signatureLength: signature.length, paramsCount: Object.keys(params).length, diff --git a/apps/sim/lib/webhooks/providers/typeform.ts b/apps/sim/lib/webhooks/providers/typeform.ts index 492e3c83981..3db80586d01 100644 --- a/apps/sim/lib/webhooks/providers/typeform.ts +++ b/apps/sim/lib/webhooks/providers/typeform.ts @@ -74,14 +74,18 @@ export const typeformHandler: WebhookProviderHandler = { const secret = config.secret as string | undefined if (!formId) { - logger.warn(`[${ctx.requestId}] Missing formId for Typeform webhook ${ctx.webhook.id}`) + logger.warn(`Missing formId for Typeform webhook ${ctx.webhook.id}`, { + requestId: ctx.requestId, + }) throw new WebhookDeploymentConfigurationError( 'Form ID is required to create a Typeform webhook. Please provide a valid form ID.' ) } if (!apiKey) { - logger.warn(`[${ctx.requestId}] Missing apiKey for Typeform webhook ${ctx.webhook.id}`) + logger.warn(`Missing apiKey for Typeform webhook ${ctx.webhook.id}`, { + requestId: ctx.requestId, + }) throw new WebhookDeploymentConfigurationError( 'Personal Access Token is required to create a Typeform webhook. Please provide your Typeform API key.' ) @@ -122,7 +126,8 @@ export const typeformHandler: WebhookProviderHandler = { (responseBody as Record).message || 'Unknown error' - logger.error(`[${ctx.requestId}] Typeform API error: ${errorMessage}`, { + logger.error(`Typeform API error: ${errorMessage}`, { + requestId: ctx.requestId, status: typeformResponse.status, response: responseBody, }) @@ -148,8 +153,8 @@ export const typeformHandler: WebhookProviderHandler = { const responseBody = await typeformResponse.json() logger.info( - `[${ctx.requestId}] Successfully created Typeform webhook for webhook ${ctx.webhook.id} with tag ${tag}`, - { webhookId: (responseBody as Record).id } + `Successfully created Typeform webhook for webhook ${ctx.webhook.id} with tag ${tag}`, + { requestId: ctx.requestId, webhookId: (responseBody as Record).id } ) if (!webhookTag && tag) { @@ -166,10 +171,9 @@ export const typeformHandler: WebhookProviderHandler = { throw error } - logger.error( - `[${ctx.requestId}] Error creating Typeform webhook for webhook ${ctx.webhook.id}`, - error - ) + logger.error(`Error creating Typeform webhook for webhook ${ctx.webhook.id}`, error, { + requestId: ctx.requestId, + }) throw new Error( getErrorMessage(error, 'Failed to create Typeform webhook. Please try again.') ) @@ -185,7 +189,8 @@ export const typeformHandler: WebhookProviderHandler = { if (!formId || !apiKey) { logger.warn( - `[${ctx.requestId}] Missing formId or apiKey for Typeform webhook deletion ${ctx.webhook.id}, skipping cleanup` + `Missing formId or apiKey for Typeform webhook deletion ${ctx.webhook.id}, skipping cleanup`, + { requestId: ctx.requestId } ) if (ctx.strict) throw new Error('Missing Typeform webhook deletion credentials') return @@ -202,17 +207,21 @@ export const typeformHandler: WebhookProviderHandler = { }) if (!typeformResponse.ok && typeformResponse.status !== 404) { - logger.warn( - `[${ctx.requestId}] Failed to delete Typeform webhook (non-fatal): ${typeformResponse.status}` - ) + logger.warn(`Failed to delete Typeform webhook (non-fatal): ${typeformResponse.status}`, { + requestId: ctx.requestId, + }) if (ctx.strict) { throw new Error(`Failed to delete Typeform webhook: ${typeformResponse.status}`) } } else { - logger.info(`[${ctx.requestId}] Successfully deleted Typeform webhook with tag ${tag}`) + logger.info(`Successfully deleted Typeform webhook with tag ${tag}`, { + requestId: ctx.requestId, + }) } } catch (error) { - logger.warn(`[${ctx.requestId}] Error deleting Typeform webhook (non-fatal)`, error) + logger.warn('Error deleting Typeform webhook (non-fatal)', error, { + requestId: ctx.requestId, + }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/utils.ts b/apps/sim/lib/webhooks/providers/utils.ts index f6487fefa3f..a9338989006 100644 --- a/apps/sim/lib/webhooks/providers/utils.ts +++ b/apps/sim/lib/webhooks/providers/utils.ts @@ -82,12 +82,13 @@ export function verifyStandardWebhookRequest({ if (verification.valid) return null if (verification.reason === 'missing_headers') { - logger.warn(`[${requestId}] ${providerLabel} webhook missing signature headers`) + logger.warn(`${providerLabel} webhook missing signature headers`, { requestId }) return new NextResponse(`Unauthorized - Missing ${providerLabel} signature headers`, { status: 401, }) } - logger.warn(`[${requestId}] ${providerLabel} signature verification failed`, { + logger.warn(`${providerLabel} signature verification failed`, { + requestId, reason: verification.reason, }) return new NextResponse(`Unauthorized - Invalid ${providerLabel} signature`, { status: 401 }) @@ -113,7 +114,7 @@ export function createHmacVerifier({ const secret = providerConfig[configKey] as string | undefined if (!secret) { if (requireSecret) { - logger.warn(`[${requestId}] ${providerLabel} webhook secret not configured`) + logger.warn(`${providerLabel} webhook secret not configured`, { requestId }) return new NextResponse(`Unauthorized - Missing ${providerLabel} webhook secret`, { status: 401, }) @@ -123,13 +124,14 @@ export function createHmacVerifier({ const signature = request.headers.get(headerName) if (!signature) { - logger.warn(`[${requestId}] ${providerLabel} webhook missing signature header`) + logger.warn(`${providerLabel} webhook missing signature header`, { requestId }) return new NextResponse(`Unauthorized - Missing ${providerLabel} signature`, { status: 401 }) } const isValid = await validateFn(secret, signature, rawBody) if (!isValid) { - logger.warn(`[${requestId}] ${providerLabel} signature verification failed`, { + logger.warn(`${providerLabel} signature verification failed`, { + requestId, signatureLength: signature.length, secretLength: secret.length, }) @@ -191,7 +193,8 @@ export function skipByEventTypes( const eventType = (body as Record)?.type as string | undefined if (eventType && !eventTypes.includes(eventType)) { eventLogger.info( - `[${requestId}] ${providerLabel} event type '${eventType}' not in allowed list for webhook ${webhook.id as string}, skipping` + `${providerLabel} event type '${eventType}' not in allowed list for webhook ${webhook.id as string}, skipping`, + { requestId } ) return true } diff --git a/apps/sim/lib/webhooks/providers/vercel.ts b/apps/sim/lib/webhooks/providers/vercel.ts index 2bafc09f8e3..96961a17bea 100644 --- a/apps/sim/lib/webhooks/providers/vercel.ts +++ b/apps/sim/lib/webhooks/providers/vercel.ts @@ -27,7 +27,7 @@ export const vercelHandler: WebhookProviderHandler = { verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext): NextResponse | null { const secret = (providerConfig.webhookSecret as string | undefined)?.trim() if (!secret) { - logger.warn(`[${requestId}] Vercel webhook secret missing; rejecting delivery`) + logger.warn('Vercel webhook secret missing; rejecting delivery', { requestId }) return new NextResponse( 'Unauthorized - Vercel webhook signing secret is not configured. Re-save the trigger so a webhook can be registered.', { status: 401 } @@ -36,12 +36,12 @@ export const vercelHandler: WebhookProviderHandler = { const signature = request.headers.get('x-vercel-signature') if (!signature) { - logger.warn(`[${requestId}] Vercel webhook missing x-vercel-signature header`) + logger.warn('Vercel webhook missing x-vercel-signature header', { requestId }) return new NextResponse('Unauthorized - Missing Vercel signature', { status: 401 }) } if (!verifyVercelSignature(secret, signature, rawBody)) { - logger.warn(`[${requestId}] Vercel signature verification failed`) + logger.warn('Vercel signature verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid Vercel signature', { status: 401 }) } @@ -56,7 +56,8 @@ export const vercelHandler: WebhookProviderHandler = { if (triggerId && triggerId !== 'vercel_webhook') { const { isVercelEventMatch } = await import('@/triggers/vercel/utils') if (!isVercelEventMatch(triggerId, eventType)) { - logger.debug(`[${requestId}] Vercel event mismatch for trigger ${triggerId}. Skipping.`, { + logger.debug(`Vercel event mismatch for trigger ${triggerId}. Skipping.`, { + requestId, webhookId: webhook.id, workflowId: workflow.id, triggerId, @@ -112,7 +113,8 @@ export const vercelHandler: WebhookProviderHandler = { : undefined const notificationUrl = getNotificationUrl(webhook) - logger.info(`[${requestId}] Creating Vercel webhook`, { + logger.info('Creating Vercel webhook', { + requestId, triggerId, events, hasTeamId: !!teamId, @@ -181,10 +183,10 @@ export const vercelHandler: WebhookProviderHandler = { throw new Error('Vercel webhook creation succeeded but no webhook ID was returned') } - logger.info( - `[${requestId}] Successfully created webhook in Vercel for webhook ${webhook.id}.`, - { vercelWebhookId: externalId } - ) + logger.info(`Successfully created webhook in Vercel for webhook ${webhook.id}.`, { + requestId, + vercelWebhookId: externalId, + }) const signingSecret = responseBody.secret as string | undefined if (!signingSecret) { @@ -202,10 +204,11 @@ export const vercelHandler: WebhookProviderHandler = { } catch (error: unknown) { if (error instanceof WebhookDeploymentConfigurationError) throw error const err = error as Error - logger.error( - `[${requestId}] Exception during Vercel webhook creation for webhook ${webhook.id}.`, - { message: err.message, stack: err.stack } - ) + logger.error(`Exception during Vercel webhook creation for webhook ${webhook.id}.`, { + requestId, + message: err.message, + stack: err.stack, + }) throw error } }, @@ -220,7 +223,8 @@ export const vercelHandler: WebhookProviderHandler = { if (!apiKey || !externalId) { logger.warn( - `[${requestId}] Missing apiKey or externalId for Vercel webhook deletion ${webhook.id}, skipping cleanup` + `Missing apiKey or externalId for Vercel webhook deletion ${webhook.id}, skipping cleanup`, + { requestId } ) if (ctx.strict) throw new Error('Missing Vercel webhook deletion credentials') return @@ -238,16 +242,16 @@ export const vercelHandler: WebhookProviderHandler = { }) if (!response.ok && response.status !== 404) { - logger.warn( - `[${requestId}] Failed to delete Vercel webhook (non-fatal): ${response.status}` - ) + logger.warn(`Failed to delete Vercel webhook (non-fatal): ${response.status}`, { + requestId, + }) if (ctx.strict) throw new Error(`Failed to delete Vercel webhook: ${response.status}`) } else { await response.body?.cancel() - logger.info(`[${requestId}] Successfully deleted Vercel webhook ${externalId}`) + logger.info(`Successfully deleted Vercel webhook ${externalId}`, { requestId }) } } catch (error) { - logger.warn(`[${requestId}] Error deleting Vercel webhook (non-fatal)`, error) + logger.warn('Error deleting Vercel webhook (non-fatal)', error, { requestId }) if (ctx.strict) throw error } }, diff --git a/apps/sim/lib/webhooks/providers/webflow.ts b/apps/sim/lib/webhooks/providers/webflow.ts index 2a806ce70d0..693afd5cbc5 100644 --- a/apps/sim/lib/webhooks/providers/webflow.ts +++ b/apps/sim/lib/webhooks/providers/webflow.ts @@ -36,7 +36,8 @@ export const webflowHandler: WebhookProviderHandler = { } if (!siteId) { - logger.warn(`[${requestId}] Missing siteId for Webflow webhook creation.`, { + logger.warn('Missing siteId for Webflow webhook creation.', { + requestId, webhookId: webhookRecord.id, }) throw new WebhookDeploymentConfigurationError( @@ -50,7 +51,8 @@ export const webflowHandler: WebhookProviderHandler = { } if (!triggerId) { - logger.warn(`[${requestId}] Missing triggerId for Webflow webhook creation.`, { + logger.warn('Missing triggerId for Webflow webhook creation.', { + requestId, webhookId: webhookRecord.id, }) throw new WebhookDeploymentConfigurationError( @@ -63,7 +65,8 @@ export const webflowHandler: WebhookProviderHandler = { : await getOAuthToken(userId, 'webflow') if (!accessToken) { logger.warn( - `[${requestId}] Could not retrieve Webflow access token for user ${userId}. Cannot create webhook in Webflow.` + `Could not retrieve Webflow access token for user ${userId}. Cannot create webhook in Webflow.`, + { requestId } ) throw new Error( 'Webflow account connection required. Please connect your Webflow account in the trigger configuration and try again.' @@ -81,7 +84,8 @@ export const webflowHandler: WebhookProviderHandler = { const webflowTriggerType = triggerTypeMap[triggerId] if (!webflowTriggerType) { - logger.warn(`[${requestId}] Invalid triggerId for Webflow: ${triggerId}`, { + logger.warn(`Invalid triggerId for Webflow: ${triggerId}`, { + requestId, webhookId: webhookRecord.id, }) throw new Error(`Invalid Webflow trigger type: ${triggerId}`) @@ -116,30 +120,26 @@ export const webflowHandler: WebhookProviderHandler = { const errorMessage = responseBody.message || responseBody.error || 'Unknown Webflow API error' logger.error( - `[${requestId}] Failed to create webhook in Webflow for webhook ${webhookRecord.id}. Status: ${webflowResponse.status}`, - { message: errorMessage, response: responseBody } + `Failed to create webhook in Webflow for webhook ${webhookRecord.id}. Status: ${webflowResponse.status}`, + { requestId, message: errorMessage, response: responseBody } ) throw new Error(errorMessage) } - logger.info( - `[${requestId}] Successfully created webhook in Webflow for webhook ${webhookRecord.id}.`, - { - webflowWebhookId: responseBody.id || responseBody._id, - } - ) + logger.info(`Successfully created webhook in Webflow for webhook ${webhookRecord.id}.`, { + requestId, + webflowWebhookId: responseBody.id || responseBody._id, + }) return { providerConfigUpdates: { externalId: responseBody.id || responseBody._id } } } catch (error: unknown) { if (error instanceof WebhookDeploymentConfigurationError) throw error const err = error as Error - logger.error( - `[${requestId}] Exception during Webflow webhook creation for webhook ${webhookRecord.id}.`, - { - message: err.message, - stack: err.stack, - } - ) + logger.error(`Exception during Webflow webhook creation for webhook ${webhookRecord.id}.`, { + requestId, + message: err.message, + stack: err.stack, + }) throw error } }, @@ -156,7 +156,8 @@ export const webflowHandler: WebhookProviderHandler = { if (!siteId) { logger.warn( - `[${requestId}] Missing siteId for Webflow webhook deletion ${webhookRecord.id}, skipping cleanup` + `Missing siteId for Webflow webhook deletion ${webhookRecord.id}, skipping cleanup`, + { requestId } ) if (strict) throw new Error('Missing Webflow siteId for webhook deletion') return @@ -164,7 +165,8 @@ export const webflowHandler: WebhookProviderHandler = { if (!externalId) { logger.warn( - `[${requestId}] Missing externalId for Webflow webhook deletion ${webhookRecord.id}, skipping cleanup` + `Missing externalId for Webflow webhook deletion ${webhookRecord.id}, skipping cleanup`, + { requestId } ) if (strict) throw new Error('Missing Webflow externalId for webhook deletion') return @@ -172,7 +174,8 @@ export const webflowHandler: WebhookProviderHandler = { const siteIdValidation = validateAlphanumericId(siteId, 'siteId', 100) if (!siteIdValidation.isValid) { - logger.warn(`[${requestId}] Invalid Webflow site ID format, skipping deletion`, { + logger.warn('Invalid Webflow site ID format, skipping deletion', { + requestId, webhookId: webhookRecord.id, siteId: siteId.substring(0, 30), }) @@ -182,7 +185,8 @@ export const webflowHandler: WebhookProviderHandler = { const webhookIdValidation = validateAlphanumericId(externalId, 'webhookId', 100) if (!webhookIdValidation.isValid) { - logger.warn(`[${requestId}] Invalid Webflow webhook ID format, skipping deletion`, { + logger.warn('Invalid Webflow webhook ID format, skipping deletion', { + requestId, webhookId: webhookRecord.id, externalId: externalId.substring(0, 30), }) @@ -192,17 +196,17 @@ export const webflowHandler: WebhookProviderHandler = { const credentialId = config.credentialId as string | undefined if (!credentialId) { - logger.warn( - `[${requestId}] Missing credentialId for Webflow webhook deletion ${webhookRecord.id}` - ) + logger.warn(`Missing credentialId for Webflow webhook deletion ${webhookRecord.id}`, { + requestId, + }) if (strict) throw new Error('Missing Webflow credentialId for webhook deletion') return } const accessToken = await getCredentialAccessToken(credentialId, requestId) if (!accessToken) { - const message = `[${requestId}] Could not retrieve Webflow access token. Cannot delete webhook.` - logger.warn(message, { webhookId: webhookRecord.id }) + const message = 'Could not retrieve Webflow access token. Cannot delete webhook.' + logger.warn(message, { requestId, webhookId: webhookRecord.id }) if (strict) throw new Error(message) return } @@ -219,18 +223,18 @@ export const webflowHandler: WebhookProviderHandler = { if (!webflowResponse.ok && webflowResponse.status !== 404) { const responseBody = await webflowResponse.json().catch(() => ({})) - logger.warn( - `[${requestId}] Failed to delete Webflow webhook (non-fatal): ${webflowResponse.status}`, - { response: responseBody } - ) + logger.warn(`Failed to delete Webflow webhook (non-fatal): ${webflowResponse.status}`, { + requestId, + response: responseBody, + }) if (strict) { throw new Error(`Failed to delete Webflow webhook: ${webflowResponse.status}`) } } else { - logger.info(`[${requestId}] Successfully deleted Webflow webhook ${externalId}`) + logger.info(`Successfully deleted Webflow webhook ${externalId}`, { requestId }) } } catch (error) { - logger.warn(`[${requestId}] Error deleting Webflow webhook (non-fatal)`, error) + logger.warn('Error deleting Webflow webhook (non-fatal)', error, { requestId }) if (strict) throw error } }, @@ -296,7 +300,8 @@ export const webflowHandler: WebhookProviderHandler = { if (payloadCollectionId && payloadCollectionId !== configuredCollectionId) { logger.info( - `[${requestId}] Webflow collection '${payloadCollectionId}' doesn't match configured collection '${configuredCollectionId}' for webhook ${webhook.id as string}, skipping` + `Webflow collection '${payloadCollectionId}' doesn't match configured collection '${configuredCollectionId}' for webhook ${webhook.id as string}, skipping`, + { requestId } ) return true } diff --git a/apps/sim/lib/webhooks/providers/whatsapp.ts b/apps/sim/lib/webhooks/providers/whatsapp.ts index 9748fdbcf0c..80b170de81b 100644 --- a/apps/sim/lib/webhooks/providers/whatsapp.ts +++ b/apps/sim/lib/webhooks/providers/whatsapp.ts @@ -173,10 +173,10 @@ async function handleWhatsAppVerification( challenge: string | null ): Promise { if (mode && token && challenge) { - logger.info(`[${requestId}] WhatsApp verification request received for path: ${path}`) + logger.info(`WhatsApp verification request received for path: ${path}`, { requestId }) if (mode !== 'subscribe') { - logger.warn(`[${requestId}] Invalid WhatsApp verification mode: ${mode}`) + logger.warn(`Invalid WhatsApp verification mode: ${mode}`, { requestId }) return new NextResponse('Invalid mode', { status: 400 }) } @@ -216,7 +216,7 @@ async function handleWhatsAppVerification( candidates++ if (safeCompare(token, verificationToken as string)) { - logger.info(`[${requestId}] WhatsApp verification successful for webhook ${wh.id}`) + logger.info(`WhatsApp verification successful for webhook ${wh.id}`, { requestId }) return new NextResponse(challenge, { status: 200, headers: { @@ -235,7 +235,7 @@ async function handleWhatsAppVerification( return null } - logger.warn(`[${requestId}] No matching WhatsApp verification token found`) + logger.warn('No matching WhatsApp verification token found', { requestId }) return new NextResponse('Verification failed', { status: 403 }) } @@ -252,20 +252,20 @@ export const whatsappHandler: WebhookProviderHandler = { verifyAuth({ request, rawBody, requestId, providerConfig }) { const appSecret = providerConfig.appSecret as string | undefined if (!appSecret) { - logger.warn( - `[${requestId}] WhatsApp webhook missing appSecret in providerConfig — rejecting request` - ) + logger.warn('WhatsApp webhook missing appSecret in providerConfig — rejecting request', { + requestId, + }) return new NextResponse('Unauthorized - WhatsApp app secret not configured', { status: 401 }) } const signature = request.headers.get('x-hub-signature-256') if (!signature) { - logger.warn(`[${requestId}] WhatsApp webhook missing signature header`) + logger.warn('WhatsApp webhook missing signature header', { requestId }) return new NextResponse('Unauthorized - Missing WhatsApp signature', { status: 401 }) } if (!validateWhatsAppSignature(appSecret, signature, rawBody)) { - logger.warn(`[${requestId}] WhatsApp signature verification failed`) + logger.warn('WhatsApp signature verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid WhatsApp signature', { status: 401 }) } @@ -427,9 +427,9 @@ export const whatsappHandler: WebhookProviderHandler = { }, handleEmptyInput(requestId: string) { - logger.info( - `[${requestId}] No messages or status updates in WhatsApp payload, skipping execution` - ) + logger.info('No messages or status updates in WhatsApp payload, skipping execution', { + requestId, + }) return { message: 'No messages or status updates in WhatsApp payload' } }, } diff --git a/apps/sim/lib/webhooks/providers/zendesk.ts b/apps/sim/lib/webhooks/providers/zendesk.ts index c231c654578..aecba884097 100644 --- a/apps/sim/lib/webhooks/providers/zendesk.ts +++ b/apps/sim/lib/webhooks/providers/zendesk.ts @@ -89,26 +89,24 @@ export const zendeskHandler: WebhookProviderHandler = { verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext) { const secret = providerConfig.webhookSecret as string | undefined if (!secret) { - logger.warn(`[${requestId}] Zendesk webhook secret not configured`) + logger.warn('Zendesk webhook secret not configured', { requestId }) return new NextResponse('Unauthorized - Missing Zendesk webhook secret', { status: 401 }) } const signature = request.headers.get('X-Zendesk-Webhook-Signature') const timestamp = request.headers.get('X-Zendesk-Webhook-Signature-Timestamp') if (!signature || !timestamp) { - logger.warn(`[${requestId}] Zendesk webhook missing signature headers`) + logger.warn('Zendesk webhook missing signature headers', { requestId }) return new NextResponse('Unauthorized - Missing Zendesk signature', { status: 401 }) } if (!isZendeskTimestampFresh(timestamp)) { - logger.warn(`[${requestId}] Zendesk webhook timestamp outside the allowed window`, { - timestamp, - }) + logger.warn('Zendesk webhook timestamp outside the allowed window', { requestId, timestamp }) return new NextResponse('Unauthorized - Stale Zendesk timestamp', { status: 401 }) } if (!validateZendeskSignature(secret, signature, timestamp, rawBody)) { - logger.warn(`[${requestId}] Zendesk signature verification failed`) + logger.warn('Zendesk signature verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid Zendesk signature', { status: 401 }) } @@ -123,9 +121,9 @@ export const zendeskHandler: WebhookProviderHandler = { const { isZendeskEventMatch } = await import('@/triggers/zendesk/utils') if (!isZendeskEventMatch(triggerId, eventType || '')) { - logger.debug( - `[${requestId}] Zendesk event '${eventType}' does not match trigger ${triggerId}, skipping` - ) + logger.debug(`Zendesk event '${eventType}' does not match trigger ${triggerId}, skipping`, { + requestId, + }) return false } return true @@ -217,7 +215,8 @@ export const zendeskHandler: WebhookProviderHandler = { if (!createRes.ok) { const detail = await createRes.text().catch(() => '') - logger.error(`[${ctx.requestId}] Failed to create Zendesk webhook (${createRes.status})`, { + logger.error(`Failed to create Zendesk webhook (${createRes.status})`, { + requestId: ctx.requestId, detail, }) if (createRes.status === 401 || createRes.status === 403) { @@ -238,8 +237,8 @@ export const zendeskHandler: WebhookProviderHandler = { if (!secretRes.ok) { const detail = await secretRes.text().catch(() => '') logger.error( - `[${ctx.requestId}] Created Zendesk webhook ${externalId} but failed to fetch signing secret (${secretRes.status})`, - { detail } + `Created Zendesk webhook ${externalId} but failed to fetch signing secret (${secretRes.status})`, + { requestId: ctx.requestId, detail } ) await deleteZendeskWebhookQuietly(apiBase, authHeader, externalId) throw new Error(`Failed to fetch Zendesk signing secret: ${secretRes.status}`) @@ -252,7 +251,7 @@ export const zendeskHandler: WebhookProviderHandler = { throw new Error('Zendesk did not return a signing secret for the webhook.') } - logger.info(`[${ctx.requestId}] Created Zendesk webhook ${externalId}`) + logger.info(`Created Zendesk webhook ${externalId}`, { requestId: ctx.requestId }) return { providerConfigUpdates: { externalId, webhookSecret: secret } } }, @@ -265,15 +264,17 @@ export const zendeskHandler: WebhookProviderHandler = { if (!subdomain || !email || !apiToken || !externalId) { if (ctx.strict) throw new Error('Missing Zendesk credentials or webhook ID for deletion.') - logger.warn( - `[${ctx.requestId}] Skipping Zendesk webhook cleanup — missing credentials or webhook ID` - ) + logger.warn('Skipping Zendesk webhook cleanup — missing credentials or webhook ID', { + requestId: ctx.requestId, + }) return } if (!isValidZendeskSubdomain(subdomain)) { if (ctx.strict) throw new Error('Invalid Zendesk subdomain for deletion.') - logger.warn(`[${ctx.requestId}] Skipping Zendesk webhook cleanup — invalid subdomain`) + logger.warn('Skipping Zendesk webhook cleanup — invalid subdomain', { + requestId: ctx.requestId, + }) return } @@ -284,11 +285,11 @@ export const zendeskHandler: WebhookProviderHandler = { if (!res.ok && res.status !== 404) { if (ctx.strict) throw new Error(`Failed to delete Zendesk webhook: ${res.status}`) - logger.warn( - `[${ctx.requestId}] Failed to delete Zendesk webhook ${externalId} (non-fatal): ${res.status}` - ) + logger.warn(`Failed to delete Zendesk webhook ${externalId} (non-fatal): ${res.status}`, { + requestId: ctx.requestId, + }) return } - logger.info(`[${ctx.requestId}] Deleted Zendesk webhook ${externalId}`) + logger.info(`Deleted Zendesk webhook ${externalId}`, { requestId: ctx.requestId }) }, } diff --git a/apps/sim/lib/webhooks/providers/zoho-desk.ts b/apps/sim/lib/webhooks/providers/zoho-desk.ts index 4ca373f9eed..3c94d389c90 100644 --- a/apps/sim/lib/webhooks/providers/zoho-desk.ts +++ b/apps/sim/lib/webhooks/providers/zoho-desk.ts @@ -295,7 +295,8 @@ export const zohoDeskHandler: WebhookProviderHandler = { const bodyText = await response.text() if (!response.ok) { - logger.error(`[${requestId}] Failed to create Zoho Desk webhook`, { + logger.error('Failed to create Zoho Desk webhook', { + requestId, status: response.status, body: truncate(bodyText, 500), }) @@ -321,7 +322,7 @@ export const zohoDeskHandler: WebhookProviderHandler = { ) } - logger.info(`[${requestId}] Created Zoho Desk webhook`, { externalId }) + logger.info('Created Zoho Desk webhook', { requestId, externalId }) return { // externalId (the JWT `aud` claim) and apiDomain are provider-managed and // are SYSTEM_MANAGED_FIELDS, so they are excluded from the redeploy config @@ -373,7 +374,7 @@ export const zohoDeskHandler: WebhookProviderHandler = { }) if (!response.ok && response.status !== 404) { - logger.warn(`[${requestId}] Failed to delete Zoho Desk webhook`, { status: response.status }) + logger.warn('Failed to delete Zoho Desk webhook', { requestId, status: response.status }) if (strict) throw new Error(`Zoho Desk webhook delete failed: ${response.status}`) } }, @@ -385,7 +386,7 @@ export const zohoDeskHandler: WebhookProviderHandler = { }: AuthContext): Promise { const token = request.headers.get('x-zdesk-jwt') if (!token) { - logger.warn(`[${requestId}] Zoho Desk webhook missing X-ZDesk-JWT header`) + logger.warn('Zoho Desk webhook missing X-ZDesk-JWT header', { requestId }) return new NextResponse('Unauthorized - Missing Zoho Desk JWT', { status: 401 }) } @@ -402,7 +403,7 @@ export const zohoDeskHandler: WebhookProviderHandler = { // subscription, so accepting any org-JWKS RS256 token matching only the // issuer would be a verification bypass. Reject instead of skipping a claim. if (!orgId || !webhookId) { - logger.warn(`[${requestId}] Zoho Desk webhook missing orgId/webhookId; rejecting`) + logger.warn('Zoho Desk webhook missing orgId/webhookId; rejecting', { requestId }) return new NextResponse('Unauthorized - webhook not fully provisioned', { status: 401 }) } @@ -438,7 +439,8 @@ export const zohoDeskHandler: WebhookProviderHandler = { }) return null } catch (error) { - logger.warn(`[${requestId}] Zoho Desk JWT verification failed`, { + logger.warn('Zoho Desk JWT verification failed', { + requestId, message: toError(error).message, }) return new NextResponse('Unauthorized - Invalid Zoho Desk JWT', { status: 401 }) @@ -455,7 +457,8 @@ export const zohoDeskHandler: WebhookProviderHandler = { // than silently drop if that ever changes, so batched deliveries are visible. if (body.length > 1) { logger.warn( - `[${requestId}] Zoho Desk delivered ${body.length} events in one payload; processing the first only` + `Zoho Desk delivered ${body.length} events in one payload; processing the first only`, + { requestId } ) } const event = body[0] diff --git a/apps/sim/lib/webhooks/providers/zoom.ts b/apps/sim/lib/webhooks/providers/zoom.ts index 85d2fe8cd40..fb50705c855 100644 --- a/apps/sim/lib/webhooks/providers/zoom.ts +++ b/apps/sim/lib/webhooks/providers/zoom.ts @@ -87,7 +87,8 @@ async function resolveZoomChallengeSecrets( const secretToken = typeof config.secretToken === 'string' ? config.secretToken : '' return { secretToken } } catch (error) { - logger.warn(`[${requestId}] Failed to resolve Zoom webhook secret for challenge`, { + logger.warn('Failed to resolve Zoom webhook secret for challenge', { + requestId, error: toError(error).message, path, }) @@ -111,9 +112,9 @@ export const zoomHandler: WebhookProviderHandler = { verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext) { const secretToken = providerConfig.secretToken as string | undefined if (!secretToken) { - logger.warn( - `[${requestId}] Zoom webhook missing secretToken in providerConfig — rejecting request` - ) + logger.warn('Zoom webhook missing secretToken in providerConfig — rejecting request', { + requestId, + }) return new NextResponse('Unauthorized - Zoom secret token not configured', { status: 401 }) } @@ -121,12 +122,12 @@ export const zoomHandler: WebhookProviderHandler = { const timestamp = request.headers.get('x-zm-request-timestamp') if (!signature || !timestamp) { - logger.warn(`[${requestId}] Zoom webhook missing signature or timestamp header`) + logger.warn('Zoom webhook missing signature or timestamp header', { requestId }) return new NextResponse('Unauthorized - Missing Zoom signature', { status: 401 }) } if (!validateZoomSignature(secretToken, signature, timestamp, rawBody)) { - logger.warn(`[${requestId}] Zoom webhook signature verification failed`) + logger.warn('Zoom webhook signature verification failed', { requestId }) return new NextResponse('Unauthorized - Invalid Zoom signature', { status: 401 }) } @@ -146,13 +147,8 @@ export const zoomHandler: WebhookProviderHandler = { const { isZoomEventMatch } = await import('@/triggers/zoom/utils') if (!isZoomEventMatch(triggerId, event)) { logger.debug( - `[${requestId}] Zoom event mismatch for trigger ${triggerId}. Event: ${event}. Skipping execution.`, - { - webhookId: wh.id, - workflowId: workflow.id, - triggerId, - receivedEvent: event, - } + `Zoom event mismatch for trigger ${triggerId}. Event: ${event}. Skipping execution.`, + { requestId, webhookId: wh.id, workflowId: workflow.id, triggerId, receivedEvent: event } ) return false } @@ -184,12 +180,12 @@ export const zoomHandler: WebhookProviderHandler = { return null } - logger.info(`[${requestId}] Zoom URL validation request received for path: ${path}`) + logger.info(`Zoom URL validation request received for path: ${path}`, { requestId }) const signature = request.headers.get('x-zm-signature') const timestamp = request.headers.get('x-zm-request-timestamp') if (!signature || !timestamp) { - logger.warn(`[${requestId}] Zoom challenge request missing signature headers — rejecting`) + logger.warn('Zoom challenge request missing signature headers — rejecting', { requestId }) return null } @@ -200,7 +196,7 @@ export const zoomHandler: WebhookProviderHandler = { try { rows = await resolveZoomChallengeSecrets(path, requestId) } catch (err) { - logger.warn(`[${requestId}] Failed to look up webhook secret for Zoom validation`, err) + logger.warn('Failed to look up webhook secret for Zoom validation', err, { requestId }) return null } @@ -220,7 +216,8 @@ export const zoomHandler: WebhookProviderHandler = { } logger.warn( - `[${requestId}] Zoom challenge: no matching secret for path ${path} (${rows.length} webhook row(s))` + `Zoom challenge: no matching secret for path ${path} (${rows.length} webhook row(s))`, + { requestId } ) return null }, diff --git a/apps/sim/lib/webhooks/slack-custom-ingress.ts b/apps/sim/lib/webhooks/slack-custom-ingress.ts index 31105ca45a7..84dab841f46 100644 --- a/apps/sim/lib/webhooks/slack-custom-ingress.ts +++ b/apps/sim/lib/webhooks/slack-custom-ingress.ts @@ -101,11 +101,11 @@ export async function authenticateSlackCustomBotRequest({ }: SlackCustomBotRequestOptions): Promise { const botCredential = await getSlackBotCredential(credentialId) if (!botCredential) { - logger.warn(`[${requestId}] Unknown Slack bot credential ${credentialId}`) + logger.warn(`Unknown Slack bot credential ${credentialId}`, { requestId }) return new NextResponse(null, { status: 404 }) } if (!botCredential.signingSecret) { - logger.warn(`[${requestId}] Slack bot credential ${credentialId} has no signing secret`) + logger.warn(`Slack bot credential ${credentialId} has no signing secret`, { requestId }) return new NextResponse(null, { status: 404 }) } @@ -128,9 +128,9 @@ export async function dispatchSlackCustomBotCredential({ }: DispatchSlackCustomBotOptions): Promise { const webhooks = await findWebhooksByRoutingKey(credentialId, requestId, 'slack') if (webhooks.length === 0) { - logger.info( - `[${requestId}] No active trigger for bot credential ${credentialId}; nothing to run` - ) + logger.info(`No active trigger for bot credential ${credentialId}; nothing to run`, { + requestId, + }) return [] } diff --git a/apps/sim/lib/webhooks/slack-dispatch.ts b/apps/sim/lib/webhooks/slack-dispatch.ts index e0628a56ac5..a92f87f6399 100644 --- a/apps/sim/lib/webhooks/slack-dispatch.ts +++ b/apps/sim/lib/webhooks/slack-dispatch.ts @@ -128,19 +128,17 @@ export async function dispatchSlackWebhooks( if (result.outcome === 'ignored' && result.reason === 'filtered') { const rawEvent = payload.event as Record | undefined const providerConfig = toRecord(foundWebhook.providerConfig) - logger.debug( - `[${requestId}] Event skipped by trigger filter for webhook ${foundWebhook.id}`, - { - eventKey: resolveSlackEventKey(payload), - configuredEvent: providerConfig.eventType, - channelType: rawEvent?.channel_type, - subtype: rawEvent?.subtype, - isThreadReply: - typeof rawEvent?.thread_ts === 'string' && rawEvent.thread_ts !== rawEvent.ts, - threadsSetting: providerConfig.threads, - botId: rawEvent?.bot_id, - } - ) + logger.debug(`Event skipped by trigger filter for webhook ${foundWebhook.id}`, { + requestId, + eventKey: resolveSlackEventKey(payload), + configuredEvent: providerConfig.eventType, + channelType: rawEvent?.channel_type, + subtype: rawEvent?.subtype, + isThreadReply: + typeof rawEvent?.thread_ts === 'string' && rawEvent.thread_ts !== rawEvent.ts, + threadsSetting: providerConfig.threads, + botId: rawEvent?.bot_id, + }) } return result diff --git a/apps/sim/lib/workflows/custom-tools/operations.ts b/apps/sim/lib/workflows/custom-tools/operations.ts index 23d8f5cf899..7c3961404a0 100644 --- a/apps/sim/lib/workflows/custom-tools/operations.ts +++ b/apps/sim/lib/workflows/custom-tools/operations.ts @@ -107,7 +107,7 @@ async function writeCustomTools( }) .where(eq(customTools.id, tool.id)) - logger.info(`[${requestId}] Updated legacy tool ${tool.id}`) + logger.info(`Updated legacy tool ${tool.id}`, { requestId }) continue } } @@ -131,7 +131,7 @@ async function writeCustomTools( .onConflictDoNothing({ target: [customTools.workspaceId, customTools.title] }) .returning({ id: customTools.id }) if (inserted.length === 0) { - logger.info(`[${requestId}] Skipped custom tool "${tool.title}": title already exists`) + logger.info(`Skipped custom tool "${tool.title}": title already exists`, { requestId }) skippedTitles.push(tool.title) } continue diff --git a/apps/sim/lib/workflows/deployment-outbox.ts b/apps/sim/lib/workflows/deployment-outbox.ts index 973640be7f5..a492e878fb4 100644 --- a/apps/sim/lib/workflows/deployment-outbox.ts +++ b/apps/sim/lib/workflows/deployment-outbox.ts @@ -871,7 +871,8 @@ const syncActiveSideEffects = async (rawPayload: unknown): Promise => { .limit(1) if (!workflowRecord) { - logger.warn(`[${requestId}] Workflow missing during deployment side-effect sync`, { + logger.warn('Workflow missing during deployment side-effect sync', { + requestId, workflowId: payload.workflowId, }) return @@ -893,7 +894,8 @@ const syncActiveSideEffects = async (rawPayload: unknown): Promise => { .limit(1) if (!versionRow?.isActive) { - logger.info(`[${requestId}] Skipping stale deployment side-effect sync`, { + logger.info('Skipping stale deployment side-effect sync', { + requestId, workflowId: payload.workflowId, deploymentVersionId: payload.deploymentVersionId, }) @@ -1073,13 +1075,14 @@ async function syncInactiveDeploymentCleanup(params: { requestId: params.requestId, }) if (complete) return - logger.info( - `[${params.requestId}] Inline inactive-deployment cleanup has more rows; continuing through the outbox` - ) + logger.info('Inline inactive-deployment cleanup has more rows; continuing through the outbox', { + requestId: params.requestId, + }) } catch (cleanupError) { logger.warn( - `[${params.requestId}] Inline inactive-deployment cleanup failed; deferring to outbox retry`, - cleanupError + 'Inline inactive-deployment cleanup failed; deferring to outbox retry', + cleanupError, + { requestId: params.requestId } ) } await enqueueWorkflowInactiveDeploymentCleanup(db, { @@ -1235,7 +1238,8 @@ async function cleanupStaleDeploymentIfNeeded(params: { return true } - logger.info(`[${params.requestId}] Cleaning up stale deployment side effects`, { + logger.info('Cleaning up stale deployment side effects', { + requestId: params.requestId, workflowId: params.payload.workflowId, deploymentVersionId: params.payload.deploymentVersionId, }) diff --git a/apps/sim/lib/workflows/executor/execute-workflow.ts b/apps/sim/lib/workflows/executor/execute-workflow.ts index 663b304d251..052cd0d7deb 100644 --- a/apps/sim/lib/workflows/executor/execute-workflow.ts +++ b/apps/sim/lib/workflows/executor/execute-workflow.ts @@ -294,7 +294,7 @@ export async function executeWorkflow( attachExecutionResult(error, executionResult) } const errorDiagnostic = loggingSession.projectDiagnosticError(error) - logger.error(`[${requestId}] Workflow execution failed`, errorDiagnostic) + logger.error('Workflow execution failed', errorDiagnostic, { requestId }) captureServerEvent( actorUserId, diff --git a/apps/sim/lib/workflows/executor/execution-core.ts b/apps/sim/lib/workflows/executor/execution-core.ts index a57118f62ad..9bcad561ff9 100644 --- a/apps/sim/lib/workflows/executor/execution-core.ts +++ b/apps/sim/lib/workflows/executor/execution-core.ts @@ -285,7 +285,7 @@ async function clearExecutionCancellationSafely( try { await clearExecutionCancellation(executionId) } catch (error) { - logger.error(`[${requestId}] Failed to clear execution cancellation`, { error, executionId }) + logger.error('Failed to clear execution cancellation', { requestId, error, executionId }) } } @@ -326,7 +326,7 @@ async function recordSettledRun( try { await updateWorkflowRunCounts(workflowId) } catch (error) { - logger.error(`[${requestId}] Failed to update run counts`, { error }) + logger.error('Failed to update run counts', { requestId, error }) } } @@ -345,8 +345,9 @@ function buildTraceSpansForLog( return buildTraceSpans(result) } catch (error) { logger.error( - `[${requestId}] Failed to build trace spans; finalizing without them`, - loggingSession.projectDiagnosticError(error, { executionId }) + 'Failed to build trace spans; finalizing without them', + loggingSession.projectDiagnosticError(error, { executionId }), + { requestId } ) return { traceSpans: [], totalDuration: result.metadata?.duration ?? 0 } } @@ -418,11 +419,12 @@ async function finalizeExecutionOutcome(params: { } } catch (error) { logger.warn( - `[${requestId}] Post-execution finalization failed`, + 'Post-execution finalization failed', loggingSession.projectDiagnosticError(error, { executionId, status: result.status, - }) + }), + { requestId } ) } @@ -462,8 +464,9 @@ async function finalizeExecutionError(params: { } } catch (postExecError) { logger.error( - `[${requestId}] Post-execution error logging failed`, - loggingSession.projectDiagnosticError(postExecError, { executionId }) + 'Post-execution error logging failed', + loggingSession.projectDiagnosticError(postExecError, { executionId }), + { requestId } ) } @@ -730,7 +733,7 @@ async function executeWorkflowCoreImpl( resumeDeploymentVersionId, providedWorkspaceId ) - logger.info(`[${requestId}] Using admitted historical deployment state (resumed execution)`) + logger.info('Using admitted historical deployment state (resumed execution)', { requestId }) return { blocks: deployedData.blocks, edges: deployedData.edges, @@ -742,7 +745,8 @@ async function executeWorkflowCoreImpl( if (metadata.workflowStateOverride) { const override = metadata.workflowStateOverride - logger.debug(`[${requestId}] Using workflow state override (diff workflow execution)`, { + logger.debug('Using workflow state override (diff workflow execution)', { + requestId, blocksCount: Object.keys(override.blocks).length, edgesCount: override.edges.length, }) @@ -762,9 +766,9 @@ async function executeWorkflowCoreImpl( throw new Error('Workflow not found or not yet saved') } - logger.info( - `[${requestId}] Using draft workflow state from normalized tables (client execution)` - ) + logger.info('Using draft workflow state from normalized tables (client execution)', { + requestId, + }) return { blocks: draftData.blocks, edges: draftData.edges, @@ -775,7 +779,7 @@ async function executeWorkflowCoreImpl( } const deployedData = await loadDeployedWorkflowState(workflowId, providedWorkspaceId) - logger.debug(`[${requestId}] Using deployed workflow state (deployed execution)`) + logger.debug('Using deployed workflow state (deployed execution)', { requestId }) return { blocks: deployedData.blocks, edges: deployedData.edges, @@ -887,7 +891,8 @@ async function executeWorkflowCoreImpl( (resumePendingQueue !== undefined || resumeRemainingEdges !== undefined || resumeTerminalNoop) ) { resolvedTriggerBlockId = undefined - logger.info(`[${requestId}] Skipping trigger resolution for resume execution`, { + logger.info('Skipping trigger resolution for resume execution', { + requestId, pendingQueueLength: resumePendingQueue?.length ?? 0, remainingEdgeCount: resumeRemainingEdges?.length ?? 0, resumeTerminalNoop, @@ -907,7 +912,8 @@ async function executeWorkflowCoreImpl( } resolvedTriggerBlockId = startBlock.blockId - logger.debug(`[${requestId}] Identified trigger block for ${executionKind} execution:`, { + logger.debug(`Identified trigger block for ${executionKind} execution:`, { + requestId, blockId: resolvedTriggerBlockId, blockType: startBlock.block.type, path: startBlock.path, @@ -959,7 +965,8 @@ async function executeWorkflowCoreImpl( // Create and execute workflow with callbacks if (resumeFromSnapshot) { - logger.info(`[${requestId}] Resume execution detected`, { + logger.info('Resume execution detected', { + requestId, resumePendingQueue, hasState: !!snapshot.state, stateBlockStatesCount: snapshot.state @@ -984,12 +991,13 @@ async function executeWorkflowCoreImpl( persistenceSucceeded = true })().catch((error) => { logger.warn( - `[${requestId}] Block completion persistence failed`, + 'Block completion persistence failed', loggingSession.projectDiagnosticError(error, { executionId, blockId, blockType, - }) + }), + { requestId } ) }) @@ -1008,12 +1016,13 @@ async function executeWorkflowCoreImpl( ) } catch (error) { logger.warn( - `[${requestId}] Block completion callback failed`, + 'Block completion callback failed', loggingSession.projectDiagnosticError(error, { executionId, blockId, blockType, - }) + }), + { requestId } ) } })() @@ -1036,12 +1045,13 @@ async function executeWorkflowCoreImpl( persistenceSucceeded = true })().catch((error) => { logger.warn( - `[${requestId}] Block start persistence failed`, + 'Block start persistence failed', loggingSession.projectDiagnosticError(error, { executionId, blockId, blockType, - }) + }), + { requestId } ) }) @@ -1060,12 +1070,13 @@ async function executeWorkflowCoreImpl( ) } catch (error) { logger.warn( - `[${requestId}] Block start callback failed`, + 'Block start callback failed', loggingSession.projectDiagnosticError(error, { executionId, blockId, blockType, - }) + }), + { requestId } ) } })() @@ -1358,14 +1369,16 @@ async function executeWorkflowCoreImpl( }) } catch (postExecError) { logger.error( - `[${requestId}] Post-execution logging failed`, - loggingSession.projectDiagnosticError(postExecError, { executionId }) + 'Post-execution logging failed', + loggingSession.projectDiagnosticError(postExecError, { executionId }), + { requestId } ) } })() ) - logger.info(`[${requestId}] Workflow execution completed`, { + logger.info('Workflow execution completed', { + requestId, success: result.success, status: result.status, duration: result.metadata?.duration, @@ -1373,13 +1386,16 @@ async function executeWorkflowCoreImpl( return result } catch (error: unknown) { - logFailureOnce(logger, `[${requestId}] Execution failed:`, error, { + logFailureOnce(logger, 'Execution failed', error, { metadata: () => { const errorCause = describeErrorCause(error) - return projectResolvedSecretDiagnosticError(error, resolvedSecretTraceRegistry, { - workflowId, - ...(errorCause ? { cause: errorCause } : {}), - }) + return { + requestId, + ...projectResolvedSecretDiagnosticError(error, resolvedSecretTraceRegistry, { + workflowId, + ...(errorCause ? { cause: errorCause } : {}), + }), + } }, executionId, }) @@ -1416,8 +1432,9 @@ async function executeWorkflowCoreImpl( } } catch (postExecError) { logger.error( - `[${requestId}] Post-execution error logging failed`, - loggingSession.projectDiagnosticError(postExecError, { executionId }) + 'Post-execution error logging failed', + loggingSession.projectDiagnosticError(postExecError, { executionId }), + { requestId } ) } })() diff --git a/apps/sim/lib/workflows/executor/resume-execution.ts b/apps/sim/lib/workflows/executor/resume-execution.ts index ebaea1ad89c..2ba5d6e4d06 100644 --- a/apps/sim/lib/workflows/executor/resume-execution.ts +++ b/apps/sim/lib/workflows/executor/resume-execution.ts @@ -190,7 +190,8 @@ export async function executeResumeWorkflow({ }) } catch (error) { const message = toError(error).message - logger.error(`[${requestId}] Failed to validate paused execution snapshot`, { + logger.error('Failed to validate paused execution snapshot', { + requestId, workflowId, executionId, error: message, @@ -201,7 +202,8 @@ export async function executeResumeWorkflow({ const { snapshot: persistedSnapshot, billingAttribution } = snapshotBinding const resumeExecutionId = generateId() - logger.info(`[${requestId}] Preprocessing resume execution`, { + logger.info('Preprocessing resume execution', { + requestId, workflowId, parentExecutionId: executionId, resumeExecutionId, @@ -227,7 +229,8 @@ export async function executeResumeWorkflow({ const statusCode = preprocessResult.error?.statusCode || 400 const message = preprocessResult.error?.message || 'Failed to validate resume execution. Please try again.' - logger.warn(`[${requestId}] Preprocessing failed for resume`, { + logger.warn('Preprocessing failed for resume', { + requestId, workflowId, parentExecutionId: executionId, error: message, @@ -236,7 +239,8 @@ export async function executeResumeWorkflow({ throw new ResumeWorkflowExecutionError(statusCode, message, statusCode < 500) } - logger.info(`[${requestId}] Preprocessing passed, proceeding with resume`, { + logger.info('Preprocessing passed, proceeding with resume', { + requestId, workflowId, parentExecutionId: executionId, resumeExecutionId, diff --git a/apps/sim/lib/workflows/lifecycle.ts b/apps/sim/lib/workflows/lifecycle.ts index f5e2269e452..971186c58f6 100644 --- a/apps/sim/lib/workflows/lifecycle.ts +++ b/apps/sim/lib/workflows/lifecycle.ts @@ -45,10 +45,11 @@ async function notifyWorkflowArchived(workflowId: string, requestId: string): Pr }) if (!socketResponse.ok) { - logger.warn(`[${requestId}] Failed to notify Socket.IO about archived workflow ${workflowId}`) + logger.warn(`Failed to notify Socket.IO about archived workflow ${workflowId}`, { requestId }) } } catch (error) { - logger.warn(`[${requestId}] Error notifying Socket.IO about archived workflow ${workflowId}`, { + logger.warn(`Error notifying Socket.IO about archived workflow ${workflowId}`, { + requestId, error, }) } @@ -82,14 +83,15 @@ async function cleanupExternalWebhooksForWorkflow( } catch (error) { if (strict) throw error logger.warn( - `[${requestId}] Failed to cleanup external webhook ${webhookData.webhook.id} for workflow ${workflowId}`, - { error } + `Failed to cleanup external webhook ${webhookData.webhook.id} for workflow ${workflowId}`, + { requestId, error } ) } } } catch (error) { if (strict) throw error - logger.warn(`[${requestId}] Error during external webhook cleanup for workflow ${workflowId}`, { + logger.warn(`Error during external webhook cleanup for workflow ${workflowId}`, { + requestId, error, }) } @@ -224,7 +226,7 @@ export async function restoreWorkflow( ) }) - logger.info(`[${options.requestId}] Restored workflow ${workflowId}`) + logger.info(`Restored workflow ${workflowId}`, { requestId: options.requestId }) return { restored: true, @@ -305,7 +307,7 @@ export async function archiveWorkflowsByIdsInWorkspace( */ export async function disableUserResources(userId: string): Promise { const requestId = generateRequestId() - logger.info(`[${requestId}] Disabling resources for banned user ${userId}`) + logger.info(`Disabling resources for banned user ${userId}`, { requestId }) const { archiveWorkspace } = await import('@/lib/workspaces/lifecycle') @@ -338,7 +340,8 @@ export async function disableUserResources(userId: string): Promise { await signalExecutionStops(executions) } catch (error) { stopFailed = true - logger.error(`[${requestId}] Failed to stop runs for banned user ${userId}`, { + logger.error(`Failed to stop runs for banned user ${userId}`, { + requestId, workspaceIds, error: getErrorMessage(error), }) @@ -367,7 +370,8 @@ export async function disableUserResources(userId: string): Promise { await Promise.all(stopping) if (failures.length > 0) { - logger.error(`[${requestId}] Failed to archive workspaces for banned user ${userId}`, { + logger.error(`Failed to archive workspaces for banned user ${userId}`, { + requestId, failures: failures.map(({ workspaceId, error }) => ({ workspaceId, error: getErrorMessage(error), @@ -380,7 +384,8 @@ export async function disableUserResources(userId: string): Promise { } logger.info( - `[${requestId}] Disabled resources for user ${userId}: archived ${ownedWorkspaces.length} workspaces, deleted API keys, ${stopFailed ? 'failed to stop some of their runs' : 'stopped their runs'}` + `Disabled resources for user ${userId}: archived ${ownedWorkspaces.length} workspaces, deleted API keys, ${stopFailed ? 'failed to stop some of their runs' : 'stopped their runs'}`, + { requestId } ) } @@ -490,10 +495,10 @@ export async function finishWorkflowArchive( try { mcpPubSub.publishWorkflowToolsChanged({ serverId, workspaceId }) } catch (error) { - logger.warn( - `[${options.requestId}] MCP tools-changed publish failed for server ${serverId}`, - { error } - ) + logger.warn(`MCP tools-changed publish failed for server ${serverId}`, { + requestId: options.requestId, + error, + }) } } } diff --git a/apps/sim/lib/workflows/operations/import-workflow.ts b/apps/sim/lib/workflows/operations/import-workflow.ts index 72454ca8bec..25aabf76f8a 100644 --- a/apps/sim/lib/workflows/operations/import-workflow.ts +++ b/apps/sim/lib/workflows/operations/import-workflow.ts @@ -300,7 +300,7 @@ async function executeImportWorkflowIntoWorkspace( */ const { state: preparedState, warnings } = prepareWorkflowStateForPersistence(parsedState) if (warnings.length > 0) { - logger.warn(`[${requestId}] Normalized imported workflow with warnings`, { warnings }) + logger.warn('Normalized imported workflow with warnings', { requestId, warnings }) } const workflowState: WorkflowState = { ...parsedState, ...preparedState } @@ -392,7 +392,8 @@ async function executeImportWorkflowIntoWorkspace( } }) } catch (error) { - logger.error(`[${requestId}] Failed to persist imported workflow, rolling back`, { + logger.error('Failed to persist imported workflow, rolling back', { + requestId, workflowId, error: getErrorMessage(error, 'Unknown error'), }) @@ -406,8 +407,13 @@ async function executeImportWorkflowIntoWorkspace( await db.delete(workflow).where(eq(workflow.id, workflowId)) } catch (rollbackError) { logger.error( - `[${requestId}] Rollback failed, workflow ${workflowId} is orphaned in workspace ${workspaceId}`, - { workflowId, workspaceId, error: getErrorMessage(rollbackError, 'Unknown error') } + `Rollback failed, workflow ${workflowId} is orphaned in workspace ${workspaceId}`, + { + requestId, + workflowId, + workspaceId, + error: getErrorMessage(rollbackError, 'Unknown error'), + } ) } return { success: false, status: 500, error: 'Failed to save workflow state' } @@ -426,19 +432,22 @@ async function executeImportWorkflowIntoWorkspace( userId ) if (saved > 0 || toolErrors.length > 0) { - logger.info(`[${requestId}] Persisted ${saved} custom tool(s) from import`, { + logger.info(`Persisted ${saved} custom tool(s) from import`, { + requestId, workflowId, errors: toolErrors, }) } } catch (error) { - logger.error(`[${requestId}] Failed to persist custom tools from import`, { + logger.error('Failed to persist custom tools from import', { + requestId, workflowId, error: getErrorMessage(error, 'Unknown error'), }) } - logger.info(`[${requestId}] Imported workflow ${workflowId} into workspace ${workspaceId}`, { + logger.info(`Imported workflow ${workflowId} into workspace ${workspaceId}`, { + requestId, name: created.workflow.name, blocksCount: Object.keys(workflowState.blocks).length, }) diff --git a/apps/sim/lib/workflows/orchestration/deploy.ts b/apps/sim/lib/workflows/orchestration/deploy.ts index 5f9fe84f0ad..d430cb8c23f 100644 --- a/apps/sim/lib/workflows/orchestration/deploy.ts +++ b/apps/sim/lib/workflows/orchestration/deploy.ts @@ -173,7 +173,7 @@ export async function performFullDeploy( idempotencyKey, }) } catch (error) { - logger.error(`[${requestId}] Deployment preparation failed`, { workflowId, error }) + logger.error('Deployment preparation failed', { requestId, workflowId, error }) return { success: false, error: getErrorMessage(error, 'Failed to prepare workflow deployment'), @@ -398,7 +398,8 @@ async function processStableDeploymentPreparationNow( try { return await processWorkflowDeploymentOutboxEvent(outboxEventId) } catch (error) { - logger.warn(`[${requestId}] Inline deployment preparation errored; outbox will retry`, { + logger.warn('Inline deployment preparation errored; outbox will retry', { + requestId, outboxEventId, error, }) @@ -602,7 +603,7 @@ export async function performFullUndeploy( return { success: false, error: result.error || 'Failed to undeploy workflow' } } - logger.info(`[${requestId}] Workflow undeployed successfully: ${workflowId}`) + logger.info(`Workflow undeployed successfully: ${workflowId}`, { requestId }) try { const { PlatformEvents } = await import('@/lib/core/telemetry') @@ -799,11 +800,7 @@ export async function performActivateVersion( idempotencyKey, }) } catch (error) { - logger.error(`[${requestId}] Version activation preparation failed`, { - workflowId, - version, - error, - }) + logger.error('Version activation preparation failed', { requestId, workflowId, version, error }) return { success: false, error: getErrorMessage(error, 'Failed to prepare version activation'), @@ -918,23 +915,22 @@ async function processDeploymentSideEffectsNow( const result = await processWorkflowDeploymentOutboxEvent(outboxEventId) if (result === 'completed') return undefined if (result === 'dead_letter' || result === 'not_found') { - logger.error(`[${requestId}] Deployment side-effect sync cannot be retried automatically`, { + logger.error('Deployment side-effect sync cannot be retried automatically', { + requestId, outboxEventId, result, }) return 'Deployment saved, but trigger, schedule, and MCP sync could not be queued. Redeploy if triggers or schedules look stale.' } - logger.warn(`[${requestId}] Deployment side-effect sync queued for retry`, { + logger.warn('Deployment side-effect sync queued for retry', { + requestId, outboxEventId, result, }) return 'Deployment saved. Trigger, schedule, and MCP sync is queued and may finish shortly.' } catch (error) { - logger.warn(`[${requestId}] Deployment side-effect sync queued for retry`, { - outboxEventId, - error, - }) + logger.warn('Deployment side-effect sync queued for retry', { requestId, outboxEventId, error }) return 'Deployment saved. Trigger, schedule, and MCP sync is queued and may finish shortly.' } } diff --git a/apps/sim/lib/workflows/orchestration/workflow-lifecycle.ts b/apps/sim/lib/workflows/orchestration/workflow-lifecycle.ts index 6f4c519d729..f5da5ffc98c 100644 --- a/apps/sim/lib/workflows/orchestration/workflow-lifecycle.ts +++ b/apps/sim/lib/workflows/orchestration/workflow-lifecycle.ts @@ -268,7 +268,8 @@ export async function createWorkflowWithState({ return { success: true, workflow } } catch (error) { if (asOrchestrationError(error)) throw error - logger.error(`[${params.requestId ?? 'create'}] Failed to persist workflow state`, { + logger.error('Failed to persist workflow state', { + requestId: params.requestId, workspaceId: params.workspaceId, error, }) @@ -359,7 +360,7 @@ export async function performCreateWorkflowTransition( const conflictingConstraint = getPostgresErrorCode(error) === '23505' ? getPostgresConstraintName(error) : undefined if (conflictingConstraint === WORKFLOW_PRIMARY_KEY) { - logger.warn(`[${requestId}] Workflow id is already taken`, { workflowId }) + logger.warn('Workflow id is already taken', { requestId, workflowId }) return { success: false, error: 'A workflow with this ID already exists', @@ -378,14 +379,15 @@ export async function performCreateWorkflowTransition( } } - logger.warn(`[${requestId}] Workflow name was claimed during creation; retrying`, { + logger.warn('Workflow name was claimed during creation; retrying', { + requestId, name, attempt: attempt + 1, }) } } - logger.info(`[${requestId}] Successfully created workflow ${workflowId}`) + logger.info(`Successfully created workflow ${workflowId}`, { requestId }) return { success: true, @@ -430,7 +432,7 @@ export async function performCreateWorkflow( } return result } catch (error) { - logger.error(`[${requestId}] Failed to create workflow`, { error }) + logger.error('Failed to create workflow', { requestId, error }) return { success: false, error: toError(error).message, errorCode: 'internal' } } } @@ -504,7 +506,8 @@ export async function updateWorkflowRecord( return { success: false, error: 'Workflow not found', errorCode: 'not_found' } } - logger.info(`[${requestId}] Successfully updated workflow ${params.workflowId}`, { + logger.info(`Successfully updated workflow ${params.workflowId}`, { + requestId, updates: updateData, }) @@ -550,7 +553,7 @@ export async function deleteWorkflowRecord( return { success: false, error: 'Workflow not found', errorCode: 'not_found' } } - logger.info(`[${requestId}] Successfully archived workflow ${workflowId}`) + logger.info(`Successfully archived workflow ${workflowId}`, { requestId }) return { success: true, archived: archiveResult.archived, @@ -610,7 +613,7 @@ export async function performRestoreWorkflow( } } - logger.info(`[${requestId}] Successfully restored workflow ${workflowId}`) + logger.info(`Successfully restored workflow ${workflowId}`, { requestId }) recordAudit({ workspaceId: restoreResult.workflow.workspaceId || null, @@ -628,7 +631,7 @@ export async function performRestoreWorkflow( return { success: true, workflow: restoreResult.workflow } } catch (error) { - logger.error(`[${requestId}] Failed to restore workflow ${workflowId}`, { error }) + logger.error(`Failed to restore workflow ${workflowId}`, { requestId, error }) return { success: false, error: toError(error).message, errorCode: 'internal' } } } diff --git a/apps/sim/lib/workflows/persistence/duplicate.ts b/apps/sim/lib/workflows/persistence/duplicate.ts index f05c08fc4fb..57431e33e78 100644 --- a/apps/sim/lib/workflows/persistence/duplicate.ts +++ b/apps/sim/lib/workflows/persistence/duplicate.ts @@ -18,6 +18,7 @@ import { normalizeWorkflowEdgeTargetHandle, } from '@sim/workflow-types/workflow' import { and, eq } from 'drizzle-orm' +import { generateRequestId } from '@/lib/core/utils/request' import type { DbOrTx, DbTransaction } from '@/lib/db/types' import { remapConditionEdgeHandle } from '@/lib/workflows/condition-ids' import { insertNewWorkflowRow } from '@/lib/workflows/persistence/new-workflow-row' @@ -117,7 +118,7 @@ export async function duplicateWorkflow( description, workspaceId, folderId, - requestId = 'unknown', + requestId = generateRequestId(), newWorkflowId: clientNewWorkflowId, tx: providedTx, workflowIdMap, @@ -309,9 +310,9 @@ export async function duplicateWorkflow( }) await tx.insert(workflowBlocks).values(newBlocks) - logger.info( - `[${requestId}] Copied ${sourceBlocks.length} blocks with updated parent relationships` - ) + logger.info(`Copied ${sourceBlocks.length} blocks with updated parent relationships`, { + requestId, + }) } // Copy all edges from source workflow with updated block references @@ -368,7 +369,8 @@ export async function duplicateWorkflow( await tx.insert(workflowEdges).values(newEdges) } logger.info( - `[${requestId}] Copied ${newEdges.length}/${sourceEdges.length} edges with updated block references` + `Copied ${newEdges.length}/${sourceEdges.length} edges with updated block references`, + { requestId } ) } @@ -386,12 +388,14 @@ export async function duplicateWorkflow( if (!newSubflowId) { logger.warn( - `[${requestId}] Subflow ${subflow.id} (${subflow.type}) has no corresponding block, skipping` + `Subflow ${subflow.id} (${subflow.type}) has no corresponding block, skipping`, + { requestId } ) return null } - logger.info(`[${requestId}] Mapping subflow ${subflow.id} → ${newSubflowId}`, { + logger.info(`Mapping subflow ${subflow.id} → ${newSubflowId}`, { + requestId, subflowType: subflow.type, }) @@ -447,7 +451,8 @@ export async function duplicateWorkflow( } logger.info( - `[${requestId}] Copied ${newSubflows.length}/${sourceSubflows.length} subflows with updated block references and matching IDs` + `Copied ${newSubflows.length}/${sourceSubflows.length} subflows with updated block references and matching IDs`, + { requestId } ) } diff --git a/apps/sim/lib/workflows/persistence/replace-normalized-state.ts b/apps/sim/lib/workflows/persistence/replace-normalized-state.ts index 131645d8499..5c480dfe5a9 100644 --- a/apps/sim/lib/workflows/persistence/replace-normalized-state.ts +++ b/apps/sim/lib/workflows/persistence/replace-normalized-state.ts @@ -244,7 +244,6 @@ export async function replaceWorkflowNormalizedState( input: ReplaceWorkflowNormalizedStateInput ): Promise { const { workflowId, workspaceId, attributedUserId, subjectUserId, state, requestId } = input - const logPrefix = requestId ? `[${requestId}] ` : '' /** * Hoisted ahead of the transaction even though the shared write checks it @@ -336,7 +335,8 @@ export async function replaceWorkflowNormalizedState( }) if (!saveResult.success) { - logger.error(`${logPrefix}Failed to save workflow ${workflowId} state`, { + logger.error(`Failed to save workflow ${workflowId} state`, { + requestId, error: saveResult.error, }) throw new WorkflowStatePersistenceError(saveResult.error ?? 'Unknown persistence failure') @@ -350,16 +350,17 @@ export async function replaceWorkflowNormalizedState( attributedUserId ) if (saved > 0) { - logger.info(`${logPrefix}Persisted ${saved} custom tool(s) to database`, { workflowId }) + logger.info(`Persisted ${saved} custom tool(s) to database`, { requestId, workflowId }) } if (errors.length > 0) { - logger.warn(`${logPrefix}Some custom tools failed to persist`, { errors, workflowId }) + logger.warn('Some custom tools failed to persist', { requestId, errors, workflowId }) } } catch (error) { - logger.error(`${logPrefix}Failed to persist custom tools`, { error, workflowId }) + logger.error('Failed to persist custom tools', { requestId, error, workflowId }) } } else { - logger.warn(`${logPrefix}Workflow has no workspaceId, skipping custom tools persistence`, { + logger.warn('Workflow has no workspaceId, skipping custom tools persistence', { + requestId, workflowId, }) } diff --git a/apps/sim/lib/workflows/persistence/save-normalized-state.ts b/apps/sim/lib/workflows/persistence/save-normalized-state.ts index c5e4239f84b..1c25c1e592b 100644 --- a/apps/sim/lib/workflows/persistence/save-normalized-state.ts +++ b/apps/sim/lib/workflows/persistence/save-normalized-state.ts @@ -70,14 +70,14 @@ export async function saveWorkflowNormalizedState(params: { const workflowData = authorization.workflow if (!workflowData) { - logger.warn(`[${requestId}] Workflow ${workflowId} not found for state update`) + logger.warn(`Workflow ${workflowId} not found for state update`, { requestId }) return { success: false, status: 404, error: 'Workflow not found' } } if (!authorization.allowed) { - logger.warn( - `[${requestId}] User ${userId} denied permission to update workflow state ${workflowId}` - ) + logger.warn(`User ${userId} denied permission to update workflow state ${workflowId}`, { + requestId, + }) return { success: false, status: authorization.status || 403, diff --git a/apps/sim/lib/workflows/skills/operations.ts b/apps/sim/lib/workflows/skills/operations.ts index 6b9ea708fd3..eea98588911 100644 --- a/apps/sim/lib/workflows/skills/operations.ts +++ b/apps/sim/lib/workflows/skills/operations.ts @@ -396,7 +396,7 @@ export async function upsertSkills(params: { .where(and(eq(skill.id, s.id), eq(skill.workspaceId, workspaceId))) touched.push({ id: s.id, name: nextName, operation: 'updated' }) - logger.info(`[${requestId}] Updated skill ${s.id}`) + logger.info(`Updated skill ${s.id}`, { requestId }) continue } @@ -438,7 +438,7 @@ export async function upsertSkills(params: { }) touched.push({ id: newId, name: s.name, operation: 'created' }) - logger.info(`[${requestId}] Created skill "${s.name}"`) + logger.info(`Created skill "${s.name}"`, { requestId }) } if (params.returnSkills === false) { diff --git a/apps/sim/lib/workflows/streaming/streaming.test.ts b/apps/sim/lib/workflows/streaming/streaming.test.ts index 11ba07fe0f5..c7f37a45302 100644 --- a/apps/sim/lib/workflows/streaming/streaming.test.ts +++ b/apps/sim/lib/workflows/streaming/streaming.test.ts @@ -326,11 +326,9 @@ describe('createStreamingResponse', () => { expect(events).toContainEqual({ event: 'error', error: message }) expect(events.some((event) => event.event === 'final')).toBe(false) expect(workflowStreamingLogger.error).toHaveBeenCalledWith( - '[request-secret-failure] Stream error', - { - errorType: 'error', - hasStack: true, - } + 'Stream error', + { errorType: 'error', hasStack: true }, + { requestId: 'request-secret-failure' } ) const loggerPayload = JSON.stringify(workflowStreamingLogger.error.mock.calls) expect(loggerPayload).not.toContain(secret) @@ -377,8 +375,9 @@ describe('createStreamingResponse', () => { expect(events).toContainEqual({ event: 'stream_error', blockId: 'agent-1', error: message }) expect(workflowStreamingLogger.error).toHaveBeenCalledWith( - '[request-block-stream-failure] Error reading stream for block agent-1', - { errorType: 'error', hasStack: true } + 'Error reading stream for block agent-1', + { errorType: 'error', hasStack: true }, + { requestId: 'request-block-stream-failure' } ) const loggerPayload = JSON.stringify(workflowStreamingLogger.error.mock.calls) expect(loggerPayload).not.toContain(secret) @@ -606,8 +605,9 @@ describe('createStreamingResponse', () => { expect(events).toContainEqual({ event: 'error', blockId: 'block', error: message }) expect(workflowStreamingLogger.warn).toHaveBeenCalledWith( - '[request-selected-output-failure] Failed to materialize selected output', + 'Failed to materialize selected output', { + requestId: 'request-selected-output-failure', blockId: 'block', outputId: 'block_value', errorType: 'error', @@ -1439,13 +1439,13 @@ describe('createStreamingResponse agent-events-v1', () => { await vi.waitFor(() => expect(workflowStreamingLogger.info).toHaveBeenCalledWith( - '[request-client-cancel] Streaming execution aborted by client disconnect' + 'Streaming execution aborted by client disconnect', + { requestId: 'request-client-cancel' } ) ) - expect(workflowStreamingLogger.error).not.toHaveBeenCalledWith( - '[request-client-cancel] Stream error', - expect.anything() - ) + expect( + workflowStreamingLogger.error.mock.calls.some((call: unknown[]) => call[0] === 'Stream error') + ).toBe(false) }) it('thinking never enters streamedChunks / log content rewrite', async () => { diff --git a/apps/sim/lib/workflows/streaming/streaming.ts b/apps/sim/lib/workflows/streaming/streaming.ts index 0aff9846de3..73f5d5d7d9d 100644 --- a/apps/sim/lib/workflows/streaming/streaming.ts +++ b/apps/sim/lib/workflows/streaming/streaming.ts @@ -403,12 +403,12 @@ async function buildMinimalResult( } if (isDangerousKey(blockId)) { - logger.warn(`[${requestId}] Blocked dangerous blockId: ${blockId}`) + logger.warn(`Blocked dangerous blockId: ${blockId}`, { requestId }) continue } if (isDangerousKey(path)) { - logger.warn(`[${requestId}] Blocked dangerous path: ${path}`) + logger.warn(`Blocked dangerous path: ${path}`, { requestId }) continue } @@ -636,7 +636,7 @@ export async function createStreamingResponse( const onStreamCallback = async (streamingExec: StreamingExecution) => { const blockId = streamingExec.blockId if (!blockId) { - logger.warn(`[${requestId}] Streaming execution missing blockId`) + logger.warn('Streaming execution missing blockId', { requestId }) return } @@ -727,8 +727,9 @@ export async function createStreamingResponse( } } catch (error) { logger.error( - `[${requestId}] Error reading stream for block ${blockId}`, - projectResolvedSecretDiagnosticError(error, undefined) + `Error reading stream for block ${blockId}`, + projectResolvedSecretDiagnosticError(error, undefined), + { requestId } ) const frame: ChatStreamStreamErrorFrame = { event: 'stream_error', @@ -863,7 +864,8 @@ export async function createStreamingResponse( }) } } catch (error) { - logger.warn(`[${requestId}] Failed to materialize selected output`, { + logger.warn('Failed to materialize selected output', { + requestId, blockId: selectedOutputBlockId, outputId: descriptor.outputId, ...projectResolvedSecretDiagnosticError(error, undefined), @@ -907,7 +909,8 @@ export async function createStreamingResponse( !requestAborted ) { const timeoutErrorMessage = getTimeoutErrorMessage(timeoutController.timeoutMs) - logger.info(`[${requestId}] Streaming execution timed out`, { + logger.info('Streaming execution timed out', { + requestId, timeoutMs: timeoutController.timeoutMs, }) if (result._streamingMetadata?.loggingSession) { @@ -916,7 +919,7 @@ export async function createStreamingResponse( const frame: ChatStreamErrorFrame = { event: 'error', error: timeoutErrorMessage } enqueue(encodeSSE(frame)) } else if (result.status === 'cancelled' && requestAborted) { - logger.info(`[${requestId}] Streaming execution aborted by client disconnect`) + logger.info('Streaming execution aborted by client disconnect', { requestId }) if (result._streamingMetadata?.loggingSession) { // LoggingSession has no cancelled status; match workflow execute route wording. await result._streamingMetadata.loggingSession.markAsFailed('Client cancelled request') @@ -972,10 +975,9 @@ export async function createStreamingResponse( if (!streamCancelled) controller.close() } catch (error) { - logger.error( - `[${requestId}] Stream error`, - projectResolvedSecretDiagnosticError(error, undefined) - ) + logger.error('Stream error', projectResolvedSecretDiagnosticError(error, undefined), { + requestId, + }) const errorMessage = streamConfig.selectedOutputs?.length && isExecutionResourceLimitError(error) ? SELECTED_OUTPUT_TOO_LARGE_MESSAGE @@ -999,8 +1001,9 @@ export async function createStreamingResponse( async cancel(reason) { streamCancelled = true logger.info( - `[${requestId}] Streaming response cancelled`, - projectResolvedSecretDiagnosticError(reason, undefined) + 'Streaming response cancelled', + projectResolvedSecretDiagnosticError(reason, undefined), + { requestId } ) requestAborted = true stopKeepalive() @@ -1012,8 +1015,9 @@ export async function createStreamingResponse( await cleanupExecutionBase64Cache(executionId) } catch (error) { logger.error( - `[${requestId}] Failed to cleanup base64 cache`, - projectResolvedSecretDiagnosticError(error, undefined) + 'Failed to cleanup base64 cache', + projectResolvedSecretDiagnosticError(error, undefined), + { requestId } ) } } diff --git a/apps/sim/lib/workflows/utils.ts b/apps/sim/lib/workflows/utils.ts index 692403d0bc7..b853b0adaa4 100644 --- a/apps/sim/lib/workflows/utils.ts +++ b/apps/sim/lib/workflows/utils.ts @@ -384,7 +384,7 @@ export async function validateWorkflowPermissions( ) { const session = await getSession() if (!session?.user?.id) { - logger.warn(`[${requestId}] No authenticated user session for workflow ${action}`) + logger.warn(`No authenticated user session for workflow ${action}`, { requestId }) return { error: { message: 'Unauthorized', status: 401 }, session: null, @@ -399,7 +399,7 @@ export async function validateWorkflowPermissions( }) if (!authorization.workflow) { - logger.warn(`[${requestId}] Workflow ${workflowId} not found`) + logger.warn(`Workflow ${workflowId} not found`, { requestId }) return { error: { message: 'Workflow not found', status: 404 }, session: null, @@ -410,13 +410,11 @@ export async function validateWorkflowPermissions( if (!authorization.allowed) { const message = authorization.message || `Unauthorized: Access denied to ${action} this workflow` - logger.warn( - `[${requestId}] User ${session.user.id} unauthorized to ${action} workflow ${workflowId}`, - { - action, - workflowId, - } - ) + logger.warn(`User ${session.user.id} unauthorized to ${action} workflow ${workflowId}`, { + requestId, + action, + workflowId, + }) return { error: { message, status: authorization.status }, session: null, diff --git a/apps/sim/lib/workspace-events/emitter.ts b/apps/sim/lib/workspace-events/emitter.ts index ce672aef57a..f63c5296c9f 100644 --- a/apps/sim/lib/workspace-events/emitter.ts +++ b/apps/sim/lib/workspace-events/emitter.ts @@ -58,16 +58,16 @@ export async function dispatchSimEvent( if (!result.success) { logger.error( - `[${requestId}] Failed to fire sim trigger for workflow ${subscription.workflow.id}:`, + `Failed to fire sim trigger for workflow ${subscription.workflow.id}:`, result.statusCode, - result.error + result.error, + { requestId } ) } } catch (error) { - logger.error( - `[${requestId}] Error firing sim trigger for workflow ${subscription.workflow.id}:`, - error - ) + logger.error(`Error firing sim trigger for workflow ${subscription.workflow.id}:`, error, { + requestId, + }) } } diff --git a/apps/sim/lib/workspaces/lifecycle.ts b/apps/sim/lib/workspaces/lifecycle.ts index 831e57d0d4d..c3824179c7c 100644 --- a/apps/sim/lib/workspaces/lifecycle.ts +++ b/apps/sim/lib/workspaces/lifecycle.ts @@ -101,7 +101,7 @@ export async function archiveWorkspace( }) if (!outcome) return { archived: false } - logger.info(`[${options.requestId}] Archived workspace ${workspaceId}`) + logger.info(`Archived workspace ${workspaceId}`, { requestId: options.requestId }) options.deliverStops?.(outcome.effects.stoppedRuns) await finishEnvironmentArchive(outcome.effects, options.requestId) @@ -172,9 +172,7 @@ export async function finishEnvironmentArchive( cleanupErrors.push(error) return } - logger.warn(`[${requestId}] Post-archive notification failed for workflow ${row.id}`, { - error, - }) + logger.warn(`Post-archive notification failed for workflow ${row.id}`, { requestId, error }) }) ) if (cleanupErrors.length > 0) throw cleanupErrors[0] @@ -185,7 +183,8 @@ export async function finishEnvironmentArchive( try { mcpPubSub.publishWorkflowToolsChanged({ serverId, workspaceId }) } catch (error) { - logger.warn(`[${requestId}] MCP tools-changed publish failed for server ${serverId}`, { + logger.warn(`MCP tools-changed publish failed for server ${serverId}`, { + requestId, error, }) } diff --git a/apps/sim/providers/file-attachments.server.ts b/apps/sim/providers/file-attachments.server.ts index 1f44fd00cd5..9940801c1a5 100644 --- a/apps/sim/providers/file-attachments.server.ts +++ b/apps/sim/providers/file-attachments.server.ts @@ -100,7 +100,8 @@ export async function attachLargeFileRemoteUrls( if (!StorageService.hasCloudStorage()) { logger.warn( - `[${requestId}] Sending "${file.name}" inline for "${providerId}": the large-file path needs cloud storage, which is not configured` + `Sending "${file.name}" inline for "${providerId}": the large-file path needs cloud storage, which is not configured`, + { requestId } ) continue } diff --git a/apps/sim/tools/index.test.ts b/apps/sim/tools/index.test.ts index 30671f74a33..e73a873df37 100644 --- a/apps/sim/tools/index.test.ts +++ b/apps/sim/tools/index.test.ts @@ -1149,8 +1149,10 @@ describe('executeTool Function', () => { expect(result.success).toBe(false) } - const toolFailureLogs = mockToolsLogger.error.mock.calls.filter(([message]) => - String(message).includes('Error executing tool http_request') + const toolFailureLogs = mockToolsLogger.error.mock.calls.filter( + ([message, fields]) => + message === 'Error executing tool' && + (fields as Record | undefined)?.toolId === 'http_request' ) expect(toolFailureLogs).toHaveLength(2) }) @@ -1998,7 +2000,7 @@ describe('executeTool Function', () => { expect(logged).not.toContain('__sim_') expect(mockToolsLogger.warn).toHaveBeenCalledWith( expect.stringContaining('Failed to read non-OK response body for function_execute'), - { errorName: 'Error' } + { requestId: 'mock-request-id', errorName: 'Error' } ) }) diff --git a/apps/sim/tools/index.ts b/apps/sim/tools/index.ts index 74e39549cca..90a8d4be164 100644 --- a/apps/sim/tools/index.ts +++ b/apps/sim/tools/index.ts @@ -583,7 +583,7 @@ async function injectHostedKeyIfNeeded( const byokResult = await getBYOKKey(workspaceId, byokProviderId as BYOKProviderId, { signal }) if (byokResult) { params[apiKeyParam] = byokResult.apiKey - logger.info(`[${requestId}] Using BYOK key for ${tool.id}`) + logger.info(`Using BYOK key for ${tool.id}`, { requestId }) return { isUsingHostedKey: false } } } @@ -593,7 +593,7 @@ async function injectHostedKeyIfNeeded( const billingActorId = workspaceId if (!billingActorId) { - logger.error(`[${requestId}] No workspace ID available for hosted key rate limiting`) + logger.error('No workspace ID available for hosted key rate limiting', { requestId }) return { isUsingHostedKey: false } } @@ -606,7 +606,8 @@ async function injectHostedKeyIfNeeded( ) if (!acquireResult.success && acquireResult.billingActorRateLimited) { - logger.warn(`[${requestId}] Billing actor ${billingActorId} rate limited for ${tool.id}`, { + logger.warn(`Billing actor ${billingActorId} rate limited for ${tool.id}`, { + requestId, provider, retryAfterMs: acquireResult.retryAfterMs, }) @@ -628,7 +629,7 @@ async function injectHostedKeyIfNeeded( } if (!acquireResult.success) { - logger.error(`[${requestId}] No hosted keys configured for ${tool.id}: ${acquireResult.error}`) + logger.error(`No hosted keys configured for ${tool.id}: ${acquireResult.error}`, { requestId }) throw new HostedKeyUnavailableError( acquireResult.error || `No hosted keys configured for ${tool.id}` ) @@ -636,7 +637,8 @@ async function injectHostedKeyIfNeeded( params[apiKeyParam] = acquireResult.key params.__usingHostedKey = true - logger.info(`[${requestId}] Using hosted key for ${tool.id} (${acquireResult.envVarName})`, { + logger.info(`Using hosted key for ${tool.id} (${acquireResult.envVarName})`, { + requestId, keyIndex: acquireResult.keyIndex, provider, }) @@ -678,14 +680,16 @@ async function reacquireHostedKey( if (!acquireResult.success || !acquireResult.key) { logger.warn( - `[${requestId}] Re-acquire of hosted key for ${tool.id} failed: ${acquireResult.error ?? 'unknown'}` + `Re-acquire of hosted key for ${tool.id} failed: ${acquireResult.error ?? 'unknown'}`, + { requestId } ) return null } params[apiKeyParam] = acquireResult.key logger.info( - `[${requestId}] Re-acquired hosted key for ${tool.id} (${acquireResult.envVarName}) after upstream throttling` + `Re-acquired hosted key for ${tool.id} (${acquireResult.envVarName}) after upstream throttling`, + { requestId } ) return acquireResult.envVarName ?? 'unknown' } @@ -780,15 +784,16 @@ async function executeWithRetry( const requeued = await reacquireAfterRetriesExhausted() if (requeued) { logger.warn( - `[${requestId}] Upstream retries exhausted for ${toolId} (${envVarName}); re-queued and retrying once with fresh key` + `Upstream retries exhausted for ${toolId} (${envVarName}); re-queued and retrying once with fresh key`, + { requestId } ) return (await requeued()) as T } } catch (requeueError) { - logger.error( - `[${requestId}] Re-queue after exhausted upstream retries failed for ${toolId}`, - { error: toError(requeueError).message } - ) + logger.error(`Re-queue after exhausted upstream retries failed for ${toolId}`, { + requestId, + error: toError(requeueError).message, + }) } } @@ -818,7 +823,8 @@ async function executeWithRetry( }) logger.warn( - `[${requestId}] Rate limited for ${toolId} (${envVarName}), retrying in ${delayMs}ms (attempt ${attempt + 1}/${maxRetries})` + `Rate limited for ${toolId} (${envVarName}), retrying in ${delayMs}ms (attempt ${attempt + 1}/${maxRetries})`, + { requestId } ) await sleep(delayMs) } @@ -902,10 +908,9 @@ async function processHostedKeyCost( if (cost <= 0) return { cost: 0 } - logger.debug( - `[${requestId}] Hosted key cost for ${tool.id}: $${cost}`, - metadata ? { metadata } : {} - ) + logger.debug(`Hosted key cost for ${tool.id}: $${cost}`, metadata ? { metadata } : {}, { + requestId, + }) return { cost, metadata } } @@ -940,14 +945,15 @@ async function reportCustomDimensionUsage( for (const dim of result.dimensions) { if (!dim.allowed) { - logger.warn(`[${requestId}] Dimension ${dim.name} overdrawn after ${tool.id} execution`, { + logger.warn(`Dimension ${dim.name} overdrawn after ${tool.id} execution`, { + requestId, consumed: dim.consumed, tokensRemaining: dim.tokensRemaining, }) } } } catch (error) { - logger.error(`[${requestId}] Failed to report custom dimension usage for ${tool.id}:`, error) + logger.error(`Failed to report custom dimension usage for ${tool.id}:`, error, { requestId }) } } @@ -1014,10 +1020,11 @@ async function applyHostedKeyCostToResult( // The provider already ran and already charged Sim's key. Failing the // execution here would destroy the caller's result without recovering the // spend, so the run stands and the gap is raised for reconciliation. - logger.error( - `[${requestId}] Hosted-key metering failed for ${tool.id}; execution succeeded unbilled`, - { provider, error: getErrorMessage(error) } - ) + logger.error(`Hosted-key metering failed for ${tool.id}; execution succeeded unbilled`, { + requestId, + provider, + error: getErrorMessage(error), + }) await recordServiceMeteringFailure(`Hosted provider ${provider}: ${getErrorMessage(error)}`) hostedKeyMetrics.recordFailed({ provider, tool: tool.id, key, reason: 'metering' }) } @@ -1141,12 +1148,10 @@ async function readToolResponseBody( throw error } - logger.warn( - `[${options.requestId}] Failed to read non-OK response body for ${options.toolId}`, - { - errorName: toError(error).name, - } - ) + logger.warn(`Failed to read non-OK response body for ${options.toolId}`, { + requestId: options.requestId, + errorName: toError(error).name, + }) return Buffer.alloc(0) } } @@ -1587,7 +1592,8 @@ async function fetchCredentialTokenFromRoute(params: { }) } catch (error: unknown) { const status = error instanceof ApiClientError ? error.status : undefined - logger.error(`[${requestId}] Token fetch failed for ${toolId}:`, { + logger.error(`Token fetch failed for ${toolId}:`, { + requestId, status, error: getErrorMessage(error), }) @@ -1802,7 +1808,7 @@ async function executeToolImplementation( signal: effectiveSignal, }) if (!tool) { - logger.error(`[${requestId}] Custom tool not found: ${normalizedToolId}`) + logger.error(`Custom tool not found: ${normalizedToolId}`, { requestId }) } } else if (isMcpTool(normalizedToolId)) { return await executeMcpTool( @@ -1816,7 +1822,7 @@ async function executeToolImplementation( } else { tool = getTool(normalizedToolId) if (!tool) { - logger.error(`[${requestId}] Built-in tool not found: ${normalizedToolId}`) + logger.error(`Built-in tool not found: ${normalizedToolId}`, { requestId }) } } @@ -1891,7 +1897,7 @@ async function executeToolImplementation( } let refreshCredential: ((signal: AbortSignal) => Promise) | undefined if (contextParams.credential) { - logger.info(`[${requestId}] Resolving tool access token`, { toolId: normalizedToolId }) + logger.info('Resolving tool access token', { requestId, toolId: normalizedToolId }) try { const workflowId = scope.workflowId const userId = scope.userId @@ -2037,7 +2043,7 @@ async function executeToolImplementation( refreshCredential = resolveCredential } - logger.info(`[${requestId}] Successfully got access token for ${toolId}`) + logger.info(`Successfully got access token for ${toolId}`, { requestId }) // Preserve credential for downstream transforms while removing it from request payload // so we don't leak it to external services. @@ -2053,7 +2059,8 @@ async function executeToolImplementation( } catch (error: any) { // A revoked credential is the user's to reconnect; the resolver already logged it. if (!(error instanceof CredentialRevokedError)) { - logger.error(`[${requestId}] Error fetching access token for ${toolId}:`, { + logger.error(`Error fetching access token for ${toolId}:`, { + requestId, error: toError(error).message, }) } @@ -2071,7 +2078,7 @@ async function executeToolImplementation( // admission slot and one top-level log row, and billing/observability match // the canvas workflow block. if (normalizedToolId === 'workflow_executor') { - logger.info(`[${requestId}] Running workflow tool ${toolId} in-process`) + logger.info(`Running workflow tool ${toolId} in-process`, { requestId }) const { runWorkflowTool } = await import('@/executor/handlers/workflow/tool-runner') const result = await runWorkflowTool( { @@ -2119,7 +2126,7 @@ async function executeToolImplementation( } if (normalizedToolId === 'deployed_block_executor') { - logger.info(`[${requestId}] Running custom block tool ${toolId}`) + logger.info(`Running custom block tool ${toolId}`, { requestId }) const { runCustomBlockTool } = await import( '@/executor/handlers/workflow/custom-block-tool-runner' ) @@ -2182,7 +2189,8 @@ async function executeToolImplementation( try { finalResult = await tool.postProcess(result, contextParams, executeNestedTool) } catch (error) { - logger.error(`[${requestId}] Post-processing error for ${toolId}`, { + logger.error(`Post-processing error for ${toolId}`, { + requestId, errorName: toError(error).name, }) } @@ -2276,7 +2284,7 @@ async function executeToolImplementation( } catch (error) { const normalizedError = toError(error) logger.error( - `[${requestId}] Post-processing error for ${toolId}:`, + `Post-processing error for ${toolId}:`, projectToolLogMetadata( { error: normalizedError.message }, resolvedSecretTraceRegistry, @@ -2284,7 +2292,8 @@ async function executeToolImplementation( errorName: normalizedError.name, }, structuralOnlyToolLogs - ) + ), + { requestId } ) finalResult = result } @@ -2336,8 +2345,9 @@ async function executeToolImplementation( /** Sim's own hosted key being refused or throttled is Sim's fault and Sim's capacity. */ if (hostedKeyFailure && hostedKeyFailure !== 'other') markFailureKind(error, 'internal') const toolContext = toRecord(params._context) - logFailureOnce(logger, `[${requestId}] Error executing tool ${toolId}:`, error, { + logFailureOnce(logger, 'Error executing tool', error, { metadata: () => ({ + requestId, toolId, workflowId: executionContext?.workflowId ?? undefined, executionId: executionContext?.executionId, @@ -2989,10 +2999,10 @@ async function executeToolRequest( baseMs: retryConfig.initialDelayMs, maxMs: retryConfig.maxDelayMs, }) - logger.warn( - `[${requestId}] Retrying ${toolId} after error (attempt ${attempt + 1}/${maxAttempts})`, - { delayMs } - ) + logger.warn(`Retrying ${toolId} after error (attempt ${attempt + 1}/${maxAttempts})`, { + requestId, + delayMs, + }) await sleep(delayMs) continue } @@ -3010,7 +3020,8 @@ async function executeToolRequest( ) if (retryAfterMs !== null && retryAfterMs > retryConfig.maxDelayMs) { logger.warn( - `[${requestId}] Retry-After (${retryAfterMs}ms) exceeds maxDelayMs (${retryConfig.maxDelayMs}ms), skipping retry` + `Retry-After (${retryAfterMs}ms) exceeds maxDelayMs (${retryConfig.maxDelayMs}ms), skipping retry`, + { requestId } ) break } @@ -3024,8 +3035,8 @@ async function executeToolRequest( maxMs: retryConfig.maxDelayMs, }) logger.warn( - `[${requestId}] Retrying ${toolId} after HTTP ${response.status} (attempt ${attempt + 1}/${maxAttempts})`, - { delayMs } + `Retrying ${toolId} after HTTP ${response.status} (attempt ${attempt + 1}/${maxAttempts})`, + { requestId, delayMs } ) await sleep(delayMs) continue @@ -3048,7 +3059,7 @@ async function executeToolRequest( errorData = errorText } } catch { - logger.error(`[${requestId}] Failed to read response body for ${toolId}`) + logger.error(`Failed to read response body for ${toolId}`, { requestId }) errorData = null } @@ -3259,7 +3270,7 @@ async function executeMcpTool( const actualStartTime = startTimeISO || new Date().toISOString() try { - logger.info(`[${actualRequestId}] Executing MCP tool: ${toolId}`) + logger.info(`Executing MCP tool: ${toolId}`, { requestId: actualRequestId }) validateRequestBodySize(JSON.stringify(params)) const handler = await getInternalToolOperationHandler(toolId) if (!handler) throw new Error(`No internal operation registered for ${toolId}`) @@ -3294,7 +3305,9 @@ async function executeMcpTool( if (!response.ok) { if (response.status === 413) { - logger.error(`[${actualRequestId}] Request body too large for mcp:${toolId} (HTTP 413)`) + logger.error(`Request body too large for mcp:${toolId} (HTTP 413)`, { + requestId: actualRequestId, + }) return { success: false, output: {}, @@ -3335,7 +3348,7 @@ async function executeMcpTool( } } - logger.info(`[${actualRequestId}] MCP tool ${toolId} executed successfully`) + logger.info(`MCP tool ${toolId} executed successfully`, { requestId: actualRequestId }) return { success: true, @@ -3361,21 +3374,18 @@ async function executeMcpTool( const errorMsg = toError(error).message if (isBodySizeLimitError(errorMsg)) { const failure = bodySizeLimitError() - logFailureOnce( - logger, - `[${actualRequestId}] Request body size limit exceeded for mcp:${toolId}:`, - failure, - { - metadata: () => ({ - ...runIdentity, - ...projectToolLogMetadata( - { originalError: errorMsg }, - context?.resolvedSecretTraceRegistry, - { hasOriginalError: errorMsg.length > 0 } - ), - }), - } - ) + logFailureOnce(logger, 'Request body size limit exceeded for MCP tool', failure, { + metadata: () => ({ + requestId: actualRequestId, + toolId, + ...runIdentity, + ...projectToolLogMetadata( + { originalError: errorMsg }, + context?.resolvedSecretTraceRegistry, + { hasOriginalError: errorMsg.length > 0 } + ), + }), + }) return { success: false, output: loggedFailureOutput(failure), @@ -3389,8 +3399,10 @@ async function executeMcpTool( } const normalizedError = toError(error) - logFailureOnce(logger, `[${actualRequestId}] Error executing MCP tool ${toolId}:`, error, { + logFailureOnce(logger, 'Error executing MCP tool', error, { metadata: () => ({ + requestId: actualRequestId, + toolId, ...runIdentity, ...projectToolLogMetadata( { diff --git a/packages/logger/src/index.test.ts b/packages/logger/src/index.test.ts index a48f79b97cd..114be4d03be 100644 --- a/packages/logger/src/index.test.ts +++ b/packages/logger/src/index.test.ts @@ -117,6 +117,58 @@ describe('Logger', () => { }) }) + describe('request id ownership', () => { + const jsonLogger = () => + new Logger('Test', { enabled: true, colorize: false, logLevel: LogLevel.INFO }) + const lines = (): Array> => + consoleLogSpy.mock.calls.map((call: unknown[]) => JSON.parse(call[0] as string)) + + test('keeps the request context id over a per-line or bound requestId', () => { + const logger = jsonLogger() + runWithRequestContext({ requestId: 'req-context' }, () => { + logger.info('Per-line id', { requestId: 'replay-key' }) + logger.withMetadata({ requestId: 'bound-id' }).info('Bound id') + }) + expect(lines().map((line) => line.requestId)).toEqual(['req-context', 'req-context']) + }) + + test('uses a per-line requestId outside a request context', () => { + jsonLogger().info('Background step', { requestId: 'task-run' }) + expect(lines()[0]).toMatchObject({ requestId: 'task-run' }) + }) + + test('exports scalar fields passed with a line as OpenTelemetry attributes', () => { + const emit = vi.fn() + const getLoggerSpy = vi + .spyOn(logs, 'getLogger') + .mockReturnValue({ emit, enabled: () => true }) + try { + const logger = jsonLogger() + logger.info('Outside a request', { requestId: 'task-run', documentId: 'doc-1' }) + runWithRequestContext({ requestId: 'req-context' }, () => { + logger.info('Inside a request', { requestId: 'replay-key', attempt: 2, nested: { a: 1 } }) + }) + const attributes = emit.mock.calls.map( + ([record]) => (record as { attributes: Record }).attributes + ) + expect(attributes[0]).toMatchObject({ requestId: 'task-run', documentId: 'doc-1' }) + expect(attributes[1]).toMatchObject({ requestId: 'req-context', attempt: '2' }) + expect(attributes[1]).not.toHaveProperty('nested') + expect(attributes[1]['log.args']).toContain('"nested"') + } finally { + getLoggerSpy.mockRestore() + } + }) + + test('never lets an absent optional field erase one the line carries', () => { + const logger = jsonLogger().withMetadata({ workflowId: 'workflow-1' }) + runWithRequestContext({ requestId: 'req-context' }, () => { + logger.info('Optional fields', { requestId: undefined, workflowId: undefined }) + }) + expect(lines()[0]).toMatchObject({ requestId: 'req-context', workflowId: 'workflow-1' }) + }) + }) + describe('structured serialization safety', () => { const createEnabledLogger = () => new Logger('Test', { enabled: true, colorize: false, logLevel: LogLevel.DEBUG }) diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index 304e070c5e3..c57fcb858f8 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -255,6 +255,8 @@ const mergeArgs = (entry: Record, args: unknown[]): Record for (const key of Object.keys(source)) { const value = source[key] + // An absent optional field must not erase one the line already carries, such as `requestId`. + if (value === undefined) continue if (value instanceof Error) { const logged = toLoggedError(value) entry[key] = logged.message @@ -312,13 +314,17 @@ const tolerantReplacer = () => { * path — losing the line and aborting whatever was being logged about. A * logger must never be able to break its caller. */ -const serializeEntry = (base: Record, args: unknown[]): string => { +const serializeEntry = ( + base: Record, + args: unknown[], + owned: Record +): string => { try { - return JSON.stringify(mergeArgs({ ...base }, args), errorReplacer(false)) + return JSON.stringify({ ...mergeArgs({ ...base }, args), ...owned }, errorReplacer(false)) } catch {} try { - return JSON.stringify(mergeArgs({ ...base }, args), tolerantReplacer()) + return JSON.stringify({ ...mergeArgs({ ...base }, args), ...owned }, tolerantReplacer()) } catch {} return minimalEntry(base) @@ -442,7 +448,9 @@ export class Logger { /** * Creates a child logger with additional metadata merged in. * The child inherits this logger's module name, config, and existing metadata. - * New metadata keys override existing ones with the same name. + * New metadata keys override existing ones with the same name, except + * `requestId`: inside a request context the context's id is authoritative, so + * a bound or per-line `requestId` only names the request outside one. */ withMetadata(metadata: LoggerMetadata): Logger { const child = Object.create(Logger.prototype) as Logger @@ -491,10 +499,12 @@ export class Logger { const formattedArgs = this.formatArgs(args) const reqCtx = getRequestContext() + // The request context owns `requestId`: a per-line or bound one only names the request outside it. + const owned: LoggerMetadata = reqCtx ? { requestId: reqCtx.requestId } : {} const effectiveMetadata = reqCtx - ? { ...requestContextMetadata(reqCtx), ...this.metadata } + ? { ...requestContextMetadata(reqCtx), ...this.metadata, ...owned } : this.metadata - emitOtelLogRecord(level, this.module, message, effectiveMetadata, args) + emitOtelLogRecord(level, this.module, message, effectiveMetadata, args, owned) const metadataEntries = Object.entries(filterUndefined(effectiveMetadata)) const metadataStr = metadataEntries.length > 0 @@ -541,7 +551,7 @@ export class Logger { base[k] = v } - const line = serializeEntry(base, args) + const line = serializeEntry(base, args, owned) if (level === LogLevel.ERROR) { console.error(line) } else { @@ -628,6 +638,12 @@ const OTEL_LOG_SEVERITY: Record + typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean' || + typeof value === 'bigint' + /** * Fans every accepted log line out through the OTel Logs API. Until an * application installs a global LoggerProvider (apps/sim does in @@ -642,7 +658,8 @@ function emitOtelLogRecord( module: string, message: string, metadata: Record, - args: unknown[] + args: unknown[], + owned: Record ): void { try { const severity = OTEL_LOG_SEVERITY[level] @@ -650,6 +667,19 @@ function emitOtelLogRecord( for (const [key, value] of Object.entries(filterUndefined(metadata))) { attributes[key] = String(value) } + // Scalar fields passed with the line become attributes, as they are top-level keys in the JSON + // line, so `{ documentId }` filters the same way in both; nested values stay in `log.args`. + for (const arg of args) { + if (!isRecordLike(arg) || arg instanceof Error) continue + try { + for (const [key, value] of Object.entries(arg)) { + if (isOtelScalar(value)) attributes[key] = String(value) + } + } catch { + // A throwing getter loses only its own argument's fields; `log.args` still records it. + } + } + for (const [key, value] of Object.entries(owned)) attributes[key] = String(value) const error = primaryError(args) if (error) { const logged = toLoggedError(error) diff --git a/scripts/check-source-conventions.baseline.json b/scripts/check-source-conventions.baseline.json index 557ea8e463b..de87ae606f5 100644 --- a/scripts/check-source-conventions.baseline.json +++ b/scripts/check-source-conventions.baseline.json @@ -668,5 +668,6 @@ "bare-route-handler": { "apps/sim/app/api/health/route.ts": 1, "apps/sim/app/api/mothership/chat/stream/route.ts": 1 - } + }, + "log-message-tag": {} } diff --git a/scripts/check-source-conventions.test.ts b/scripts/check-source-conventions.test.ts index c832ca7b418..2563c702540 100644 --- a/scripts/check-source-conventions.test.ts +++ b/scripts/check-source-conventions.test.ts @@ -347,6 +347,133 @@ describe('bare-route-handler', () => { }) }) +describe('log-message-tag', () => { + const MODULE = 'apps/sim/lib/feature/service.ts' + const withLogger = (...body: string[]) => + [ + "import { createLogger } from '@sim/logger'", + "const logger = createLogger('Feature')", + ...body, + ].join('\n') + + it('flags a message that opens with an interpolated tag, also after static tags', () => { + const source = withLogger( + `logger.info(\`[\${requestId}] Started\`)`, + `logger.warn(\`[\${documentId}] Retrying\`, { attempt })`, + `logger.error(\`[Cascade] [\${dispatchId}] Failed\`)`, + `logger.debug(\`[\${label}/phase] Complete\`)` + ) + expect(lines(MODULE, source, 'log-message-tag')).toEqual([3, 4, 5, 6]) + }) + + it('flags a request id interpolated anywhere, through any receiver named like a logger', () => { + const source = withLogger( + `logger.error(\`Polling failed (\${requestId}):\`, error)`, + `this.logger.info(\`Done for \${ctx.requestId}\`)`, + `routeLogger.warn(\`Slow \${options?.requestId ?? "unknown"}\`)` + ) + expect(lines(MODULE, source, 'log-message-tag')).toEqual([3, 4, 5]) + }) + + it('follows the message through concatenation, a ternary, a local const and a prefix const', () => { + const source = withLogger( + "logger.info('[' + requestId + '] Started')", + `logger.info(done ? \`[\${id}] Done\` : \`Pending\`)`, + `const message = \`[\${requestId}] Could not refresh\``, + 'logger.warn(message)', + `const logPrefix = requestId ? \`[\${requestId}] \` : ''`, + `logger.error(\`\${logPrefix}Failed to save\`)` + ) + expect(lines(MODULE, source, 'log-message-tag')).toEqual([3, 4, 6, 8]) + }) + + it('checks the message a helper logs for its caller', () => { + const source = withLogger( + `logFailureOnce(logger, \`[\${requestId}] Execution failed\`, error)`, + `logFn.call(logger, \`Run ended (\${requestId})\`, error)` + ) + expect(lines(MODULE, source, 'log-message-tag')).toEqual([3, 4]) + }) + + it('sees optional calls, computed levels, derived loggers and log-named receivers', () => { + const source = withLogger( + `logger?.warn(\`[\${requestId}] Skipped\`)`, + `logger[level](\`[\${requestId}] Done\`)`, + `logger.withMetadata({ a }).info(\`[\${requestId}] Bound\`)`, + `childLog.error(\`[\${requestId}] Child\`)`, + `log2.info(\`Retry for \${reqId}\`)` + ) + expect(lines(MODULE, source, 'log-message-tag')).toEqual([3, 4, 5, 6, 7]) + }) + + it('follows a let prefix but resolves each name in its own function', () => { + const source = withLogger( + 'function first() {', + ` let prefix = \`[\${requestId}] \``, + ` logger.info(\`\${prefix}Saved\`)`, + '}', + 'function second(message) {', + ' logger.info(message)', + '}', + 'function third() {', + " const prefix = 'Sync: '", + ` logger.info(\`\${prefix}done\`)`, + '}' + ) + expect(lines(MODULE, source, 'log-message-tag')).toEqual([5]) + }) + + it('checks a module whose only request id is a reqId', () => { + const source = withLogger(`log.info(\`Retry for \${reqId}\`)`) + expect(lines(MODULE, source, 'log-message-tag')).toEqual([3]) + }) + + it('stops following a let binding that is reassigned before it is logged', () => { + const source = withLogger( + `let message = \`[\${requestId}] Failed\``, + "message = 'Started'", + 'logger.info(message)' + ) + expect(hits(MODULE, source, 'log-message-tag')).toEqual([]) + }) + + it('does not treat other uses of request ids or loggers as a tagged message', () => { + const source = withLogger( + `logger.info(\`Kept \${rows.filter((row) => row.requestId).length} rows\`)`, + `logger.info(\`Approved access request \${accessRequestId}\`)`, + `withSpan(logger, \`poll:\${requestId}\`, run)` + ) + expect(hits(MODULE, source, 'log-message-tag')).toEqual([]) + }) + + it('allows static messages, static tags, ids in metadata and bracketed lists mid-message', () => { + const source = withLogger( + "logger.info('Started', { requestId })", + "logger.info('[Cascade] dispatch started', { requestId, dispatchId })", + `logger.info(\`Updated columns [\${columnIds.join(", ")}] in \${tableId}\`)`, + `logger.warn(\`Retrying \${attempt} of \${max}\`, { requestId })` + ) + expect(hits(MODULE, source, 'log-message-tag')).toEqual([]) + }) + + it('ignores receivers that are not loggers, modules without @sim/logger, and tests', () => { + expect( + hits(MODULE, withLogger(`cache.info(\`[\${requestId}] hit\`)`), 'log-message-tag') + ).toEqual([]) + const electronLog = `import log from 'electron-log'\nlog.info(\`[\${requestId}] Started\`)` + expect(hits('apps/desktop/src/main/window.ts', electronLog, 'log-message-tag')).toEqual([]) + const tagged = withLogger(`logger.info(\`[\${requestId}] Started\`)`) + expect(hits('apps/sim/lib/feature/service.test.ts', tagged, 'log-message-tag')).toEqual([]) + }) + + it('covers every app and package, while the apps/sim rules stay scoped to apps/sim', () => { + const tagged = withLogger("import { a } from './a'", `logger.info(\`[\${requestId}] Started\`)`) + expect(hits('packages/audit/src/log.ts', tagged, 'log-message-tag')).toHaveLength(1) + expect(hits('apps/realtime/src/handlers/rooms.ts', tagged, 'log-message-tag')).toHaveLength(1) + expect(hits('packages/audit/src/log.ts', tagged, 'relative-import')).toEqual([]) + }) +}) + describe('baseline ratchet', () => { const EMPTY: CurrentCounts = { 'relative-import': {}, @@ -354,6 +481,7 @@ describe('baseline ratchet', () => { 'static-inline-style': {}, 'api-db-import': {}, 'bare-route-handler': {}, + 'log-message-tag': {}, } const counts = (partial: Partial): CurrentCounts => ({ ...EMPTY, ...partial }) const BASELINE: Baseline = { ...EMPTY, 'relative-import': { 'a.ts': 2, 'b.ts': 1 } } @@ -437,7 +565,7 @@ describe('baseline ratchet', () => { it('rejects an --init-rule that names no rule', () => { expect(nextBaseline(BASELINE, EMPTY, 'no-such-rule')).toEqual({ errors: [ - '--init-rule no-such-rule: not a rule in this script (relative-import, text-size-literal, static-inline-style, api-db-import, bare-route-handler)', + '--init-rule no-such-rule: not a rule in this script (relative-import, text-size-literal, static-inline-style, api-db-import, bare-route-handler, log-message-tag)', ], }) }) diff --git a/scripts/check-source-conventions.ts b/scripts/check-source-conventions.ts index 0f7ad113d9d..ba4ce7ce89d 100644 --- a/scripts/check-source-conventions.ts +++ b/scripts/check-source-conventions.ts @@ -31,6 +31,14 @@ * member of such a result, and a synchronous expression arrow that delegates to one, such as * `(req, ctx) => withSpan(…, () => handler(req, ctx))`. Whether a factory in another module * applies `withRouteHandler` is that module's concern; this rule only sees the route file. + * - `log-message-tag` (CLAUDE.md "Logging"): a `@sim/logger` message opens with an interpolated tag + * (`[${requestId}] Started`, also after static tags such as `[Cascade] [${id}]`) or interpolates a + * request id anywhere (`Polling failed (${requestId})`). Correlation ids belong in metadata, where + * queries can filter on them, and a message whose text varies per request cannot be grouped. The + * request context already stamps `requestId` on every line inside `withRouteHandler` and the + * background tasks that bind one; elsewhere pass it as a field. A bracketed list mid-message + * (`columns [${ids.join(', ')}]`) is not a tag. Unlike the rules above, this one covers every + * `apps/*` and `packages/*` module that imports `@sim/logger`, not only `apps/sim`. * * Existing hits are counted per rule and file in `scripts/check-source-conventions.baseline.json`. * A file whose count rises, or that gains its first hit, fails. A count that drops also fails until @@ -56,6 +64,7 @@ const RULES = [ 'static-inline-style', 'api-db-import', 'bare-route-handler', + 'log-message-tag', ] as const export type Rule = (typeof RULES)[number] @@ -88,6 +97,8 @@ const HOW_TO_FIX: Record = { 'Move the query into an application use case or manager and call it from the route; surface adapters never query protected data (CLAUDE.md "Application Operation Boundary"). Type-only imports are fine.', 'bare-route-handler': 'Export the verb as the result of a shared route builder (`defineInternalJsonRoute`, `defineV2JsonRoute`, …) or `withRouteHandler`, never a bare handler function (CLAUDE.md "API Contracts and Routes").', + 'log-message-tag': + 'Keep the message static and move the id into metadata: `logger.info(\'Started\', { requestId })`. Inside `withRouteHandler`, a `generateRequestId()` id is already on every line, so drop it (CLAUDE.md "Logging").', } const HTTP_VERBS = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']) @@ -117,6 +128,23 @@ const TRANSPARENT_WRAPPERS = new Set([ type Node = { type: string; loc?: { start: { line: number } }; [key: string]: unknown } +const LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error']) +/** Names a logger is bound to: `logger`, `log`, `log2`, `routeLogger`, `childLog`, `this.logger`. */ +const LOGGER_NAME = /^(?:log|logger|[a-z][A-Za-z0-9]*(?:Log|Logger))\d*$/ +/** Helpers that log a message for their caller, handed its logger first: `logFailureOnce`. */ +const LOGGING_HELPER = /^log[A-Z]/ +/** Message text before its first interpolation that leaves a tag open: `[`, `[Cascade] [`. */ +const OPEN_TAG = /^(?:\[[^\]]*\]\s*)*\[$/ +const REQUEST_ID_NAMES = new Set(['requestId', 'reqId']) +const FUNCTION_NODES = new Set([ + 'FunctionDeclaration', + 'FunctionExpression', + 'ArrowFunctionExpression', + 'ObjectMethod', + 'ClassMethod', + 'ClassPrivateMethod', +]) + function walk(node: unknown, visit: (node: Node) => void): void { if (!node || typeof node !== 'object') return if (Array.isArray(node)) { @@ -533,6 +561,241 @@ function isContractCopy(file: string, source: string): boolean { return file.startsWith(CONTRACT_COPY_DIR) && source.startsWith(CONTRACT_COPY_HEADER) } +function isMemberExpression(node: Node): boolean { + return node.type === 'MemberExpression' || node.type === 'OptionalMemberExpression' +} + +function isCallExpression(node: Node): boolean { + return node.type === 'CallExpression' || node.type === 'OptionalCallExpression' +} + +/** `logger` for `logger`, `this.logger` and `a?.logger`; undefined for anything else. */ +function boundName(node: Node): string | undefined { + if (node.type === 'Identifier') return node.name as string + const property = node.property as Node | undefined + return isMemberExpression(node) && !node.computed && property?.type === 'Identifier' + ? (property.name as string) + : undefined +} + +/** A logger by name, or one derived from it with `withMetadata(…)`. */ +function isLogger(node: Node | undefined): boolean { + const current = unwrap(node) + if (!current) return false + if (isCallExpression(current)) { + const callee = unwrap(current.callee as Node) + return ( + callee !== undefined && + isMemberExpression(callee) && + boundName(callee) === 'withMetadata' && + isLogger(callee.object as Node) + ) + } + const name = boundName(current) + return name !== undefined && LOGGER_NAME.test(name) +} + +/** + * The message a call logs: the first argument of `logger.info(…)`, `logger?.warn(…)` or + * `logger[level](…)`, or the second of `logFn.call(logger, message)` and of a `log*` helper handed + * the caller's logger, such as `logFailureOnce(logger, message, error)`. + */ +function loggedMessage(call: Node): Node | undefined { + const callee = unwrap(call.callee as Node) + const args = call.arguments as Node[] + if (!callee) return undefined + if (isMemberExpression(callee) && isLogger(callee.object as Node)) { + const level = boundName(callee) + return callee.computed || (level !== undefined && LOG_LEVELS.has(level)) ? args[0] : undefined + } + const handsLogger = isLogger(args[0]) + if (isMemberExpression(callee) && boundName(callee) === 'call') + return handsLogger ? args[1] : undefined + return callee.type === 'Identifier' && LOGGING_HELPER.test(callee.name as string) && handsLogger + ? args[1] + : undefined +} + +/** `${requestId}`, `${ctx.requestId}`, `${requestId ?? 'unknown'}`: an interpolated request id. */ +function isRequestIdReference(expression: Node): boolean { + let current = unwrap(expression) + while ( + current?.type === 'LogicalExpression' && + (current.operator === '??' || current.operator === '||') + ) { + current = unwrap(current.left as Node) + } + const name = current && boundName(current) + return name !== undefined && REQUEST_ID_NAMES.has(name) +} + +/** A message's parts in order: static text as strings, interpolations as nodes. */ +function messageParts(message: Node): Array | undefined { + if (message.type === 'TemplateLiteral') { + const quasis = message.quasis as Node[] + const expressions = message.expressions as Node[] + return quasis.flatMap((quasi, index) => { + const text = (quasi.value as { cooked: string | null }).cooked ?? '' + return index < expressions.length ? [text, expressions[index]] : [text] + }) + } + if (message.type === 'BinaryExpression' && message.operator === '+') { + const left = messageParts(message.left as Node) ?? [message.left as Node] + const right = messageParts(message.right as Node) ?? [message.right as Node] + return [...left, ...right] + } + const text = stringValue(message) + return text === undefined ? undefined : [text] +} + +/** + * Local bindings by function, so a message or prefix built before the call is checked where it is + * logged. A parameter is recorded without a value: it shadows an outer binding of the same name. + */ +type Bindings = Map + +class MessageResolver { + private readonly bindings = new Map() + /** `let` bindings by function; one reassigned anywhere in the file has no single value to follow. */ + private readonly lets = new Map>() + private readonly reassigned = new Set() + + constructor(program: Node) { + this.collect(program, program) + } + + private declare(scope: Node, name: string, init?: Node): void { + const names = this.bindings.get(scope) ?? new Map() + names.set(name, [...(names.get(name) ?? []), ...(init ? [init] : [])]) + this.bindings.set(scope, names) + } + + private collect(node: unknown, scope: Node): void { + if (!node || typeof node !== 'object') return + if (Array.isArray(node)) { + for (const child of node) this.collect(child, scope) + return + } + const current = node as Node + let inner = scope + if (FUNCTION_NODES.has(current.type)) { + inner = current + for (const param of (current.params as Node[]) ?? []) { + if (param.type === 'Identifier') this.declare(inner, param.name as string) + } + } + if (current.type === 'VariableDeclaration' && current.kind !== 'var') { + for (const declarator of current.declarations as Node[]) { + const id = declarator.id as Node + if (id.type === 'Identifier') { + this.declare(scope, id.name as string, (declarator.init as Node | null) ?? undefined) + if (current.kind === 'let') { + this.lets.set(scope, (this.lets.get(scope) ?? new Set()).add(id.name as string)) + } + } + } + } + const target = (current.type === 'AssignmentExpression' ? current.left : undefined) as + | Node + | undefined + if (target?.type === 'Identifier') this.reassigned.add(target.name as string) + for (const [key, value] of Object.entries(current)) { + if (key === 'loc' || key === 'start' || key === 'end' || key === 'extra') continue + this.collect(value, inner) + } + } + + /** What `name` holds where it is used: the nearest enclosing function that declares it wins. */ + private resolve(name: string, scopes: Node[]): Node[] { + for (let index = scopes.length - 1; index >= 0; index--) { + const values = this.bindings.get(scopes[index])?.get(name) + if (!values) continue + return this.lets.get(scopes[index])?.has(name) && this.reassigned.has(name) ? [] : values + } + return [] + } + + /** The message's possible values: both arms of a ternary, and the local binding it names. */ + candidates(message: Node, scopes: Node[], seen = new Set()): Node[] { + const current = unwrap(message) ?? message + if (current.type === 'ConditionalExpression') { + return [ + ...this.candidates(current.consequent as Node, scopes, seen), + ...this.candidates(current.alternate as Node, scopes, seen), + ] + } + if (current.type === 'Identifier' && !seen.has(current.name as string)) { + seen.add(current.name as string) + return this.resolve(current.name as string, scopes).flatMap((init) => + this.candidates(init, scopes, seen) + ) + } + return [current] + } + + /** + * Whether a message opens with an interpolated tag or interpolates a request id. An interpolated + * local binding counts as what it holds, so `${logPrefix}Saved` is checked as the + * `[${requestId}] ` it was built from. + */ + isTagged(message: Node, scopes: Node[], seen: Set): boolean { + const parts = messageParts(message) + if (!parts) return false + const firstInterpolation = parts.findIndex((part) => typeof part !== 'string') + if (firstInterpolation === -1) return false + const lead = parts + .slice(0, firstInterpolation) + .filter((part): part is string => typeof part === 'string') + .join('') + if (OPEN_TAG.test(lead)) return true + return parts.some( + (part) => + typeof part !== 'string' && + (isRequestIdReference(part) || + (part.type === 'Identifier' && + this.candidates(part, scopes, seen).some( + (candidate) => candidate !== part && this.isTagged(candidate, scopes, seen) + ))) + ) + } +} + +function findLogMessageTags(file: string, source: string, program: Node): Violation[] { + const violations: Violation[] = [] + const resolver = new MessageResolver(program) + const visit = (node: unknown, scopes: Node[]): void => { + if (!node || typeof node !== 'object') return + if (Array.isArray(node)) { + for (const child of node) visit(child, scopes) + return + } + const current = node as Node + const inner = FUNCTION_NODES.has(current.type) ? [...scopes, current] : scopes + const message = isCallExpression(current) ? loggedMessage(current) : undefined + if (message) { + const seen = new Set() + const tagged = resolver + .candidates(message, inner, seen) + .find((candidate) => resolver.isTagged(candidate, inner, seen)) + if (tagged) { + const text = source.slice(tagged.start as number, tagged.end as number).replace(/\s+/g, ' ') + violations.push({ + rule: 'log-message-tag', + file, + line: lineOf(current), + detail: text.length > 80 ? `${text.slice(0, 77)}...` : text, + }) + } + } + for (const [key, value] of Object.entries(current)) { + if (key === 'loc' || key === 'start' || key === 'end' || key === 'extra') continue + visit(value, inner) + } + } + visit(program, [program]) + return violations +} + /** Unit, integration and live tests, plus fixtures: they bind real boundaries on purpose. */ function isTestFile(file: string): boolean { return /\.(?:test|spec|integration)\.tsx?$/.test(file) || file.includes('/__fixtures__/') @@ -540,16 +803,23 @@ function isTestFile(file: string): boolean { /** Every convention violation in one file; `file` is repo-relative with forward slashes. */ export function findViolations(file: string, source: string): Violation[] { - if (!file.startsWith('apps/sim/') || !/\.tsx?$/.test(file) || file.endsWith('.d.ts')) return [] + if (!/^(?:apps|packages)\//.test(file) || !/\.tsx?$/.test(file) || file.endsWith('.d.ts')) { + return [] + } + const inSim = file.startsWith('apps/sim/') const test = isTestFile(file) const isApi = file.startsWith('apps/sim/app/api/') && !test // Cheap textual gates; each is a superset of what its rule can match, and the AST decides. - const wantsRelative = !isContractCopy(file, source) && /['"`]\.\.?\//.test(source) - const wantsTextSize = !test && /text-\[\d/.test(source) - const wantsStyle = !test && file.endsWith('.tsx') && /\bstyle\s*=\s*\{/.test(source) + const wantsRelative = inSim && !isContractCopy(file, source) && /['"`]\.\.?\//.test(source) + const wantsTextSize = inSim && !test && /text-\[\d/.test(source) + const wantsStyle = inSim && !test && file.endsWith('.tsx') && /\bstyle\s*=\s*\{/.test(source) const wantsDb = isApi && source.includes('@sim/db') const wantsRoute = isApi && /(?:^|\/)route\.ts$/.test(file) - if (!(wantsRelative || wantsTextSize || wantsStyle || wantsDb || wantsRoute)) return [] + const wantsLogTag = + !test && source.includes('@sim/logger') && /\[(?:\$\{|['"`]\s*\+)|req(?:uest)?Id/i.test(source) + if (!(wantsRelative || wantsTextSize || wantsStyle || wantsDb || wantsRoute || wantsLogTag)) { + return [] + } const program = parseSource(file, source) return [ @@ -558,6 +828,7 @@ export function findViolations(file: string, source: string): Violation[] { ...(wantsStyle ? findStaticInlineStyles(file, program) : []), ...(wantsDb ? findApiDbImports(file, program) : []), ...(wantsRoute ? findBareRouteHandlers(file, program) : []), + ...(wantsLogTag ? findLogMessageTags(file, source, program) : []), ] } @@ -639,7 +910,7 @@ export function nextBaseline( function sourceFiles(): string[] { const output = execFileSync( 'git', - ['ls-files', '--cached', '--others', '--exclude-standard', '-z', 'apps/sim'], + ['ls-files', '--cached', '--others', '--exclude-standard', '-z', 'apps', 'packages'], { cwd: ROOT, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 } ) return [...new Set(output.split('\0'))]