CloudLine
SDK

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

discord.js itself is not pulled in by this package — your bot already has it.

Minimal setup (one function call)

index.js
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 line

That 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):

OptionDefaultWhat it does
botIdRequired. Your bot's ID from the dashboard.
secretRequired. The clb_live_… heartbeat secret.
intervalMs30000How often the heartbeat is sent, in milliseconds. Minimum 1000.
maxRetries2How many times to retry one heartbeat if the network drops or the server returns 5xx / 429. A bad secret (401) is never retried.
loggerconsolePass your own { warn, error } object, or false to silence SDK logs.
collectEventLoopLagtrueWhether 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.
baseUrlhttps://cloudline.kescohhtwitch.workers.devOverride only if you're on a self-hosted CloudLine.
getLatencyMs / getGuildsManual metric providers if you are NOT passing a discord.js client.

Third argument to attach() controls the auto-instrumentation:

OptionDefaultWhat it does
autoStarttrueStart the heartbeat loop when clientReady fires.
instrumentCommandstrueTime every slash command, context menu, and modal submit end-to-end.
instrumentComponentstrueTime every button click and select menu interaction.
captureErrorstrueForward discord.js error and shardError events to your dashboard Error Log.
captureRateLimitstrueCount Discord REST 429 responses (when Discord throttles your bot) and report them.
captureUnhandledfalseAlso 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 submit
  • recordComponent(durationMs) — record one button or select menu interaction
  • recordAutocomplete(durationMs) — record one slash-autocomplete response
  • recordRateLimitHit() — note that Discord returned 429 (rate-limited)
  • gauge(name, value) / counter(name, delta) — custom metrics, see Custom metrics
  • captureError(error, { level, context }) — push an error to the Error Log
  • captureGlobalErrors() — install handlers for unhandledRejection + uncaughtException

Runtime support

RuntimeStatus
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() after client.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 strip process.memoryUsage). The bot keeps working; the tile stays blank.
  • 401 — check botId/secret in the logs: you copied the wrong secret, or you rotated it on the dashboard without redeploying.