CloudLine
SDK

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 botgauge
Queue depthgauge
Number of cached entriesgauge
Open WebSocket connectionsgauge
Jobs processedcounter
Errors caught and recoveredcounter
Payments handledcounter
Messages reacted tocounter

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-recovered

Examples 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_metrics payload 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:

  1. Telemetry panel → Custom metrics section — one tile per metric name, with current value and a sparkline trend.
  2. 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)