From 40aa955aa389bea7c1dab7123a4f3da2a15042a0 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 5 Nov 2026 16:18:53 -0800 Subject: [PATCH 1/5] improvement(logs): move correlation ids from message tags into metadata and enforce it Log messages no longer open with an interpolated [${id}] tag or interpolate a request id. The id moves into the line's metadata, so messages group in queries and lines filter by field. Inside withRouteHandler a generateRequestId() id is already stamped on every line by the request context, so those tags are dropped without a field. Routes, table runners, the Sim event emitter and the poll orchestrator that minted their own correlation ids now use generateRequestId(), so their lines carry the request's id. Each webhook poll logs through a logger bound to its webhook id, so concurrent polls in one tick stay distinguishable. check:source-conventions gains log-message-tag: it covers every app and package that imports @sim/logger, follows a message through concatenation, ternaries, local consts and helpers handed the caller's logger, and starts at zero. --- .agents/skills/add-trigger/SKILL.md | 2 +- AGENTS.md | 2 +- CLAUDE.md | 2 +- .../app/api/auth/oauth/credentials/route.ts | 13 +- apps/sim/app/api/auth/oauth/token/route.ts | 20 +- apps/sim/app/api/billing/update-cost/route.ts | 28 +-- .../app/api/chat/[identifier]/otp/route.ts | 23 ++- apps/sim/app/api/chat/[identifier]/route.ts | 26 ++- .../app/api/chat/[identifier]/sso/route.ts | 10 +- apps/sim/app/api/contact/route.ts | 33 ++-- apps/sim/app/api/copilot/confirm/route.ts | 6 +- apps/sim/app/api/copilot/feedback/route.ts | 18 +- .../app/api/copilot/tool-permission/route.ts | 3 +- apps/sim/app/api/demo-requests/route.ts | 18 +- apps/sim/app/api/environment/route.ts | 17 +- apps/sim/app/api/files/authorization.ts | 4 +- .../app/api/folders/[id]/duplicate/route.ts | 15 +- apps/sim/app/api/folders/reorder/route.ts | 12 +- .../app/api/help/integration-request/route.ts | 18 +- apps/sim/app/api/help/route.ts | 21 +- apps/sim/app/api/jobs/[jobId]/route.ts | 12 +- .../knowledge/connectors/member-sync/route.ts | 21 +- .../api/knowledge/connectors/sync/route.ts | 19 +- apps/sim/app/api/logs/stats/route.ts | 9 +- .../app/api/mcp/servers/[id]/refresh/route.ts | 20 +- apps/sim/app/api/mcp/servers/[id]/route.ts | 16 +- apps/sim/app/api/mcp/servers/route.ts | 27 +-- .../api/mcp/servers/test-connection/route.ts | 16 +- apps/sim/app/api/mcp/tools/discover/route.ts | 22 ++- apps/sim/app/api/mcp/tools/stored/route.ts | 7 +- .../api/mcp/workflow-servers/[id]/route.ts | 22 +-- .../[id]/tools/[toolId]/route.ts | 16 +- .../mcp/workflow-servers/[id]/tools/route.ts | 14 +- .../sim/app/api/mcp/workflow-servers/route.ts | 21 +- apps/sim/app/api/resume/poll/route.ts | 4 +- apps/sim/app/api/schedules/[id]/route.ts | 28 +-- apps/sim/app/api/schedules/execute/route.ts | 104 ++++++---- apps/sim/app/api/schedules/route.ts | 8 +- apps/sim/app/api/skills/route.ts | 31 ++- .../api/table/[tableId]/cancel-runs/route.ts | 4 +- .../app/api/table/[tableId]/columns/route.ts | 12 +- .../api/table/[tableId]/delete-async/route.ts | 2 +- .../api/table/[tableId]/dispatches/route.ts | 2 +- .../app/api/table/[tableId]/import/route.ts | 4 +- .../app/api/table/[tableId]/metadata/route.ts | 4 +- .../app/api/table/[tableId]/restore/route.ts | 4 +- apps/sim/app/api/table/[tableId]/route.ts | 8 +- .../api/table/[tableId]/rows/find/route.ts | 4 +- .../table/[tableId]/views/[viewId]/route.ts | 4 +- .../app/api/table/[tableId]/views/route.ts | 4 +- apps/sim/app/api/table/import-csv/route.ts | 2 +- apps/sim/app/api/table/jobs/route.ts | 5 +- apps/sim/app/api/table/utils.ts | 8 +- apps/sim/app/api/tools/custom/route.ts | 53 ++--- .../users/me/settings/unsubscribe/route.ts | 31 ++- .../v1/admin/workflows/[id]/deploy/route.ts | 2 +- .../versions/[versionId]/activate/route.ts | 7 +- apps/sim/app/api/v1/audit-logs/[id]/route.ts | 5 +- apps/sim/app/api/v1/audit-logs/route.ts | 5 +- apps/sim/app/api/v1/files/[fileId]/route.ts | 13 +- apps/sim/app/api/v1/files/route.ts | 11 +- apps/sim/app/api/v1/knowledge/utils.ts | 2 +- apps/sim/app/api/v1/logs/[id]/route.ts | 5 +- apps/sim/app/api/v1/logs/route.ts | 7 +- .../api/v1/tables/[tableId]/columns/route.ts | 6 +- apps/sim/app/api/v1/tables/[tableId]/route.ts | 4 +- .../v1/tables/[tableId]/rows/[rowId]/route.ts | 6 +- .../app/api/v1/tables/[tableId]/rows/route.ts | 10 +- .../v1/tables/[tableId]/rows/upsert/route.ts | 2 +- apps/sim/app/api/v1/tables/route.ts | 6 +- .../app/api/v1/workflows/[id]/deploy/route.ts | 8 +- .../app/api/v1/workflows/[id]/export/route.ts | 7 +- .../api/v1/workflows/[id]/rollback/route.ts | 7 +- apps/sim/app/api/v1/workflows/[id]/route.ts | 7 +- apps/sim/app/api/v1/workflows/import/route.ts | 8 +- apps/sim/app/api/v1/workflows/route.ts | 7 +- .../workflows/[workflowId]/execute/route.ts | 7 +- apps/sim/app/api/wand/route.ts | 52 +++-- apps/sim/app/api/webhooks/[id]/route.ts | 36 ++-- .../api/webhooks/cleanup/idempotency/route.ts | 4 +- .../app/api/webhooks/poll/[provider]/route.ts | 6 +- .../api/webhooks/quickbooks/[appKey]/route.ts | 8 +- apps/sim/app/api/webhooks/route.ts | 71 +++---- apps/sim/app/api/webhooks/slack/route.ts | 2 +- apps/sim/app/api/webhooks/tiktok/route.ts | 12 +- .../app/api/webhooks/trigger/[path]/route.ts | 26 +-- .../api/workflows/[id]/autolayout/route.ts | 24 +-- .../[id]/deployments/[version]/route.ts | 12 +- .../app/api/workflows/[id]/duplicate/route.ts | 21 +- .../app/api/workflows/[id]/restore/route.ts | 4 +- .../sim/app/api/workflows/[id]/state/route.ts | 9 +- .../app/api/workflows/[id]/variables/route.ts | 19 +- apps/sim/app/api/workflows/reorder/route.ts | 14 +- apps/sim/app/api/workflows/route.ts | 23 +-- .../app/api/workspace-events/poll/route.ts | 8 +- .../app/api/workspaces/[id]/api-keys/route.ts | 17 +- .../api/workspaces/[id]/byok-keys/route.ts | 10 +- .../api/workspaces/[id]/environment/route.ts | 24 +-- .../async-preprocessing-correlation.test.ts | 19 +- apps/sim/background/cleanup-file-versions.ts | 2 +- apps/sim/background/cleanup-logs.ts | 30 ++- apps/sim/background/cleanup-soft-deletes.ts | 30 +-- apps/sim/background/cleanup-tasks.ts | 11 +- .../knowledge-connector-directory-sync.ts | 6 +- .../knowledge-connector-member-sync.ts | 7 +- .../background/knowledge-connector-sync.ts | 10 +- apps/sim/background/knowledge-processing.ts | 32 +-- .../background/quickbooks-webhook-ingress.ts | 15 +- apps/sim/background/schedule-execution.ts | 119 +++++------ apps/sim/background/table-run-dispatcher.ts | 2 +- apps/sim/background/webhook-execution.test.ts | 18 +- apps/sim/background/webhook-execution.ts | 114 ++++++----- apps/sim/background/workflow-execution.ts | 25 ++- .../lib/copy/cleanup-failed.ts | 37 ++-- .../lib/copy/content-copy-runner.ts | 3 +- .../workspace-forking/lib/copy/copy-chats.ts | 3 +- .../workspace-forking/lib/copy/copy-files.ts | 24 +-- .../lib/copy/copy-resources.ts | 49 +++-- .../lib/copy/copy-workflows.ts | 3 +- .../ee/workspace-forking/lib/create-fork.ts | 6 +- .../workspace-forking/lib/lineage/unlink.ts | 3 +- .../workspace-forking/lib/promote/promote.ts | 33 ++-- .../workspace-forking/lib/promote/rollback.ts | 31 +-- apps/sim/executor/utils/credential-token.ts | 3 +- apps/sim/executor/utils/vertex-credential.ts | 9 +- .../queries/utils/optimistic-mutation.ts | 8 +- apps/sim/lib/auth/auth.ts | 7 +- apps/sim/lib/billing/cleanup-dispatcher.ts | 18 +- apps/sim/lib/cleanup/batch-delete.ts | 16 +- apps/sim/lib/cleanup/chat-cleanup.ts | 26 +-- .../lib/core/async-jobs/backends/database.ts | 9 +- apps/sim/lib/core/security/deployment-auth.ts | 2 +- .../core/security/deployment-credentials.ts | 14 +- apps/sim/lib/core/utils/request.ts | 4 +- apps/sim/lib/execution/files.ts | 5 +- apps/sim/lib/execution/isolated-vm.test.ts | 3 +- apps/sim/lib/execution/isolated-vm.ts | 4 +- apps/sim/lib/execution/preprocessing.ts | 55 +++--- apps/sim/lib/file-retention/legacy-archive.ts | 13 +- .../lib/function-execution/execute-request.ts | 43 ++-- .../lib/guardrails/validate-hallucination.ts | 24 +-- apps/sim/lib/guardrails/validate-pii.ts | 13 +- apps/sim/lib/internal/a2a/execute-tool.ts | 3 +- apps/sim/lib/internal/a2a/operations.ts | 12 +- apps/sim/lib/internal/ashby/operations.ts | 3 +- apps/sim/lib/internal/buffer/operations.ts | 5 +- apps/sim/lib/internal/cursor/operations.ts | 3 +- apps/sim/lib/internal/firecrawl/operations.ts | 3 +- apps/sim/lib/internal/fireflies/operations.ts | 6 +- .../lib/internal/guardrails/execute-tool.ts | 2 +- .../sim/lib/internal/guardrails/operations.ts | 7 +- apps/sim/lib/internal/image/operations.ts | 15 +- apps/sim/lib/internal/jupyter/execute-tool.ts | 2 +- apps/sim/lib/internal/jupyter/operations.ts | 9 +- .../lib/internal/knowledge/execute-tool.ts | 2 +- apps/sim/lib/internal/llm/credentials.ts | 6 +- apps/sim/lib/internal/llm/execute-tool.ts | 2 +- apps/sim/lib/internal/llm/operations.ts | 6 +- apps/sim/lib/internal/sap-concur/client.ts | 5 +- .../sim/lib/internal/sap-s4hana/operations.ts | 8 +- apps/sim/lib/internal/slack/operations.ts | 6 +- apps/sim/lib/internal/square/operations.ts | 3 +- apps/sim/lib/internal/stt/execute-tool.ts | 5 +- apps/sim/lib/internal/stt/operations.ts | 20 +- apps/sim/lib/internal/table/execute-tool.ts | 2 +- .../lib/internal/textract/document-input.ts | 8 +- apps/sim/lib/internal/textract/errors.ts | 2 +- apps/sim/lib/internal/textract/operations.ts | 33 ++-- apps/sim/lib/internal/textract/poll-job.ts | 6 +- apps/sim/lib/internal/tiktok/operations.ts | 3 +- apps/sim/lib/internal/tiktok/upload.ts | 3 +- apps/sim/lib/internal/video/client.ts | 7 +- .../lib/internal/windchill/execute-tool.ts | 3 +- apps/sim/lib/internal/zoom/operations.ts | 3 +- apps/sim/lib/knowledge/chunks/service.ts | 24 +-- .../lib/knowledge/connectors/sync-engine.ts | 3 +- .../documents/processing-source.test.ts | 3 +- apps/sim/lib/knowledge/documents/service.ts | 158 ++++++++------- .../orchestration/connector-access.ts | 30 +-- .../lib/knowledge/orchestration/connectors.ts | 60 +++--- .../lib/knowledge/orchestration/documents.ts | 8 +- .../orchestration/knowledge-bases.ts | 6 +- .../lib/knowledge/orchestration/restore.ts | 2 +- .../sim/lib/knowledge/orchestration/shared.ts | 2 +- apps/sim/lib/knowledge/service.ts | 12 +- apps/sim/lib/knowledge/tags/service.ts | 28 +-- .../sim/lib/logs/execution/logging-session.ts | 82 ++++---- apps/sim/lib/mcp/client.ts | 12 +- apps/sim/lib/mcp/connection-manager.ts | 50 +++-- apps/sim/lib/mcp/middleware.ts | 11 +- apps/sim/lib/mcp/service.ts | 76 +++---- apps/sim/lib/mcp/workflow-mcp-sync.ts | 29 +-- apps/sim/lib/mothership/chat/completion.ts | 6 +- apps/sim/lib/mothership/chat/post.ts | 8 +- .../mothership/chat/resource-attachments.ts | 8 +- .../mothership/request/lifecycle/finalize.ts | 11 +- .../lib/mothership/request/lifecycle/start.ts | 25 ++- apps/sim/lib/oauth/credential-service.ts | 25 +-- apps/sim/lib/oauth/token-resolution.ts | 17 +- apps/sim/lib/realtime/event-stream-route.ts | 7 +- apps/sim/lib/table/backfill-runner.ts | 22 ++- apps/sim/lib/table/columns/service.ts | 33 ++-- apps/sim/lib/table/delete-runner.ts | 25 ++- apps/sim/lib/table/dispatcher.ts | 10 +- apps/sim/lib/table/export-runner.ts | 24 ++- apps/sim/lib/table/export-stream.ts | 7 +- apps/sim/lib/table/import-data.ts | 2 +- apps/sim/lib/table/import-runner.ts | 35 ++-- apps/sim/lib/table/orchestration/columns.ts | 3 +- apps/sim/lib/table/orchestration/import.ts | 17 +- apps/sim/lib/table/orchestration/restore.ts | 4 +- apps/sim/lib/table/orchestration/tables.ts | 6 +- apps/sim/lib/table/rows/service.ts | 58 +++--- apps/sim/lib/table/service.ts | 19 +- apps/sim/lib/table/snapshot-cache.ts | 7 +- apps/sim/lib/table/trigger.ts | 14 +- apps/sim/lib/table/workflow-columns.ts | 19 +- apps/sim/lib/table/workflow-groups/service.ts | 31 +-- apps/sim/lib/uploads/utils/file.server.ts | 21 +- apps/sim/lib/uploads/utils/file.ts | 10 +- .../uploads/utils/user-file-base64.server.ts | 36 ++-- apps/sim/lib/webhooks/attachment-processor.ts | 13 +- apps/sim/lib/webhooks/deploy.ts | 115 ++++++----- apps/sim/lib/webhooks/polling/gmail.ts | 71 +++---- .../lib/webhooks/polling/google-calendar.ts | 30 +-- apps/sim/lib/webhooks/polling/google-drive.ts | 52 ++--- .../sim/lib/webhooks/polling/google-sheets.ts | 49 ++--- apps/sim/lib/webhooks/polling/hubspot.ts | 56 +++--- apps/sim/lib/webhooks/polling/imap.ts | 36 ++-- apps/sim/lib/webhooks/polling/orchestrator.ts | 8 +- apps/sim/lib/webhooks/polling/outlook.ts | 87 ++++---- apps/sim/lib/webhooks/polling/rss.ts | 44 +++-- apps/sim/lib/webhooks/polling/utils.ts | 6 +- apps/sim/lib/webhooks/processor.ts | 70 ++++--- .../webhooks/provider-subscription-utils.ts | 4 +- .../lib/webhooks/provider-subscriptions.ts | 3 +- apps/sim/lib/webhooks/providers/airtable.ts | 181 +++++++++-------- apps/sim/lib/webhooks/providers/ashby.ts | 69 +++---- apps/sim/lib/webhooks/providers/attio.ts | 77 ++++---- .../lib/webhooks/providers/azure-devops.ts | 19 +- apps/sim/lib/webhooks/providers/bitbucket.ts | 50 ++--- apps/sim/lib/webhooks/providers/calendly.ts | 75 +++---- apps/sim/lib/webhooks/providers/checkr.ts | 40 ++-- apps/sim/lib/webhooks/providers/clerk.ts | 5 +- apps/sim/lib/webhooks/providers/clickup.ts | 73 ++++--- apps/sim/lib/webhooks/providers/confluence.ts | 16 +- apps/sim/lib/webhooks/providers/emailbison.ts | 32 +-- apps/sim/lib/webhooks/providers/fathom.ts | 57 +++--- apps/sim/lib/webhooks/providers/fivetran.ts | 36 ++-- apps/sim/lib/webhooks/providers/generic.ts | 15 +- apps/sim/lib/webhooks/providers/github.ts | 8 +- apps/sim/lib/webhooks/providers/gitlab.ts | 41 ++-- apps/sim/lib/webhooks/providers/gmail.ts | 28 +-- apps/sim/lib/webhooks/providers/gong.ts | 19 +- .../lib/webhooks/providers/google-forms.ts | 4 +- apps/sim/lib/webhooks/providers/grain.ts | 79 ++++---- apps/sim/lib/webhooks/providers/granola.ts | 40 ++-- apps/sim/lib/webhooks/providers/greenhouse.ts | 3 +- apps/sim/lib/webhooks/providers/helpscout.ts | 10 +- apps/sim/lib/webhooks/providers/imap.ts | 18 +- apps/sim/lib/webhooks/providers/incidentio.ts | 9 +- apps/sim/lib/webhooks/providers/instantly.ts | 30 +-- apps/sim/lib/webhooks/providers/intercom.ts | 18 +- apps/sim/lib/webhooks/providers/jira.ts | 7 +- apps/sim/lib/webhooks/providers/jotform.ts | 34 ++-- apps/sim/lib/webhooks/providers/jsm.ts | 3 +- apps/sim/lib/webhooks/providers/lemlist.ts | 63 +++--- apps/sim/lib/webhooks/providers/linear.ts | 57 +++--- apps/sim/lib/webhooks/providers/linq.ts | 33 ++-- apps/sim/lib/webhooks/providers/loops.ts | 5 +- .../lib/webhooks/providers/microsoft-teams.ts | 87 ++++---- apps/sim/lib/webhooks/providers/monday.ts | 64 +++--- apps/sim/lib/webhooks/providers/notion.ts | 5 +- apps/sim/lib/webhooks/providers/otter.ts | 2 +- apps/sim/lib/webhooks/providers/outlook.ts | 28 +-- apps/sim/lib/webhooks/providers/pagerduty.ts | 29 +-- apps/sim/lib/webhooks/providers/quickbooks.ts | 2 +- apps/sim/lib/webhooks/providers/registry.ts | 2 +- apps/sim/lib/webhooks/providers/resend.ts | 54 ++--- apps/sim/lib/webhooks/providers/revenuecat.ts | 43 ++-- apps/sim/lib/webhooks/providers/rootly.ts | 59 +++--- apps/sim/lib/webhooks/providers/rss.ts | 11 +- apps/sim/lib/webhooks/providers/salesforce.ts | 14 +- apps/sim/lib/webhooks/providers/sendblue.ts | 4 +- apps/sim/lib/webhooks/providers/sentry.ts | 3 +- apps/sim/lib/webhooks/providers/servicenow.ts | 14 +- apps/sim/lib/webhooks/providers/slack.ts | 10 +- apps/sim/lib/webhooks/providers/stripe.ts | 11 +- apps/sim/lib/webhooks/providers/telegram.ts | 46 ++--- apps/sim/lib/webhooks/providers/tiktok.ts | 17 +- .../webhooks/providers/twilio-signature.ts | 15 +- apps/sim/lib/webhooks/providers/typeform.ts | 39 ++-- apps/sim/lib/webhooks/providers/utils.ts | 15 +- apps/sim/lib/webhooks/providers/vercel.ts | 42 ++-- apps/sim/lib/webhooks/providers/webflow.ts | 75 +++---- apps/sim/lib/webhooks/providers/whatsapp.ts | 24 +-- apps/sim/lib/webhooks/providers/zendesk.ts | 43 ++-- apps/sim/lib/webhooks/providers/zoho-desk.ts | 17 +- apps/sim/lib/webhooks/providers/zoom.ts | 31 ++- apps/sim/lib/webhooks/slack-custom-ingress.ts | 10 +- apps/sim/lib/webhooks/slack-dispatch.ts | 24 ++- .../lib/workflows/custom-tools/operations.ts | 4 +- apps/sim/lib/workflows/deployment-outbox.ts | 20 +- .../workflows/executor/execute-workflow.ts | 2 +- .../lib/workflows/executor/execution-core.ts | 87 ++++---- .../workflows/executor/resume-execution.ts | 12 +- apps/sim/lib/workflows/lifecycle.ts | 33 ++-- .../workflows/operations/import-workflow.ts | 23 ++- .../sim/lib/workflows/orchestration/deploy.ts | 24 +-- .../orchestration/workflow-lifecycle.ts | 21 +- .../lib/workflows/persistence/duplicate.ts | 18 +- .../persistence/replace-normalized-state.ts | 13 +- .../persistence/save-normalized-state.ts | 8 +- apps/sim/lib/workflows/skills/operations.ts | 4 +- .../lib/workflows/streaming/streaming.test.ts | 26 +-- apps/sim/lib/workflows/streaming/streaming.ts | 36 ++-- apps/sim/lib/workflows/utils.ts | 16 +- apps/sim/lib/workspace-events/emitter.ts | 16 +- apps/sim/lib/workspaces/lifecycle.ts | 9 +- apps/sim/providers/file-attachments.server.ts | 3 +- apps/sim/tools/index.test.ts | 8 +- apps/sim/tools/index.ts | 148 +++++++------- .../check-source-conventions.baseline.json | 3 +- scripts/check-source-conventions.test.ts | 79 +++++++- scripts/check-source-conventions.ts | 185 +++++++++++++++++- 325 files changed, 3883 insertions(+), 3273 deletions(-) diff --git a/.agents/skills/add-trigger/SKILL.md b/.agents/skills/add-trigger/SKILL.md index 53311498b56..db54eca9059 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 ${webhookId}:`, error, { requestId }) 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/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.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..619387d9ecf 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.ts @@ -158,10 +158,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 +184,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 +205,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 +218,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 +240,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 +409,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..2d3e7993b48 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 @@ -234,7 +234,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..4f37fa1765b 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts @@ -286,7 +286,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 +327,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 +446,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 +457,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 +479,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..9091e187f0f 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts @@ -1143,7 +1143,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 +1184,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 +1227,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 +1355,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 +1476,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 +1495,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 +1508,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 +1577,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 +1620,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..31dbfdccfe9 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts @@ -651,7 +651,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..1b60522336b 100644 --- a/apps/sim/ee/workspace-forking/lib/create-fork.ts +++ b/apps/sim/ee/workspace-forking/lib/create-fork.ts @@ -560,7 +560,8 @@ export async function createFork(params: CreateForkParams): 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 +1236,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 +1369,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 +1543,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/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/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..717620da21d 100644 --- a/apps/sim/lib/uploads/utils/user-file-base64.server.ts +++ b/apps/sim/lib/uploads/utils/user-file-base64.server.ts @@ -214,10 +214,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 +226,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 +238,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 +303,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 +411,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 +427,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,7 +438,9 @@ 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 } } @@ -463,7 +467,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 +493,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..9e8b5a947c4 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 } ) } } @@ -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..f0a016ee84c 100644 --- a/apps/sim/lib/webhooks/polling/orchestrator.ts +++ b/apps/sim/lib/webhooks/polling/orchestrator.ts @@ -1,6 +1,6 @@ import { createLogger } from '@sim/logger' -import { generateShortId } from '@sim/utils/id' import { withResourceOutboundScope } from '@/lib/core/network/resource-scope.server' +import { generateRequestId } from '@/lib/core/utils/request' import { findRecentlyRefusedWorkspaces } from '@/lib/webhooks/polling/admission-refusals' import { getPollingHandler } from '@/lib/webhooks/polling/registry' import type { PollSummary } from '@/lib/webhooks/polling/types' @@ -47,13 +47,13 @@ export async function pollProvider(providerName: string): Promise { return 'skipped' } - const requestId = generateShortId() + // Polls share the tick's request id; the webhook id tells concurrent polls apart. return withResourceOutboundScope({ workspaceId: entry.workflow.workspaceId }, () => handler.pollWebhook({ webhookData: entry.webhook, workflowData: entry.workflow, - requestId, - logger, + requestId: generateRequestId(), + 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..6347b05cdf9 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) { @@ -1006,7 +1015,7 @@ export async function processPolledWebhookEvent( const ticket = tryAdmit() if (!ticket) { const gateFailure = ADMISSION_ERROR_DESCRIPTOR.GATE_CAPACITY - logger.warn(`[${requestId}] Admission gate rejected polled webhook event`) + logger.warn('Admission gate rejected polled webhook event', { requestId }) return { success: false, error: 'Server at capacity', @@ -1024,7 +1033,8 @@ export async function processPolledWebhookEvent( 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}` + `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 +1052,8 @@ export async function processPolledWebhookEvent( typeof errorBody.error === 'string' ? errorBody.error : 'Preprocessing failed' - logger.warn(`[${requestId}] Polled webhook preprocessing failed`, { + logger.warn('Polled webhook preprocessing failed', { + requestId, statusCode, error: errorMessage, }) @@ -1073,7 +1084,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}`) + logger.error(`Missing billing context for webhook ${foundWebhook.id}`, { requestId }) return { success: false, error: 'Unable to resolve billing account', statusCode: 500 } } @@ -1136,7 +1147,8 @@ export async function processPolledWebhookEvent( }) reservationTransferred = true logger.info( - `[${requestId}] Queued polling webhook execution task ${jobId} for ${provider} webhook via job queue` + `Queued polling webhook execution task ${jobId} for ${provider} webhook via job queue`, + { requestId } ) } else { const jobQueue = await getInlineJobQueue() @@ -1152,12 +1164,12 @@ export async function processPolledWebhookEvent( executeWebhookJob(payload, signal), }) reservationTransferred = true - logger.info(`[${requestId}] Queued ${provider} webhook execution ${jobId} via inline backend`) + logger.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) + logger.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..c607c13a75e 100644 --- a/apps/sim/lib/workflows/persistence/duplicate.ts +++ b/apps/sim/lib/workflows/persistence/duplicate.ts @@ -309,9 +309,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 +368,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 +387,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 +450,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..8e7c776b7e6 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(([message]) => message === '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..6a85d680fbf 100644 --- a/apps/sim/lib/workspace-events/emitter.ts +++ b/apps/sim/lib/workspace-events/emitter.ts @@ -1,6 +1,6 @@ import { createLogger } from '@sim/logger' import { getActiveWorkflowContext } from '@sim/platform-authz/workflow' -import { generateShortId } from '@sim/utils/id' +import { generateRequestId } from '@/lib/core/utils/request' import type { WorkflowExecutionLog } from '@/lib/logs/types' import { isSimRuleEventType, @@ -46,7 +46,7 @@ export async function dispatchSimEvent( subscription: SimSubscription, payload: SimEventPayload ): Promise { - const requestId = generateShortId() + const requestId = generateRequestId() try { const { processPolledWebhookEvent } = await import('@/lib/webhooks/processor') const result = await processPolledWebhookEvent( @@ -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/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..44a64085a8a 100644 --- a/scripts/check-source-conventions.test.ts +++ b/scripts/check-source-conventions.test.ts @@ -347,6 +347,82 @@ 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('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 +430,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 +514,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..62f018e2d91 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,13 @@ 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`, `routeLogger`, `this.logger`. */ +const LOGGER_RECEIVER = /^(?:logger|log|[a-z][A-Za-z]*Logger)$/ +/** Message text before its first interpolation that leaves a tag open: `[`, `[Cascade] [`. */ +const OPEN_TAG = /^(?:\[[^\]]*\]\s*)*\[$/ +const REQUEST_ID_NAME = /requestId$/i + function walk(node: unknown, visit: (node: Node) => void): void { if (!node || typeof node !== 'object') return if (Array.isArray(node)) { @@ -533,6 +551,153 @@ function isContractCopy(file: string, source: string): boolean { return file.startsWith(CONTRACT_COPY_DIR) && source.startsWith(CONTRACT_COPY_HEADER) } +/** `logger` for `logger` and `this.logger`; undefined for anything that is not a plain name. */ +function boundName(node: Node): string | undefined { + if (node.type === 'Identifier') return node.name as string + const member = node.property as Node | undefined + return node.type === 'MemberExpression' && !node.computed && member?.type === 'Identifier' + ? (member.name as string) + : undefined +} + +function isLogger(node: Node | undefined): boolean { + const name = node && boundName(node) + return name !== undefined && LOGGER_RECEIVER.test(name) +} + +/** + * The message a call logs: the first argument of `logger.info(…)` and its siblings, or the second + * of a helper handed the caller's logger, such as `logFailureOnce(logger, message, error)`. + */ +function loggedMessage(node: Node): Node | undefined { + const callee = node.callee as Node + const args = node.arguments as Node[] + const level = callee.property as Node | undefined + if ( + callee.type === 'MemberExpression' && + !callee.computed && + level?.type === 'Identifier' && + LOG_LEVELS.has(level.name as string) && + isLogger(callee.object as Node) + ) { + return args[0] + } + return isLogger(args[0]) ? args[1] : undefined +} + +function mentionsRequestId(expression: Node): boolean { + let found = false + walk(expression, (node) => { + if (node.type === 'Identifier' && REQUEST_ID_NAME.test(node.name as string)) found = true + }) + return found +} + +/** 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] +} + +/** + * Whether a message opens with an interpolated tag or interpolates a request id anywhere. An + * interpolated local `const` counts as what it holds, so `${logPrefix}Saved` is checked as the + * `[${requestId}] ` it was built from. + */ +function isTaggedMessage(message: Node, inits: Map, 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' && + (mentionsRequestId(part) || + (part.type === 'Identifier' && + messageCandidates(part, inits, seen).some( + (candidate) => candidate !== part && isTaggedMessage(candidate, inits, seen) + ))) + ) +} + +/** `const` initializers by name, so a message built before the call is checked where it is logged. */ +function constInitializers(program: Node): Map { + const inits = new Map() + walk(program, (node) => { + if (node.type !== 'VariableDeclaration' || node.kind !== 'const') return + for (const declarator of node.declarations as Node[]) { + const id = declarator.id as Node + const init = declarator.init as Node | null + if (id.type !== 'Identifier' || !init) continue + inits.set(id.name as string, [...(inits.get(id.name as string) ?? []), init]) + } + }) + return inits +} + +/** The message's possible values: both arms of a ternary, and a local `const` it names. */ +function messageCandidates( + message: Node, + inits: Map, + seen = new Set() +): Node[] { + const current = unwrap(message) ?? message + if (current.type === 'ConditionalExpression') { + return [ + ...messageCandidates(current.consequent as Node, inits, seen), + ...messageCandidates(current.alternate as Node, inits, seen), + ] + } + if (current.type === 'Identifier' && !seen.has(current.name as string)) { + seen.add(current.name as string) + return (inits.get(current.name as string) ?? []).flatMap((init) => + messageCandidates(init, inits, seen) + ) + } + return [current] +} + +function findLogMessageTags(file: string, source: string, program: Node): Violation[] { + const violations: Violation[] = [] + const inits = constInitializers(program) + walk(program, (node) => { + if (node.type !== 'CallExpression') return + const message = loggedMessage(node) + if (!message) return + const seen = new Set() + const tagged = messageCandidates(message, inits, seen).find((candidate) => + isTaggedMessage(candidate, inits, seen) + ) + if (!tagged) return + const text = source.slice(tagged.start as number, tagged.end as number).replace(/\s+/g, ' ') + violations.push({ + rule: 'log-message-tag', + file, + line: lineOf(node), + detail: text.length > 80 ? `${text.slice(0, 77)}...` : text, + }) + }) + 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 +705,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*\+)|requestId/i.test(source) + if (!(wantsRelative || wantsTextSize || wantsStyle || wantsDb || wantsRoute || wantsLogTag)) { + return [] + } const program = parseSource(file, source) return [ @@ -558,6 +730,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 +812,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'))] From 1b4726969c6f67e3c2a24032af9d57d7b84841cd Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 5 Nov 2026 16:37:57 -0800 Subject: [PATCH 2/5] fix(logger): let the request context own requestId and keep absent fields from erasing it An explicit requestId on a line, or one bound with withMetadata, replaced the request context's id in the JSON line, and an undefined one deleted it, so a line could stop correlating with its request. Inside a request context the context's id is now authoritative, matching the OpenTelemetry export; a requestId passed with a line names the request only outside one. Undefined fields no longer erase fields the line already carries. v2 chat logs its mothership request id as simRequestId. Request ids that defaulted to 'unknown' default to generateRequestId(). log-message-tag now sees optional calls, computed levels, withMetadata-derived loggers and log-named receivers, follows let prefixes, resolves names per function, and only treats an interpolated requestId or reqId, and log* helpers or .call(logger), as message sources. --- apps/sim/app/api/v2/chat/route.ts | 4 +- .../lib/copy/cleanup-failed.ts | 3 +- .../lib/copy/content-copy-runner.ts | 3 +- .../workspace-forking/lib/copy/copy-files.ts | 3 +- .../lib/copy/copy-resources.ts | 3 +- .../lib/copy/copy-workflows.ts | 3 +- .../ee/workspace-forking/lib/create-fork.ts | 3 +- .../workspace-forking/lib/promote/promote.ts | 3 +- .../payloads/materialization.server.ts | 3 +- .../uploads/utils/user-file-base64.server.ts | 3 +- apps/sim/lib/webhooks/polling/hubspot.ts | 8 +- .../lib/workflows/persistence/duplicate.ts | 3 +- packages/logger/src/index.test.ts | 29 ++ packages/logger/src/index.ts | 22 +- scripts/check-source-conventions.test.ts | 37 +++ scripts/check-source-conventions.ts | 293 ++++++++++++------ 16 files changed, 298 insertions(+), 125 deletions(-) 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/ee/workspace-forking/lib/copy/cleanup-failed.ts b/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.ts index 619387d9ecf..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) => 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 2d3e7993b48..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([ 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 4f37fa1765b..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[] }> { 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 9091e187f0f..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 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 31dbfdccfe9..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 diff --git a/apps/sim/ee/workspace-forking/lib/create-fork.ts b/apps/sim/ee/workspace-forking/lib/create-fork.ts index 1b60522336b..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( diff --git a/apps/sim/ee/workspace-forking/lib/promote/promote.ts b/apps/sim/ee/workspace-forking/lib/promote/promote.ts index 3cddec49efb..2bff246ef3e 100644 --- a/apps/sim/ee/workspace-forking/lib/promote/promote.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/promote.ts @@ -9,6 +9,7 @@ import type { ForkSyncBlocker, PromoteCopyResources, } from '@/lib/api/contracts/workspace-fork' +import { generateRequestId } from '@/lib/core/utils/request' import type { DbOrTx } from '@/lib/db/types' import { MAX_FOLDERS_PER_WORKSPACE } from '@/lib/folders/constants' import { isFolderPathEffectivelyLocked } from '@/lib/folders/paths' @@ -510,7 +511,7 @@ function groupDependentOverrides( */ export async function promoteFork(params: PromoteForkParams): 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) { 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/uploads/utils/user-file-base64.server.ts b/apps/sim/lib/uploads/utils/user-file-base64.server.ts index 717620da21d..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 { @@ -444,7 +445,7 @@ async function resolveBase64( return { base64: null } } - const requestId = options.requestId ?? 'unknown' + const requestId = options.requestId ?? generateRequestId() try { const result = await readUserFileContentWithContributors(file, { requestId, diff --git a/apps/sim/lib/webhooks/polling/hubspot.ts b/apps/sim/lib/webhooks/polling/hubspot.ts index 9e8b5a947c4..047d01831a0 100644 --- a/apps/sim/lib/webhooks/polling/hubspot.ts +++ b/apps/sim/lib/webhooks/polling/hubspot.ts @@ -590,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'), + }) } } diff --git a/apps/sim/lib/workflows/persistence/duplicate.ts b/apps/sim/lib/workflows/persistence/duplicate.ts index c607c13a75e..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, diff --git a/packages/logger/src/index.test.ts b/packages/logger/src/index.test.ts index a48f79b97cd..e2f1e92eef9 100644 --- a/packages/logger/src/index.test.ts +++ b/packages/logger/src/index.test.ts @@ -117,6 +117,35 @@ 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('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..dca33ddc418 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,8 +499,10 @@ 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) const metadataEntries = Object.entries(filterUndefined(effectiveMetadata)) @@ -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 { diff --git a/scripts/check-source-conventions.test.ts b/scripts/check-source-conventions.test.ts index 44a64085a8a..3705a1e790c 100644 --- a/scripts/check-source-conventions.test.ts +++ b/scripts/check-source-conventions.test.ts @@ -395,6 +395,43 @@ describe('log-message-tag', () => { 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('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 })", diff --git a/scripts/check-source-conventions.ts b/scripts/check-source-conventions.ts index 62f018e2d91..f3a6926b027 100644 --- a/scripts/check-source-conventions.ts +++ b/scripts/check-source-conventions.ts @@ -129,11 +129,21 @@ 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`, `routeLogger`, `this.logger`. */ -const LOGGER_RECEIVER = /^(?:logger|log|[a-z][A-Za-z]*Logger)$/ +/** 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_NAME = /requestId$/i +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 @@ -551,46 +561,72 @@ function isContractCopy(file: string, source: string): boolean { return file.startsWith(CONTRACT_COPY_DIR) && source.startsWith(CONTRACT_COPY_HEADER) } -/** `logger` for `logger` and `this.logger`; undefined for anything that is not a plain name. */ +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 member = node.property as Node | undefined - return node.type === 'MemberExpression' && !node.computed && member?.type === 'Identifier' - ? (member.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 name = node && boundName(node) - return name !== undefined && LOGGER_RECEIVER.test(name) + 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(…)` and its siblings, or the second - * of a helper handed the caller's logger, such as `logFailureOnce(logger, message, error)`. + * 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(node: Node): Node | undefined { - const callee = node.callee as Node - const args = node.arguments as Node[] - const level = callee.property as Node | undefined - if ( - callee.type === 'MemberExpression' && - !callee.computed && - level?.type === 'Identifier' && - LOG_LEVELS.has(level.name as string) && - isLogger(callee.object as Node) - ) { - return args[0] +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 } - return isLogger(args[0]) ? args[1] : 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 } -function mentionsRequestId(expression: Node): boolean { - let found = false - walk(expression, (node) => { - if (node.type === 'Identifier' && REQUEST_ID_NAME.test(node.name as string)) found = true - }) - return found +/** `${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. */ @@ -613,88 +649,139 @@ function messageParts(message: Node): Array | undefined { } /** - * Whether a message opens with an interpolated tag or interpolates a request id anywhere. An - * interpolated local `const` counts as what it holds, so `${logPrefix}Saved` is checked as the - * `[${requestId}] ` it was built from. + * 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. */ -function isTaggedMessage(message: Node, inits: Map, 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' && - (mentionsRequestId(part) || - (part.type === 'Identifier' && - messageCandidates(part, inits, seen).some( - (candidate) => candidate !== part && isTaggedMessage(candidate, inits, seen) - ))) - ) -} +type Bindings = Map -/** `const` initializers by name, so a message built before the call is checked where it is logged. */ -function constInitializers(program: Node): Map { - const inits = new Map() - walk(program, (node) => { - if (node.type !== 'VariableDeclaration' || node.kind !== 'const') return - for (const declarator of node.declarations as Node[]) { - const id = declarator.id as Node - const init = declarator.init as Node | null - if (id.type !== 'Identifier' || !init) continue - inits.set(id.name as string, [...(inits.get(id.name as string) ?? []), init]) +class MessageResolver { + private readonly bindings = new Map() + + 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 } - }) - return inits -} - -/** The message's possible values: both arms of a ternary, and a local `const` it names. */ -function messageCandidates( - message: Node, - inits: Map, - seen = new Set() -): Node[] { - const current = unwrap(message) ?? message - if (current.type === 'ConditionalExpression') { - return [ - ...messageCandidates(current.consequent as Node, inits, seen), - ...messageCandidates(current.alternate as Node, inits, seen), - ] - } - if (current.type === 'Identifier' && !seen.has(current.name as string)) { - seen.add(current.name as string) - return (inits.get(current.name as string) ?? []).flatMap((init) => - messageCandidates(init, inits, seen) + 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) + } + } + } + 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) return 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) + ))) ) } - return [current] } function findLogMessageTags(file: string, source: string, program: Node): Violation[] { const violations: Violation[] = [] - const inits = constInitializers(program) - walk(program, (node) => { - if (node.type !== 'CallExpression') return - const message = loggedMessage(node) - if (!message) return - const seen = new Set() - const tagged = messageCandidates(message, inits, seen).find((candidate) => - isTaggedMessage(candidate, inits, seen) - ) - if (!tagged) return - const text = source.slice(tagged.start as number, tagged.end as number).replace(/\s+/g, ' ') - violations.push({ - rule: 'log-message-tag', - file, - line: lineOf(node), - detail: text.length > 80 ? `${text.slice(0, 77)}...` : text, - }) - }) + 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 } From 5f7899b5677cab1ee070a48b4acf92da5cdad5ce Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 5 Nov 2026 16:44:05 -0800 Subject: [PATCH 3/5] fix(logs): keep a unique request id per webhook poll and per emitted event Executions started by one poll tick, or by one parent request, would otherwise share a request id, and tool code that keys idempotency on it (deployments) could replay one execution's result in another. The logger now owns requestId on the line, so these ids no longer need to match the request to correlate. --- apps/sim/lib/webhooks/polling/orchestrator.ts | 5 ++--- apps/sim/lib/workspace-events/emitter.ts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/sim/lib/webhooks/polling/orchestrator.ts b/apps/sim/lib/webhooks/polling/orchestrator.ts index f0a016ee84c..fadfa6f21ef 100644 --- a/apps/sim/lib/webhooks/polling/orchestrator.ts +++ b/apps/sim/lib/webhooks/polling/orchestrator.ts @@ -1,6 +1,6 @@ import { createLogger } from '@sim/logger' +import { generateShortId } from '@sim/utils/id' import { withResourceOutboundScope } from '@/lib/core/network/resource-scope.server' -import { generateRequestId } from '@/lib/core/utils/request' import { findRecentlyRefusedWorkspaces } from '@/lib/webhooks/polling/admission-refusals' import { getPollingHandler } from '@/lib/webhooks/polling/registry' import type { PollSummary } from '@/lib/webhooks/polling/types' @@ -47,12 +47,11 @@ export async function pollProvider(providerName: string): Promise { return 'skipped' } - // Polls share the tick's request id; the webhook id tells concurrent polls apart. return withResourceOutboundScope({ workspaceId: entry.workflow.workspaceId }, () => handler.pollWebhook({ webhookData: entry.webhook, workflowData: entry.workflow, - requestId: generateRequestId(), + requestId: generateShortId(), logger: logger.withMetadata({ webhookId: entry.webhook.id }), }) ) diff --git a/apps/sim/lib/workspace-events/emitter.ts b/apps/sim/lib/workspace-events/emitter.ts index 6a85d680fbf..f63c5296c9f 100644 --- a/apps/sim/lib/workspace-events/emitter.ts +++ b/apps/sim/lib/workspace-events/emitter.ts @@ -1,6 +1,6 @@ import { createLogger } from '@sim/logger' import { getActiveWorkflowContext } from '@sim/platform-authz/workflow' -import { generateRequestId } from '@/lib/core/utils/request' +import { generateShortId } from '@sim/utils/id' import type { WorkflowExecutionLog } from '@/lib/logs/types' import { isSimRuleEventType, @@ -46,7 +46,7 @@ export async function dispatchSimEvent( subscription: SimSubscription, payload: SimEventPayload ): Promise { - const requestId = generateRequestId() + const requestId = generateShortId() try { const { processPolledWebhookEvent } = await import('@/lib/webhooks/processor') const result = await processPolledWebhookEvent( From a82a36a73d52ff80944c7ceb67d56c8bda40ad47 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 5 Nov 2026 17:14:09 -0800 Subject: [PATCH 4/5] fix(logs): export call fields to OpenTelemetry and tell concurrent polls apart Scalar fields passed with a log line now become OpenTelemetry attributes, matching their top-level keys in the JSON line, so ids moved out of message text stay filterable in both sinks. Nested values stay in log.args. The request context still owns requestId. The polled-webhook processor logs through a logger bound to the webhook id, so lines from concurrent polls in one tick stay distinguishable. Test expectations follow the new metadata shape. --- .agents/skills/add-trigger/SKILL.md | 2 +- .../knowledge-connector-sync.test.ts | 3 ++- .../chat/project-file-context.integration.ts | 4 ++-- apps/sim/lib/webhooks/processor.ts | 16 +++++++------ .../lib/workflows/streaming/streaming.test.ts | 2 +- packages/logger/src/index.test.ts | 23 ++++++++++++++++++ packages/logger/src/index.ts | 24 +++++++++++++++++-- 7 files changed, 60 insertions(+), 14 deletions(-) diff --git a/.agents/skills/add-trigger/SKILL.md b/.agents/skills/add-trigger/SKILL.md index db54eca9059..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(`Error processing {service} webhook ${webhookId}:`, error, { requestId }) + logger.error('Error processing {service} webhook:', error) await markWebhookFailed(webhookId, logger) return 'failure' } 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/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/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index 6347b05cdf9..6dfd7a55199 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -1011,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('Admission gate rejected polled webhook event', { requestId }) + log.warn('Admission gate rejected polled webhook event', { requestId }) return { success: false, error: 'Server at capacity', @@ -1032,7 +1034,7 @@ export async function processPolledWebhookEvent( if (foundWebhook.blockId) { const blockExists = await blockExistsInDeployment(foundWorkflow.id, foundWebhook.blockId) if (!blockExists) { - logger.info( + log.info( `Trigger block ${foundWebhook.blockId} not found in deployment for workflow ${foundWorkflow.id}`, { requestId } ) @@ -1052,7 +1054,7 @@ export async function processPolledWebhookEvent( typeof errorBody.error === 'string' ? errorBody.error : 'Preprocessing failed' - logger.warn('Polled webhook preprocessing failed', { + log.warn('Polled webhook preprocessing failed', { requestId, statusCode, error: errorMessage, @@ -1084,7 +1086,7 @@ export async function processPolledWebhookEvent( const billingAttribution = preprocessResult.billingAttribution const workspaceId = foundWorkflow.workspaceId ?? undefined if (!actorUserId || !billingAttribution || !workspaceId) { - logger.error(`Missing billing context for webhook ${foundWebhook.id}`, { requestId }) + log.error(`Missing billing context for webhook ${foundWebhook.id}`, { requestId }) return { success: false, error: 'Unable to resolve billing account', statusCode: 500 } } @@ -1146,7 +1148,7 @@ export async function processPolledWebhookEvent( maxDurationSeconds, }) reservationTransferred = true - logger.info( + log.info( `Queued polling webhook execution task ${jobId} for ${provider} webhook via job queue`, { requestId } ) @@ -1164,12 +1166,12 @@ export async function processPolledWebhookEvent( executeWebhookJob(payload, signal), }) reservationTransferred = true - logger.info(`Queued ${provider} webhook execution ${jobId} via inline backend`, { requestId }) + log.info(`Queued ${provider} webhook execution ${jobId} via inline backend`, { requestId }) } return { success: true, executionId } } catch (error: unknown) { - logger.error('Failed to process polled webhook event:', error, { requestId }) + 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/workflows/streaming/streaming.test.ts b/apps/sim/lib/workflows/streaming/streaming.test.ts index 8e7c776b7e6..c7f37a45302 100644 --- a/apps/sim/lib/workflows/streaming/streaming.test.ts +++ b/apps/sim/lib/workflows/streaming/streaming.test.ts @@ -1444,7 +1444,7 @@ describe('createStreamingResponse agent-events-v1', () => { ) ) expect( - workflowStreamingLogger.error.mock.calls.some(([message]) => message === 'Stream error') + workflowStreamingLogger.error.mock.calls.some((call: unknown[]) => call[0] === 'Stream error') ).toBe(false) }) diff --git a/packages/logger/src/index.test.ts b/packages/logger/src/index.test.ts index e2f1e92eef9..114be4d03be 100644 --- a/packages/logger/src/index.test.ts +++ b/packages/logger/src/index.test.ts @@ -137,6 +137,29 @@ describe('Logger', () => { 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' }, () => { diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index dca33ddc418..c57fcb858f8 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -504,7 +504,7 @@ export class Logger { const effectiveMetadata = reqCtx ? { ...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 @@ -638,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 @@ -652,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] @@ -660,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) From 5c9823b14a6a358300c45bcfb5e76e129f359cba Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 5 Nov 2026 17:46:02 -0800 Subject: [PATCH 5/5] fix(lint): check reqId-only modules and skip reassigned let messages in log-message-tag The cheap pre-filter now admits a module whose only request id is reqId, and a let binding reassigned before it is logged is no longer followed to its initializer. --- scripts/check-source-conventions.test.ts | 14 ++++++++++++++ scripts/check-source-conventions.ts | 15 +++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/scripts/check-source-conventions.test.ts b/scripts/check-source-conventions.test.ts index 3705a1e790c..2563c702540 100644 --- a/scripts/check-source-conventions.test.ts +++ b/scripts/check-source-conventions.test.ts @@ -423,6 +423,20 @@ describe('log-message-tag', () => { 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\`)`, diff --git a/scripts/check-source-conventions.ts b/scripts/check-source-conventions.ts index f3a6926b027..ba4ce7ce89d 100644 --- a/scripts/check-source-conventions.ts +++ b/scripts/check-source-conventions.ts @@ -656,6 +656,9 @@ 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) @@ -686,9 +689,16 @@ class MessageResolver { 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) @@ -699,7 +709,8 @@ class MessageResolver { 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) return values + if (!values) continue + return this.lets.get(scopes[index])?.has(name) && this.reassigned.has(name) ? [] : values } return [] } @@ -805,7 +816,7 @@ export function findViolations(file: string, source: string): Violation[] { const wantsDb = isApi && source.includes('@sim/db') const wantsRoute = isApi && /(?:^|\/)route\.ts$/.test(file) const wantsLogTag = - !test && source.includes('@sim/logger') && /\[(?:\$\{|['"`]\s*\+)|requestId/i.test(source) + !test && source.includes('@sim/logger') && /\[(?:\$\{|['"`]\s*\+)|req(?:uest)?Id/i.test(source) if (!(wantsRelative || wantsTextSize || wantsStyle || wantsDb || wantsRoute || wantsLogTag)) { return [] }