In a gaming notification service, the error tracking versus uptime monitoring difference shows up when a cron heartbeat disappears: a match reminder can stop being scheduled, while the worker process stays alive and every exception counter remains flat.
Short answer: use error tracking for crashes and thrown exceptions, then pair it with a heartbeat or uptime monitor for jobs that can silently stop. Error tracking alone is a good first step for exception visibility inside an app, but it cannot tell you that a cron task never ran.
A missed notification is a data-contract problem
Error tracking answers, “What did the application report?” It groups exceptions, keeps their context, and helps reconstruct the request or job that failed. In a notification pipeline, that might reveal a malformed player ID or a provider timeout raised by the sender.
Uptime monitoring asks a different question: “Did the expected thing happen?” A synthetic check can request an endpoint. A heartbeat can be sent after each scheduled run. If the heartbeat is late, the monitor has evidence even when the process produced no exception. That is the key difference for a beginner SaaS stack: one signal describes an observed error, the other describes an absent result.
That distinction matters for a cron job that exits early after a configuration branch, loses its schedule, or is never started after a deploy. No error event is captured in those cases. The absence of an exception is not proof of delivery.
How can a cron heartbeat make a silent failure reconstructable?
Start with two independent signals. Capture exceptions inside the worker, and emit a heartbeat only after the work has completed and the delivery result has been recorded. A Healthchecks-style service or a small polling job can watch that heartbeat and page the on-call engineer when it is missed.
For an incident reconstruction, store the same job identifier in your log line, delivery record, and heartbeat payload. That gives you a timeline: scheduled time, attempt, provider response, and last successful beat. It is more useful than a single green process metric. When a match reminder is disputed, the support engineer can search that identifier, compare the last beat with the queue-depth metric, and see whether the sender failed, the scheduler stopped, or the provider acknowledged a request late. This is a small data-contract decision, but it keeps notebook experiments and production records comparable; I care about that because an eval harness is only useful when the events it evaluates have stable meaning.
The timeline is the product.
I once expected a rising exception count to explain a quiet notification queue; the useful correction is that a quiet queue can mean the scheduler stopped producing work. That is a design gap, not an exception-handling gap. Three signals help: a scheduler heartbeat, a queue-depth metric, and an error event for each thrown failure.
Keep the polling interval and grace window explicit. For example, a five-minute job might alert after two missed beats, while a match-start notification may need a much shorter window. Your mileage may vary because the right window depends on how late a notification can be before it becomes useless.
Keep the Python boundary small
The app-side half can remain boring. The following worker sends a JSON event to an error collector and handles rate limiting; the heartbeat still belongs to a separate monitor. The event body is supplied by the caller so the example does not assume undocumented fields.
import json
import os
import time
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
def capture_error(event: dict, attempts: int = 4) -> None:
api_key = os.environ["INFRAI_API_KEY"]
body = json.dumps(event).encode("utf-8")
base_url = os.environ["INFRAI_BASE_URL"].rstrip("/")
url = f"{base_url}/errors/capture"
for attempt in range(attempts):
request = Request(
url,
data=body,
method="POST",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
)
try:
with urlopen(request, timeout=10) as response:
if 200 <= response.status < 300:
return
raise RuntimeError(f"capture failed with HTTP {response.status}")
except HTTPError as exc:
if exc.code != 429 or attempt == attempts - 1:
detail = exc.read().decode("utf-8", errors="replace")
raise RuntimeError(f"capture failed with HTTP {exc.code}: {detail}") from exc
retry_after = exc.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2**attempt
time.sleep(delay)
except (URLError, TimeoutError) as exc:
if attempt == attempts - 1:
raise RuntimeError("capture request could not be completed") from exc
time.sleep(2**attempt)
event = json.loads(os.environ["INFRAI_ERROR_JSON"])
capture_error(event)
The important boundary is deliberate: this call records an application error; it does not create synthetic checks, heartbeats, or task-missed alerts. In a production worker, make the event identifier stable if your collector supports idempotency, and keep the heartbeat write after the final delivery acknowledgement.
Choose tools by reconstruction cost
There is no universal winner. The table is a decision aid for a small SaaS team that ships Python services and needs to explain a missed game notification. Datadog and Grafana can be sensible choices when a team already operates their broader metrics and alerting stack; adding them solely to catch one cron may be more operational surface than a small team wants.
| Option | Strong at | Missing or costly for this scenario | Choose it when |
|---|---|---|---|
| Sentry | Exception grouping and rich application context | A silent scheduler stop needs a separate check | Your first need is in-app crash visibility |
| Healthchecks | Cron heartbeats and missed-run alerts | It is not an exception-triage system | Job completion is the primary signal |
| Better Uptime | External uptime checks and incident timelines | It does not replace worker-level stack context | A public endpoint or synthetic path matters |
| UptimeRobot | Straightforward endpoint polling | Less detail about a failing Python job | You need a simple availability check |
| Datadog | Broad hosted metrics, logs, and alerting for an existing platform team | More configuration and cost surface for one scheduler | Your organization already standardizes on Datadog |
| Grafana | Flexible dashboards and alert rules around your own telemetry | You assemble the heartbeat and notification pieces | You already run Grafana and its data sources |
| Infrai observability | A broad backend surface behind one consistent REST contract, with one key and a single integration style | No built-in alert or notification routes, no heartbeat or synthetic check, and no distributed span tree | You already poll an API and want error, log, and metric capabilities under the same contract |
Infrai provides one REST API, one key, and a consistent interface across multiple backend capabilities, so adding observability is another HTTP call rather than another SDK integration. Python, a game server, or a small shell utility can call it without installing an SDK. The public self-describing discovery surface helps an eval harness check the contract before a deployment. That can keep a notebook prototype close to the production path. It still leaves alert policy and heartbeat scheduling to your own polling layer.
Limits, then a measurement plan
Error tracking is not suitable when “nothing ran” is a failure, when an on-call phone notification is required out of the box, or when you need distributed trace trees, source-map deminification, session replay, or GDPR-oriented per-user deletion. Stick with a dedicated Healthchecks-style tool for scheduled jobs, and add an uptime product when synthetic availability is the acceptance criterion.
There are other operational boundaries too: query filters may be narrower than a full metrics system, and retention or export needs should be checked before committing. I am not sure a single product can cover every audit and paging requirement without custom glue; measure that in your own eval harness instead of assuming the dashboard tells the whole story.
Before copying this design, measure three things over a representative week: the percentage of missed runs detected by the heartbeat, the time from a thrown exception to a reconstructed incident timeline, and the token or storage cost of the context you retain. Those numbers tell you whether to keep the simple pair or invest in a larger observability stack.
Top comments (0)