Custom Metrics
Push your own numbers alongside the built-in tiles — queue depth, jobs processed, payments handled, anything you want to watch.
CloudLine measures the standard bot metrics for you (latency, RAM, CPU, slash command timing). Custom metrics let you add your own numbers — whatever matters for your specific bot. They appear in the Telemetry panel under Custom metrics, with the same sparkline trends as the built-in tiles.
Two kinds:
- Gauges — point-in-time values. "What is X right now?" The latest value is sent on every heartbeat until you set it again or the process exits.
- Counters — accumulated deltas. "How many of X happened since the last heartbeat?" The total is sent on every heartbeat and then resets to zero.
When to use which
| You want to track... | Use |
|---|---|
| Active matches in your game bot | gauge |
| Queue depth | gauge |
| Number of cached entries | gauge |
| Open WebSocket connections | gauge |
| Jobs processed | counter |
| Errors caught and recovered | counter |
| Payments handled | counter |
| Messages reacted to | counter |
Rule of thumb: if asking "what's the value right now?" makes sense, it's a gauge. If asking "how many in the last 30 seconds?" makes sense, it's a counter.
Node.js
const { attach } = require('@cloudline/bot-sdk')
const monitor = attach(client, {
botId: 'bot_abc123',
secret: process.env.CLOUDLINE_SECRET,
})
// Gauge — set the latest value:
monitor.gauge('queue_depth', myQueue.size)
// Counter — increment by 1 (default):
monitor.counter('jobs_processed')
// Counter — increment by N:
monitor.counter('errors_caught', 3)attach() returns the same monitor instance, so you can call gauge() / counter() from anywhere you have a reference to it.
Python
from cloudline import attach
monitor = attach(
bot,
bot_id="bot_abc123",
secret=os.environ["CLOUDLINE_SECRET"],
)
# Gauge — set the latest value:
monitor.gauge("queue_depth", my_queue.qsize())
# Counter — increment by 1 (default):
monitor.counter("jobs_processed")
# Counter — increment by N:
monitor.counter("errors_caught", 3)Raw HTTP
Add a custom_metrics object to your heartbeat payload:
{
"latency_ms": 42,
"memory_mb": 180,
"custom_metrics": {
"queue_depth": 14,
"jobs_processed": 127,
"errors_caught": 3
},
"seq": 12,
"sent_at": 1735862400000
}Note that with raw HTTP, you manage the gauge-vs-counter distinction yourself — the server just stores whatever number you send. If you want counter semantics (drain per heartbeat), reset your local counter to 0 after each successful POST.
Naming rules
- Allowed characters: ASCII letters, digits, dot
., underscore_, dash-. - Length: 1 to 64 characters.
- Names that don't match the rules are silently dropped — the metric just won't appear. The SDK won't crash your bot.
Examples that work:
queue_depth
matches.active
http.requests_total
errors-recoveredExamples that don't:
queue depth (space)
queue/depth (slash)
🔥counter (emoji)
queue_depth_of_the_active_match_queue_for_premium_servers_only_v2 (66 chars — too long)Limits
- 32 unique names per bot, total across gauges + counters. After 32, additional new names are silently dropped (existing ones keep updating).
- The whole
custom_metricspayload is capped at 2 KB (serialized). This is a byte budget, not just a count — long names eat into it, so the effective number of keys drops below 32 if you use long names. Keep names short. Both SDKs and the server keep as many entries as fit (gauges first, then counters, in order) rather than dropping everything on overflow. - Non-finite values (
NaN,Infinity) are silently dropped.
CAUTION
Don't use dynamic, per-user, or per-guild keys as metric names. If your code builds a name like "user_" + userId + "_score" and pushes it as a gauge, you'll blow past the 32-key cap within seconds and start losing data. Instead, aggregate first and push a small fixed set of names.
Good:
monitor.gauge('users_active', activeUserCount)
monitor.counter('user_actions_handled', actionsSinceLastBeat)Bad:
// Will hit the 32-key cap immediately on any moderately busy bot.
monitor.gauge('user_' + userId + '_score', score)Where they appear
Custom metrics show up in two places:
- Telemetry panel → Custom metrics section — one tile per metric name, with current value and a sparkline trend.
- Bot detail → History — same data, retained per your plan's retention policy.
Both gauges and counters appear identically in the UI — the distinction only matters for what you push from your code.
Common patterns
Queue depth (gauge)
setInterval(() => {
monitor.gauge('queue_depth', myJobQueue.size)
}, 5_000)Per-feature usage (counter)
async function handleCheckout(interaction) {
monitor.counter('checkouts_started')
try {
await processCheckout(interaction)
monitor.counter('checkouts_completed')
} catch (err) {
monitor.counter('checkouts_failed')
throw err
}
}External-service health (gauge)
async function pingExternalAPI() {
const t0 = Date.now()
const res = await fetch(EXTERNAL_URL).catch(() => null)
monitor.gauge('external_api_up', res?.ok ? 1 : 0)
monitor.gauge('external_api_latency_ms', Date.now() - t0)
}
setInterval(pingExternalAPI, 30_000)