Windshaft-cartodb/lib/api/middlewares/pubsub-metrics.js

125 lines
3.5 KiB
JavaScript
Raw Normal View History

'use strict';
const EVENT_VERSION = '1';
2020-02-26 20:24:46 +08:00
const MAX_LENGTH = 100;
module.exports = function pubSubMetrics ({ enabled, metricsBackend, logger, tags }) {
if (!enabled) {
return function pubSubMetricsDisabledMiddleware (req, res, next) {
next();
};
}
if (!tags || !tags.event) {
throw new Error('Missing required "event" parameter to report metrics');
}
return function pubSubMetricsMiddleware (req, res, next) {
res.on('finish', () => {
const { event, attributes } = getEventData(req, res, tags);
metricsBackend.send(event, attributes)
.then(() => logger.debug(`PubSubTracker: event '${event}' published succesfully`))
.catch((error) => logger.error(`ERROR: pubsub middleware failed to publish event '${event}': ${error.message}`));
});
return next();
};
};
function getEventData (req, res, tags) {
const event = tags.event;
const extra = {};
if (tags.from) {
if (tags.from.req) {
Object.assign(extra, getFromReq(req, tags.from.req));
}
if (tags.from.res) {
Object.assign(extra, getFromRes(res, tags.from.res));
}
}
const attributes = Object.assign({}, {
metrics_event: normalizedField(req.get('Carto-Event')),
event_source: normalizedField(req.get('Carto-Event-Source')),
event_group_id: normalizedField(req.get('Carto-Event-Group-Id')),
event_time: new Date().toISOString(),
user_id: res.locals.userId,
user_agent: req.get('User-Agent'),
response_code: res.statusCode.toString(),
response_time: getResponseTime(res),
source_domain: req.hostname,
event_version: EVENT_VERSION
}, tags.attributes, extra);
// remove undefined properties
Object.keys(attributes).forEach(key => attributes[key] === undefined && delete attributes[key]);
return { event, attributes };
}
2020-02-26 20:24:46 +08:00
function normalizedField (field) {
2020-02-27 00:41:41 +08:00
if (!field) {
return undefined;
}
2020-02-27 00:44:53 +08:00
2020-02-26 21:50:41 +08:00
return field.toString().trim().substr(0, MAX_LENGTH);
2020-02-26 20:24:46 +08:00
}
2020-04-27 19:35:19 +08:00
// FIXME: 'X-Tiler-Profiler' might not be accurate enough
function getResponseTime (res) {
const profiler = res.get('X-Tiler-Profiler');
let stats;
try {
stats = JSON.parse(profiler);
} catch (e) {
return undefined;
}
return stats && stats.total ? stats.total.toString() : undefined;
}
function getFromReq (req, { query = {}, body = {}, params = {}, headers = {} } = {}) {
const extra = {};
for (const [queryParam, eventName] of Object.entries(query)) {
extra[eventName] = req.query[queryParam];
}
for (const [bodyParam, eventName] of Object.entries(body)) {
extra[eventName] = req.body[bodyParam];
}
for (const [pathParam, eventName] of Object.entries(params)) {
extra[eventName] = req.params[pathParam];
}
for (const [header, eventName] of Object.entries(headers)) {
extra[eventName] = req.get(header);
}
return extra;
}
function getFromRes (res, { body = {}, headers = {}, locals = {} } = {}) {
const extra = {};
if (res.body) {
for (const [bodyParam, eventName] of Object.entries(body)) {
extra[eventName] = res.body[bodyParam];
}
}
for (const [header, eventName] of Object.entries(headers)) {
extra[eventName] = res.get(header);
}
for (const [localParam, eventName] of Object.entries(locals)) {
extra[eventName] = res.locals[localParam];
}
return extra;
}