Node.js / discord.js
Install @cloudline/bot-sdk, attach it to your discord.js client, and your dashboard fills with telemetry on the next bot restart.
@cloudline/bot-sdk is the official Node package for monitoring discord.js bots. Once attached, it sends a heartbeat (a small HTTP POST that says "I'm alive") every 30 seconds and includes the metrics the dashboard shows — gateway ping, RAM, CPU, slash command timing, and shard health.
You do not write the heartbeat loop, the timers, or the metric collection yourself. The SDK reads them off your Client instance.
Install
npm install @cloudline/bot-sdkdiscord.js itself is not pulled in by this package — your bot already has it.
Minimal setup (one function call)
const { Client, GatewayIntentBits } = require('discord.js')
const { attach } = require('@cloudline/bot-sdk')
const client = new Client({ intents: [GatewayIntentBits.Guilds] })
attach(client, {
botId: 'bot_abc123', // from your dashboard URL
secret: process.env.CLOUDLINE_SECRET, // the clb_live_… string
})
client.login(process.env.DISCORD_TOKEN) // must be the LAST lineThat is the whole integration. attach() reads the client, wires listeners, and starts the heartbeat loop when discord.js emits ready.
IMPORTANT
Call attach() before client.login(). The SDK listens for the ready event, and if you call login() first, that event may fire before the listener is in place — you will see a grey status that never goes green.
Options
Pass these as the second argument to attach() (or to new CloudLine() in manual mode):
| Option | Default | What it does |
|---|---|---|
botId | — | Required. Your bot's ID from the dashboard. |
secret | — | Required. The clb_live_… heartbeat secret. |
intervalMs | 30000 | How often the heartbeat is sent, in milliseconds. Minimum 1000. |
maxRetries | 2 | How many times to retry one heartbeat if the network drops or the server returns 5xx / 429. A bad secret (401) is never retried. |
logger | console | Pass your own { warn, error } object, or false to silence SDK logs. |
collectEventLoopLag | true | Whether to measure how long Node's event loop is blocked. Turn off only if you are running on a runtime that doesn't ship perf_hooks. |
baseUrl | https://cloudline.kescohhtwitch.workers.dev | Override only if you're on a self-hosted CloudLine. |
getLatencyMs / getGuilds | — | Manual metric providers if you are NOT passing a discord.js client. |
Third argument to attach() controls the auto-instrumentation:
| Option | Default | What it does |
|---|---|---|
autoStart | true | Start the heartbeat loop when clientReady fires. |
instrumentCommands | true | Time every slash command, context menu, and modal submit end-to-end. |
instrumentComponents | true | Time every button click and select menu interaction. |
captureErrors | true | Forward discord.js error and shardError events to your dashboard Error Log. |
captureRateLimits | true | Count Discord REST 429 responses (when Discord throttles your bot) and report them. |
captureUnhandled | false | Also catch process-level unhandledRejection and uncaughtException. The process exits after reporting (250 ms grace) so your host can restart cleanly. |
Example with non-defaults:
attach(client, {
botId: 'bot_abc123',
secret: process.env.CLOUDLINE_SECRET,
intervalMs: 10_000, // 10s instead of 30s — needs Pro plan or higher
}, {
captureUnhandled: true, // crash → reported → exit(1)
instrumentComponents: false, // disable button/select timing only
})What gets sent
Each heartbeat POSTs to /api/bots/{botId}/heartbeat with a JSON body. Every field is optional — if the runtime can't measure something, it is simply omitted.
- Connection:
latency_ms(gateway ping),gateway_ok,gateway_stale_sec - Guilds + shards:
guilds,shards_total,shards_connected,shard_detail - Slash + components:
slash_p50_ms,slash_p95_ms,slash_count,component_p50_ms,component_p95_ms,component_count,autocomplete_p50_ms,autocomplete_p95_ms,autocomplete_count - Process:
memory_mb,cpu_pct,uptime_sec,event_loop_lag_ms - Discord REST:
discord_rate_limit_hits(429 count since last heartbeat) - Custom:
custom_metrics— see Custom metrics
p50 is the typical response time. p95 is the slow tail — 5% of interactions are slower than this number. Both reset and recalculate per heartbeat.
Manual mode (no discord.js)
If you're using a different Discord library (eris, oceanic.js, etc.) or you want full control:
const { CloudLine } = require('@cloudline/bot-sdk')
const monitor = new CloudLine({
botId: 'bot_abc123',
secret: process.env.CLOUDLINE_SECRET,
intervalMs: 30_000,
getLatencyMs: () => myGatewayPing(),
getGuilds: () => myGuildCount(),
})
monitor.start()
// Time your slash commands yourself:
const t0 = Date.now()
await handleCommand()
monitor.recordCommand(Date.now() - t0)
// On shutdown:
monitor.stop()Available methods on the monitor:
recordCommand(durationMs)— record one slash / context menu / modal submitrecordComponent(durationMs)— record one button or select menu interactionrecordAutocomplete(durationMs)— record one slash-autocomplete responserecordRateLimitHit()— note that Discord returned 429 (rate-limited)gauge(name, value)/counter(name, delta)— custom metrics, see Custom metricscaptureError(error, { level, context })— push an error to the Error LogcaptureGlobalErrors()— install handlers forunhandledRejection+uncaughtException
Runtime support
| Runtime | Status |
|---|---|
| Node.js 18+ | Full support |
| Bun 1.0+ | Full support |
| Deno 1.40+ | Heartbeat + slash timing work. event_loop_lag_ms reports null unless you start with --unstable-node-builtins. |
Safety guarantees
The SDK is built so it cannot break your bot:
- Every HTTP request has a 10-second timeout. A hung TCP connection cannot stall the bot.
- If one heartbeat takes longer than the interval, the next one is skipped instead of running in parallel.
- The error reporter has a per-minute cap (30 errors / 60 seconds) and a 1-minute dedupe on identical messages, so a crash loop cannot spam the API.
- A bad secret (401) stops retrying and just logs an error — no infinite reconnect loop.
Common problems
- Status stays grey: you called
attach()afterclient.login(), or the env vars aren't loaded. See No data showing. - CPU tile shows
—: process stats are unavailable in that runtime (e.g. some serverless platforms stripprocess.memoryUsage). The bot keeps working; the tile stays blank. 401 — check botId/secretin the logs: you copied the wrong secret, or you rotated it on the dashboard without redeploying.