mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-10 01:16:39 +02:00
Server Metrics with Prometheus
Implementation use internal eventBus to enable loose counting in the app. read more at https://prometheus.io/ Closes #98
This commit is contained in:
parent
a4d2da872a
commit
b46fb7507f
@ -7,4 +7,8 @@ Version: 1.0
|
|||||||
* [Feature Toggles API](feature-toggles-api.md)
|
* [Feature Toggles API](feature-toggles-api.md)
|
||||||
* [Strategies API](strategies-api.md)
|
* [Strategies API](strategies-api.md)
|
||||||
* [Events API](events-api.md)
|
* [Events API](events-api.md)
|
||||||
* [Metrics API](metrics-api.md)
|
* [Metrics API](metrics-api.md)
|
||||||
|
|
||||||
|
|
||||||
|
Others:
|
||||||
|
* [Internal Backstage API](internal-backstage-api.ms)
|
8
docs/api/internal-backstage-api.md
Normal file
8
docs/api/internal-backstage-api.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Internal Backstage API
|
||||||
|
|
||||||
|
`GET http://unleash.host.com/internal-backstage/prometheus`
|
||||||
|
|
||||||
|
Unleash uses prometheus internally to collect metrics. These are
|
||||||
|
available on the given url if the `serverMetrics` option is enabled (default=true).
|
||||||
|
|
||||||
|
[Read more about Prometheus](https://prometheus.io/)
|
6
lib/events.js
Normal file
6
lib/events.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
TOGGLES_FETCH: 'toggles:fetch',
|
||||||
|
TOGGLES_CREATE: 'toggles:create',
|
||||||
|
CLIENT_REGISTER: 'client:register',
|
||||||
|
CLIENT_METRICS: 'toggles:metrics',
|
||||||
|
}
|
24
lib/metrics.js
Normal file
24
lib/metrics.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const events = require('./events');
|
||||||
|
|
||||||
|
exports.startMonitoring = (enable, eventBus) => {
|
||||||
|
if (!enable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = require('prom-client');
|
||||||
|
const toggleFetch = new client.Counter('toggles_fetch_counter', 'Number of fetch toggles request');
|
||||||
|
const clientRegister = new client.Counter('client_register_counter', 'Number client register requests');
|
||||||
|
const clientMetrics = new client.Counter('client_metrics_counter', 'Number client metrics requests');
|
||||||
|
|
||||||
|
eventBus.on(events.TOGGLES_FETCH, () => {
|
||||||
|
toggleFetch.inc();
|
||||||
|
});
|
||||||
|
|
||||||
|
eventBus.on(events.CLIENT_REGISTER, () => {
|
||||||
|
clientRegister.inc();
|
||||||
|
});
|
||||||
|
|
||||||
|
eventBus.on(events.CLIENT_METRICS, () => {
|
||||||
|
clientMetrics.inc();
|
||||||
|
});
|
||||||
|
};
|
18
lib/options.js
Normal file
18
lib/options.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const DEFAULT_OPTIONS = {
|
||||||
|
databaseUri: process.env.DATABASE_URL || 'postgres://unleash_user:passord@localhost:5432/unleash',
|
||||||
|
port: process.env.HTTP_PORT || process.env.PORT || 4242,
|
||||||
|
baseUriPath: process.env.BASE_URI_PATH || '',
|
||||||
|
serverMetrics: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createOptions: (opts) => {
|
||||||
|
const options = Object.assign({}, DEFAULT_OPTIONS, opts);
|
||||||
|
if (!options.databaseUri) {
|
||||||
|
throw new Error('You must either pass databaseUri option or set environemnt variable DATABASE_URL');
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
}
|
12
lib/routes/backstage.js
Normal file
12
lib/routes/backstage.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const prometheusRegister = require('prom-client/lib/register');
|
||||||
|
|
||||||
|
module.exports = function (app, config) {
|
||||||
|
if(config.serverMetrics) {
|
||||||
|
app.get('/internal-backstage/prometheus', (req, res) => {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||||
|
res.end(prometheusRegister.metrics());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -8,6 +8,8 @@ const ValidationError = require('../error/validation-error.js');
|
|||||||
const validateRequest = require('../error/validate-request');
|
const validateRequest = require('../error/validate-request');
|
||||||
const extractUser = require('../extract-user');
|
const extractUser = require('../extract-user');
|
||||||
|
|
||||||
|
const { TOGGLES_FETCH } = require('../events');
|
||||||
|
|
||||||
const legacyFeatureMapper = require('../data-helper/legacy-feature-mapper');
|
const legacyFeatureMapper = require('../data-helper/legacy-feature-mapper');
|
||||||
const version = 1;
|
const version = 1;
|
||||||
|
|
||||||
@ -37,8 +39,11 @@ const handleErrors = (req, res, error) => {
|
|||||||
|
|
||||||
module.exports = function (app, config) {
|
module.exports = function (app, config) {
|
||||||
const { featureToggleStore, eventStore } = config.stores;
|
const { featureToggleStore, eventStore } = config.stores;
|
||||||
|
const { eventBus } = config;
|
||||||
|
|
||||||
app.get('/features', (req, res) => {
|
app.get('/features', (req, res) => {
|
||||||
|
eventBus.emit(TOGGLES_FETCH);
|
||||||
|
|
||||||
featureToggleStore.getFeatures()
|
featureToggleStore.getFeatures()
|
||||||
.then((features) => features.map(legacyFeatureMapper.addOldFields))
|
.then((features) => features.map(legacyFeatureMapper.addOldFields))
|
||||||
.then(features => res.json({ version, features }));
|
.then(features => res.json({ version, features }));
|
||||||
|
@ -13,4 +13,5 @@ exports.createAPI = function (router, config) {
|
|||||||
exports.createLegacy = function (router, config) {
|
exports.createLegacy = function (router, config) {
|
||||||
require('./feature')(router, config);
|
require('./feature')(router, config);
|
||||||
require('./health-check')(router, config);
|
require('./health-check')(router, config);
|
||||||
|
require('./backstage')(router, config);
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ const logger = require('../logger');
|
|||||||
const ClientMetrics = require('../client-metrics');
|
const ClientMetrics = require('../client-metrics');
|
||||||
const joi = require('joi');
|
const joi = require('joi');
|
||||||
const { clientMetricsSchema, clientRegisterSchema } = require('./metrics-schema');
|
const { clientMetricsSchema, clientRegisterSchema } = require('./metrics-schema');
|
||||||
|
const { CLIENT_REGISTER, CLIENT_METRICS } = require('../events');
|
||||||
/*
|
/*
|
||||||
* TODO:
|
* TODO:
|
||||||
* - always catch errors and always return a response to client!
|
* - always catch errors and always return a response to client!
|
||||||
@ -18,6 +18,8 @@ module.exports = function (app, config) {
|
|||||||
clientStrategyStore,
|
clientStrategyStore,
|
||||||
clientInstanceStore,
|
clientInstanceStore,
|
||||||
} = config.stores;
|
} = config.stores;
|
||||||
|
|
||||||
|
const { eventBus } = config;
|
||||||
|
|
||||||
const metrics = new ClientMetrics(clientMetricsStore);
|
const metrics = new ClientMetrics(clientMetricsStore);
|
||||||
|
|
||||||
@ -39,6 +41,8 @@ module.exports = function (app, config) {
|
|||||||
return res.status(400).json(err);
|
return res.status(400).json(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eventBus.emit(CLIENT_METRICS);
|
||||||
|
|
||||||
clientMetricsStore
|
clientMetricsStore
|
||||||
.insert(cleaned)
|
.insert(cleaned)
|
||||||
.then(() => clientInstanceStore.insert({
|
.then(() => clientInstanceStore.insert({
|
||||||
@ -61,6 +65,8 @@ module.exports = function (app, config) {
|
|||||||
return res.status(400).json(err);
|
return res.status(400).json(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eventBus.emit(CLIENT_REGISTER);
|
||||||
|
|
||||||
clientStrategyStore
|
clientStrategyStore
|
||||||
.insert(cleaned.appName, cleaned.strategies)
|
.insert(cleaned.appName, cleaned.strategies)
|
||||||
.then(() => clientInstanceStore.insert({
|
.then(() => clientInstanceStore.insert({
|
||||||
|
@ -1,40 +1,42 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const { EventEmitter } = require('events');
|
||||||
|
|
||||||
const logger = require('./logger');
|
const logger = require('./logger');
|
||||||
const migrator = require('../migrator');
|
const migrator = require('../migrator');
|
||||||
const { createStores } = require('./db');
|
|
||||||
const getApp = require('./app');
|
const getApp = require('./app');
|
||||||
|
const events = require('./events');
|
||||||
|
|
||||||
const DEFAULT_OPTIONS = {
|
const { startMonitoring } = require('./metrics');
|
||||||
databaseUri: process.env.DATABASE_URL || 'postgres://unleash_user:passord@localhost:5432/unleash',
|
const { createStores } = require('./db');
|
||||||
port: process.env.HTTP_PORT || process.env.PORT || 4242,
|
const { createOptions } = require('./options');
|
||||||
baseUriPath: process.env.BASE_URI_PATH || '',
|
|
||||||
};
|
|
||||||
|
|
||||||
function createApp (options) {
|
function createApp (options) {
|
||||||
// Database dependecies (statefull)
|
// Database dependecies (statefull)
|
||||||
const stores = createStores(options);
|
const stores = createStores(options);
|
||||||
|
const eventBus = new EventEmitter();
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
prometheusPath: options.prometheusPath,
|
||||||
baseUriPath: options.baseUriPath,
|
baseUriPath: options.baseUriPath,
|
||||||
port: options.port,
|
port: options.port,
|
||||||
publicFolder: options.publicFolder,
|
publicFolder: options.publicFolder,
|
||||||
stores,
|
stores,
|
||||||
|
eventBus,
|
||||||
};
|
};
|
||||||
|
|
||||||
const app = getApp(config);
|
const app = getApp(config);
|
||||||
const server = app.listen(app.get('port'), () => {
|
const server = app.listen(app.get('port'), () => {
|
||||||
logger.info(`Unleash started on ${app.get('port')}`);
|
logger.info(`Unleash started on ${app.get('port')}`);
|
||||||
});
|
});
|
||||||
return { app, server };
|
|
||||||
|
startMonitoring(options.serverMetrics, eventBus);
|
||||||
|
|
||||||
|
return { app, server, eventBus };
|
||||||
}
|
}
|
||||||
|
|
||||||
function start (opts) {
|
function start (opts) {
|
||||||
const options = Object.assign({}, DEFAULT_OPTIONS, opts);
|
const options = createOptions(opts);
|
||||||
|
|
||||||
if (!options.databaseUri) {
|
|
||||||
throw new Error('You must either pass databaseUri option or set environemnt variable DATABASE_URL');
|
|
||||||
}
|
|
||||||
|
|
||||||
return migrator({ databaseUri: options.databaseUri })
|
return migrator({ databaseUri: options.databaseUri })
|
||||||
.catch(err => logger.error('failed to migrate db', err))
|
.catch(err => logger.error('failed to migrate db', err))
|
||||||
|
@ -71,6 +71,7 @@
|
|||||||
"moment": "^2.15.2",
|
"moment": "^2.15.2",
|
||||||
"parse-database-url": "^0.3.0",
|
"parse-database-url": "^0.3.0",
|
||||||
"pg": "^6.1.0",
|
"pg": "^6.1.0",
|
||||||
|
"prom-client": "^6.1.2",
|
||||||
"serve-favicon": "^2.3.0",
|
"serve-favicon": "^2.3.0",
|
||||||
"unleash-frontend": "github:unleash/unleash-frontend",
|
"unleash-frontend": "github:unleash/unleash-frontend",
|
||||||
"yallist": "^2.0.0"
|
"yallist": "^2.0.0"
|
||||||
|
@ -15,6 +15,9 @@ require('db-migrate-shared').log.silence(true);
|
|||||||
// because of migrator bug
|
// because of migrator bug
|
||||||
delete process.env.DATABASE_URL;
|
delete process.env.DATABASE_URL;
|
||||||
|
|
||||||
|
const { EventEmitter } = require('events');
|
||||||
|
const eventBus = new EventEmitter();
|
||||||
|
|
||||||
function createApp (databaseSchema = 'test') {
|
function createApp (databaseSchema = 'test') {
|
||||||
const options = {
|
const options = {
|
||||||
databaseUri: require('./database-config').getDatabaseUri(),
|
databaseUri: require('./database-config').getDatabaseUri(),
|
||||||
@ -29,7 +32,7 @@ function createApp (databaseSchema = 'test') {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
db.destroy();
|
db.destroy();
|
||||||
const stores = createStores(options);
|
const stores = createStores(options);
|
||||||
const app = getApp({ stores });
|
const app = getApp({ stores, eventBus });
|
||||||
return {
|
return {
|
||||||
stores,
|
stores,
|
||||||
request: supertest(app),
|
request: supertest(app),
|
||||||
|
31
test/unit/routes/backstage.test.js
Normal file
31
test/unit/routes/backstage.test.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const test = require('ava');
|
||||||
|
const store = require('./fixtures/store');
|
||||||
|
const supertest = require('supertest');
|
||||||
|
const logger = require('../../../lib/logger');
|
||||||
|
const getApp = require('../../../lib/app');
|
||||||
|
|
||||||
|
const { EventEmitter } = require('events');
|
||||||
|
const eventBus = new EventEmitter();
|
||||||
|
|
||||||
|
test.beforeEach(() => {
|
||||||
|
logger.setLevel('FATAL');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should use enable prometheus', t => {
|
||||||
|
const stores = store.createStores();
|
||||||
|
const app = getApp({
|
||||||
|
baseUriPath: '',
|
||||||
|
serverMetrics: true,
|
||||||
|
stores,
|
||||||
|
eventBus,
|
||||||
|
});
|
||||||
|
|
||||||
|
const request = supertest(app);
|
||||||
|
|
||||||
|
return request
|
||||||
|
.get('/internal-backstage/prometheus')
|
||||||
|
.expect('Content-Type', /text/)
|
||||||
|
.expect(200)
|
||||||
|
});
|
@ -6,6 +6,9 @@ const supertest = require('supertest');
|
|||||||
const logger = require('../../../lib/logger');
|
const logger = require('../../../lib/logger');
|
||||||
const getApp = require('../../../lib/app');
|
const getApp = require('../../../lib/app');
|
||||||
|
|
||||||
|
const { EventEmitter } = require('events');
|
||||||
|
const eventBus = new EventEmitter();
|
||||||
|
|
||||||
test.beforeEach(() => {
|
test.beforeEach(() => {
|
||||||
logger.setLevel('FATAL');
|
logger.setLevel('FATAL');
|
||||||
});
|
});
|
||||||
@ -16,6 +19,7 @@ function getSetup () {
|
|||||||
const app = getApp({
|
const app = getApp({
|
||||||
baseUriPath: base,
|
baseUriPath: base,
|
||||||
stores,
|
stores,
|
||||||
|
eventBus,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -6,6 +6,9 @@ const supertest = require('supertest');
|
|||||||
const logger = require('../../../lib/logger');
|
const logger = require('../../../lib/logger');
|
||||||
const getApp = require('../../../lib/app');
|
const getApp = require('../../../lib/app');
|
||||||
|
|
||||||
|
const { EventEmitter } = require('events');
|
||||||
|
const eventBus = new EventEmitter();
|
||||||
|
|
||||||
test.beforeEach(() => {
|
test.beforeEach(() => {
|
||||||
logger.setLevel('FATAL');
|
logger.setLevel('FATAL');
|
||||||
});
|
});
|
||||||
@ -17,6 +20,7 @@ function getSetup () {
|
|||||||
const app = getApp({
|
const app = getApp({
|
||||||
baseUriPath: '',
|
baseUriPath: '',
|
||||||
stores,
|
stores,
|
||||||
|
eventBus,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -6,6 +6,9 @@ const supertest = require('supertest');
|
|||||||
const logger = require('../../../lib/logger');
|
const logger = require('../../../lib/logger');
|
||||||
const getApp = require('../../../lib/app');
|
const getApp = require('../../../lib/app');
|
||||||
|
|
||||||
|
const { EventEmitter } = require('events');
|
||||||
|
const eventBus = new EventEmitter();
|
||||||
|
|
||||||
test.beforeEach(() => {
|
test.beforeEach(() => {
|
||||||
logger.setLevel('FATAL');
|
logger.setLevel('FATAL');
|
||||||
});
|
});
|
||||||
@ -15,6 +18,7 @@ function getSetup () {
|
|||||||
const app = getApp({
|
const app = getApp({
|
||||||
baseUriPath: '',
|
baseUriPath: '',
|
||||||
stores,
|
stores,
|
||||||
|
eventBus,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -6,6 +6,9 @@ const supertest = require('supertest');
|
|||||||
const logger = require('../../../lib/logger');
|
const logger = require('../../../lib/logger');
|
||||||
const getApp = require('../../../lib/app');
|
const getApp = require('../../../lib/app');
|
||||||
|
|
||||||
|
const { EventEmitter } = require('events');
|
||||||
|
const eventBus = new EventEmitter();
|
||||||
|
|
||||||
test.beforeEach(() => {
|
test.beforeEach(() => {
|
||||||
logger.setLevel('FATAL');
|
logger.setLevel('FATAL');
|
||||||
});
|
});
|
||||||
@ -15,6 +18,7 @@ test('should add version numbers for /stategies', t => {
|
|||||||
const app = getApp({
|
const app = getApp({
|
||||||
baseUriPath: '',
|
baseUriPath: '',
|
||||||
stores,
|
stores,
|
||||||
|
eventBus,
|
||||||
});
|
});
|
||||||
|
|
||||||
const request = supertest(app);
|
const request = supertest(app);
|
||||||
|
Loading…
Reference in New Issue
Block a user