2021-12-03 17:58:48 +01:00

128 lines
5.2 KiB
JavaScript

'use strict';
var assert = require('assert-plus');
var hrTimeDurationInMs = require('./utils/hrTimeDurationInMs');
/**
* Timing internals
*
* Timings are also saved when there is no handler in the given category.
* Some handler categories are optional, for example there is no
* `use` and `route` for 404.
*
* @private
*
* req._timeStart - request lifecycle started in restify
* req._timePreStart - pre handlers started
* req._timePreEnd - all pre handlers finished
* req._timeUseStart - use handlers started
* req._timeUseEnd - all use handlers finished
* req._timeRouteStart - route handlers started
* req._timeRouteEnd - all route handlers finished
* req._timeFlushed - request flushed, may happens before handlers finished
* req._timeFinished - both all handlers finished and request flushed
*/
///--- API
/**
* The module includes the following plugins to be used with restify's `after`
* event, e.g., `server.on('after', restify.plugins.metrics());`:
*
* A plugin that listens to the server's after event and emits information
* about that request.
*
* @public
* @function metrics
* @param {Object} opts - an options obj
* @param {Server} opts.server - restify server
* @param {createMetrics~callback} callback - a callback fn
* @returns {Function} returns a function suitable to be used
* with restify server's `after` event
* @example
* server.on('after', restify.plugins.metrics({ server: server },
* function (err, metrics, req, res, route) {
* // metrics is an object containing information about the request
* }));
*/
function createMetrics(opts, callback) {
assert.object(opts, 'opts');
assert.object(opts.server, 'opts.server');
assert.func(callback, 'callback');
return function metrics(req, res, route, err) {
var data = {
// response status code. in most cases this should be a proper
// http status code, but in the case of an uncaughtException it can
// be undefined. otherwise, in most normal scenarios, even calling
// res.send() or res.end() should result in a 200 by default.
statusCode: res.statusCode,
// REST verb
method: req.method,
// overall request latency
totalLatency: hrTimeDurationInMs(req._timeStart, req._timeFinished),
latency: hrTimeDurationInMs(req._timeStart, req._timeFlushed),
preLatency: hrTimeDurationInMs(req._timePreStart, req._timePreEnd),
useLatency: hrTimeDurationInMs(req._timeUseStart, req._timeUseEnd),
routeLatency: hrTimeDurationInMs(
req._timeRouteStart,
req._timeRouteEnd
),
// the cleaned up url path
// e.g., /foo?a=1 => /foo
path: req.path(),
// connection state can currently only have the following values:
// 'close' | undefined.
//
// if the connection state is 'close'
// the status code will be set to 444
// it is possible to get a 200 statusCode with a connectionState
// value of 'close'. i.e., the client timed out,
// but restify thinks it "sent" a response. connectionState should
// always be the primary source of truth here, and check it first
// before consuming statusCode. otherwise, it may result in skewed
// metrics.
connectionState: req.connectionState && req.connectionState(),
unfinishedRequests:
opts.server.inflightRequests && opts.server.inflightRequests(),
inflightRequests:
opts.server.inflightRequests && opts.server.inflightRequests()
};
return callback(err, data, req, res, route);
};
}
/**
* Callback used by metrics plugin
* @callback metrics~callback
* @param {Error} err
* @param {Object} metrics - metrics about the request
* @param {Number} metrics.statusCode status code of the response. can be
* undefined in the case of an uncaughtException
* @param {String} metrics.method http request verb
* @param {Number} metrics.totalLatency latency includes both request is flushed
* and all handlers finished
* @param {Number} metrics.latency latency when request is flushed
* @param {Number|null} metrics.preLatency pre handlers latency
* @param {Number|null} metrics.useLatency use handlers latency
* @param {Number|null} metrics.routeLatency route handlers latency
* @param {String} metrics.path `req.path()` value
* @param {Number} metrics.inflightRequests Number of inflight requests pending
* in restify.
* @param {Number} metrics.unifinishedRequests Same as `inflightRequests`
* @param {String} metrics.connectionState can be either `'close'` or
* `undefined`. If this value is set, err will be a
* corresponding `RequestCloseError`.
* If connectionState is either
* `'close'`, then the `statusCode` is not applicable since the
* connection was severed before a response was written.
* @param {Request} req the request obj
* @param {Response} res the response obj
* @param {Route} route the route obj that serviced the request
*/
///-- Exports
module.exports = createMetrics;