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
| Metric | Type | Meaning |
|---|---|---|
goodcharge_requests_total{method, outcome} | counter | Calls 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} | histogram | Duration of each HTTP attempt, retries included. |
goodcharge_retries_total{method} | counter | Attempts retried after a 429, a 5xx, a timeout or a network error. |
goodcharge_last_success_timestamp_seconds{method} | gauge | Unix time of the last successful call. |
goodcharge_heartbeat_running | gauge | 1 while startHeartbeat runs for the station. |
goodcharge_station_published | gauge | 1 when the station is visible in the app. |
goodcharge_station_status{dot} | gauge | 1 for the station's current dot (grey, orange, green, red). |
goodcharge_station_last_data_timestamp_seconds | gauge | Unix time goodcharge last accepted data. |
goodcharge_station_last_rejection_timestamp_seconds | gauge | Unix time goodcharge last rejected data. |
goodcharge_sdk_info{version} | gauge | The 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.
| Alert | Fires when |
|---|---|
GoodchargeStationSilent | No publish, updatePoints or heartbeat succeeded for 10 minutes, sustained for 1 more minute. |
GoodchargeStationRed | The station has reported a red dot for 5 minutes straight. |
GoodchargeDataRejected | goodcharge 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. |
GoodchargeKeyRefused | A 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. |
GoodchargeHighErrorRate | More than 20% of calls failed over a 15-minute window, sustained for 10 minutes. |
GoodchargeHeartbeatStopped | A 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.