This commit is contained in:
Konstantin Pogorelov
2016-04-18 02:50:26 +02:00
commit 616e5daacc
9 changed files with 229 additions and 0 deletions

42
.eslintrc Normal file
View File

@@ -0,0 +1,42 @@
{
"env": {
"node": true,
"es6": true
},
"extends": "eslint:recommended",
"rules": {
"no-cond-assign": 0,
"no-constant-condition": 0,
"no-empty": 0,
"no-fallthrough": 0,
"no-unused-vars": 1,
"no-console": 1,
"semi": 2,
"curly": 2,
"consistent-this": [2, "self"],
"indent": [ 2, 4, { "SwitchCase": 1 } ],
"linebreak-style": [2, "unix"],
"no-nested-ternary": 2,
"new-parens": 2,
"no-dupe-class-members": 2,
"require-yield": 2,
"arrow-spacing": 1,
"no-var": 2,
"no-multi-spaces": 1,
"space-return-throw-case": 0,
"space-infix-ops": [1, {"int32Hint": false}],
"brace-style": 1,
"space-before-blocks": 1,
"operator-linebreak": [1, "before"],
"no-unneeded-ternary": 1,
"no-lonely-if": 1,
"key-spacing": 1,
"quotes": [1, "double", "avoid-escape"],
"no-trailing-spaces": [1, { "skipBlankLines": true }]
}
}

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules

4
.npmignore Normal file
View File

@@ -0,0 +1,4 @@
docker-compose.yml
test
.travis.yml
.eslintrc

4
.travis.yml Normal file
View File

@@ -0,0 +1,4 @@
language: node_js
node_js:
- "5"
- "4"

9
README.md Normal file
View File

@@ -0,0 +1,9 @@
# express prometheus bundle
A bundle of standard metrics for an express application.
Included metrics:
* **up**: normally is just 1
* **nodejs_memory_heap_total_bytes** and **nodejs_memory_heap_used_bytes**
* **http_request_total**: count of http requests labeled with status_code

13
example.js Normal file
View File

@@ -0,0 +1,13 @@
"use strict";
const express = require("express"),
app = express(),
promBundle = require(".");
app.use(promBundle({
prefix: "demo_app:something"
}));
app.get("/hello", (req, res) => res.send("ok"));
app.listen(3000);

21
package.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "express-prom-bundle",
"version": "1.0.0",
"description": "express middleware with standard prometheus metrics in one bundle",
"main": "src/index.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "node_modules/jasme/run.js"
},
"author": "Konstantin Pogorelov <or@pluseq.com> (https://github.com/disjunction)",
"license": "ISC",
"dependencies": {
"on-finished": "^2.3.0",
"prom-client": "^3.4.0"
},
"devDependencies": {
"eslint": "^2.8.0"
}
}

51
src/PrometheusHelper.js Normal file
View File

@@ -0,0 +1,51 @@
"use strict";
const onFinished = require("on-finished");
module.exports = class {
constructor(opts, promClient) {
this.opts = opts || {};
this.promClient = this.opts.promClient || require("prom-client");
this.metrics = {};
}
metricExists(name) {
return !!this.metrics[name];
}
checkDuplicate(name) {
if (this.metricExists(name)) {
throw new Error("trying to add already existing metric: " + name);
}
}
makeRealName(name) {
const prefix = this.opts.prefix ? (this.opts.prefix + ":") : "";
return prefix + name;
}
makeMetric(TheClass, name, description, param) {
this.checkDuplicate(name);
const realName = this.makeRealName(name);
this.metrics[name] = new TheClass(
realName, description, param
);
return this.metrics[name];
}
newCounter(name, description, labels) {
return this.makeMetric(this.promClient.Counter, name, description, labels);
}
newGauge(name, description, labels) {
return this.makeMetric(this.promClient.Gauge, name, description, labels);
}
newHistogram(name, description, options) {
return this.makeMetric(this.promClient.Histogram, name, description, options);
}
newSummary(name, description, options) {
return this.makeMetric(this.promClient.Histogram, name, description, options);
}
};

84
src/index.js Normal file
View File

@@ -0,0 +1,84 @@
"use strict";
const
PrometheusHelper = require("./PrometheusHelper"),
onFinished = require("on-finished");
function filterArrayByRegExps(array, regexps) {
let compiled = regexps.map(regexp => new RegExp(regexp));
return array.filter(element => {
for (let regexp of compiled) {
if (element.match(regexp)) {
return true;
}
}
return false;
});
}
function main(opts) {
if (arguments[2] && arguments[1] && arguments[1].send) {
arguments[1].status(500)
.send("<h1>500 Error</h1>\n"
+ "<p>Unexapected 3d param.\n"
+ "<p>Did you just put express-prom-bundle into app.use "
+ "without calling it as a function first?");
return;
}
let helper = new PrometheusHelper(opts);
let metricTemplates = {
"up": () => helper.newGauge(
"up",
"1 = up, 0 = not up"
),
"nodejs_memory_heap_total_bytes": () => helper.newGauge(
"nodejs_memory_heap_total_bytes",
"value of process.memoryUsage().heapTotal"
),
"nodejs_memory_heap_used_bytes": () => helper.newGauge(
"nodejs_memory_heap_used_bytes",
"value of process.memoryUsage().heapUsed"
),
"http_request_total": () => helper.newCounter(
"http_request_total",
"number of http responses labeled with status code",
["status_code"]
)
};
const metrics = {};
for (let name of Object.keys(metricTemplates)) {
metrics[name] = metricTemplates[name]();
}
metrics.up.set(1);
let middleware = function (req, res, next) {
if (req.path == "/metrics") {
let memoryUsage = process.memoryUsage();
metrics["nodejs_memory_heap_total_bytes"].set(memoryUsage.heapTotal);
metrics["nodejs_memory_heap_used_bytes"].set(memoryUsage.heapUsed);
res.contentType("text/plain")
.send(helper.promClient.register.metrics());
return;
}
onFinished(res, () => {
if (res.statusCode) {
metrics["http_request_total"].inc({"status_code": res.statusCode});
}
});
next();
};
middleware.helper = helper;
middleware.metricTemplates = metricTemplates;
return middleware;
}
module.exports = main;