diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..cc391a8a2f0a569021057c6a976737383cd58f1f --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,50 @@ +include: + - project: 'general/templates' + file: '/cicd/SAST-nodejs.gitlab-ci.yml' + - project: 'general/templates' + file: '/cicd/npm-audit.gitlab-ci.yml' + - project: 'general/templates' + file: '/cicd/sonarqube.gitlab-ci.yml' + - project: 'general/templates' + file: '/cicd/npm-outdated.gitlab-ci.yml' + +variables: + SONAR_PROJECT_KEY: metrics + SONAR_TOKEN: 11922a8e774494f51e1d2f0e695949e4073e7df8 + NPM_REGISTRY: https://npm.br-edv.brnet.int + NPM_TOKEN: 5w2Gy80rdH+2Tch0afNI6Q== + +cache: + paths: + - node_modules/ + - docs/ + +stages: + - build + - test + - quality + - publish + +build: + stage: build + script: + - npm install + - npm run build + +test: + stage: test + before_script: + - npm install -g jest + script: + - jest + artifacts: + paths: + - docs/test-report.html + - docs/coverage/lcov.info + +publish: + stage: publish + script: + - npm config set strict-ssl false + - npm config set //${NPM_REGISTRY}/:_authToken ${NPM_TOKEN} + - npm publish --registry $NPM_REGISTRY diff --git a/.npmignore b/.npmignore index 240193a219e06a7b09c62e3696d104bbf44b5d38..e0b3277c5c316e79675349ce80f42f820cac4d72 100644 --- a/.npmignore +++ b/.npmignore @@ -1,2 +1,3 @@ src/ -docs/ \ No newline at end of file +docs/ +examples/ \ No newline at end of file diff --git a/README.md b/README.md index edd779792e81189521774b9f38eccfce89548772..ae224418d73e352c9b738567c46fdce8db86b20a 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,34 @@ A small express middleware to get base metrics for any node.js app. `import { Metrics } from '@br/metrics'` `let metrics = new Metrics(options)` -Note: The options Part may be omitted, as all parts are optional. +Note: The options Part may be omitted, as all parts are optional. -Before your Routes: -`router.use(metrics.start.bind(metrics))` +Before your Routes: +`router.use(metrics.start.bind(metrics))` -And to enable the *_metrics*-Endpoint: -`router.get('/_metrics', metrics.endpoint)` +And to enable the *_metrics*-Endpoint: +`router.get('/_metrics', metrics.endpoint)` ### Options -TODO: fill options \ No newline at end of file +The Following Options may be used to configure the behaviour. + +- ignore: A String Array with routes to ignore, e.g. ['/foo'] . Default: []. Important: */_metrics* and */favicon.ico* are always ignored +- disableErrorCounter: Disable the Error Counter. Default: false +- disableRouteCounter: Disable the Route Counter. Default: false +- disableDurationCounter: Disable the Duration Counter. Default: false + +## Examples + +You can find prebuilt examples in the fitting folder. + +Just call `node examples/dist/server.js 3000` to start a Test-Server on Port 3000. + +Then use your Browser to call the Endpoints: + +- /foo +- /bar +- /404 +- /401 + +And check the results on the Endpoint */_metrics* diff --git a/TODO.md b/TODO.md index dac536c71d9463b25a6029f705886bf300ca2046..8f74238470f893b762354bbbef47865eacc72dea 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,7 @@ # TODO -- config - - tests -- README: options, examples \ No newline at end of file +- get to gitlab-server + +- publish to npm diff --git a/examples/src/app.ts b/examples/src/app.ts index ba2f4cb497e1f8a30f70be056ead3f86d3122572..021f7b2814242f7c8ea2c595477a1b450315b692 100644 --- a/examples/src/app.ts +++ b/examples/src/app.ts @@ -17,7 +17,12 @@ class App { this.router = express.Router() - this.metrics = new Metrics({}) + this.metrics = new Metrics({ + ignore: ['/bar'], + disableErrorCounter: false, + disableRouteCounter: false, + disableDurationCounter: false + }) this.router.use(this.metrics.start.bind(this.metrics)) diff --git a/package.json b/package.json index 693a382f492aab1dd18eef0af54a7d4e0bbf78cf..a0d8497cb89cc535175272d886e3471754f86e3b 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { + "prepublish":"npm run build", "test": "jest", "test:mutation": "stryker run", "lint": "eslint src/**/*.ts", @@ -55,5 +56,8 @@ "pre-push": "npm run lint", "post-commit": "./ho-copy" } + }, + "publishConfig": { + "@br:registry": "https://npm.br-edv.brnet.int" } } diff --git a/src/index.spec.ts b/src/index.spec.ts index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..64c185ffeb68be1260c77b171c1874bbffd039af 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -0,0 +1,6 @@ +/* + Import. + - Start with different option packages. + - Call all Routes + - Check if _metrics has a good start +*/ diff --git a/src/index.ts b/src/index.ts index 969af810139cc86a1cd9db2784973ebf768135e8..34f8900d5022b9156ec2ec72f18baf546ad3be90 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,47 @@ import * as express from 'express' import Prometheus = require('prom-client') +export interface Options { + ignore: string[] + disableRouteCounter: boolean + disableErrorCounter: boolean + disableDurationCounter: boolean +} + export class Metrics { - private readonly _options: any + private readonly _ignore: string[] + private readonly _disableRouteCounter: boolean + private readonly _disableErrorCounter: boolean + private readonly _disableDurationCounter: boolean + public readonly _httpRequestDurationMicroseconds: any public readonly _numOfRequests: any public readonly _numOfErrors: any - constructor (options: any) { - this._options = options + constructor (options: Partial<Options> = {}) { + if (typeof options.ignore !== 'undefined') { + this._ignore = options.ignore + this._ignore.push('/_metrics') + this._ignore.push('/favicon.ico') + } else { + this._ignore = ['/_metrics', '/favicon.ico'] + } + if (typeof options.disableRouteCounter !== 'undefined') { + this._disableRouteCounter = options.disableRouteCounter + } else { + this._disableRouteCounter = false + } + if (typeof options.disableErrorCounter !== 'undefined') { + this._disableErrorCounter = options.disableErrorCounter + } else { + this._disableErrorCounter = false + } + if (typeof options.disableDurationCounter !== 'undefined') { + this._disableDurationCounter = options.disableDurationCounter + } else { + this._disableDurationCounter = false + } + this._httpRequestDurationMicroseconds = new Prometheus.Histogram({ name: 'http_request_duration_ms', help: 'Duration of HTTP requests in ms', @@ -25,20 +58,25 @@ export class Metrics { help: 'Number of errors', labelNames: ['error'] }) - // TODO: config: ignore routes, disable metrics } public start (req: express.Request, res: express.Response, next: express.NextFunction): void { res.locals.startEpoch = Date.now() res.on('finish', () => { - if (req.originalUrl !== '/_metrics' && req.originalUrl !== './favicon.ico') { + if (!this._ignore.includes(req.originalUrl)) { const responseTimeInMs = Date.now() - res.locals.startEpoch - this._httpRequestDurationMicroseconds - .labels(req.method, req.originalUrl, res.statusCode.toString()) - .observe(responseTimeInMs) - this._numOfRequests.inc({ route: req.originalUrl }) + if (!this._disableDurationCounter) { + this._httpRequestDurationMicroseconds + .labels(req.method, req.originalUrl, res.statusCode.toString()) + .observe(responseTimeInMs) + } + if (!this._disableRouteCounter) { + this._numOfRequests.inc({ route: req.originalUrl }) + } if (res.statusCode >= 400) { - this._numOfErrors.inc({ error: res.statusCode }) + if (!this._disableErrorCounter) { + this._numOfErrors.inc({ error: res.statusCode }) + } } } }) @@ -50,5 +88,4 @@ export class Metrics { res.end(Prometheus.register.metrics()) } } - export default Metrics