{"id":13465110,"url":"https://github.com/tdeekens/promster","last_synced_at":"2025-05-14T19:03:49.075Z","repository":{"id":32433575,"uuid":"133235517","full_name":"tdeekens/promster","owner":"tdeekens","description":"⏰A Prometheus exporter for Hapi, express, Apollo, undici and Marble.js servers to automatically measure request timings 📊","archived":false,"fork":false,"pushed_at":"2025-05-12T06:32:13.000Z","size":16621,"stargazers_count":223,"open_issues_count":3,"forks_count":29,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-12T07:37:02.357Z","etag":null,"topics":["devops","express","hacktoberfest","hapi","javascript","monitoring","prometheus"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tdeekens.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"tdeekens","ko_fi":"tdeekens","custom":"https://paypal.me/tdeekens"}},"created_at":"2018-05-13T12:35:33.000Z","updated_at":"2025-05-12T06:31:19.000Z","dependencies_parsed_at":"2023-11-06T04:26:20.976Z","dependency_job_id":"44ee02e8-f0fe-4f21-9153-ccd81550a792","html_url":"https://github.com/tdeekens/promster","commit_stats":{"total_commits":1449,"total_committers":26,"mean_commits":55.73076923076923,"dds":0.5942028985507246,"last_synced_commit":"e5900d64018ffb34a8aa642affaba2251cfaaa41"},"previous_names":[],"tags_count":541,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tdeekens%2Fpromster","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tdeekens%2Fpromster/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tdeekens%2Fpromster/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tdeekens%2Fpromster/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tdeekens","download_url":"https://codeload.github.com/tdeekens/promster/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253699672,"owners_count":21949683,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["devops","express","hacktoberfest","hapi","javascript","monitoring","prometheus"],"created_at":"2024-07-31T14:00:59.864Z","updated_at":"2025-05-14T19:03:49.068Z","avatar_url":"https://github.com/tdeekens.png","language":"TypeScript","funding_links":["https://github.com/sponsors/tdeekens","https://ko-fi.com/tdeekens","https://paypal.me/tdeekens"],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"Logo\" height=\"150\" src=\"https://raw.githubusercontent.com/tdeekens/promster/main/logo.png\" /\u003e\u003cbr /\u003e\u003cbr /\u003e\n\u003c/p\u003e\n\n\u003ch2 align=\"center\"\u003e⏰ Promster - Measure metrics from Hapi, express, Marble.js, Apollo or Fastify servers with Prometheus 🚦\u003c/h2\u003e\n\u003cp align=\"center\"\u003e\n  \u003cb\u003ePromster is an Prometheus Exporter for Node.js servers written for Express, Hapi, Marble.js, Apollo or Fastify.\u003c/b\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003csub\u003e\n  ❤️\n  Hapi\n  · Express\n  · Marble.js\n  · Fastify\n  · Apollo\n  · TypeScript\n  · Jest\n  · Biome\n  · Changesets\n  · Prometheus\n  🙏\n  \u003c/sub\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/tdeekens/promster/actions/workflows/test.yml\"\u003e\n    \u003cimg alt=\"Test \u0026 build status\" src=\"https://github.com/tdeekens/promster/actions/workflows/test.yml/badge.svg\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/tdeekens/promster\"\u003e\n    \u003cimg alt=\"Codecov Coverage Status\" src=\"https://img.shields.io/codecov/c/github/tdeekens/promster.svg?style=flat-square\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://app.fossa.io/projects/git%2Bgithub.com%2Ftdeekens%2Fpromster?ref=badge_shield\" alt=\"FOSSA Status\"\u003e\u003cimg src=\"https://app.fossa.io/api/projects/git%2Bgithub.com%2Ftdeekens%2Fpromster.svg?type=shield\"/\u003e\u003c/a\u003e\n  \u003ca href=\"https://snyk.io/test/github/tdeekens/promster\"\u003e\u003cimg src=\"https://snyk.io/test/github/tdeekens/promster/badge.svg\" alt=\"Known Vulnerabilities\" data-canonical-src=\"https://snyk.io/test/github/{username}/{repo}\" style=\"max-width:100%;\"/\u003e\u003c/a\u003e\n  \u003cimg alt=\"Made with Coffee\" src=\"https://img.shields.io/badge/made%20with-%E2%98%95%EF%B8%8F%20coffee-yellow.svg\"\u003e\n\u003c/p\u003e\n\n## ❯ Package Status\n\n| Package                                   | Version                                                | Downloads                                                       |\n| ----------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------- |\n| [`promster/hapi`](/packages/hapi)         | [![hapi Version][hapi-icon]][hapi-version]             | [![hapi Downloads][hapi-downloads]][hapi-downloads]             |\n| [`promster/express`](/packages/express)   | [![express Version][express-icon]][express-version]    | [![express Downloads][express-downloads]][express-downloads]    |\n| [`promster/marblejs`](/packages/marblejs) | [![marblejs Version][marblejs-icon]][marblejs-version] | [![marblejs Downloads][marblejs-downloads]][marblejs-downloads] |\n| [`promster/fastify`](/packages/fastify)   | [![fastify Version][fastify-icon]][fastify-version]    | [![fastify Downloads][fastify-downloads]][fastify-downloads]    |\n| [`promster/apollo`](/packages/apollo)     | [![apollo Version][apollo-icon]][apollo-version]       | [![apollo Downloads][apollo-downloads]][apollo-downloads]       |\n| [`promster/undici`](/packages/undici)     | [![hapi Version][undici-icon]][undici-version]         | [![undici Downloads][undici-downloads]][undici-downloads]       |\n| [`promster/server`](/packages/server)     | [![server Version][server-icon]][server-version]       | [![server Downloads][server-downloads]][server-downloads]       |\n| [`promster/metrics`](/packages/metrics)   | [![metrics Version][metrics-icon]][metrics-version]    | [![metrics Downloads][metrics-downloads]][metrics-downloads]    |\n\n[metrics-version]: https://www.npmjs.com/package/@promster/metrics\n[metrics-icon]: https://img.shields.io/npm/v/@promster/metrics.svg?style=flat-square\n[metrics-downloads]: https://img.shields.io/npm/dm/@promster/metrics.svg\n[hapi-version]: https://www.npmjs.com/package/@promster/hapi\n[hapi-icon]: https://img.shields.io/npm/v/@promster/hapi.svg?style=flat-square\n[hapi-dependencies]: https://david-dm.org/tdeekens/promster?path=packages/hapi\n[hapi-dependencies-icon]: https://david-dm.org/tdeekens/promster/status.svg?style=flat-square\u0026\n[hapi-downloads]: https://img.shields.io/npm/dm/@promster/hapi.svg\n[express-version]: https://www.npmjs.com/package/@promster/express\n[express-icon]: https://img.shields.io/npm/v/@promster/express.svg?style=flat-square\n[express-downloads]: https://img.shields.io/npm/dm/@promster/express.svg\n[marblejs-version]: https://www.npmjs.com/package/@promster/marblejs\n[marblejs-icon]: https://img.shields.io/npm/v/@promster/marblejs.svg?style=flat-square\n[marblejs-downloads]: https://img.shields.io/npm/dm/@promster/marblejs.svg\n[fastify-version]: https://www.npmjs.com/package/@promster/fastify\n[fastify-icon]: https://img.shields.io/npm/v/@promster/fastify.svg?style=flat-square\n[fastify-downloads]: https://img.shields.io/npm/dm/@promster/fastify.svg\n[apollo-version]: https://www.npmjs.com/package/@promster/apollo\n[apollo-icon]: https://img.shields.io/npm/v/@promster/apollo.svg?style=flat-square\n[apollo-downloads]: https://img.shields.io/npm/dm/@promster/apollo.svg\n[undici-version]: https://www.npmjs.com/package/@promster/undici\n[undici-icon]: https://img.shields.io/npm/v/@promster/undici.svg?style=flat-square\n[undici-downloads]: https://img.shields.io/npm/dm/@promster/undici.svg\n[server-version]: https://www.npmjs.com/package/@promster/server\n[server-icon]: https://img.shields.io/npm/v/@promster/server.svg?style=flat-square\n[server-downloads]: https://img.shields.io/npm/dm/@promster/server.svg\n\n## ❯ Why another Prometheus exporter for Express and Hapi?\n\n\u003e These packages are a combination of observations and experiences I have had with other exporters which I tried to fix.\n\n1.  🏎 Use `process.hrtime.bigint()` for high-resolution real time in metrics in seconds (converting from nanoseconds)\n    - `process.hrtime.bigint()` calls libuv's `uv_hrtime`, without system call like `new Date`\n2.  ⚔️ Allow normalization of all pre-defined label values\n3.  🖥 Expose Garbage Collection among other metric of the Node.js process by default\n4.  🚨 Expose a built-in server to expose metrics quickly (on a different port) while also allowing users to integrate with existing servers\n5.  📊 Define two metrics one histogram for buckets and a summary for percentiles for performant graphs in e.g. Grafana\n6.  👩‍👩‍👧 One library to integrate with Hapi, Express and potentially more (managed as a mono repository)\n7.  🦄 Allow customization of labels while sorting them internally before reporting\n8.  🐼 Expose Prometheus client on Express locals or Hapi app to easily allow adding more app metrics\n\n## ❯ Installation\n\nThis is a mono repository maintained using\n[changesets](https://github.com/atlassian/changesets). It currently contains four\n[packages](/packages) in a `metrics`, a `hapi` or\n`express` integration, and a `server` exposing the metrics for you if you do not want to do that via your existing server.\n\nDepending on the preferred integration use:\n\n`yarn add @promster/express` or `npm i @promster/express --save`\n\nor\n\n`yarn add @promster/hapi` or `npm i @promster/hapi --save`\n\nPlease additionally make sure you have a `prom-client` installed. It is a peer dependency of `@promster` as some projects might already have an existing `prom-client` installed. Which otherwise would result in different default registries.\n\n`yarn add prom-client` or `npm i prom-client --save`\n\n## ❯ Documentation\n\nPromster has to be setup with your server. Either as an Express middleware of an Hapi plugin. You can expose the gathered metrics via a built-in small server or through our own.\n\n\u003e Please, do not be scared by the variety of options. `@promster` can be setup without any additional configuration options and has sensible defaults. However, trying to suit many needs and different existing setups (e.g. metrics having recording rules over histograms) it comes with all those options listed below.\n\n## The following metrics are exposed\n\n### Garbage Collection\n\n- `nodejs_up`: an indication if the nodejs server is started: either 0 (not up) or 1 (up)\n- `nodejs_gc_runs_total`: total garbage collections count\n- `nodejs_gc_pause_seconds_total`: time spent in garbage collection\n- `nodejs_gc_reclaimed_bytes_total`: number of bytes reclaimed by garbage collection\n\nWith all garbage collection metrics a `gc_type` label with one of: `unknown`, `scavenge`, `mark_sweep_compact`, `scavenge_and_mark_sweep_compact`, `incremental_marking`, `weak_phantom` or `all` will be recorded.\n\n### HTTP Timings (Hapi, Express, Marble.js and Fastify)\n\n- `http_requests_total`: a Prometheus counter for the http request total\n  - This metric is also exposed on the following histogram and summary which both have a `_sum` and `_count` and enabled for ease of use. It can be disabled by configuring with `metricTypes: Array\u003cString\u003e`.\n- `http_request_duration_seconds`: a Prometheus histogram with request time buckets in seconds (defaults to `[ 0.05, 0.1, 0.3, 0.5, 0.8, 1, 1.5, 2, 3, 5, 10 ]`)\n  - A histogram exposes a `_sum` and `_count` which are a duplicate to the above counter metric.\n  - A histogram can be used to compute percentiles with a PromQL query using the `histogram_quantile` function. It is advised to create a Prometheus recording rule for performance.\n- `http_request_duration_per_percentile_seconds`: a Prometheus summary with request time percentiles in seconds (defaults to `[ 0.5, 0.9, 0.99 ]`)\n  - This metric is disabled by default and can be enabled by passing `metricTypes: ['httpRequestsSummary]`. It exists for cases in which the above histogram is not sufficient, slow or recording rules can not be set up.\n- `http_request_content_length_bytes`: a Prometheus histogram with the request content length in bytes (defaults to `[ 100000, 200000, 500000, 1000000, 1500000, 2000000, 3000000, 5000000, 10000000, ]`)\n  - This metric is disabled by default and can be enabled by passing `metricTypes: ['httpContentLengthHistogram]`.\n- `http_response_content_length_bytes`: a Prometheus histogram with the request content length in bytes (defaults to `[ 100000, 200000, 500000, 1000000, 1500000, 2000000, 3000000, 5000000, 10000000, ]`)\n  - This metric is disabled by default and can be enabled by passing `metricTypes: ['httpContentLengthHistogram]`.\n\nIn addition with each http request metric the following default labels are measured: `method`, `status_code` and `path`. You can configure more `labels` (see below).\n\n- `http_requests_total`: a Prometheus counter for the total amount of http requests\n\nYou can also opt out of either the Prometheus summary or histogram by passing in `{ metricTypes: ['httpRequestsSummary'] }`, `{ metricTypes: ['httpRequestsHistogram'] }` or `{ metricTypes: ['httpRequestsTotal'] }`.\n\n### GraphQL Timings (Apollo)\n\n- `graphql_parse_duration_seconds`: a Prometheus histogram with the request parse duration in seconds.\n- `graphql_validation_duration_seconds`: a Prometheus histogram with the request validation duration in seconds.\n- `graphql_resolve_field_duration_seconds`: a Prometheus histogram with the field resolving duration in seconds.\n- `graphql_request_duration_seconds`: a Prometheus histogram with the request duration in seconds.\n- `graphql_errors_total`: a Prometheus counter with the errors occurred during parsing, validation or field resolving.\n\nIn addition with each GraphQL request metric the following default labels are measured: `operation_name` and `field_name`. For errors a `phase` label is present.\n\n### Customizing buckets and percentiles\n\nEach Prometheus histogram or summary can be customized in regard to its bucket or percentile values. While `@promster` offers some defaults, these might not always match your needs. To customize the metrics you can pass a `metricBuckets` or `metricPercentiles` object whose key is the metric name you intend to customize the the value is the `percentile` or `bucket` value passed to the underlying Prometheus metric.\n\nTo illustrate this, we can use the `@promster/express` middleware:\n\n```js\nconst middleware = createMiddleware({\n  app,\n  options: {\n    metricBuckets: {\n      httpRequestContentLengthInBytes: [\n        100000, 200000, 500000, 1000000, 1500000, 2000000, 3000000, 5000000,\n        10000000,\n      ],\n      httpRequestDurationInSeconds: [\n        0.05, 0.1, 0.3, 0.5, 0.8, 1, 1.5, 2, 3, 10,\n      ],\n    },\n    metricPercentiles: {\n      httpRequestDurationPerPercentileInSeconds: [0.5, 0.9, 0.95, 0.98, 0.99],\n      httpResponseContentLengthInBytes: [\n        100000, 200000, 500000, 1000000, 1500000, 2000000, 3000000, 5000000,\n        10000000,\n      ],\n    },\n  },\n});\n```\n\n### `@promster/express`\n\n```js\nimport app from \"./your-express-app\";\nimport { createMiddleware } from \"@promster/express\";\n\n// Note: This should be done BEFORE other routes\n// Pass 'app' as middleware parameter to additionally expose Prometheus under 'app.locals'\napp.use(createMiddleware({ app, options }));\n```\n\nPassing the `app` into the `createMiddleware` call attaches the internal `prom-client` to your Express app's locals. This may come in handy as later you can:\n\n```js\n// Create an e.g. custom counter\nconst counter = new app.locals.Prometheus.Counter({\n  name: \"metric_name\",\n  help: \"metric_help\",\n});\n\n// to later increment it\ncounter.inc();\n```\n\n### `@promster/fastify`\n\n```js\nimport app from \"./your-fastify-app\";\nimport { plugin as promsterPlugin } from \"@promster/fastify\";\n\nfastify.register(promsterPlugin);\n```\n\nPlugin attaches the internal `prom-client` to your Fastify instance. This may come in handy as later you can:\n\n```js\n// Create an e.g. custom counter\nconst counter = new fastify.Prometheus.Counter({\n  name: \"metric_name\",\n  help: \"metric_help\",\n});\n\n// to later increment it\ncounter.inc();\n```\n\n### `@promster/hapi`\n\n```js\nimport { createPlugin } from \"@promster/hapi\";\nimport app form \"./your-hapi-app\";\n\napp.register(createPlugin({ options }));\n```\n\nHere you do not have to pass in the `app` into the `createPlugin` call as the internal `prom-client` will be exposed onto Hapi as in:\n\n```js\n// Create an e.g. custom counter\nconst counter = new app.Prometheus.Counter({\n  name: \"metric_name\",\n  help: \"metric_help\",\n});\n\n// to later increment it\ncounter.inc();\n```\n\n### `@promster/marblejs`\n\n```js\nimport { createMiddleware, getContentType, getSummary } = from \"@promster/marblejs\";\n\nconst middlewares = [\n  createMiddleware(),\n  //...\n];\n\nconst serveMetrics$ = r\n  .matchPath(\"/metrics\")\n  .matchType(\"GET\")\n  .use(async (req$) =\u003e\n    req$.pipe(\n      mapTo({\n        headers: { \"Content-Type\": getContentType() },\n        body: await getSummary(),\n      })\n    )\n  );\n```\n\n### `@promster/undici`\n\nDepending on the `undici` version used you have to use a pool metrics exporter or can use the agent metric exporter.\n\n### `undici` version `7.9.0` or later\n\n```js\nimport { createAgentMetricsExporter } = from \"@promster/undici\";\n\ncreateAgentMetricsExporter([ agentA, agentB ]);\n```\n\nYou can then also always add additional agents\n\n```js\nimport { addObservedAgent } = from \"@promster/undici\";\n\naddObservedAgent(agent);\n```\n\n### `undici` version `7.9.0` or before\n\n```js\nimport { createPoolMetricsExporter } = from \"@promster/undici\";\n\ncreatePoolMetricsExporter({ poolA, poolB });\n```\n\nYou can then also always add additional pools\n\n```js\nimport { addObservedPool } = from \"@promster/undici\";\n\naddObservedPool(origin, pool);\n```\n\nTo integrate this with an `undici` agent we can use the factory function\n\n```js\nconst agent = new Agent({\n  factory(origin: string, opts: Pool.Options) {\n    return observedPoolFactory(origin, opts);\n  },\n});\n```\n\n### `@promster/apollo`\n\n```js\nimport { createPlugin as createPromsterMetricsPlugin } from \"@promster/apollo\";\n\nconst server = new ApolloServer({\n  typeDefs,\n  resolvers,\n  plugins: [createPromsterMetricsPlugin()],\n});\n\nawait server.listen();\n```\n\nWhen creating either the Express middleware or Hapi plugin the following options can be passed:\n\n- `labels`: an `Array\u003cString\u003e` of custom labels to be configured both on all metrics mentioned above\n- `metricPrefix`: a prefix applied to all metrics. The prom-client's default metrics and the request metrics\n- `metricTypes`: an `Array\u003cString\u003e` containing one of `histogram`, `summary` or both\n- `metricNames`: an object containing custom names for one or all metrics with keys of `up, countOfGcs, durationOfGc, reclaimedInGc, httpRequestDurationPerPercentileInSeconds, httpRequestDurationInSeconds`\n  - Note that each value can be an `Array\u003cString\u003e` so `httpRequestDurationInSeconds: ['deprecated_name', 'next_name']` which helps when migrated metrics without having gaps in their intake. In such a case `deprecated_name` would be removed after e.g. Recording Rules and dashboards have been adjusted to use `next_name`. During the transition each metric will be captured/recorded twice.\n- `getLabelValues`: a function receiving `req` and `res` on reach request. It has to return an object with keys of the configured `labels` above and the respective values\n- `normalizePath`: a function called on each request to normalize the request's path. Invoked with `(path: string, { request, response })`\n- `normalizeStatusCode`: a function called on each request to normalize the respond's status code (e.g. to get 2xx, 5xx codes instead of detailed ones). Invoked with `(statusCode: number, { request, response })`\n- `normalizeMethod`: a function called on each request to normalize the request's method (to e.g. hide it fully). Invoked with `(method: string, { request, response })`\n- `skip`: a function called on each response giving the ability to skip a metric. The method receives `req`, `res` and `labels` and returns a boolean: `skip(req, res, labels) =\u003e Boolean`\n- `detectKubernetes`: a boolean defaulting to `false`. Whenever `true`is passed the process does not run within Kubernetes any metric intake is skipped (good e.g. during testing).\n- `disableGcMetrics`: a boolean defaulted to `false` to indicate if Garbage Collection metric should be disabled and hence not collected.\n\nMoreover, both `@promster/hapi` and `@promster/express` expose the request recorder configured with the passed options and used to measure request timings. It allows easy tracking of other requests not handled through express or Hapi for instance calls to an external API while using promster's already defined metric types (the `httpRequestsHistogram` etc).\n\n```js\n// Note that a getter is exposed as the request recorder is only available after initialisation.\nimport { getRequestRecorder, timing } from '@promster/express';\n\nconst async fetchSomeData = () =\u003e {\n  const recordRequest = getRequestRecorder();\n  const requestTiming = timing.start();\n\n  const data = await fetch('https://another-api.com').then(res =\u003e res.json());\n\n  recordRequest(requestTiming, {\n    other: 'label-values'\n  });\n\n  return data;\n}\n```\n\nLastly, both `@promster/hapi` and `@promster/express` expose setters for the `up` Prometheus gauge. Whenever the server finished booting and is ready you can call `signalIsUp()`. Given the server goes down again you can call `signalIsNotUp()` to set the gauge back to `0`. There is no standard hook in both `express` and `Hapi` to tie this into automatically. Other tools to indicate service health such as `lightship` indicating Kubernetes Pod liveliness and readiness probes also offer setters to alter state.\n\n### `@promster/server`\n\nIn some cases you might want to expose the gathered metrics through an individual server. This is useful for instance to not have `GET /metrics` expose internal server and business metrics to the outside world. For this you can use `@promster/server`:\n\n```js\nimport { createServer } form \"@promster/server\";\n\n// NOTE: The port defaults to `7788`.\ncreateServer({ port: 8888 }).then((server) =\u003e\n  console.log(`@promster/server started on port 8888.`)\n);\n```\n\nOptions with their respective defaults are `port: 7788`, `hostname: '0.0.0.0'` and `detectKubernetes: false`. Whenever `detectKubernetes` is passed as `true` and the server will not start locally.\n\n### `@promster/{express,hapi}`\n\nYou can use the `express` or `hapi` package to expose the gathered metrics through your existing server. To do so just:\n\n```js\nimport app from \"./your-express-app\";\nimport { getSummary, getContentType } from \"@promster/express\";\n\napp.use(\"/metrics\", async (req, res) =\u003e {\n  req.statusCode = 200;\n\n  res.setHeader(\"Content-Type\", getContentType());\n  res.end(await getSummary());\n});\n```\n\nThis may slightly depend on the server you are using but should be roughly the same for all.\n\nThe packages re-export most things from the `@promster/metrics` package including two other potentially useful exports in `Prometheus` (the actual client) and `defaultRegister` which is the default register of the client. After all you should never really have to install `@promster/metrics` as it is only and internally shared packages between the others.\n\nAdditionally you can import the default normalizers via `import { defaultNormalizers } from '@promster/express'` and use `normalizePath`, `normalizeStatusCode` and `normalizeMethod` from you `getLabelValues`. A more involved example with `getLabelValues` could look like:\n\n```js\napp.use(\n  createMiddleware({\n    app,\n    options: {\n      labels: [\"proxied_to\"],\n      getLabelValues: (req, res) =\u003e {\n        if (res.proxyTo === \"someProxyTarget\")\n          return {\n            proxied_to: \"someProxyTarget\",\n            path: \"/\",\n          };\n        if (req.get(\"x-custom-header\"))\n          return {\n            path: null,\n            proxied_to: null,\n          };\n      },\n    },\n  })\n);\n```\n\nNote that the same configuration can be passed to `@promster/hapi`.\n\n### Example PromQL queries\n\nIn the past we have struggled and learned a lot getting appropriate operational insights into our various Node.js based services. PromQL is powerful and a great tool but can have a steep learning curve. Here are a few queries per metric type to maybe flatten that curve. Remember that you may need to configure the `metricTypes: Array\u003cString\u003e` to e.g. `metricTypes: ['httpRequestsTotal', 'httpRequestsSummary', 'httpRequestsHistogram'] }`.\n\n#### `http_requests_total`\n\n\u003e HTTP requests averaged over the last 5 minutes\n\n`rate(http_requests_total[5m])`\n\nA recording rule for this query could be named `http_requests:rate5m`\n\n\u003e HTTP requests averaged over the last 5 minutes by Kubernetes pod\n\n`sum by (kubernetes_pod_name) (rate(http_requests_total[5m]))`\n\nA recording rule for this query could be named `kubernetes_pod_name:http_requests:rate5m`\n\n\u003e Http requests in the last hour\n\n`increase(http_requests_total[1h])`\n\n\u003e Average Http requests by status code over the last 5 minutes\n\n`sum by (status_code) (rate(http_requests[5m]))`\n\nA recording rule for this query could be named `status_code:http_requests:rate5m`\n\n\u003e Http error rates as a percentage of the traffic averaged over the last 5 minutes\n\n`rate(http_requests_total{status_code=~\"5.*\"}[5m]) / rate(http_requests_total[5m])`\n\nA recording rule for this query could be named `http_requests_per_status_code5xx:ratio_rate5m`\n\n#### `http_request_duration_seconds`\n\n\u003e Http requests per proxy target\n\n`sum by (proxied_to) (increase(http_request_duration_seconds_count{proxied_to!=\"\"}[2m]))`\n\nA recording rule for this query should be named something like `proxied_to_:http_request_duration_seconds:increase2m`.\n\n\u003e 99th percentile of http request latency per proxy target\n\n`histogram_quantile(0.99, sum by (proxied_to,le) (rate(http_request_duration_seconds_bucket{proxied_to!=\"\"}[5m])))`\n\nA recording rule for this query could be named `proxied_to_le:http_request_duration_seconds_bucket:p99_rate5m`\n\n#### `http_request_duration_per_percentile_seconds`\n\n\u003e Maximum 99th percentile of http request latency by Kubernetes pod\n\n`max(http_request_duration_per_percentile_seconds{quantile=\"0.99\") by (kubernetes_pod_name)`\n\n#### `nodejs_eventloop_lag_seconds`\n\n\u003e Event loop lag averaged over the last 5 minutes by release\n\n`sum by (release) (rate(nodejs_eventloop_lag_seconds[5m]))`\n\n#### `network_concurrent_connections_count`\n\n\u003e Concurrent network connections\n\n`sum(rate(network_concurrent_connections_count[5m]))`\n\nA recording rule for this query could be named `network_concurrent_connections:rate5m`\n\n#### `nodejs_gc_reclaimed_bytes_total`\n\n\u003e Bytes reclaimed in garbage collection by type\n\n`sum by (gc_type) (rate(nodejs_gc_reclaimed_bytes_total[5m]))`\n\n#### `nodejs_gc_pause_seconds_total`\n\n\u003e Time spend in garbage collection by type\n\n`sum by (gc_type) (rate(nodejs_gc_pause_seconds_total[5m]))`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftdeekens%2Fpromster","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftdeekens%2Fpromster","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftdeekens%2Fpromster/lists"}