From 6fd6233c6f679b785057f6dfc996965c3e4ec205 Mon Sep 17 00:00:00 2001 From: Thibault Le Ouay Date: Tue, 21 Oct 2025 11:26:11 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A5=20Improve=20error=20handling=20wor?= =?UTF-8?q?kflows=20(#1466)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🥅 * 🔥 * 😭 * 😭 * 🔥 * 🔥 * 🚀 * 😭 * 🔥 --- .github/workflows/workflow-preview.yml | 56 ++++++++++++++++++++++++++ apps/workflows/docker-compose.yaml | 2 +- apps/workflows/src/cron/checker.ts | 11 ++++- apps/workflows/src/cron/index.ts | 29 +++++++++++-- pnpm-lock.yaml | 30 +++++++------- 5 files changed, 107 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/workflow-preview.yml diff --git a/.github/workflows/workflow-preview.yml b/.github/workflows/workflow-preview.yml new file mode 100644 index 00000000..a5d59ee4 --- /dev/null +++ b/.github/workflows/workflow-preview.yml @@ -0,0 +1,56 @@ +name: Fly Preview Workflows +on: + pull_request: + types: [opened, reopened, synchronize, closed] + paths: + - "apps/workflows/**" + - "packages/db/**" + - "packages/emails/**" + - "packages/utils/**" + - "packages/tsconfig/**" +env: + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} + # Set these to your Fly.io organization and preferred region. + FLY_REGION: ams + FLY_ORG: openstatus + +jobs: + review_app: + runs-on: ubuntu-latest + outputs: + url: ${{ steps.deploy.outputs.url }} + # Only run one deployment at a time per PR. + concurrency: + group: pr-${{ github.event.number }} + + # Deploying apps with this "review" environment allows the URL for the app to be displayed in the PR UI. + # Feel free to change the name of this environment. + environment: + name: pr-${{ github.event.number }} # The script in the `deploy` sets the URL output for each review app. + url: ${{ steps.deploy.outputs.url }} + steps: + - name: Get code + uses: actions/checkout@v4 + + - name: Deploy PR app to Fly.io + id: deploy + uses: superfly/fly-pr-review-apps@1.2.1 + with: + config: apps/workflows/fly.toml + vmsize: shared-cpu-1x + name: openstatus-workflows-pr-${{ github.event.number }} + secrets: | + DATABASE_URL=${{ secrets.STAGING_DB_URL }} + DATABASE_AUTH_TOKEN=${{ env.STAGING_DB_AUTH_TOKEN }} + RESEND_API_KEY=${{ secrets.STAGING_RESEND_API_KEY }} + UPSTASH_REDIS_REST_URL=test + UPSTASH_REDIS_REST_TOKEN=test + GCP_PROJECT_ID=test + + - name: Clean up GitHub environment + uses: strumwolf/delete-deployment-environment@v2 + if: ${{ github.event.action == 'closed' }} + with: + # ⚠️ The provided token needs permission for admin write:org + token: ${{ secrets.GITHUB_TOKEN }} + environment: pr-${{ github.event.number }} diff --git a/apps/workflows/docker-compose.yaml b/apps/workflows/docker-compose.yaml index dfce7556..6eff4522 100644 --- a/apps/workflows/docker-compose.yaml +++ b/apps/workflows/docker-compose.yaml @@ -23,4 +23,4 @@ services: source: ./data target: /app/data image: workflows-test - command: . + # command: . diff --git a/apps/workflows/src/cron/checker.ts b/apps/workflows/src/cron/checker.ts index 8b4f2eda..a8292c67 100644 --- a/apps/workflows/src/cron/checker.ts +++ b/apps/workflows/src/cron/checker.ts @@ -16,12 +16,14 @@ import type { Region } from "@openstatus/db/src/schema/constants"; import { regionDict } from "@openstatus/regions"; import { db } from "../lib/db"; +import { getSentry } from "@hono/sentry"; import type { monitorPeriodicitySchema } from "@openstatus/db/src/schema/constants"; import { type httpPayloadSchema, type tpcPayloadSchema, transformHeaders, } from "@openstatus/utils"; +import type { Context } from "hono"; import { env } from "../env"; export const isAuthorizedDomain = (url: string) => { @@ -39,6 +41,7 @@ const channelOptions = { export async function sendCheckerTasks( periodicity: z.infer, + c: Context, ) { const client = new CloudTasksClient({ fallback: "rest", @@ -156,12 +159,16 @@ export async function sendCheckerTasks( const success = allRequests.filter((r) => r.status === "fulfilled").length; const failed = allRequests.filter((r) => r.status === "rejected").length; - const failedRequest = allRequests.filter((r) => r.status === "rejected"); - console.log(failedRequest?.at(0)); console.log( `End cron for ${periodicity} with ${allResult.length} jobs with ${success} success and ${failed} failed`, ); + if (failed > 0) { + getSentry(c).captureMessage( + `sendCheckerTasks for ${periodicity} ended with ${failed} failed tasks`, + "error", + ); + } } // timestamp needs to be in ms const createCronTask = async ({ diff --git a/apps/workflows/src/cron/index.ts b/apps/workflows/src/cron/index.ts index d589d469..c3c2071e 100644 --- a/apps/workflows/src/cron/index.ts +++ b/apps/workflows/src/cron/index.ts @@ -1,3 +1,4 @@ +import { getSentry } from "@hono/sentry"; import { monitorPeriodicitySchema } from "@openstatus/db/src/schema/constants"; import { Hono } from "hono"; import { env } from "../env"; @@ -29,13 +30,27 @@ app.get("/checker/:period", async (c) => { if (!schema.success) { return c.json({ error: schema.error.issues?.[0].message }, 400); } - + const sentry = getSentry(c); + const checkInId = sentry.captureCheckIn({ + monitorSlug: period, + status: "in_progress", + }); try { - await sendCheckerTasks(schema.data); - + await sendCheckerTasks(schema.data, c); + sentry.captureCheckIn({ + checkInId, + monitorSlug: period, + status: "ok", + }); return c.json({ success: schema.data }, 200); } catch (e) { console.error(e); + sentry.captureMessage(`Error in /checker/${period} cron: ${e}`, "error"); + sentry.captureCheckIn({ + checkInId, + monitorSlug: period, + status: "error", + }); return c.text("Internal Server Error", 500); } }); @@ -66,9 +81,17 @@ app.get("/monitors/:step", async (c) => { } if (!userId) { + getSentry(c).captureMessage( + "userId is missing in /monitors/:step cron", + "error", + ); return c.json({ error: "userId is required" }, 400); } if (!initialRun) { + getSentry(c).captureMessage( + "initalRun is missing in /monitors/:step cron", + "error", + ); return c.json({ error: "initialRun is required" }, 400); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 250ce73d..f4740d1c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1144,7 +1144,7 @@ importers: version: 2.6.2 drizzle-orm: specifier: 0.44.4 - version: 0.44.4(@cloudflare/workers-types@4.20250303.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(better-sqlite3@11.7.0)(bun-types@1.2.22(@types/react@19.2.2)) + version: 0.44.4(@cloudflare/workers-types@4.20250303.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(better-sqlite3@11.7.0)(bun-types@1.3.0(@types/react@19.2.2)) hono: specifier: 4.5.3 version: 4.5.3 @@ -1160,7 +1160,7 @@ importers: version: link:../../packages/tsconfig '@types/bun': specifier: latest - version: 1.2.22(@types/react@19.2.2) + version: 1.3.0(@types/react@19.2.2) typescript: specifier: 5.7.2 version: 5.7.2 @@ -1301,10 +1301,10 @@ importers: version: 0.7.1(typescript@5.7.2)(zod@3.24.2) drizzle-orm: specifier: 0.44.4 - version: 0.44.4(@cloudflare/workers-types@4.20250303.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(better-sqlite3@11.7.0)(bun-types@1.2.22(@types/react@19.2.2)) + version: 0.44.4(@cloudflare/workers-types@4.20250303.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(better-sqlite3@11.7.0)(bun-types@1.3.0(@types/react@19.2.2)) drizzle-zod: specifier: 0.5.1 - version: 0.5.1(drizzle-orm@0.44.4(@cloudflare/workers-types@4.20250303.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(better-sqlite3@11.7.0)(bun-types@1.2.22(@types/react@19.2.2)))(zod@3.24.2) + version: 0.5.1(drizzle-orm@0.44.4(@cloudflare/workers-types@4.20250303.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(better-sqlite3@11.7.0)(bun-types@1.3.0(@types/react@19.2.2)))(zod@3.24.2) zod: specifier: 3.24.2 version: 3.24.2 @@ -6433,8 +6433,8 @@ packages: '@types/braces@3.0.5': resolution: {integrity: sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==} - '@types/bun@1.2.22': - resolution: {integrity: sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA==} + '@types/bun@1.3.0': + resolution: {integrity: sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA==} '@types/caseless@0.12.4': resolution: {integrity: sha512-2in/lrHRNmDvHPgyormtEralhPcN3An1gLjJzj2Bw145VBxkQ75JEXW6CTdMAwShiHQcYsl2d10IjQSdJSJz4g==} @@ -7096,8 +7096,8 @@ packages: bun-types@1.0.8: resolution: {integrity: sha512-2dNB+dBwAcFW7RSd4y5vKycRjouKVklSwPk4EjBKWvcMYUBOqZGGNzV7+b2tfKBG3BeRXnozbnegVKR1azuATg==} - bun-types@1.2.22: - resolution: {integrity: sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA==} + bun-types@1.3.0: + resolution: {integrity: sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ==} peerDependencies: '@types/react': ^19 @@ -17388,9 +17388,9 @@ snapshots: '@types/braces@3.0.5': {} - '@types/bun@1.2.22(@types/react@19.2.2)': + '@types/bun@1.3.0(@types/react@19.2.2)': dependencies: - bun-types: 1.2.22(@types/react@19.2.2) + bun-types: 1.3.0(@types/react@19.2.2) transitivePeerDependencies: - '@types/react' @@ -18260,7 +18260,7 @@ snapshots: bun-types@1.0.8: {} - bun-types@1.2.22(@types/react@19.2.2): + bun-types@1.3.0(@types/react@19.2.2): dependencies: '@types/node': 24.0.8 '@types/react': 19.2.2 @@ -18830,7 +18830,7 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.44.4(@cloudflare/workers-types@4.20250303.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(better-sqlite3@11.7.0)(bun-types@1.2.22(@types/react@19.2.2)): + drizzle-orm@0.44.4(@cloudflare/workers-types@4.20250303.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(better-sqlite3@11.7.0)(bun-types@1.3.0(@types/react@19.2.2)): optionalDependencies: '@cloudflare/workers-types': 4.20250303.0 '@libsql/client': 0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5) @@ -18838,11 +18838,11 @@ snapshots: '@opentelemetry/api': 1.9.0 '@types/pg': 8.11.10 better-sqlite3: 11.7.0 - bun-types: 1.2.22(@types/react@19.2.2) + bun-types: 1.3.0(@types/react@19.2.2) - drizzle-zod@0.5.1(drizzle-orm@0.44.4(@cloudflare/workers-types@4.20250303.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(better-sqlite3@11.7.0)(bun-types@1.2.22(@types/react@19.2.2)))(zod@3.24.2): + drizzle-zod@0.5.1(drizzle-orm@0.44.4(@cloudflare/workers-types@4.20250303.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(better-sqlite3@11.7.0)(bun-types@1.3.0(@types/react@19.2.2)))(zod@3.24.2): dependencies: - drizzle-orm: 0.44.4(@cloudflare/workers-types@4.20250303.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(better-sqlite3@11.7.0)(bun-types@1.2.22(@types/react@19.2.2)) + drizzle-orm: 0.44.4(@cloudflare/workers-types@4.20250303.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(better-sqlite3@11.7.0)(bun-types@1.3.0(@types/react@19.2.2)) zod: 3.24.2 dset@3.1.4: {} -- 2.51.2