This episode covers monitoring WebSocket applications in production: connection and message metrics, logging strategies, exposing metrics to Prometheus, Grafana, distributed tracing with OpenTelemetry, and alerting.

An application that runs fine on a laptop can fall apart in production without us noticing. Connections pile up, latency creeps up, memory leaks — and users leave one by one before anyone suspects anything. Monitoring is the eyes that watch the application 24 hours a day.
Episode 18 covers monitoring: collecting numbers, and observability: understanding the system state from its signals. You will learn the important WebSocket metrics, effective logging, Prometheus and Grafana integration, and distributed tracing.
Some numbers are especially important for WebSocket applications:
// contoh metrik yang dicatat
const metrik = {
koneksiAktif: 0,
totalPesan: 0,
pesanPerDetik: 0,
errorTerakhir: null,
totalKoneksi: 0,
};The core metrics of a WebSocket application include:
Connections growing without limit signal a leak — clients never close connections properly.
Good logs tell the story of what happens to a connection.
wss.on("connection", (ws, req) => {
console.log("koneksi baru:", {
ip: req.socket.remoteAddress,
waktu: new Date().toISOString(),
total: wss.clients.size,
});
ws.on("message", (data) => {
console.log("pesan masuk:", data.length, "byte");
});
ws.on("close", (code, reason) => {
console.log("koneksi ditutup:", code, reason.toString());
});
});Logging new connections, messages, and closures gives a complete timeline for every connection. Error logs must include context: the close code, the client identity, and a stack trace.
Do not log everything all the time — a log flood actually hides problems. Use levels: debug for development, info for important events, warn for anomalies, and error for failures. In production, restrict debug and rotate logs so the disk does not fill up.
Prometheus pulls metrics from an HTTP endpoint in the application.
const prometheus = require("prom-client");
const koneksiAktif = new prometheus.Gauge({
name: "websocket_koneksi_aktif",
help: "Jumlah koneksi WebSocket aktif",
});
const totalPesan = new prometheus.Counter({
name: "websocket_pesan_total",
help: "Total pesan diproses",
});
wss.on("connection", (ws) => {
koneksiAktif.inc();
ws.on("message", () => totalPesan.inc());
ws.on("close", () => koneksiAktif.dec());
});
http.createServer(async (req, res) => {
if (req.url === "/metrics") {
res.setHeader("Content-Type", prometheus.register.contentType);
res.end(await prometheus.register.metrics());
}
}).listen(9100);A Gauge represents a value that rises and falls, like the number of connections; a Counter represents a value that only increases, like the total messages. The /metrics endpoint on port 9100 is read periodically by Prometheus.
Prometheus schedules metric collection with a scrape configuration.
scrape_configs:
- job_name: "websocket-server"
scrape_interval: 15s
static_configs:
- targets: ["server-1:9100", "server-2:9100"]scrape_interval: 15s sets the collection frequency. Each server instance is exposed as a separate target, and Prometheus combines them all.
Grafana turns numbers into visuals. A WebSocket dashboard usually shows panels for active connections, message throughput, latency percentiles, and error rate. Grafana alerting can fire notifications when a metric crosses a threshold, for example active connections breaking past 80 percent of capacity.
Distributed tracing follows a single event across many services.
const tracer = opentelemetry.trace.getTracer("websocket-server");
socket.on("message:send", async (pesan) => {
const span = tracer.startSpan("proses-pesan");
span.setAttribute("pesan.id", pesan.id);
try {
await simpanDanKirim(pesan);
span.setStatus({ code: 1 });
} catch (err) {
span.recordException(err);
span.setStatus({ code: 2 });
} finally {
span.end();
}
});tracer.startSpan(...) marks the start and end of one operation. With context propagation, you can see where a slow message gets stuck across services.
Alerts must be meaningful and actionable.
One important rule: an alert you cannot act on is noise. Every alert must have a playbook — concrete steps to respond.
Episode 18 gave you sight into the running application: key metrics, meaningful logs, Grafana dashboards, and actionable alerts.
Key takeaways:
In the next episode we cover security best practices: TLS with wss, authentication and input validation, protection against CSWSH and DoS, CORS, and security headers.