SMS, Telegram or Bale? Choosing Alert Channels Without Alert Fatigue
Which downtime alerts should go by SMS and which to Telegram or Bale, how to route by urgency, and how to keep the important alerts from drowning in noise.
A plain-text log line is written for a human reading one line at a time. Production debugging rarely works that way: you search millions of lines for one order ID, count errors per endpoint, or follow one request across five services. Structured logs - usually JSON - make each value a field that tools can query directly.
noc724 checks your websites, servers and APIs around the clock and alerts you by SMS, Telegram or Bale the moment something breaks.
Plain text:
2026-10-18 09:14:03 ERROR Payment failed for order 58114 after 10002ms (gateway timeout)Structured:
{"ts":"2026-10-18T09:14:03.211Z","level":"error","service":"payments","msg":"payment failed","order_id":58114,"duration_ms":10002,"reason":"gateway_timeout","request_id":"7f3c2a"}With the second form, "all failed payments over 5 seconds in the last hour, grouped by reason" is a query, not a regular expression you hope is right.
| Field | Purpose |
|---|---|
ts | ISO 8601 timestamp in UTC |
level | debug / info / warn / error / fatal |
service | which application wrote it |
msg | a short, constant message ("payment failed"), not a sentence with values inside |
request_id / trace_id | ties together everything one request did |
duration_ms | how long the operation took |
| domain IDs | user_id, order_id - whatever you search by |
error | error type and message; stack trace as its own field |
The key rule: keep the message constant and put variables in fields. "payment failed" with order_id: 58114 groups perfectly; "Payment failed for order 58114" creates a unique message per order.
import logging, json, sys
class JsonFormatter(logging.Formatter):
def format(self, record):
event = {"ts": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"), "level": record.levelname.lower(),
"service": "payments", "msg": record.getMessage()}
event.update(getattr(record, "fields", {}))
return json.dumps(event)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JsonFormatter())
log = logging.getLogger("payments")
log.addHandler(handler)
log.setLevel(logging.INFO)
log.error("payment failed", extra={"fields": {"order_id": 58114, "reason": "gateway_timeout"}})logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Error("payment failed", "order_id", 58114, "reason", "gateway_timeout", "duration_ms", 10002)console.log(JSON.stringify({ ts: new Date().toISOString(), level: "error", msg: "payment failed", order_id: 58114 }));In real projects use an established library (structlog, pino, zap and similar) rather than hand-written formatting.
Generate a request ID at the edge (load balancer or first service), pass it on in a header such as X-Request-ID or the W3C traceparent, and log it in every event. One search then shows the whole story of a failing request across services - the basis of observability.
{"user_58114": ...}); log platforms index every key.userId, user_id and uid in three services make cross-service search painful. Agree on a short schema.Slightly. Tools such as jq or a log viewer format them nicely, and many libraries print readable output in development and JSON in production.
No. Even with files on one server, jq can filter and count JSON logs far more reliably than grep. A central platform adds search across servers and retention.
They complement each other. Metrics are cheap and great for alerting and trends; logs explain individual events. Count in metrics, explain with logs.
Written by
The noc724 team
Uptime monitoring engineers
We build and run noc724, an uptime monitoring service that checks websites, servers and APIs from several cities in Iran. These guides come from the problems we and our users run into while keeping services online.
Contact the teamExpert on:Logging
Which downtime alerts should go by SMS and which to Telegram or Bale, how to route by urgency, and how to keep the important alerts from drowning in noise.
Why uptime monitors raise false alarms - network blips, timeouts, rate limits, content changes - and the settings that remove them while keeping real alerts fast.
How to set up a simple on-call rotation and escalation policy so every critical alert is answered, without burning out a small team.