diff --git a/CHANGELOG.md b/CHANGELOG.md index 13a9a01..563ccf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ ### Features - Add support for custom labels addition to metrics +- Add `httpMetricsPrefix` option to optionally add a prefix to HTTP metrics. +- Add `excludeDefaultMetricLabels` option to exclude all or certain metrics that are added by default. +- Add `useCountersForRequestSizeMetric` option to expose two counters for Request Size (`_sum` and `_count`) instead of Histogram. +- Add `useCountersForResponseSizeMetric` option to expose two counters for Response Size (`_sum` and `_count`) instead of Histogram. ### Improvements diff --git a/README.md b/README.md index 6308904..60010dc 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,25 @@ - [Access the metrics](#access-the-metrics) - [Custom Metrics](#custom-metrics) - [Note](#note) +- [Additional Metric Labels](#additional-metric-labels) - [Request.js HTTP request duration collector](#requestjs-http-request-duration-collector) - [Usage](#usage-1) + - [Initialize](#initialize) + - [Options](#options-1) - [request](#request) - [request-promise-native](#request-promise-native) - [axios](#axios) -- [Test](#test) - [Usage in koa](#usage-in-koa) +- [Test](#test) - [Prometheus Examples Queries](#prometheus-examples-queries) + - [Apdex](#apdex) + - [95th Response Time by specific route and status code](#95th-response-time-by-specific-route-and-status-code) + - [Median Response Time Overall](#median-response-time-overall) + - [Median Request Size Overall](#median-request-size-overall) + - [Median Response Size Overall](#median-response-size-overall) + - [Avarage Memory Usage - All services](#avarage-memory-usage---all-services) + - [Avarage Eventloop Latency - All services](#avarage-eventloop-latency---all-services) +- [Changelog](#changelog) @@ -55,24 +66,28 @@ API and process monitoring with [Prometheus](https://prometheus.io) for Node.js ```js const apiMetrics = require('prometheus-api-metrics'); -app.use(apiMetrics()) +app.use(apiMetrics()); ``` ### Options -| Option | Type | Description | Default Value | -|--------------------------|-----------|-------------|---------------| -| `metricsPath` | `String` | Path to access the metrics | `/metrics` | -| `defaultMetricsInterval` | `Number` | Interval to collect the process metrics in milliseconds | `10000` | -| `durationBuckets` | `Array` | Buckets for response time in seconds | `[0.001, 0.005, 0.015, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5]` | -| `requestSizeBuckets` | `Array` | Buckets for request size in bytes | `[5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]` | -| `responseSizeBuckets` | `Array` | Buckets for response size in bytes | `[5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]` | -| `useUniqueHistogramName` | `Boolean` | Add to metrics names the project name as a prefix (from package.json) | `false` | -| `metricsPrefix` | `String` | A custom metrics names prefix, the package will add underscore between your prefix to the metric name | | -| `excludeRoutes` | `Array` | Array of routes to exclude. Routes should be in your framework syntax | | -| `includeQueryParams` | `Boolean` | Indicate if to include query params in route, the query parameters will be sorted in order to eliminate the number of unique labels | `false` | -| `additionalLabels` | `Array` | Indicating custom labels that can be included on each `http_*` metric. Use in conjunction with `extractAdditionalLabelValuesFn`. | -| `extractAdditionalLabelValuesFn` | `Function` | A function that can be use to generate the value of custom labels for each of the `http_*` metrics. When using koa, the function takes `ctx`, when using express, it takes `req, res` as arguments | | +| Option | Type | Description | Default Value | +| ---------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | +| `metricsPath` | `String` | Path to access the metrics | `/metrics` | +| `defaultMetricsInterval` | `Number` | Interval to collect the process metrics in milliseconds | `10000` | +| `durationBuckets` | `Array` | Buckets for response time in seconds | `[0.001, 0.005, 0.015, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5]` | +| `requestSizeBuckets` | `Array` | Buckets for request size in bytes | `[5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]` | +| `responseSizeBuckets` | `Array` | Buckets for response size in bytes | `[5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]` | +| `useUniqueHistogramName` | `Boolean` | Add to metrics names the project name as a prefix (from package.json) | `false` | +| `metricsPrefix` | `String` | A custom metrics names prefix, the package will add underscore between your prefix to the metric name | | +| `httpMetricsPrefix` | `String` | A custom prefix for HTTP metrics only, the package will add underscore between your prefix to the metric name | | +| `excludeRoutes` | `Array` | Array of routes to exclude. Routes should be in your framework syntax | | +| `includeQueryParams` | `Boolean` | Indicate if to include query params in route, the query parameters will be sorted in order to eliminate the number of unique labels | `false` | +| `additionalLabels` | `Array` | Indicating custom labels that can be included on each `http_*` metric. Use in conjunction with `extractAdditionalLabelValuesFn`. | +| `extractAdditionalLabelValuesFn` | `Function` | A function that can be use to generate the value of custom labels for each of the `http_*` metrics. When using koa, the function takes `ctx`, when using express, it takes `req, res` as arguments | | +| `excludeDefaultMetricLabels` | `Boolean` or `Array` | Excludes the metric labels added by default (`code`, `method`, `route`). If `true` is passed, it will exclude them all. An array of the labels that you need to exclude can also be passed | | +| `useCountersForRequestSizeMetric` | `Boolean` | Uses two counters for Request Size (`_sum` and `_count`) instead of Histogram | `false` | +| `useCountersForResponseSizeMetric` | `Boolean` | Uses two counters for Response Size (`_sum` and `_count`) instead of Histogram | `false` | ### Access the metrics @@ -111,7 +126,7 @@ Create new metric from the kind that you like const checkoutsTotal = new Prometheus.Counter({ name: 'checkouts_total', help: 'Total number of checkouts', - labelNames: ['payment_method'] + labelNames: ['payment_method'], }); ``` @@ -119,8 +134,8 @@ Update it: ```js checkoutsTotal.inc({ - payment_method: paymentMethod -}) + payment_method: paymentMethod, +}); ``` The custom metrics will be exposed under the same endpoint as the API metrics. @@ -139,16 +154,18 @@ For instance: ```js const apiMetrics = require('prometheus-api-metrics'); -app.use(apiMetrics({ - additionalLabels: ['customer', 'cluster'], - extractAdditionalLabelValuesFn: (req, res) => { +app.use( + apiMetrics({ + additionalLabels: ['customer', 'cluster'], + extractAdditionalLabelValuesFn: (req, res) => { const { headers } = req.headers; return { customer: headers['x-custom-header-customer'], - cluster: headers['x-custom-header-cluster'] - } - } -})) + cluster: headers['x-custom-header-cluster'], + }; + }, + }) +); ``` ## Request.js HTTP request duration collector @@ -164,7 +181,8 @@ You can choose to initialized this functionality as a Class or not **Class:** ```js -const HttpMetricsCollector = require('prometheus-api-metrics').HttpMetricsCollector; +const HttpMetricsCollector = require('prometheus-api-metrics') + .HttpMetricsCollector; const collector = new HttpMetricsCollector(); collector.init(); ``` @@ -172,7 +190,8 @@ collector.init(); **Singelton:** ```js -const HttpMetricsCollector = require('prometheus-api-metrics').HttpMetricsCollector; +const HttpMetricsCollector = require('prometheus-api-metrics') + .HttpMetricsCollector; HttpMetricsCollector.init(); ``` @@ -189,18 +208,26 @@ For Example: ```js request({ url: 'http://www.google.com', time: true }, (err, response) => { - Collector.collect(err || response); + Collector.collect(err || response); }); ``` #### request-promise-native ```js -return requestPromise({ method: 'POST', url: 'http://www.mocky.io/v2/5bd9984b2f00006d0006d1fd', route: 'v2/:id', time: true, resolveWithFullResponse: true }).then((response) => { +return requestPromise({ + method: 'POST', + url: 'http://www.mocky.io/v2/5bd9984b2f00006d0006d1fd', + route: 'v2/:id', + time: true, + resolveWithFullResponse: true, +}) + .then((response) => { Collector.collect(response); -}).catch((error) => { + }) + .catch((error) => { Collector.collect(error); -}); + }); ``` **Notes:** @@ -208,6 +235,7 @@ return requestPromise({ method: 'POST', url: 'http://www.mocky.io/v2/5bd9984b2f0 1. In order to use this feature you must use `{ time: true }` as part of your request configuration and then pass to the collector the response or error you got. 2. In order to use the timing feature in request-promise/request-promise-native you must also use `resolveWithFullResponse: true` 3. Override - you can override the `route` and `target` attribute instead of taking them from the request object. In order to do that you should set a `metrics` object on your request with those attribute: + ```js request({ method: 'POST', url: 'http://www.mocky.io/v2/5bd9984b2f00006d0006d1fd', metrics: { target: 'www.google.com', route: 'v2/:id' }, time: true }, (err, response) => {...}; }); @@ -222,10 +250,14 @@ const axiosTime = require('axios-time'); axiosTime(axios); try { - const response = await axios({ baseURL: 'http://www.google.com', method: 'get', url: '/' }); - Collector.collect(response); + const response = await axios({ + baseURL: 'http://www.google.com', + method: 'get', + url: '/', + }); + Collector.collect(response); } catch (error) { - Collector.collect(error); + Collector.collect(error); } ``` @@ -238,9 +270,9 @@ try { This package supports koa server that uses [`koa-router`](https://www.npmjs.com/package/koa-router) and [`koa-bodyparser`](https://www.npmjs.com/package/koa-bodyparser) ```js -const { koaMiddleware } = require('prometheus-api-metrics') +const { koaMiddleware } = require('prometheus-api-metrics'); -app.use(koaMiddleware()) +app.use(koaMiddleware()); ``` ## Test @@ -293,6 +325,10 @@ avg(nodejs_external_memory_bytes / 1024 / 1024) by ( Record) | ((ctx: Context) => Record) + metricsPath?: string; + defaultMetricsInterval?: number; + durationBuckets?: number[]; + requestSizeBuckets?: number[]; + responseSizeBuckets?: number[]; + useUniqueHistogramName?: boolean; + metricsPrefix?: string; + httpMetricsPrefix?: string; + excludeRoutes?: string[]; + includeQueryParams?: boolean; + additionalLabels?: string[]; + extractAdditionalLabelValuesFn?: + | ((req: Request, res: Response) => Record) + | ((ctx: Context) => Record); + excludeDefaultMetricLabels?: boolean | string[]; + useCountersForResponseSizeMetric?: boolean; + useCountersForRequestSizeMetric?: boolean; } export interface CollectorOpts { - durationBuckets?: number[]; - countClientErrors?: boolean; - useUniqueHistogramName?: boolean - prefix?: string; + durationBuckets?: number[]; + countClientErrors?: boolean; + useUniqueHistogramName?: boolean; + prefix?: string; } diff --git a/src/koa-middleware.js b/src/koa-middleware.js index 3b5dffc..18f59d6 100644 --- a/src/koa-middleware.js +++ b/src/koa-middleware.js @@ -45,9 +45,19 @@ class KoaMiddleware { code: ctx.res.statusCode, ...this.setupOptions.extractAdditionalLabelValuesFn(ctx) }; - this.setupOptions.requestSizeHistogram.observe(labels, ctx.req.metrics.contentLength); + if (this.setupOptions.useCountersForRequestSizeMetric) { + this.setupOptions.requestSizeSum.inc(labels, ctx.req.metrics.contentLength); + this.setupOptions.requestSizeCount.inc(labels); + } else { + this.setupOptions.requestSizeHistogram.observe(labels, ctx.req.metrics.contentLength); + } + if (this.setupOptions.useCountersForResponseSizeMetric) { + this.setupOptions.responseSizeSum.inc(labels, responseLength); + this.setupOptions.responseSizeCount.inc(labels); + } else { + this.setupOptions.responseSizeHistogram.observe(labels, responseLength); + } ctx.req.metrics.timer(labels); - this.setupOptions.responseSizeHistogram.observe(labels, responseLength); debug(`metrics updated, request length: ${ctx.req.metrics.contentLength}, response length: ${responseLength}`); } } diff --git a/src/metrics-middleware.js b/src/metrics-middleware.js index b6c6173..7e68e9a 100644 --- a/src/metrics-middleware.js +++ b/src/metrics-middleware.js @@ -18,13 +18,29 @@ module.exports = (appVersion, projectName, framework = 'express') => { responseSizeBuckets, useUniqueHistogramName, metricsPrefix, + httpMetricsPrefix, excludeRoutes, includeQueryParams, additionalLabels = [], - extractAdditionalLabelValuesFn + extractAdditionalLabelValuesFn, + excludeDefaultMetricLabels, + useCountersForRequestSizeMetric, + useCountersForResponseSizeMetric } = options; debug(`Init metrics middleware with options: ${JSON.stringify(options)}`); + utils.validateInput({ + input: metricsPrefix, + isValidInputFn: utils.isString, + errorMessage: 'metricsPrefix should be an string' + }); + + utils.validateInput({ + input: httpMetricsPrefix, + isValidInputFn: utils.isString, + errorMessage: 'httpMetricsPrefix should be an string' + }); + setupOptions.metricsRoute = utils.validateInput({ input: metricsPath, isValidInputFn: utils.isString, @@ -40,6 +56,7 @@ module.exports = (appVersion, projectName, framework = 'express') => { }); setupOptions.includeQueryParams = includeQueryParams; + setupOptions.defaultMetricsInterval = defaultMetricsInterval; setupOptions.additionalLabels = utils.validateInput({ @@ -56,28 +73,58 @@ module.exports = (appVersion, projectName, framework = 'express') => { errorMessage: 'extractAdditionalLabelValuesFn should be a function' }); - const metricNames = utils.getMetricNames( - { - http_request_duration_seconds: 'http_request_duration_seconds', + setupOptions.excludeDefaultMetricLabels = utils.validateInput({ + input: excludeDefaultMetricLabels, + isValidInputFn: (input) => utils.isArray(input) || utils.isBoolean(input), + defaultValue: [], + errorMessage: 'excludeDefaultMetricLabels should be an array or a boolean' + }); + + setupOptions.useCountersForRequestSizeMetric = utils.validateInput({ + input: useCountersForRequestSizeMetric, + isValidInputFn: utils.isBoolean, + defaultValue: false, + errorMessage: 'useCountersForRequestSizeMetric should be a boolean' + }); + + setupOptions.useCountersForResponseSizeMetric = utils.validateInput({ + input: useCountersForResponseSizeMetric, + isValidInputFn: utils.isBoolean, + defaultValue: false, + errorMessage: 'useCountersForResponseSizeMetric should be a boolean' + }); + + const metricNames = utils.getMetricNames({ + metricNames: { app_version: 'app_version', + http_request_duration_seconds: 'http_request_duration_seconds', http_request_size_bytes: 'http_request_size_bytes', + http_request_size_bytes_sum: 'http_request_size_bytes_sum', + http_request_size_bytes_count: 'http_request_size_bytes_count', http_response_size_bytes: 'http_response_size_bytes', + http_response_size_bytes_sum: 'http_response_size_bytes_sum', + http_response_size_bytes_count: 'http_response_size_bytes_count', defaultMetricsPrefix: '' }, useUniqueHistogramName, metricsPrefix, + httpMetricsPrefix, projectName - ); + }); Prometheus.collectDefaultMetrics({ timeout: defaultMetricsInterval, prefix: `${metricNames.defaultMetricsPrefix}` }); PrometheusRegisterAppVersion(appVersion, metricNames.app_version); + const defaultMetricLabels = ['method', 'route', 'code']; + const metricLabels = [ - 'method', - 'route', - 'code', - ...additionalLabels + ...additionalLabels, + ...excludeDefaultMetricLabels === true + ? [] + : Array.isArray(excludeDefaultMetricLabels) + ? defaultMetricLabels.filter((defaultLabel) => excludeDefaultMetricLabels.indexOf(defaultLabel) < 0) + : defaultMetricLabels ].filter(Boolean); // Buckets for response time from 1ms to 500ms @@ -85,6 +132,49 @@ module.exports = (appVersion, projectName, framework = 'express') => { // Buckets for request size from 5 bytes to 10000 bytes const defaultSizeBytesBuckets = [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]; + // Request Size Metric + if (useCountersForRequestSizeMetric) { + setupOptions.requestSizeSum = Prometheus.register.getSingleMetric(metricNames.http_request_size_bytes_sum) || new Prometheus.Counter({ + name: metricNames.http_request_size_bytes_sum, + help: 'Sum of the size of HTTP requests in bytes', + labelNames: metricLabels + }); + setupOptions.requestSizeCount = Prometheus.register.getSingleMetric(metricNames.http_request_size_bytes_count) || new Prometheus.Counter({ + name: metricNames.http_request_size_bytes_count, + help: 'Count of the size of HTTP requests', + labelNames: metricLabels + }); + } else { + setupOptions.requestSizeHistogram = Prometheus.register.getSingleMetric(metricNames.http_request_size_bytes) || new Prometheus.Histogram({ + name: metricNames.http_request_size_bytes, + help: 'Size of HTTP requests in bytes', + labelNames: metricLabels, + buckets: requestSizeBuckets || defaultSizeBytesBuckets + }); + } + + // Response Size Metric + if (useCountersForResponseSizeMetric) { + setupOptions.responseSizeSum = Prometheus.register.getSingleMetric(metricNames.http_response_size_bytes_sum) || new Prometheus.Counter({ + name: metricNames.http_response_size_bytes_sum, + help: 'Sum of the size of HTTP responses in bytes', + labelNames: metricLabels + }); + setupOptions.responseSizeCount = Prometheus.register.getSingleMetric(metricNames.http_response_size_bytes_count) || new Prometheus.Counter({ + name: metricNames.http_response_size_bytes_count, + help: 'Count of the size of HTTP responses', + labelNames: metricLabels + }); + } else { + setupOptions.responseSizeHistogram = Prometheus.register.getSingleMetric(metricNames.http_response_size_bytes) || new Prometheus.Histogram({ + name: metricNames.http_response_size_bytes, + help: 'Size of HTTP response in bytes', + labelNames: metricLabels, + buckets: responseSizeBuckets || defaultSizeBytesBuckets + }); + } + + // Response Time Metric setupOptions.responseTimeHistogram = Prometheus.register.getSingleMetric(metricNames.http_request_duration_seconds) || new Prometheus.Histogram({ name: metricNames.http_request_duration_seconds, help: 'Duration of HTTP requests in seconds', @@ -92,20 +182,6 @@ module.exports = (appVersion, projectName, framework = 'express') => { buckets: durationBuckets || defaultDurationSecondsBuckets }); - setupOptions.requestSizeHistogram = Prometheus.register.getSingleMetric(metricNames.http_request_size_bytes) || new Prometheus.Histogram({ - name: metricNames.http_request_size_bytes, - help: 'Size of HTTP requests in bytes', - labelNames: metricLabels, - buckets: requestSizeBuckets || defaultSizeBytesBuckets - }); - - setupOptions.responseSizeHistogram = Prometheus.register.getSingleMetric(metricNames.http_response_size_bytes) || new Prometheus.Histogram({ - name: metricNames.http_response_size_bytes, - help: 'Size of HTTP response in bytes', - labelNames: metricLabels, - buckets: responseSizeBuckets || defaultSizeBytesBuckets - }); - return frameworkMiddleware(framework); }; }; diff --git a/src/request-response-collector.js b/src/request-response-collector.js index 2278f04..7a76a55 100644 --- a/src/request-response-collector.js +++ b/src/request-response-collector.js @@ -96,7 +96,7 @@ function _init(options = {}) { }; const { durationBuckets, countClientErrors, useUniqueHistogramName, prefix } = options; - metricNames = utils.getMetricNames(metricNames, useUniqueHistogramName, prefix, projectName); + metricNames = utils.getMetricNames({ metricNames, useUniqueHistogramName, metricsPrefix: prefix, projectName }); southboundResponseTimeHistogram = Prometheus.register.getSingleMetric(metricNames.southbound_request_duration_seconds) || new Prometheus.Histogram({ diff --git a/src/utils.js b/src/utils.js index 7d78bf0..c845076 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,19 +1,23 @@ 'use strict'; -const getMetricNames = (metricNames, useUniqueHistogramName, metricsPrefix, projectName) => { +const getMetricNames = ({ metricNames, useUniqueHistogramName, metricsPrefix, httpMetricsPrefix, projectName }) => { const prefix = useUniqueHistogramName === true ? projectName : metricsPrefix; - if (prefix) { - Object.keys(metricNames).forEach(key => { + Object.keys(metricNames).forEach(key => { + if (httpMetricsPrefix && key.startsWith('http_')) { + metricNames[key] = `${httpMetricsPrefix}_${metricNames[key]}`; + } else if (prefix) { metricNames[key] = `${prefix}_${metricNames[key]}`; - }); - } + } + }); return metricNames; }; const isArray = (input) => Array.isArray(input); +const isBoolean = (input) => typeof input === 'boolean'; + const isFunction = (input) => typeof input === 'function'; const isString = (input) => typeof input === 'string'; @@ -34,6 +38,7 @@ const validateInput = ({ input, isValidInputFn, defaultValue, errorMessage }) => module.exports.getMetricNames = getMetricNames; module.exports.isArray = isArray; +module.exports.isBoolean = isBoolean; module.exports.isFunction = isFunction; module.exports.isString = isString; module.exports.shouldLogMetrics = shouldLogMetrics; diff --git a/test/unit-test/metric-middleware-koa-test.js b/test/unit-test/metric-middleware-koa-test.js index ba7c959..5f68525 100644 --- a/test/unit-test/metric-middleware-koa-test.js +++ b/test/unit-test/metric-middleware-koa-test.js @@ -788,5 +788,77 @@ describe('metrics-middleware', () => { Prometheus.register.clear(); }); }); + describe('when calling the function with excludeDefaultMetricLabels option', () => { + it('or it\'s undefined', () => { + middleware(); + expect(Prometheus.register.getSingleMetric('http_request_size_bytes').labelNames).to.have.members(['method', 'route', 'code']); + }); + it('and it\'s false', () => { + middleware({ + excludeDefaultMetricLabels: false + }); + expect(Prometheus.register.getSingleMetric('http_request_size_bytes').labelNames).to.have.members(['method', 'route', 'code']); + }); + it('and it\'s true', () => { + middleware({ + excludeDefaultMetricLabels: true + }); + expect(Prometheus.register.getSingleMetric('http_request_size_bytes').labelNames).to.have.members([]); + }); + it('and it\'s an array', () => { + middleware({ + excludeDefaultMetricLabels: ['route', 'code'] + }); + expect(Prometheus.register.getSingleMetric('http_request_size_bytes').labelNames).to.have.members(['method']); + }); + it('and it\'s other type', () => { + expect(() => { + middleware({ + excludeDefaultMetricLabels: 'invalid', + }); + }).to.throw('excludeDefaultMetricLabels should be an array or a boolean'); + }); + afterEach(() => { + Prometheus.register.clear(); + }); + }); + describe('when calling the function with useCountersForRequestSizeMetric option', () => { + before(() => { + middleware({ + useCountersForRequestSizeMetric: true + }); + }); + it('shouldn\'t have http_request_size_bytes metric', () => { + expect(Prometheus.register.getSingleMetric('http_request_size_bytes')).to.equal(undefined); + }); + it('should have http_request_size_bytes_sum with the right labels', () => { + expect(Prometheus.register.getSingleMetric('http_request_size_bytes_sum').labelNames).to.have.members(['method', 'route', 'code']); + }); + it('should have http_request_size_bytes_count with the right labels', () => { + expect(Prometheus.register.getSingleMetric('http_request_size_bytes_count').labelNames).to.have.members(['method', 'route', 'code']); + }); + after(() => { + Prometheus.register.clear(); + }); + }); + describe('when calling the function with useCountersForResponseSizeMetric option', () => { + before(() => { + middleware({ + useCountersForResponseSizeMetric: true + }); + }); + it('shouldn\'t have http_response_size_bytes metric', () => { + expect(Prometheus.register.getSingleMetric('http_response_size_bytes')).to.equal(undefined); + }); + it('should have http_response_size_bytes_sum with the right labels', () => { + expect(Prometheus.register.getSingleMetric('http_response_size_bytes_sum').labelNames).to.have.members(['method', 'route', 'code']); + }); + it('should have http_response_size_bytes_count with the right labels', () => { + expect(Prometheus.register.getSingleMetric('http_response_size_bytes_count').labelNames).to.have.members(['method', 'route', 'code']); + }); + after(() => { + Prometheus.register.clear(); + }); + }); }); }); diff --git a/test/unit-test/metric-middleware-test.js b/test/unit-test/metric-middleware-test.js index e095763..48fb4b7 100644 --- a/test/unit-test/metric-middleware-test.js +++ b/test/unit-test/metric-middleware-test.js @@ -654,4 +654,76 @@ describe('metrics-middleware', () => { Prometheus.register.clear(); }); }); + describe('when calling the function with excludeDefaultMetricLabels option', () => { + it('or it\'s undefined', () => { + middleware(); + expect(Prometheus.register.getSingleMetric('http_request_size_bytes').labelNames).to.have.members(['method', 'route', 'code']); + }); + it('and it\'s false', () => { + middleware({ + excludeDefaultMetricLabels: false + }); + expect(Prometheus.register.getSingleMetric('http_request_size_bytes').labelNames).to.have.members(['method', 'route', 'code']); + }); + it('and it\'s true', () => { + middleware({ + excludeDefaultMetricLabels: true + }); + expect(Prometheus.register.getSingleMetric('http_request_size_bytes').labelNames).to.have.members([]); + }); + it('and it\'s an array', () => { + middleware({ + excludeDefaultMetricLabels: ['route', 'code'] + }); + expect(Prometheus.register.getSingleMetric('http_request_size_bytes').labelNames).to.have.members(['method']); + }); + it('and it\'s other type', () => { + expect(() => { + middleware({ + excludeDefaultMetricLabels: 'invalid', + }); + }).to.throw('excludeDefaultMetricLabels should be an array or a boolean'); + }); + afterEach(() => { + Prometheus.register.clear(); + }); + }); + describe('when calling the function with useCountersForRequestSizeMetric option', () => { + before(() => { + middleware({ + useCountersForRequestSizeMetric: true + }); + }); + it('shouldn\'t have http_request_size_bytes metric', () => { + expect(Prometheus.register.getSingleMetric('http_request_size_bytes')).to.equal(undefined); + }); + it('should have http_request_size_bytes_sum with the right labels', () => { + expect(Prometheus.register.getSingleMetric('http_request_size_bytes_sum').labelNames).to.have.members(['method', 'route', 'code']); + }); + it('should have http_request_size_bytes_count with the right labels', () => { + expect(Prometheus.register.getSingleMetric('http_request_size_bytes_count').labelNames).to.have.members(['method', 'route', 'code']); + }); + after(() => { + Prometheus.register.clear(); + }); + }); + describe('when calling the function with useCountersForResponseSizeMetric option', () => { + before(() => { + middleware({ + useCountersForResponseSizeMetric: true + }); + }); + it('shouldn\'t have http_response_size_bytes metric', () => { + expect(Prometheus.register.getSingleMetric('http_response_size_bytes')).to.equal(undefined); + }); + it('should have http_response_size_bytes_sum with the right labels', () => { + expect(Prometheus.register.getSingleMetric('http_response_size_bytes_sum').labelNames).to.have.members(['method', 'route', 'code']); + }); + it('should have http_response_size_bytes_count with the right labels', () => { + expect(Prometheus.register.getSingleMetric('http_response_size_bytes_count').labelNames).to.have.members(['method', 'route', 'code']); + }); + after(() => { + Prometheus.register.clear(); + }); + }); }); diff --git a/test/unit-test/utils-test.js b/test/unit-test/utils-test.js index 82217b0..f5ead01 100644 --- a/test/unit-test/utils-test.js +++ b/test/unit-test/utils-test.js @@ -6,11 +6,21 @@ const utils = require('../../src/utils'); describe('utils', () => { describe('getMetricNames', () => { it('should include project name', () => { - const metricNames = ['metric1']; + const metricNames = { metric: 'metric', http_metric: 'http_metric' }; const useUniqueHistogramName = true; - const metricsPrefix = true; + const metricsPrefix = 'prefix'; const projectName = 'mock_project'; - expect(utils.getMetricNames(metricNames, useUniqueHistogramName, metricsPrefix, projectName)[0]).to.equal('mock_project_metric1'); + const newMetricNames = utils.getMetricNames({ metricNames, useUniqueHistogramName, metricsPrefix, projectName }); + expect(Object.values(newMetricNames)).to.have.members(['mock_project_metric', 'mock_project_http_metric']); + }); + it('should include prefix and http prefix', () => { + const metricNames = { metric: 'metric', http_metric: 'http_metric' }; + const useUniqueHistogramName = false; + const metricsPrefix = 'prefix'; + const projectName = 'mock_project'; + const httpMetricsPrefix = 'http_prefix'; + const newMetricNames = utils.getMetricNames({ metricNames, useUniqueHistogramName, metricsPrefix, httpMetricsPrefix, projectName }); + expect(Object.values(newMetricNames)).to.have.members(['prefix_metric', 'http_prefix_http_metric']); }); }); describe('isArray', () => {