Monitor hạn chót: việc hẹn giờ phải xong trước hạn
Đăng ký từng lượt bằng khoá ingest hoặc API, kết thúc bằng span, log hoặc check-in; lượt không thành công trước hạn chót sẽ cảnh báo.
Trong bài này
Monitor hạn chót canh những việc app của anh hẹn cho một thời điểm cụ thể: bài hẹn đăng lúc 08:30, đợt xuất hoá đơn ngày mùng 1, báo cáo hứa gửi trước 09:00. App đăng ký từng lượt (occurrence) trước giờ hẹn; nếu đến giờ hẹn cộng thời gian ân hạn vẫn chưa có thành công, Belvador cảnh báo MONITOR_DEADLINE_MISSED.
Lượt chưa từng được đăng ký thì không thể báo là lỡ. Hãy đăng ký lúc lên lịch công việc, không phải lúc nó bắt đầu chạy.
1. Tạo monitor#
Monitor → Tạo monitor → Hạn chót. Chọn:
Ân hạn sau giờ hẹn — từ 0 đến 1440 phút. Hạn chót của mỗi lượt = giờ hẹn + ân hạn.
Cũng cảnh báo khi lượt đã bắt đầu im lặng (không bắt buộc) — xem lượt chạy bị treo.
Môi trường — mỗi môi trường được xét riêng.
Slug của monitor là thứ app gửi lên.
2. Lấy khoá ingest#
Ở Project → Cài đặt → Khoá ingest, tạo khoá có scope monitors (có thể giới hạn theo môi trường) và lưu làm secret phía server, ví dụ BELVADOR_INGEST_KEY. Khoá ingest là bí mật: chỉ đăng ký từ server, không bao giờ từ trình duyệt — yêu cầu có header Origin bị từ chối.
3. Đăng ký từng lượt#
curl -fsS -m 10 --retry 3 -X POST "https://ingest.belvador.dev/api/<project id>/occurrences/" \ -H "Authorization: Bearer $BELVADOR_INGEST_KEY" \ -H "Content-Type: application/json" \ -d '{"monitor_slug":"scheduled-posts","occurrence_id":"post_8f2a91","due_at":"2026-11-02T08:30:00Z","environment":"production"}'Hoặc bằng TypeScript trên server:
// Server only: the ingest key is a secret. Call when the job is scheduled, before it is due.export async function expectRun(occurrenceId: string, dueAt: Date, environment = "production") { const res = await fetch("https://ingest.belvador.dev/api/<project id>/occurrences/", { method: "POST", headers: { authorization: `Bearer ${process.env.BELVADOR_INGEST_KEY}`, "content-type": "application/json", }, body: JSON.stringify({ monitor_slug: "scheduled-posts", occurrence_id: occurrenceId, // the same id again moves its due time due_at: dueAt.toISOString(), environment, }), signal: AbortSignal.timeout(10_000), }); // 200 registered | rescheduled | duplicate. 429 and 503 carry Retry-After: try again later. if (!res.ok) throw new Error(`Belvador occurrence: HTTP ${res.status}`);}| Trường | Quy tắc |
|---|---|
monitor_slug | Slug của monitor hạn chót |
occurrence_id | Id của lượt này do app đặt, 1–128 ký tự A–Z a–z 0–9 . _ : -. Dùng UUID nếu lượt kết thúc bằng check-in |
due_at | Thời điểm ISO 8601 có múi giờ, từ 5 phút trước đến tối đa 35 ngày tới |
environment | Không bắt buộc, mặc định production; phải là môi trường monitor canh và khoá cho phép |
Thân yêu cầu tối đa 4 KB. Kết quả:
| Mã | Ý nghĩa |
|---|---|
200 registered / rescheduled / duplicate | Đã nhận. Gửi lại cùng id với due_at mới sẽ dời lượt đang chờ |
400 | Thân yêu cầu không đúng các trường ở trên |
401 / 403 | Thiếu hoặc sai khoá, khoá không có scope monitors hoặc của project khác, yêu cầu từ trình duyệt, hoặc môi trường khoá không cho phép |
404 | Project không có monitor hạn chót với slug này |
422 | due_at ngoài khoảng cho phép, môi trường monitor không canh, hoặc lượt đã kết thúc |
429 | Quá nhiều lượt đang chờ (mặc định của gói: 10,000 mỗi monitor, 100,000 mỗi project). Thử lại sau Retry-After (300 giây); mỗi lần bị từ chối được đếm trên trang monitor |
503 | Node đang không liên lạc được. Chưa có gì được đăng ký — thử lại sau Retry-After |
Cùng thao tác này có ở POST /v1/occurrences bằng API token (scope monitors:write), cho công cụ và AI agent.
4. Kết thúc lượt#
Một trong các tín hiệu sau sẽ chốt lượt có cùng id:
span hoặc log có
belvador.run.id= occurrence id vàbelvador.outcome=success(hoặcfailure):
import { trace } from "@opentelemetry/api";// Inside the job: the span that does the work names the run and its outcome.await trace.getTracer("jobs").startActiveSpan("publish-post", async (span) => { span.setAttribute("belvador.run.id", occurrenceId); try { await publishPost(); span.setAttribute("belvador.outcome", "success"); } catch (err) { span.setAttribute("belvador.outcome", "failure"); throw err; } finally { span.end(); }});check-in
ok(hoặcerror) cócheck_in_idlà occurrence id — id của check-in là UUID, nên dùng occurrence id dạng UUID với cách này.
Thành công trước hạn chót là đúng hạn. Thất bại sẽ cảnh báo MONITOR_FAILED. Thành công đến sau khi đã lỡ hạn được ghi là trễ (trong vòng 7 ngày); cảnh báo lỡ hạn vẫn nằm trong lịch sử và monitor hồi phục.
Huỷ một lượt#
Nếu app dời hoặc bỏ công việc, hãy huỷ lượt đang chờ để nó không bao giờ cảnh báo:
curl -fsS -m 10 --retry 3 -X DELETE "https://ingest.belvador.dev/api/<project id>/occurrences/" \ -H "Authorization: Bearer $BELVADOR_INGEST_KEY" \ -H "Content-Type: application/json" \ -d '{"monitor_slug":"scheduled-posts","occurrence_id":"post_8f2a91","environment":"production"}'DELETE /v1/occurrences/<monitor_slug>/<occurrence_id> làm việc tương tự qua API.
Trang monitor hiển thị gì#
Hạn gần nhất kèm đếm ngược, dải 24 giờ qua (đúng hạn · trễ · lỡ hạn · thất bại · đã huỷ · đang chờ) và bảng các lượt. Occurrence id hiển thị đúng như app gửi và không bao giờ được sao chép ra khỏi node; cảnh báo không bao giờ chứa chúng.
Thấy chỗ sai hoặc thiếu? Gửi cho chúng tôi: hello@belvador.dev