Openstatus www.openstatus.dev

๐Ÿ”ฅ Improve error handling workflows (#1466)

* ๐Ÿฅ…

* ๐Ÿ”ฅ

* ๐Ÿ˜ญ

* ๐Ÿ˜ญ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ๐Ÿš€

* ๐Ÿ˜ญ

* ๐Ÿ”ฅ

authored by

Thibault Le Ouay and committed by
GitHub
6fd6233c dbbcca42

+107 -21
+56
.github/workflows/workflow-preview.yml
··· 1 + name: Fly Preview Workflows 2 + on: 3 + pull_request: 4 + types: [opened, reopened, synchronize, closed] 5 + paths: 6 + - "apps/workflows/**" 7 + - "packages/db/**" 8 + - "packages/emails/**" 9 + - "packages/utils/**" 10 + - "packages/tsconfig/**" 11 + env: 12 + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 13 + # Set these to your Fly.io organization and preferred region. 14 + FLY_REGION: ams 15 + FLY_ORG: openstatus 16 + 17 + jobs: 18 + review_app: 19 + runs-on: ubuntu-latest 20 + outputs: 21 + url: ${{ steps.deploy.outputs.url }} 22 + # Only run one deployment at a time per PR. 23 + concurrency: 24 + group: pr-${{ github.event.number }} 25 + 26 + # Deploying apps with this "review" environment allows the URL for the app to be displayed in the PR UI. 27 + # Feel free to change the name of this environment. 28 + environment: 29 + name: pr-${{ github.event.number }} # The script in the `deploy` sets the URL output for each review app. 30 + url: ${{ steps.deploy.outputs.url }} 31 + steps: 32 + - name: Get code 33 + uses: actions/checkout@v4 34 + 35 + - name: Deploy PR app to Fly.io 36 + id: deploy 37 + uses: superfly/fly-pr-review-apps@1.2.1 38 + with: 39 + config: apps/workflows/fly.toml 40 + vmsize: shared-cpu-1x 41 + name: openstatus-workflows-pr-${{ github.event.number }} 42 + secrets: | 43 + DATABASE_URL=${{ secrets.STAGING_DB_URL }} 44 + DATABASE_AUTH_TOKEN=${{ env.STAGING_DB_AUTH_TOKEN }} 45 + RESEND_API_KEY=${{ secrets.STAGING_RESEND_API_KEY }} 46 + UPSTASH_REDIS_REST_URL=test 47 + UPSTASH_REDIS_REST_TOKEN=test 48 + GCP_PROJECT_ID=test 49 + 50 + - name: Clean up GitHub environment 51 + uses: strumwolf/delete-deployment-environment@v2 52 + if: ${{ github.event.action == 'closed' }} 53 + with: 54 + # โš ๏ธ The provided token needs permission for admin write:org 55 + token: ${{ secrets.GITHUB_TOKEN }} 56 + environment: pr-${{ github.event.number }}
+1 -1
apps/workflows/docker-compose.yaml
··· 23 23 source: ./data 24 24 target: /app/data 25 25 image: workflows-test 26 - command: . 26 + # command: .
+9 -2
apps/workflows/src/cron/checker.ts
··· 16 16 import { regionDict } from "@openstatus/regions"; 17 17 import { db } from "../lib/db"; 18 18 19 + import { getSentry } from "@hono/sentry"; 19 20 import type { monitorPeriodicitySchema } from "@openstatus/db/src/schema/constants"; 20 21 import { 21 22 type httpPayloadSchema, 22 23 type tpcPayloadSchema, 23 24 transformHeaders, 24 25 } from "@openstatus/utils"; 26 + import type { Context } from "hono"; 25 27 import { env } from "../env"; 26 28 27 29 export const isAuthorizedDomain = (url: string) => { ··· 39 41 40 42 export async function sendCheckerTasks( 41 43 periodicity: z.infer<typeof monitorPeriodicitySchema>, 44 + c: Context, 42 45 ) { 43 46 const client = new CloudTasksClient({ 44 47 fallback: "rest", ··· 156 159 157 160 const success = allRequests.filter((r) => r.status === "fulfilled").length; 158 161 const failed = allRequests.filter((r) => r.status === "rejected").length; 159 - const failedRequest = allRequests.filter((r) => r.status === "rejected"); 160 162 161 - console.log(failedRequest?.at(0)); 162 163 console.log( 163 164 `End cron for ${periodicity} with ${allResult.length} jobs with ${success} success and ${failed} failed`, 164 165 ); 166 + if (failed > 0) { 167 + getSentry(c).captureMessage( 168 + `sendCheckerTasks for ${periodicity} ended with ${failed} failed tasks`, 169 + "error", 170 + ); 171 + } 165 172 } 166 173 // timestamp needs to be in ms 167 174 const createCronTask = async ({
+26 -3
apps/workflows/src/cron/index.ts
··· 1 + import { getSentry } from "@hono/sentry"; 1 2 import { monitorPeriodicitySchema } from "@openstatus/db/src/schema/constants"; 2 3 import { Hono } from "hono"; 3 4 import { env } from "../env"; ··· 29 30 if (!schema.success) { 30 31 return c.json({ error: schema.error.issues?.[0].message }, 400); 31 32 } 32 - 33 + const sentry = getSentry(c); 34 + const checkInId = sentry.captureCheckIn({ 35 + monitorSlug: period, 36 + status: "in_progress", 37 + }); 33 38 try { 34 - await sendCheckerTasks(schema.data); 35 - 39 + await sendCheckerTasks(schema.data, c); 40 + sentry.captureCheckIn({ 41 + checkInId, 42 + monitorSlug: period, 43 + status: "ok", 44 + }); 36 45 return c.json({ success: schema.data }, 200); 37 46 } catch (e) { 38 47 console.error(e); 48 + sentry.captureMessage(`Error in /checker/${period} cron: ${e}`, "error"); 49 + sentry.captureCheckIn({ 50 + checkInId, 51 + monitorSlug: period, 52 + status: "error", 53 + }); 39 54 return c.text("Internal Server Error", 500); 40 55 } 41 56 }); ··· 66 81 } 67 82 68 83 if (!userId) { 84 + getSentry(c).captureMessage( 85 + "userId is missing in /monitors/:step cron", 86 + "error", 87 + ); 69 88 return c.json({ error: "userId is required" }, 400); 70 89 } 71 90 if (!initialRun) { 91 + getSentry(c).captureMessage( 92 + "initalRun is missing in /monitors/:step cron", 93 + "error", 94 + ); 72 95 return c.json({ error: "initialRun is required" }, 400); 73 96 } 74 97
+15 -15
pnpm-lock.yaml
··· 1144 1144 version: 2.6.2 1145 1145 drizzle-orm: 1146 1146 specifier: 0.44.4 1147 - 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)) 1147 + 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)) 1148 1148 hono: 1149 1149 specifier: 4.5.3 1150 1150 version: 4.5.3 ··· 1160 1160 version: link:../../packages/tsconfig 1161 1161 '@types/bun': 1162 1162 specifier: latest 1163 - version: 1.2.22(@types/react@19.2.2) 1163 + version: 1.3.0(@types/react@19.2.2) 1164 1164 typescript: 1165 1165 specifier: 5.7.2 1166 1166 version: 5.7.2 ··· 1301 1301 version: 0.7.1(typescript@5.7.2)(zod@3.24.2) 1302 1302 drizzle-orm: 1303 1303 specifier: 0.44.4 1304 - 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)) 1304 + 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)) 1305 1305 drizzle-zod: 1306 1306 specifier: 0.5.1 1307 - 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) 1307 + 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) 1308 1308 zod: 1309 1309 specifier: 3.24.2 1310 1310 version: 3.24.2 ··· 6433 6433 '@types/braces@3.0.5': 6434 6434 resolution: {integrity: sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==} 6435 6435 6436 - '@types/bun@1.2.22': 6437 - resolution: {integrity: sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA==} 6436 + '@types/bun@1.3.0': 6437 + resolution: {integrity: sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA==} 6438 6438 6439 6439 '@types/caseless@0.12.4': 6440 6440 resolution: {integrity: sha512-2in/lrHRNmDvHPgyormtEralhPcN3An1gLjJzj2Bw145VBxkQ75JEXW6CTdMAwShiHQcYsl2d10IjQSdJSJz4g==} ··· 7096 7096 bun-types@1.0.8: 7097 7097 resolution: {integrity: sha512-2dNB+dBwAcFW7RSd4y5vKycRjouKVklSwPk4EjBKWvcMYUBOqZGGNzV7+b2tfKBG3BeRXnozbnegVKR1azuATg==} 7098 7098 7099 - bun-types@1.2.22: 7100 - resolution: {integrity: sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA==} 7099 + bun-types@1.3.0: 7100 + resolution: {integrity: sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ==} 7101 7101 peerDependencies: 7102 7102 '@types/react': ^19 7103 7103 ··· 17388 17388 17389 17389 '@types/braces@3.0.5': {} 17390 17390 17391 - '@types/bun@1.2.22(@types/react@19.2.2)': 17391 + '@types/bun@1.3.0(@types/react@19.2.2)': 17392 17392 dependencies: 17393 - bun-types: 1.2.22(@types/react@19.2.2) 17393 + bun-types: 1.3.0(@types/react@19.2.2) 17394 17394 transitivePeerDependencies: 17395 17395 - '@types/react' 17396 17396 ··· 18260 18260 18261 18261 bun-types@1.0.8: {} 18262 18262 18263 - bun-types@1.2.22(@types/react@19.2.2): 18263 + bun-types@1.3.0(@types/react@19.2.2): 18264 18264 dependencies: 18265 18265 '@types/node': 24.0.8 18266 18266 '@types/react': 19.2.2 ··· 18830 18830 transitivePeerDependencies: 18831 18831 - supports-color 18832 18832 18833 - 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)): 18833 + 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)): 18834 18834 optionalDependencies: 18835 18835 '@cloudflare/workers-types': 4.20250303.0 18836 18836 '@libsql/client': 0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5) ··· 18838 18838 '@opentelemetry/api': 1.9.0 18839 18839 '@types/pg': 8.11.10 18840 18840 better-sqlite3: 11.7.0 18841 - bun-types: 1.2.22(@types/react@19.2.2) 18841 + bun-types: 1.3.0(@types/react@19.2.2) 18842 18842 18843 - 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): 18843 + 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): 18844 18844 dependencies: 18845 - 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)) 18845 + 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)) 18846 18846 zod: 3.24.2 18847 18847 18848 18848 dset@3.1.4: {}