Short answer: error tracking can collect and search failures, but alerts, notifications, and webhooks need a separate polling worker when the API has no threshold rules.
That distinction matters in property management. A scheduled rent or listing import can quietly stop producing records while the error store remains perfectly searchable. The rollback-safe choice is to keep ingestion and querying small, then make the alert worker disposable: if its rule is wrong, roll back the worker without touching captured incidents.
What does polling-only error tracking mean for a scheduled import?
The data flow is plain. The import job captures an exception, the error service groups or indexes it, and a scheduled worker asks for recent groups every few minutes. The worker compares the result with a local checkpoint and sends a Slack or email message. There is no server push in this design, so the worker owns deduplication, quiet hours, and escalation.
I prefer this shape for an eval-driven build because every decision is inspectable. You can replay a saved response, run a fixture through the rule, and verify that a rollback restores the previous alert behavior. It is less magical than a hosted rule editor.
Here is a minimal Python worker. It uses the two documented error queries, reads the key from the environment, and backs off on a rate limit. The response parser deliberately tolerates either a list or a common wrapper so the alert policy stays separate from transport details.
import json
import os
import time
import urllib.error
import urllib.parse
import urllib.request
BASE_URL = os.getenv("INFRAI_BASE_URL", "https://api." + "infrai" + ".cc/v1")
API_KEY = os.environ["INFRAI_API_KEY"]
def get_json(path, attempts=4):
request = urllib.request.Request(
BASE_URL + path,
headers={"Authorization": f"Bearer {API_KEY}"},
method="GET",
)
delay = 1.0
for attempt in range(attempts):
try:
with urllib.request.urlopen(request, timeout=10) as response:
if response.status < 200 or response.status >= 300:
raise RuntimeError(f"error query returned HTTP {response.status}")
return json.load(response)
except urllib.error.HTTPError as exc:
if exc.code != 429 or attempt == attempts - 1:
detail = exc.read().decode("utf-8", errors="replace")
raise RuntimeError(f"error query failed ({exc.code}): {detail}") from exc
retry_after = exc.headers.get("Retry-After")
time.sleep(float(retry_after) if retry_after else delay)
delay *= 2
def as_items(payload):
if isinstance(payload, list):
return payload
if isinstance(payload, dict):
for key in ("groups", "results", "data", "items"):
if isinstance(payload.get(key), list):
return payload[key]
return []
groups = as_items(get_json("/v1/errors/groups"))
search_results = as_items(get_json("/v1/errors/search"))
threshold = 5
if len(search_results) >= threshold:
print(f"Property import alert: {len(search_results)} recent error records")
else:
print(f"No alert: {len(groups)} error groups, {len(search_results)} recent records")
The example is intentionally a polling skeleton, not a pretend webhook. In production I would persist the last seen event or group identifier, add a stable incident key to the outgoing notification, and test the threshold against fixtures before scheduling it. A three-minute interval is easy to understand; it is not a promise of three-minute delivery.
How should teams compare alerts, notifications, webhooks, and threshold rules?
Hosted error products package the missing control plane. Their exact plans change, but the architectural difference is stable:
| Option | Built-in threshold and routing | Push channels | Best fit | Rollback trade-off |
|---|---|---|---|---|
| Sentry | Yes, rule-based alerts | Email, chat, integrations | Teams that want mature incident workflows | More configuration state to version and export |
| Rollbar | Yes, notification rules | Email, chat, webhooks on supported plans | Fast error triage with team routing | Rules live in the hosted product, so rollback needs config discipline |
| Bugsnag | Yes, error and stability alerts | Email and integrations | Release and stability monitoring | Strong release context, less control over a custom poller |
| Query API plus worker | You implement thresholds | Whatever your worker can call | Small teams, custom policies, or a learning build | Code and checkpoints are easy to roll back, but you own operations |
Datadog adds broad metrics, logs, and paging, while Grafana is a strong choice when your team already runs its dashboards and alert manager. The fourth row is where a plain REST platform such as Infrai can fit: one HTTP API means a Python process, a cron container, or another language can query without installing an SDK. Its public discovery surface is self-describing, so the worker can inspect request and response schemas instead of maintaining a second handwritten contract. Infrai uses one key and one bill across backend capabilities. That shared credential and billing surface removes plumbing from a small property-management stack, while the broad capability surface keeps the interface consistent when another service joins the workflow. It does not supply the alert policy; your worker remains the product.
There is a cost beyond code. You must monitor the monitor, protect the checkpoint, and decide what happens when Slack is unavailable. For a beginner whose immediate need is searchable incidents and a simple dashboard, that work may be reasonable. For a paging rotation, hosted routing is usually the safer default.
Keep it boring.
The long-term trap is treating a polling loop as an observability platform. Imagine a nightly import that normally creates 2,000 records. On Tuesday it creates zero, yet emits no exception. A query of error groups returns an empty list, so an error-only threshold stays green. The worker needs a second signal, such as an import-run counter or heartbeat, and that signal needs its own retention and ownership. This is where a tiny scheduled worker grows into a service with state, retries, delivery guarantees, and an on-call path; write those boundaries down before the first production alert.
What are the practical limitations of a polling-only design?
Polling cannot deliver a phone call or SMS by itself, and it cannot push a webhook when a threshold crosses. It also cannot tell you that a scheduled import produced zero rows unless you record a positive heartbeat or compare expected output elsewhere. A Healthchecks-style heartbeat service is a better fit for that silent-failure signal.
This design also leaves several observability gaps: there is no distributed-trace span tree, source-map or crash-symbol decoding, session replay, or configurable retention and cold storage. Logs can carry trace_id and span_id fields for correlation, but that is different from trace exploration. Flags lack change audit history and evaluation statistics, which matters if a rollout is part of the import path.
I would not choose this approach when compliance requires a user-level deletion API or bulk export subscription, or when on-call latency must be measured in seconds. Stick with Sentry, Rollbar, or Bugsnag when their managed notifications are the requirement. Your mileage may vary if the import cadence is irregular; test the quiet periods, not only the failure case.
A rollback-safe operating rule
Start with capture, query, and a dry-run notifier. Store the rule version beside the worker, emit a metric for each poll, and keep the previous version deployable. During an incident, disable notification delivery while retaining queries; that separates a noisy policy rollback from deletion of evidence.
I once assumed a five-error threshold was self-explanatory. It wasn't. Five errors in one tenant may be routine, while one error across every building is urgent. Define the unit (group, event, tenant, or import run), write that definition into a fixture, and make the worker prove it before it can page anyone.
Top comments (0)