Merge pull request #1173 from CartoDB/dgaubert/ch78384/maps-api-replace-log4js-logger-by-pino-bis
Logger improvements
This commit is contained in:
commit
649f8d701e
@ -199,20 +199,21 @@ module.exports = class ApiRouter {
|
||||
const apiRouter = router({ mergeParams: true });
|
||||
const { paths, middlewares = [] } = route;
|
||||
|
||||
middlewares.forEach(middleware => apiRouter.use(middleware()));
|
||||
|
||||
apiRouter.use(initLogger({ logger: this.serverOptions.logger }));
|
||||
apiRouter.use(initializeStatusCode());
|
||||
apiRouter.use(bodyParser.json());
|
||||
apiRouter.use(servedByHostHeader());
|
||||
apiRouter.use(clientHeader());
|
||||
apiRouter.use(profiler({
|
||||
enabled: this.serverOptions.useProfiler,
|
||||
statsClient: global.statsClient
|
||||
}));
|
||||
apiRouter.use(user(this.metadataBackend));
|
||||
|
||||
middlewares.forEach(middleware => apiRouter.use(middleware()));
|
||||
|
||||
apiRouter.use(initializeStatusCode());
|
||||
apiRouter.use(bodyParser.json());
|
||||
apiRouter.use(servedByHostHeader());
|
||||
apiRouter.use(clientHeader());
|
||||
apiRouter.use(lzmaMiddleware());
|
||||
apiRouter.use(cors());
|
||||
apiRouter.use(user(this.metadataBackend));
|
||||
|
||||
this.templateRouter.route(apiRouter, route.template);
|
||||
this.mapRouter.route(apiRouter, route.map);
|
||||
|
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const PSQL = require('cartodb-psql');
|
||||
const tag = require('../middlewares/tag');
|
||||
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
|
||||
const credentials = require('../middlewares/credentials');
|
||||
const authorize = require('../middlewares/authorize');
|
||||
@ -23,6 +24,7 @@ module.exports = class AnalysesController {
|
||||
|
||||
middlewares () {
|
||||
return [
|
||||
tag({ tags: ['analysis', 'catalog'] }),
|
||||
credentials(),
|
||||
authorize(this.authBackend),
|
||||
dbConnSetup(this.pgConnection),
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const tag = require('../middlewares/tag');
|
||||
const layergroupToken = require('../middlewares/layergroup-token');
|
||||
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
|
||||
const credentials = require('../middlewares/credentials');
|
||||
@ -23,6 +24,7 @@ module.exports = class AnalysisLayergroupController {
|
||||
|
||||
middlewares () {
|
||||
return [
|
||||
tag({ tags: ['analysis', 'node'] }),
|
||||
layergroupToken(),
|
||||
credentials(),
|
||||
authorize(this.authBackend),
|
||||
|
@ -3,6 +3,7 @@
|
||||
const windshaft = require('windshaft');
|
||||
const MapConfig = windshaft.model.MapConfig;
|
||||
const Datasource = windshaft.model.Datasource;
|
||||
const tag = require('../middlewares/tag');
|
||||
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
|
||||
const credentials = require('../middlewares/credentials');
|
||||
const dbConnSetup = require('../middlewares/db-conn-setup');
|
||||
@ -90,6 +91,7 @@ module.exports = class AnonymousMapController {
|
||||
};
|
||||
|
||||
return [
|
||||
tag({ tags: ['map', 'anonymous'] }),
|
||||
metrics({
|
||||
enabled: this.config.pubSubMetrics.enabled,
|
||||
metricsBackend: this.metricsBackend,
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const tag = require('../middlewares/tag');
|
||||
const layergroupToken = require('../middlewares/layergroup-token');
|
||||
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
|
||||
const credentials = require('../middlewares/credentials');
|
||||
@ -38,6 +39,7 @@ module.exports = class AttributesLayergroupController {
|
||||
|
||||
middlewares () {
|
||||
return [
|
||||
tag({ tags: ['attributes'] }),
|
||||
layergroupToken(),
|
||||
credentials(),
|
||||
authorize(this.authBackend),
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const tag = require('../middlewares/tag');
|
||||
const layergroupToken = require('../middlewares/layergroup-token');
|
||||
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
|
||||
const credentials = require('../middlewares/credentials');
|
||||
@ -38,6 +39,7 @@ module.exports = class AggregatedFeaturesLayergroupController {
|
||||
|
||||
middlewares () {
|
||||
return [
|
||||
tag({ tags: ['cluster'] }),
|
||||
layergroupToken(),
|
||||
credentials(),
|
||||
authorize(this.authBackend),
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const tag = require('../middlewares/tag');
|
||||
const layergroupToken = require('../middlewares/layergroup-token');
|
||||
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
|
||||
const credentials = require('../middlewares/credentials');
|
||||
@ -76,6 +77,7 @@ module.exports = class DataviewLayergroupController {
|
||||
|
||||
middlewares ({ action, rateLimitGroup }) {
|
||||
return [
|
||||
tag({ tags: ['dataview', action] }),
|
||||
layergroupToken(),
|
||||
credentials(),
|
||||
authorize(this.authBackend),
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const tag = require('../middlewares/tag');
|
||||
const layergroupToken = require('../middlewares/layergroup-token');
|
||||
const coordinates = require('../middlewares/coordinates');
|
||||
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
|
||||
@ -61,6 +62,7 @@ module.exports = class PreviewLayergroupController {
|
||||
}
|
||||
|
||||
return [
|
||||
tag({ tags: ['static', 'tile'] }),
|
||||
layergroupToken(),
|
||||
validateZoom ? coordinates({ z: true, x: false, y: false }) : noop(),
|
||||
credentials(),
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const tag = require('../middlewares/tag');
|
||||
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
|
||||
const credentials = require('../middlewares/credentials');
|
||||
const dbConnSetup = require('../middlewares/db-conn-setup');
|
||||
@ -67,6 +68,7 @@ module.exports = class PreviewTemplateController {
|
||||
};
|
||||
|
||||
return [
|
||||
tag({ tags: ['named', 'static', 'tile'] }),
|
||||
metrics({
|
||||
enabled: this.config.pubSubMetrics.enabled,
|
||||
metricsBackend: this.metricsBackend,
|
||||
@ -346,11 +348,9 @@ function incrementMapViews ({ metadataBackend }) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const statTag = mapConfig.obj().stat_tag;
|
||||
res.locals.mapConfig = mapConfig;
|
||||
|
||||
if (statTag) {
|
||||
res.set('Carto-Stat-Tag', `${statTag}`);
|
||||
}
|
||||
const statTag = mapConfig.obj().stat_tag;
|
||||
|
||||
metadataBackend.incMapviewCount(user, statTag, (err) => {
|
||||
if (err) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const tag = require('../middlewares/tag');
|
||||
const layergroupToken = require('../middlewares/layergroup-token');
|
||||
const coordinates = require('../middlewares/coordinates');
|
||||
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
|
||||
@ -59,6 +60,7 @@ module.exports = class TileLayergroupController {
|
||||
|
||||
middlewares () {
|
||||
return [
|
||||
tag({ tags: ['tile'] }),
|
||||
layergroupToken(),
|
||||
coordinates(),
|
||||
credentials(),
|
||||
|
@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const setCommonHeaders = require('../../utils/common-headers');
|
||||
|
||||
module.exports = function errorMiddleware (/* options */) {
|
||||
return function error (err, req, res, next) {
|
||||
const { logger } = res.locals;
|
||||
@ -7,6 +9,7 @@ module.exports = function errorMiddleware (/* options */) {
|
||||
|
||||
logger.error({ error: errors });
|
||||
|
||||
setCommonHeaders(req, res, () => {
|
||||
const errorResponseBody = {
|
||||
errors: errors.map(errorMessage),
|
||||
errors_with_context: errors.map(errorMessageWithContext)
|
||||
@ -22,6 +25,7 @@ module.exports = function errorMiddleware (/* options */) {
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -5,11 +5,6 @@ module.exports = function incrementMapViewCount (metadataBackend) {
|
||||
const { mapConfig, user, logger } = res.locals;
|
||||
const statTag = mapConfig.obj().stat_tag;
|
||||
|
||||
if (statTag) {
|
||||
res.set('Carto-Stat-Tag', `${statTag}`);
|
||||
}
|
||||
|
||||
// Error won't blow up, just be logged.
|
||||
metadataBackend.incMapviewCount(user, statTag, (err) => {
|
||||
if (err) {
|
||||
err.message = `Failed to increment mapview count for user '${user}'. ${err.message}`;
|
||||
|
@ -9,6 +9,7 @@ module.exports = function initLogger ({ logger }) {
|
||||
|
||||
res.locals.logger.info({ request: req });
|
||||
res.on('finish', () => res.locals.logger.info({ response: res }));
|
||||
res.on('close', () => res.locals.logger.info({ end: true }));
|
||||
|
||||
next();
|
||||
};
|
||||
|
@ -2,13 +2,13 @@
|
||||
|
||||
const Profiler = require('../../stats/profiler-proxy');
|
||||
const debug = require('debug')('windshaft:cartodb:stats');
|
||||
const { name: prefix } = require('../../../package.json');
|
||||
|
||||
module.exports = function profiler (options) {
|
||||
const { enabled = true, statsClient } = options;
|
||||
|
||||
return function profilerMiddleware (req, res, next) {
|
||||
const { logger } = res.locals;
|
||||
const { id } = logger.bindings();
|
||||
|
||||
// TODO: stop using profiler and log stats instead of adding them to the profiler
|
||||
req.profiler = new Profiler({
|
||||
@ -16,7 +16,7 @@ module.exports = function profiler (options) {
|
||||
profile: enabled
|
||||
});
|
||||
|
||||
req.profiler.start(id);
|
||||
req.profiler.start(prefix);
|
||||
|
||||
res.on('finish', () => {
|
||||
req.profiler.done('response');
|
||||
|
@ -1,7 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const setCommonHeaders = require('../../utils/common-headers');
|
||||
|
||||
module.exports = function sendResponse () {
|
||||
return function sendResponseMiddleware (req, res, next) {
|
||||
setCommonHeaders(req, res, () => {
|
||||
res.status(res.statusCode);
|
||||
|
||||
if (Buffer.isBuffer(res.body)) {
|
||||
@ -16,5 +19,6 @@ module.exports = function sendResponse () {
|
||||
|
||||
res.json(res.body);
|
||||
return next();
|
||||
});
|
||||
};
|
||||
};
|
||||
|
15
lib/api/middlewares/tag.js
Normal file
15
lib/api/middlewares/tag.js
Normal file
@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function tag ({ tags }) {
|
||||
if (!Array.isArray(tags) || !tags.every((tag) => typeof tag === 'string')) {
|
||||
throw new Error('Required "tags" option must be a valid Array: [string, string, ...]');
|
||||
}
|
||||
|
||||
return function tagMiddleware (req, res, next) {
|
||||
const { logger } = res.locals;
|
||||
res.locals.tags = tags;
|
||||
res.on('finish', () => logger.info({ tags: res.locals.tags }));
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
@ -18,7 +18,7 @@ module.exports = function user (metadataBackend) {
|
||||
}
|
||||
|
||||
res.locals.userId = userId;
|
||||
res.set('Carto-User-Id', `${userId}`);
|
||||
|
||||
return next();
|
||||
});
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const { templateName } = require('../../backends/template-maps');
|
||||
const tag = require('../middlewares/tag');
|
||||
const credentials = require('../middlewares/credentials');
|
||||
const rateLimit = require('../middlewares/rate-limit');
|
||||
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
|
||||
@ -76,6 +77,7 @@ module.exports = class AdminTemplateController {
|
||||
}
|
||||
|
||||
return [
|
||||
tag({ tags: ['named', 'admin', action] }),
|
||||
credentials(),
|
||||
authorizedByAPIKey({ authBackend: this.authBackend, action, label }),
|
||||
rateLimit(this.userLimitsBackend, rateLimitGroup),
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const tag = require('../middlewares/tag');
|
||||
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
|
||||
const credentials = require('../middlewares/credentials');
|
||||
const dbConnSetup = require('../middlewares/db-conn-setup');
|
||||
@ -88,6 +89,7 @@ module.exports = class NamedMapController {
|
||||
};
|
||||
|
||||
return [
|
||||
tag({ tags: ['map', 'named'] }),
|
||||
metrics({
|
||||
enabled: this.config.pubSubMetrics.enabled,
|
||||
metricsBackend: this.metricsBackend,
|
||||
@ -181,6 +183,8 @@ function getTemplate (
|
||||
params
|
||||
);
|
||||
|
||||
mapConfigProvider.logger = res.locals.logger;
|
||||
|
||||
mapConfigProvider.getMapConfig((err, mapConfig, rendererParams, context, stats = {}) => {
|
||||
req.profiler.add(stats);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const tag = require('../middlewares/tag');
|
||||
const coordinates = require('../middlewares/coordinates');
|
||||
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
|
||||
const credentials = require('../middlewares/credentials');
|
||||
@ -37,6 +38,7 @@ module.exports = class TileTemplateController {
|
||||
|
||||
middlewares () {
|
||||
return [
|
||||
tag({ tags: ['tile', 'named'] }),
|
||||
coordinates(),
|
||||
credentials(),
|
||||
authorize(this.authBackend),
|
||||
|
@ -129,6 +129,7 @@ module.exports = class NamedMapMapConfigProvider extends BaseMapConfigProvider {
|
||||
|
||||
context.analysisConfiguration = {
|
||||
user: this.user,
|
||||
logger: this.logger,
|
||||
db: {
|
||||
host: rendererParams.dbhost,
|
||||
port: rendererParams.dbport,
|
||||
|
127
lib/utils/common-headers.js
Normal file
127
lib/utils/common-headers.js
Normal file
@ -0,0 +1,127 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function setCommonHeaders (req, res, callback) {
|
||||
const { logger } = res.locals;
|
||||
|
||||
res.set('X-Request-Id', logger.bindings().id);
|
||||
|
||||
// TODO: x-layergroupid header??
|
||||
|
||||
const user = getUser({ res });
|
||||
|
||||
if (user) {
|
||||
res.set('Carto-User', user);
|
||||
}
|
||||
|
||||
const userId = getUserId({ res });
|
||||
|
||||
if (userId) {
|
||||
res.set('Carto-User-Id', `${userId}`);
|
||||
}
|
||||
|
||||
const mapId = getMapId({ res });
|
||||
|
||||
if (mapId) {
|
||||
res.set('Carto-Map-Id', mapId);
|
||||
}
|
||||
|
||||
const cacheBuster = getCacheBuster({ res });
|
||||
|
||||
if (cacheBuster) {
|
||||
res.set('Carto-Cache-Buster', cacheBuster);
|
||||
}
|
||||
|
||||
const templateHash = getTemplateHash({ res });
|
||||
|
||||
if (templateHash) {
|
||||
res.set('Carto-Template-Hash', templateHash);
|
||||
}
|
||||
|
||||
getStatTag({ res }, (err, statTag) => {
|
||||
if (err) {
|
||||
err.message = `Error generating Stat Tag header: ${err.message}`;
|
||||
logger.warn({ error: err });
|
||||
}
|
||||
|
||||
if (statTag) {
|
||||
res.set('Carto-Stat-Tag', statTag);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
function getUser ({ res }) {
|
||||
if (res.locals.user) {
|
||||
return res.locals.user;
|
||||
}
|
||||
}
|
||||
|
||||
function getUserId ({ res }) {
|
||||
if (res.locals.userId) {
|
||||
return res.locals.userId;
|
||||
}
|
||||
}
|
||||
|
||||
function getMapId ({ res }) {
|
||||
if (res.locals.token) {
|
||||
return res.locals.token;
|
||||
}
|
||||
|
||||
if (res.locals.mapConfig) {
|
||||
return res.locals.mapConfig.id();
|
||||
}
|
||||
|
||||
if (res.locals.mapConfigProvider && res.locals.mapConfigProvider.mapConfig) {
|
||||
return res.locals.mapConfigProvider.mapConfig.id();
|
||||
}
|
||||
}
|
||||
|
||||
function getCacheBuster ({ res }) {
|
||||
if (res.locals.cache_buster !== undefined) {
|
||||
return `${res.locals.cache_buster}`;
|
||||
}
|
||||
|
||||
if (res.locals.mapConfigProvider) {
|
||||
return `${res.locals.mapConfigProvider.getCacheBuster()}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getTemplateHash ({ res }) {
|
||||
const { logger } = res.locals;
|
||||
|
||||
if (res.locals.templateHash) {
|
||||
return res.locals.templateHash;
|
||||
}
|
||||
|
||||
if (res.locals.mapConfigProvider && typeof res.locals.mapConfigProvider.getTemplateHash === 'function') {
|
||||
let templateHash;
|
||||
|
||||
try {
|
||||
templateHash = res.locals.mapConfigProvider.getTemplateHash().substring(0, 8);
|
||||
} catch (err) {
|
||||
err.message = `Error generating Stat Tag header: ${err.message}`;
|
||||
logger.warn({ error: err });
|
||||
}
|
||||
|
||||
return templateHash;
|
||||
}
|
||||
}
|
||||
|
||||
function getStatTag ({ res }, callback) {
|
||||
if (res.locals.mapConfig) {
|
||||
return callback(null, res.locals.mapConfig.obj().stat_tag);
|
||||
}
|
||||
|
||||
if (!res.locals.mapConfigProvider) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
res.locals.mapConfigProvider.getMapConfig((err, mapConfig) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
return callback(null, mapConfig.obj().stat_tag);
|
||||
});
|
||||
}
|
@ -1,12 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const pino = require('pino');
|
||||
const { req: requestSerializer, res: responseSerializer, err: errorSerializer } = pino.stdSerializers;
|
||||
const { req: requestSerializer, res: responseSerializer, err, wrapErrorSerializer } = pino.stdSerializers;
|
||||
const DEV_ENVS = ['test', 'development'];
|
||||
|
||||
module.exports = class Logger {
|
||||
constructor () {
|
||||
const { LOG_LEVEL, NODE_ENV } = process.env;
|
||||
const logLevelFromNodeEnv = NODE_ENV === 'test' ? 'fatal' : 'info';
|
||||
const errorSerializer = DEV_ENVS.includes(NODE_ENV) ? err : wrapErrorSerializer(err => {
|
||||
err.stack = err.stack.split('\n').slice(0, 3).join('\n');
|
||||
return err;
|
||||
});
|
||||
const options = {
|
||||
base: null, // Do not bind hostname, pid and friends by default
|
||||
level: LOG_LEVEL || logLevelFromNodeEnv,
|
||||
|
11
metro/index.js
Normal file
11
metro/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const split = require('split2');
|
||||
const logCollector = require('./log-collector');
|
||||
const metricsCollector = require('./metrics-collector');
|
||||
|
||||
process.stdin
|
||||
.pipe(split())
|
||||
.pipe(logCollector())
|
||||
.pipe(metricsCollector())
|
||||
.pipe(process.stdout);
|
78
metro/log-collector.js
Normal file
78
metro/log-collector.js
Normal file
@ -0,0 +1,78 @@
|
||||
'use strict'
|
||||
|
||||
const split = require('split2');
|
||||
const assingDeep = require('assign-deep');
|
||||
const { Transform } = require('stream');
|
||||
const DEV_ENVS = ['test', 'development'];
|
||||
const logs = new Map();
|
||||
|
||||
const LEVELS = {
|
||||
10: 'trace',
|
||||
20: 'debug',
|
||||
30: 'info',
|
||||
40: 'warn',
|
||||
50: 'error',
|
||||
60: 'fatal'
|
||||
}
|
||||
|
||||
module.exports = function logCollector () {
|
||||
return new Transform({
|
||||
transform (chunk, enc, callback) {
|
||||
let entry;
|
||||
|
||||
try {
|
||||
entry = JSON.parse(chunk);
|
||||
const { level, time } = entry;
|
||||
|
||||
if (level === undefined && time === undefined) {
|
||||
throw new Error('Entry log is not valid');
|
||||
}
|
||||
} catch (e) {
|
||||
if (DEV_ENVS.includes(process.env.NODE_ENV)) {
|
||||
this.push(chunk + '\n');
|
||||
}
|
||||
return callback();
|
||||
}
|
||||
|
||||
const { id, end } = entry;
|
||||
|
||||
if (id === undefined) {
|
||||
entry.level = LEVELS[entry.level];
|
||||
this.push(`${JSON.stringify(entry)}\n`);
|
||||
return callback();
|
||||
}
|
||||
|
||||
if (end === true) {
|
||||
const accEntry = logs.get(id);
|
||||
accEntry.level = LEVELS[accEntry.level];
|
||||
accEntry.time = entry.time;
|
||||
this.push(`${JSON.stringify(accEntry)}\n`);
|
||||
logs.delete(id);
|
||||
return callback();
|
||||
}
|
||||
|
||||
if (logs.has(id)) {
|
||||
const accEntry = logs.get(id);
|
||||
|
||||
if (accEntry.level > entry.level) {
|
||||
delete entry.level
|
||||
}
|
||||
|
||||
let error;
|
||||
if (hasProperty(accEntry, 'error') && hasProperty(entry, 'error')) {
|
||||
logs.set(id, assingDeep({}, accEntry, entry, { error: accEntry.error.concat(entry.error) }));
|
||||
} else {
|
||||
logs.set(id, assingDeep({}, accEntry, entry));
|
||||
}
|
||||
} else {
|
||||
logs.set(id, entry);
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function hasProperty(obj, prop) {
|
||||
return Object.prototype.hasOwnProperty.call(obj, prop)
|
||||
}
|
119
metro/metrics-collector.js
Normal file
119
metro/metrics-collector.js
Normal file
@ -0,0 +1,119 @@
|
||||
'use strict'
|
||||
|
||||
const http = require('http');
|
||||
const { Counter, Histogram, register } = require('prom-client');
|
||||
const split = require('split2');
|
||||
const { Transform } = require('stream');
|
||||
const DEV_ENVS = ['test', 'development'];
|
||||
|
||||
const requestCounter = new Counter({
|
||||
name: 'maps_api_requests_total',
|
||||
help: 'MAPS API requests total'
|
||||
});
|
||||
|
||||
const requestOkCounter = new Counter({
|
||||
name: 'maps_api_requests_ok_total',
|
||||
help: 'MAPS API requests ok total'
|
||||
});
|
||||
|
||||
const requestErrorCounter = new Counter({
|
||||
name: 'maps_api_requests_errors_total',
|
||||
help: 'MAPS API requests errors total'
|
||||
});
|
||||
|
||||
const responseTimeHistogram = new Histogram({
|
||||
name: 'maps_api_response_time_total',
|
||||
help: 'MAPS API response time total'
|
||||
});
|
||||
|
||||
const userRequestCounter = new Counter({
|
||||
name: 'maps_api_requests',
|
||||
help: 'MAPS API requests per user',
|
||||
labelNames: ['user', 'http_code']
|
||||
});
|
||||
|
||||
const userRequestOkCounter = new Counter({
|
||||
name: 'maps_api_requests_ok',
|
||||
help: 'MAPS API requests per user with success HTTP code',
|
||||
labelNames: ['user', 'http_code']
|
||||
});
|
||||
|
||||
const userRequestErrorCounter = new Counter({
|
||||
name: 'maps_api_requests_errors',
|
||||
help: 'MAPS API requests per user with error HTTP code',
|
||||
labelNames: ['user', 'http_code']
|
||||
});
|
||||
|
||||
const userResponseTimeHistogram = new Histogram({
|
||||
name: 'maps_api_response_time',
|
||||
help: 'MAPS API response time total',
|
||||
labelNames: ['user']
|
||||
});
|
||||
|
||||
module.exports = function metricsCollector () {
|
||||
return new Transform({
|
||||
transform (chunk, enc, callback) {
|
||||
let entry;
|
||||
|
||||
try {
|
||||
entry = JSON.parse(chunk);
|
||||
const { level, time } = entry;
|
||||
|
||||
if (level === undefined && time === undefined) {
|
||||
throw new Error('Entry log is not valid');
|
||||
}
|
||||
} catch (e) {
|
||||
if (DEV_ENVS.includes(process.env.NODE_ENV)) {
|
||||
this.push(chunk);
|
||||
}
|
||||
return callback();
|
||||
}
|
||||
|
||||
const { request, response, stats } = entry;
|
||||
|
||||
if (request === undefined || response === undefined || stats === undefined) {
|
||||
this.push(chunk);
|
||||
return callback();
|
||||
}
|
||||
|
||||
const { statusCode, headers } = response;
|
||||
const { 'carto-user': user } = headers;
|
||||
|
||||
if (statusCode === undefined || headers === undefined || user === undefined) {
|
||||
this.push(chunk);
|
||||
return callback();
|
||||
}
|
||||
|
||||
requestCounter.inc();
|
||||
userRequestCounter.labels(user, `${statusCode}`).inc();
|
||||
|
||||
if (statusCode >= 200 && statusCode < 400) {
|
||||
requestOkCounter.inc();
|
||||
userRequestOkCounter.labels(user, `${statusCode}`).inc();
|
||||
} else {
|
||||
requestErrorCounter.inc();
|
||||
userRequestErrorCounter.labels(user, `${statusCode}`).inc();
|
||||
}
|
||||
|
||||
const { response: responseTime } = stats;
|
||||
|
||||
if (Number.isFinite(responseTime)) {
|
||||
responseTimeHistogram.observe(responseTime);
|
||||
userResponseTimeHistogram.labels(user).observe(responseTime);
|
||||
}
|
||||
|
||||
this.push(chunk);
|
||||
callback();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const port = process.env.PORT || 9145;
|
||||
|
||||
http
|
||||
.createServer((req, res) => {
|
||||
res.writeHead(200, { 'Content-Type': register.contentType });
|
||||
res.end(register.metrics());
|
||||
})
|
||||
.listen(port)
|
||||
.unref();
|
42
package-lock.json
generated
42
package-lock.json
generated
@ -633,6 +633,19 @@
|
||||
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
|
||||
"dev": true
|
||||
},
|
||||
"assign-deep": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz",
|
||||
"integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==",
|
||||
"requires": {
|
||||
"assign-symbols": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"assign-symbols": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz",
|
||||
"integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA=="
|
||||
},
|
||||
"astral-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
|
||||
@ -706,6 +719,11 @@
|
||||
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
|
||||
"dev": true
|
||||
},
|
||||
"bintrees": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz",
|
||||
"integrity": "sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.18.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
|
||||
@ -796,7 +814,7 @@
|
||||
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
|
||||
},
|
||||
"camshaft": {
|
||||
"version": "github:cartodb/camshaft#8d8f62d3d98b4ac316d8444ac3f3f321c28040dc",
|
||||
"version": "github:cartodb/camshaft#633d3b85869d894d52ac5c9970462f326d2f42cc",
|
||||
"from": "github:cartodb/camshaft#dgaubert/ch78389/camshaft-replace-logger-from-bunyan-to-pino",
|
||||
"requires": {
|
||||
"async": "^1.5.2",
|
||||
@ -5396,6 +5414,14 @@
|
||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
||||
"dev": true
|
||||
},
|
||||
"prom-client": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-12.0.0.tgz",
|
||||
"integrity": "sha512-JbzzHnw0VDwCvoqf8y1WDtq4wSBAbthMB1pcVI/0lzdqHGJI3KBJDXle70XK+c7Iv93Gihqo0a5LlOn+g8+DrQ==",
|
||||
"requires": {
|
||||
"tdigest": "^0.1.1"
|
||||
}
|
||||
},
|
||||
"propagate": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz",
|
||||
@ -5530,7 +5556,6 @@
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
@ -5970,7 +5995,6 @@
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-3.1.1.tgz",
|
||||
"integrity": "sha512-emNzr1s7ruq4N+1993yht631/JH+jaj0NYBosuKmLcq+JkGQ9MmTw1RB1fGaTCzUuseRIClrlSLHRNYGwWQ58Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"readable-stream": "^3.0.0"
|
||||
}
|
||||
@ -6251,7 +6275,6 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
},
|
||||
@ -6259,8 +6282,7 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -6389,6 +6411,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tdigest": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.1.tgz",
|
||||
"integrity": "sha1-Ljyyw56kSeVdHmzZEReszKRYgCE=",
|
||||
"requires": {
|
||||
"bintrees": "1.0.1"
|
||||
}
|
||||
},
|
||||
"test-exclude": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz",
|
||||
|
@ -36,6 +36,7 @@
|
||||
"dependencies": {
|
||||
"@carto/fqdn-sync": "0.2.2",
|
||||
"@google-cloud/pubsub": "1.5.0",
|
||||
"assign-deep": "^1.0.1",
|
||||
"basic-auth": "2.0.0",
|
||||
"body-parser": "1.18.3",
|
||||
"camshaft": "github:cartodb/camshaft#dgaubert/ch78389/camshaft-replace-logger-from-bunyan-to-pino",
|
||||
@ -52,10 +53,12 @@
|
||||
"lzma": "2.3.2",
|
||||
"node-statsd": "0.1.1",
|
||||
"pino": "^6.3.1",
|
||||
"prom-client": "^12.0.0",
|
||||
"queue-async": "1.1.0",
|
||||
"redis-mpool": "^0.8.0",
|
||||
"request": "2.87.0",
|
||||
"semver": "5.5.0",
|
||||
"split2": "^3.1.1",
|
||||
"step-profiler": "0.3.0",
|
||||
"turbo-carto": "0.21.2",
|
||||
"underscore": "1.6.0",
|
||||
|
Loading…
Reference in New Issue
Block a user