Skip to main content

Monitoring with Prometheus

goodcharge shows a station as red 15 minutes after its last accepted data. The SDK counts every call it makes and keeps the station state goodcharge returns, so you can scrape it with Prometheus and be alerted well before that. Metrics cost nothing until you read them: no timer, no extra API call.

Expose the metrics

Serve them on an internal port or path only. Never expose /metrics to the internet.

Node.js http

import { createServer } from 'node:http';
import { GoodchargeStation, metricsHandler } from '@goodcharge/sdk';

const lyon = new GoodchargeStation({ apiKey: process.env.LYON_KEY!, metricsLabel: 'lyon-bellecour' });
const nice = new GoodchargeStation({ apiKey: process.env.NICE_KEY!, metricsLabel: 'nice-gare' });

createServer(metricsHandler([lyon, nice])).listen(9464, '127.0.0.1');

metricsHandler fixes the list of stations when you create it: a station added to (or removed from) your own array afterwards is not served. Create a new handler to change the set. Each station's numbers are still read fresh on every scrape.

Express

import { metricsHandler } from '@goodcharge/sdk';

app.get('/metrics', metricsHandler([lyon, nice]));

Next.js, Hono, Bun, Deno

import { metricsResponse } from '@goodcharge/sdk';

// Next.js 14 caches a GET handler that takes no request at build time; this keeps every scrape live.
export const dynamic = 'force-dynamic';

export function GET() {
return metricsResponse([lyon, nice]);
}

prom-client

If your service already exposes a prom-client registry, add the goodcharge metrics to it:

import { register } from 'prom-client';
import { registerGoodchargeMetrics } from '@goodcharge/sdk/prom-client';

registerGoodchargeMetrics(register, [lyon, nice]);

prom-client 15 or 16 is required for this entry only; the SDK itself has no dependency. registerGoodchargeMetrics copies the stations array at registration time: a station added to (or removed from) your own array afterwards is not reflected in what that registry exports — register again on a fresh registry to pick up a changed set of stations. It throws invalid_option if the goodcharge metrics are already registered on the registry you pass, so call it once per registry.

Each station is labelled station, with metricsLabel when you set it, or else the public prefix of its key (gc_XXXXXXXX). The secret part of the key never appears. Two stations in one list must have different labels: metricsText, metricsHandler, metricsResponse and registerGoodchargeMetrics all throw invalid_option, naming the label, the first time two stations share one — they never merge two stations' series under it.

Metrics

MetricTypeMeaning
goodcharge_requests_total{method, outcome}counterCalls by method (get, publish, update_points, heartbeat) and final outcome, after retries: ok, the error code, or unknown_error when a call rejected with something other than a GoodchargeError (a misbehaving custom fetch).
goodcharge_request_duration_seconds{method}histogramDuration of each HTTP attempt, retries included.
goodcharge_retries_total{method}counterAttempts retried after a 429, a 5xx, a timeout or a network error.
goodcharge_last_success_timestamp_seconds{method}gaugeUnix time of the last successful call.
goodcharge_heartbeat_runninggauge1 while startHeartbeat runs for the station.
goodcharge_station_publishedgauge1 when the station is visible in the app.
goodcharge_station_status{dot}gauge1 for the station's current dot (grey, orange, green, red).
goodcharge_station_last_data_timestamp_secondsgaugeUnix time goodcharge last accepted data.
goodcharge_station_last_rejection_timestamp_secondsgaugeUnix time goodcharge last rejected data.
goodcharge_sdk_info{version}gaugeThe SDK version, always 1.

A per-method series (goodcharge_requests_total, goodcharge_request_duration_seconds, goodcharge_retries_total, goodcharge_last_success_timestamp_seconds) appears only after that method's first call, and goodcharge_last_success_timestamp_seconds only after its first success. goodcharge_heartbeat_running and goodcharge_sdk_info are there from the start.

The station gauges come from the responses of get, publish and updatePoints, and appear after the first such response. A successful heartbeat only refreshes the last data time, so goodcharge_station_last_data_timestamp_seconds can also appear after the first heartbeat alone.

Alert rules

Download alerts.yml and add it to rule_files in prometheus.yml. It also ships in the package, at node_modules/@goodcharge/sdk/monitoring/alerts.yml.

AlertFires when
GoodchargeStationSilentNo publish, updatePoints or heartbeat succeeded for 10 minutes, sustained for 1 more minute.
GoodchargeStationRedThe station has reported a red dot for 5 minutes straight.
GoodchargeDataRejectedgoodcharge rejected data for the station in the last 15 minutes — including a rejection reported right after a restart, with no earlier data to compare against.
GoodchargeKeyRefusedA call was answered unauthorized: fires on a first refusal right after process start too, and keeps firing while refusals continue. The key was regenerated or the station deleted.
GoodchargeHighErrorRateMore than 20% of calls failed over a 15-minute window, sustained for 10 minutes.
GoodchargeHeartbeatStoppedA heartbeat that ran in the last hour stopped, and has stayed stopped for 10 minutes.

GoodchargeStationSilent compares the time of the last success, so it cannot fire for a station that never succeeded once since the process started: a wrong key from the first call, say, or a process that never calls publish. Check the first publish at startup and fail loudly when it rejects, or add a rule on absence. This one fires for every exported station that has had no publish, updatePoints or heartbeat succeed in its first 10 minutes:

- alert: GoodchargeStationNeverSucceeded
expr: >-
goodcharge_heartbeat_running unless on (station) goodcharge_last_success_timestamp_seconds{method=~"publish|update_points|heartbeat"}
for: 10m

It needs the process to be scraped at all. To be told when the target itself disappears, add absent(goodcharge_sdk_info{job="your-job"}) with a for of a few minutes.

Grafana dashboard

Download grafana-dashboard.json, then in Grafana open Dashboards → New → Import, upload the file and pick your Prometheus data source. It also ships at node_modules/@goodcharge/sdk/monitoring/grafana-dashboard.json.