Skip to content

Latest commit

 

History

History
306 lines (240 loc) · 11.4 KB

express.md

File metadata and controls

306 lines (240 loc) · 11.4 KB

Integrate Express with StatLite Metrics

This guide adds lightweight Node.js/Express application monitoring using the fixed statlite-metrics/v1 JSON profile and a copyable application-owned helper.

A runnable and tested demo accompanies the guide.

Copy statlite-metrics.js into your application, then register its Express middleware and GET /statlite/metrics route as shown below.

Note

No StatLite SDK or package and no additional third-party runtime dependency are required. It uses Express and Node.js built-in APIs. The complete helper is shown below and included in the runnable demo. You can inspect, modify, and test it as application-owned source using your usual code-review and security tools.

Your own StatLite instance polls the endpoint; the helper makes no outbound network requests and sends no telemetry to PVR Labs or another external service. Snapshot collection reads in-memory counters and runtime APIs without database I/O. The JSON response contains aggregate request and HTTP error counts, cumulative request duration, process CPU and runtime memory values, uptime, start time, application status, and schema/integration identifiers. It does not include request or response bodies, headers, URL lists, or user identifiers.

Caution

Those operational metrics leave the application in the endpoint response, so restrict access through loopback, a private network, or proxy controls. The StatLite Metrics target currently sends no authentication credentials; see the access caveats below.

When to use this integration

Express does not have a first-class StatLite target type. Use this direct v1 integration when StatLite's fixed traffic, error, average-latency, status, restart, and process signals fit the application's operational needs. This in-memory helper supports one process or worker. Cluster mode, multi-process managers, and replica deployments are outside this drop-in integration.

StatLite cannot determine request counts, HTTP errors, or request latency from outside the application. The middleware below measures those values where requests and responses pass through Express.

The example was exercised with Node.js 24.21.0 LTS and Express 5.1.0. It uses stable Node.js process APIs and the standard Express middleware and response APIs, but this guide claims only that tested baseline rather than compatibility with every supported Express release.

Copyable helper

Save this complete helper as statlite-metrics.js:

// Source and updates: https://github.com/PVRLabs/statlite/blob/main/docs/integrate/node/express.md

const { performance } = require("node:perf_hooks");

const METRICS_PATH = "/statlite/metrics";
const startedAt = new Date(Date.now() - process.uptime() * 1000);

const counters = {
  requestsTotal: 0,
  responses404Total: 0,
  responses4xxTotal: 0,
  responses5xxTotal: 0,
  requestDurationSecondsTotal: 0,
};

let previousCpu = process.cpuUsage();
let previousCpuTime = performance.now();

function record(statusCode, durationSeconds) {
  counters.requestsTotal += 1;
  counters.requestDurationSecondsTotal += durationSeconds;
  if (statusCode === 404) counters.responses404Total += 1;
  if (statusCode >= 400 && statusCode < 500) counters.responses4xxTotal += 1;
  if (statusCode >= 500 && statusCode < 600) counters.responses5xxTotal += 1;
}

function statliteMetricsMiddleware(req, res, next) {
  if (req.path === METRICS_PATH) return next();

  const requestStarted = performance.now();
  res.once("finish", () => {
    record(res.statusCode, (performance.now() - requestStarted) / 1000);
  });
  next();
}

function snapshot() {
  const now = performance.now();
  const cpu = process.cpuUsage();
  const elapsedSeconds = (now - previousCpuTime) / 1000;
  const cpuSeconds =
    (cpu.user - previousCpu.user + cpu.system - previousCpu.system) / 1e6;
  const processCpuUsage = elapsedSeconds > 0 ? cpuSeconds / elapsedSeconds : 0;
  previousCpu = cpu;
  previousCpuTime = now;

  return {
    schema: "statlite-metrics/v1",
    integration: "express",
    status: "UP",
    started_at: startedAt.toISOString(),
    metrics: {
      requests_total: counters.requestsTotal,
      responses_404_total: counters.responses404Total,
      responses_4xx_total: counters.responses4xxTotal,
      responses_5xx_total: counters.responses5xxTotal,
      request_duration_seconds_total: counters.requestDurationSecondsTotal,
      process_cpu_usage: processCpuUsage,
      runtime_heap_used_bytes: process.memoryUsage().heapUsed,
      uptime_seconds: process.uptime(),
    },
  };
}

function statliteMetricsEndpoint(req, res) {
  res.json(snapshot());
}

module.exports = {
  METRICS_PATH,
  statliteMetricsEndpoint,
  statliteMetricsMiddleware,
};

Register it before application routes so every application response is observed. Keep Express's error handler after the routes:

const express = require("express");
const {
  METRICS_PATH,
  statliteMetricsEndpoint,
  statliteMetricsMiddleware,
} = require("./statlite-metrics");

const app = express();

app.use(statliteMetricsMiddleware);
app.get(METRICS_PATH, statliteMetricsEndpoint);

app.get("/", (req, res) => res.json({ message: "hello" }));
app.get("/failure", (req, res, next) => next(new Error("example failure")));

app.use((err, req, res, next) => {
  console.error(err);
  if (res.headersSent) return next(err);
  res.status(500).json({ error: "internal server error" });
});

app.listen(3000, "127.0.0.1");

Existing-library path

No additional metrics-library path is recommended for this integration. A general instrumentation library would still need an adapter that emits the fixed v1 JSON fields. StatLite does not ingest arbitrary Prometheus or OpenMetrics output.

Endpoint behavior

GET /statlite/metrics returns a JSON snapshot with a successful 2xx response. The middleware excludes that path, including requests with a query string, so StatLite polling does not inflate application traffic or latency.

The finish listener runs once after Express and any later error middleware choose the final response status. It therefore counts completed normal, 404, 4xx, and 5xx responses without double-counting an error passed through next(err). Connections that close before a response finishes are not counted as completed responses.

A 404 increments both responses_404_total and responses_4xx_total, as the v1 contract defines 404 as a 4xx subset. Snapshot collection performs no database or network I/O.

Required, optional, and status fields

The response always includes the required schema: "statlite-metrics/v1" and a non-empty status. Its optional integration: "express" identifies the canonical helper that produced the response. The example's status: "UP" is the application's simple operational assertion at snapshot time. It does not assert that every dependency is healthy. A successful response proves reporting availability, not universal application or dependency health.

The helper omits database_status. Add it only from an authoritative, inexpensive or cached application signal. Do not query the database merely to serve a StatLite poll, and do not invent a value when no suitable signal exists.

All metrics fields and started_at are optional under v1. This helper emits cumulative response metrics, process CPU use in CPU cores, V8 heap use in bytes, process uptime, and an estimated process start time. V8 heapUsed is runtime-managed heap, not process RSS, container memory, or a maximum heap value. Unsupported host CPU, memory, and disk fields are omitted.

Configure StatLite

Application integration is still required. The YAML tells StatLite where to poll; it does not add the middleware or endpoint to Express.

server.listen, storage.sqlite_path, and polling.interval are required. Save this complete file as statlite.yaml:

server:
  listen: "127.0.0.1:9090"

storage:
  sqlite_path: "./statlite.sqlite"

polling:
  interval: "30s"

targets:
  - name: "node-express-app"
    type: "statlite-metrics"
    url: "http://127.0.0.1:3000/statlite/metrics"

After the endpoint is running, this writes a file of the same shape when ./statlite.yaml does not already exist:

statlite inspect 'http://127.0.0.1:3000/statlite/metrics' --create-config ./statlite.yaml

Run and verify the integration

Install Express and start the application:

npm install express@5.1.0
node app.js

Leave the application running. In another terminal, generate normal, missing, and error responses:

curl -s http://127.0.0.1:3000/
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/missing
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/failure
curl -s http://127.0.0.1:3000/statlite/metrics

The snapshot should report three completed application requests, one 404 in both the 404 and 4xx counters, and one 5xx. Verify discovery separately:

statlite inspect 'http://127.0.0.1:3000/statlite/metrics'
statlite

When ./statlite.yaml is not saved yet, add --create-config ./statlite.yaml to that inspect command. It writes the file only when the path is absent. Open http://127.0.0.1:9090. Use a 30-second or longer polling interval in production.

Optional metrics and deployment caveats

This in-memory helper is supported by default only for a single Node.js process. Cluster mode and process managers give each worker separate counters and a separate started_at. When load-balanced polls alternate workers, counters can decrease and started_at can change, producing misleading deltas or apparent restarts rather than merely a partial aggregate. StatLite does not aggregate workers. Multi-process, cluster, and replica deployments are outside this helper's supported model. If you need one of those setups, open an issue or start a GitHub Discussion.

Add host fields only when the application can accurately describe its visible execution environment. Do not substitute process RSS for runtime_heap_used_bytes.

If a reverse proxy mounts the application below a prefix, ensure the configured URL reaches the same /statlite/metrics path that the middleware excludes. The statlite-metrics target type does not currently send target credentials, and statlite inspect does not accept authentication options. Make the endpoint reachable by StatLite through network or proxy access controls that do not require StatLite to authenticate. Do not place a Basic Auth or other credential challenge between StatLite and this endpoint. Restrict access because the endpoint exposes operational data.

References and future first-class support

Interested in a first-class Express target? Open an issue or start a GitHub Discussion. We're open to exploring it.