@talks.converse/js-monitoring
Version:
Express monitoring middleware with Datadog and Prometheus backends, plus conditional logging.
126 lines (107 loc) • 4.84 kB
JavaScript
const { loggingRequest } = require('./lib/logging');
/**
* Setup monitoring (Datadog + Prometheus) and request logging.
*
* Usage:
* const setupMonitoring = require('js-monitoring');
* setupMonitoring(app, { isEnableDatadog: true, isEnablePrometheus: true, appName: 'myapp' });
*
* If options are omitted, values fall back to environment variables:
* - ENABLE_DATADOG, ENABLE_PROMETHEUS, PROMETHEUS_METRICS_PATH, APP_NAME
*
* @param {import('express').Application} app
* @param {Object} [opts]
* @param {boolean} [opts.isEnableDatadog]
* @param {boolean} [opts.isEnablePrometheus]
* @param {string} [opts.prometheusPath]
* @param {string} [opts.appName]
*/
/**
* Main entry point to set up monitoring and logging on the Express app.
*/
module.exports = (app, opts = {}) => {
const datadog = require('./lib/datadog');
const prometheus = require('./lib/prometheus');
const helper = require('./lib/helper');
const appName = helper.normalizeName(process.env.APP_NAME);
const isEnableDatadog = opts.isEnableDatadog ?? (process.env.ENABLE_DATADOG === 'true');
const isEnablePrometheus = opts.isEnablePrometheus ?? (process.env.ENABLE_PROMETHEUS === 'true');
const prometheusPath = opts.prometheusPath ?? (process.env.PROMETHEUS_METRICS_PATH || '/metrics');
const isGroupingStatusCode = opts.isGroupingStatusCode ?? (process.env.GROUPING_STATUS_CODE === 'true');
/**
* Register the /metrics endpoint for Prometheus scraping if enabled.
*/
if (isEnablePrometheus && typeof prometheus.registerMetricsRoute === 'function') {
prometheus.registerMetricsRoute(app, prometheusPath);
}
/**
* Middleware that measures request latency and handles tagging for monitoring and logging.
*/
app.use((req, res, next) => {
const start = Date.now();
/**
* Runs when a request is completed to record metrics and log data.
*/
res.on('finish', () => {
const latency = Date.now() - start;
const method = req.method;
const statusCode = res.statusCode;
const statusCodeGroup = helper.getStatusGroup(statusCode);
const endpointPath = helper.getEndpointPath(req);
const body = req.body;
const query = req.query;
const responseMessage = res.locals.responseMessage || '';
const monitoring_tags = {}
/**
* Checks if the endpoint is in the default exclude list.
*/
const isDefaultExcluded = (endpoint) => helper.DEFAULT_EXCLUDES.some((re) => re.test(endpoint || ''));
if (isGroupingStatusCode){
monitoring_tags.status_code_group = statusCodeGroup
} else {
monitoring_tags.status_code = statusCode
}
/**
* Handles sending metrics to Datadog.
*/
if (isEnableDatadog && typeof datadog.monitor === 'function') {
const datadogEndpointRules = datadog.buildDatadogMonitoringEndpointsConfig
? datadog.buildDatadogMonitoringEndpointsConfig()
: { include: [], exclude: [] };
const endpointTagValue =
helper.isEndpointProcessed(endpointPath, datadogEndpointRules) && !isDefaultExcluded(endpointPath)
? endpointPath
: '_others';
monitoring_tags.endpoint = endpointTagValue
datadog.monitor(appName, latency, monitoring_tags);
}
/**
* Handles sending metrics to Prometheus.
*/
if (isEnablePrometheus && typeof prometheus.monitor === 'function') {
const prometheusEndpointRules = prometheus.buildPrometheusMonitoringEndpointsConfig
? prometheus.buildPrometheusMonitoringEndpointsConfig()
: { include: [], exclude: [] };
const endpointTagValue =
helper.isEndpointProcessed(endpointPath, prometheusEndpointRules) && !isDefaultExcluded(endpointPath)
? endpointPath
: '_others';
monitoring_tags.endpoint = endpointTagValue
prometheus.monitor(appName, latency, monitoring_tags);
}
/**
* Handles logging with its own include/exclude rules.
*/
loggingRequest({
endpoint: endpointPath,
method: method,
status_code: statusCode,
req_query: query,
req_body: body,
response: responseMessage,
latencyMs: latency,
});
});
next();
});
};