Bỏ qua tới nội dung
Mục lục

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.

Bản Markdown
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#

Terminal
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:

TypeScript
// 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ườngQuy tắc
monitor_slugSlug của monitor hạn chót
occurrence_idId 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_atThời điểm ISO 8601 có múi giờ, từ 5 phút trước đến tối đa 35 ngày tới
environmentKhô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ờ
400Thân yêu cầu không đúng các trường ở trên
401 / 403Thiế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
404Project không có monitor hạn chót với slug này
422due_at ngoài khoảng cho phép, môi trường monitor không canh, hoặc lượt đã kết thúc
429Quá 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
503Node đ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ặc failure):

TypeScript
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ặc error) có check_in_id là 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:

Terminal
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