CloudLine
SDK

Python / discord.py

Install cloudline-bot, attach it to your discord.py bot, and the dashboard starts filling with telemetry on the next restart.

cloudline-bot is the official Python package for monitoring discord.py bots. Once attached, it sends a heartbeat (a small HTTP POST that says "I'm alive") every 30 seconds with gateway ping, RAM, CPU, slash command timing, and shard health.

You do not write the heartbeat loop or the metric collection yourself — the SDK reads them off your bot instance.

Install

pip install "cloudline-bot[metrics]"

The [metrics] extra pulls in psutil, which is what unlocks CPU %, resident-set memory, and true process uptime. Without it the dashboard tiles for CPU / RAM / uptime stay blank — strongly recommended unless you're packaging for an unusual environment.

NOTE

Requires Python 3.9 or newer. discord.py is not installed by this package — your bot already has it.

Minimal setup

bot.py
import os
import discord
from discord.ext import commands
from cloudline import attach

bot = commands.Bot(command_prefix="!", intents=discord.Intents.default())

attach(
    bot,
    bot_id="bot_abc123",                     # from your dashboard URL
    secret=os.environ["CLOUDLINE_SECRET"],   # the clb_live_… string
)

bot.run(os.environ["DISCORD_TOKEN"])

That's the whole integration. attach() adds the listeners, and the heartbeat loop starts when discord.py emits on_ready.

IMPORTANT

attach() must run before bot.run()bot.run() blocks until the bot is shut down, so anything after it never executes.

Auto-instrumentation requires commands.Bot or AutoShardedBot (anything with add_listener). A bare discord.Client works in manual mode (see below).

Options

ArgumentDefaultWhat it does
clientNoneThe bot to auto-instrument. Omit for manual mode.
bot_idRequired. Your bot's ID from the dashboard.
secretRequired. The clb_live_… heartbeat secret.
interval30.0Heartbeat interval in seconds. Minimum 1.0.
max_retries2How many times to retry one heartbeat on a transient failure (network drop / 5xx / 429). A bad secret (401) is never retried.
base_urlhttps://cloudline.kescohhtwitch.workers.devOverride only for self-hosted CloudLine.
get_latency_ms / get_guildsManual metric providers if you're NOT passing a discord.py bot.

Example with non-defaults:

attach(
    bot,
    bot_id="bot_abc123",
    secret=os.environ["CLOUDLINE_SECRET"],
    interval=10.0,        # every 10s — needs Pro plan or higher
    max_retries=3,
)

What gets sent

Each heartbeat POSTs to /api/bots/{bot_id}/heartbeat with a JSON body. Every field is optional — anything the runtime can't measure 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 (asyncio loop lag)
  • Discord REST: discord_rate_limit_hits (count of 429 responses; needs discord.py 2.4+)
  • 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.

Auto-instrumented events

When you pass a commands.Bot, the SDK listens for:

  • on_ready → starts the heartbeat loop
  • on_app_command_completion → records slash command end-to-end timing
  • on_interaction → records button / select-menu, slash autocomplete, and modal-submit timing
  • on_error → forwards exceptions to your Error Log
  • on_rate_limit_hit → counts Discord REST 429 responses (discord.py 2.4+ only — older versions just report 0)

Manual mode (no discord.py)

If you're using a different library or want full control:

import os, time
from cloudline import CloudLine

monitor = CloudLine(
    bot_id="bot_abc123",
    secret=os.environ["CLOUDLINE_SECRET"],
    interval=30.0,
    get_latency_ms=lambda: my_gateway_ping_ms(),
    get_guilds=lambda: my_guild_count(),
)

# inside your running event loop:
monitor.start()

# feed command durations yourself:
t0 = time.monotonic()
await handle_command()
monitor.record_command((time.monotonic() - t0) * 1000)

# on shutdown:
monitor.stop()

Available methods:

  • record_command(duration_ms) — record one slash / context menu / modal submit
  • record_component(duration_ms) — record one button or select menu interaction
  • record_autocomplete(duration_ms) — record one slash-autocomplete response
  • record_rate_limit_hit() — note that Discord returned 429
  • gauge(name, value) / counter(name, delta=1) — custom metrics, see Custom metrics
  • capture_error(error, level="error", context=None) — push an error to the Error Log
  • capture_global_errors() — install sys.excepthook + asyncio exception handler

Safety guarantees

The SDK is built so it cannot break your bot:

  • Every HTTP request has a 10-second timeout. A hung connection cannot stall the bot.
  • The heartbeat loop awaits each beat before sleeping, so two POSTs can never overlap.
  • 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.

Common problems

  • Status stays grey: you called attach() after bot.run() (unreachable code), or the env vars aren't loaded. See No data showing.
  • CPU / RAM tiles show : you installed cloudline-bot without the [metrics] extra. Run pip install "cloudline-bot[metrics]".
  • 401 — check bot_id/secret in the logs: you copied the wrong secret, or rotated it on the dashboard without redeploying.