238 lines
13 KiB
JavaScript
238 lines
13 KiB
JavaScript
|
"use strict";
|
||
|
var http = require("http");
|
||
|
var https = require("https");
|
||
|
var Logging = require("../Library/Logging");
|
||
|
var Util = require("../Library/Util");
|
||
|
var RequestResponseHeaders = require("../Library/RequestResponseHeaders");
|
||
|
var HttpRequestParser = require("./HttpRequestParser");
|
||
|
var CorrelationContextManager_1 = require("./CorrelationContextManager");
|
||
|
var AutoCollectPerformance = require("./Performance");
|
||
|
var AutoCollectHttpRequests = (function () {
|
||
|
function AutoCollectHttpRequests(client) {
|
||
|
if (!!AutoCollectHttpRequests.INSTANCE) {
|
||
|
throw new Error("Server request tracking should be configured from the applicationInsights object");
|
||
|
}
|
||
|
AutoCollectHttpRequests.INSTANCE = this;
|
||
|
this._client = client;
|
||
|
}
|
||
|
AutoCollectHttpRequests.prototype.enable = function (isEnabled) {
|
||
|
this._isEnabled = isEnabled;
|
||
|
// Autocorrelation requires automatic monitoring of incoming server requests
|
||
|
// Disabling autocollection but enabling autocorrelation will still enable
|
||
|
// request monitoring but will not produce request events
|
||
|
if ((this._isAutoCorrelating || this._isEnabled || AutoCollectPerformance.isEnabled()) && !this._isInitialized) {
|
||
|
this.useAutoCorrelation(this._isAutoCorrelating);
|
||
|
this._initialize();
|
||
|
}
|
||
|
};
|
||
|
AutoCollectHttpRequests.prototype.useAutoCorrelation = function (isEnabled, forceClsHooked) {
|
||
|
if (isEnabled && !this._isAutoCorrelating) {
|
||
|
CorrelationContextManager_1.CorrelationContextManager.enable(forceClsHooked);
|
||
|
}
|
||
|
else if (!isEnabled && this._isAutoCorrelating) {
|
||
|
CorrelationContextManager_1.CorrelationContextManager.disable();
|
||
|
}
|
||
|
this._isAutoCorrelating = isEnabled;
|
||
|
};
|
||
|
AutoCollectHttpRequests.prototype.isInitialized = function () {
|
||
|
return this._isInitialized;
|
||
|
};
|
||
|
AutoCollectHttpRequests.prototype.isAutoCorrelating = function () {
|
||
|
return this._isAutoCorrelating;
|
||
|
};
|
||
|
AutoCollectHttpRequests.prototype._generateCorrelationContext = function (requestParser) {
|
||
|
if (!this._isAutoCorrelating) {
|
||
|
return;
|
||
|
}
|
||
|
return CorrelationContextManager_1.CorrelationContextManager.generateContextObject(requestParser.getOperationId(this._client.context.tags), requestParser.getRequestId(), requestParser.getOperationName(this._client.context.tags), requestParser.getCorrelationContextHeader(), requestParser.getTraceparent(), requestParser.getTracestate());
|
||
|
};
|
||
|
AutoCollectHttpRequests.prototype._initialize = function () {
|
||
|
var _this = this;
|
||
|
this._isInitialized = true;
|
||
|
var wrapOnRequestHandler = function (onRequest) {
|
||
|
if (!onRequest) {
|
||
|
return undefined;
|
||
|
}
|
||
|
if (typeof onRequest !== 'function') {
|
||
|
throw new Error('onRequest handler must be a function');
|
||
|
}
|
||
|
return function (request, response) {
|
||
|
CorrelationContextManager_1.CorrelationContextManager.wrapEmitter(request);
|
||
|
CorrelationContextManager_1.CorrelationContextManager.wrapEmitter(response);
|
||
|
var shouldCollect = request && !request[AutoCollectHttpRequests.alreadyAutoCollectedFlag];
|
||
|
if (request && shouldCollect) {
|
||
|
// Set up correlation context
|
||
|
var requestParser_1 = new HttpRequestParser(request);
|
||
|
var correlationContext = _this._generateCorrelationContext(requestParser_1);
|
||
|
// Note: Check for if correlation is enabled happens within this method.
|
||
|
// If not enabled, function will directly call the callback.
|
||
|
CorrelationContextManager_1.CorrelationContextManager.runWithContext(correlationContext, function () {
|
||
|
if (_this._isEnabled) {
|
||
|
// Mark as auto collected
|
||
|
request[AutoCollectHttpRequests.alreadyAutoCollectedFlag] = true;
|
||
|
// Auto collect request
|
||
|
AutoCollectHttpRequests.trackRequest(_this._client, { request: request, response: response }, requestParser_1);
|
||
|
}
|
||
|
if (typeof onRequest === "function") {
|
||
|
onRequest(request, response);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
if (typeof onRequest === "function") {
|
||
|
onRequest(request, response);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
// The `http.createServer` function will instantiate a new http.Server object.
|
||
|
// Inside the Server's constructor, it is using addListener to register the
|
||
|
// onRequest handler. So there are two ways to inject the wrapped onRequest handler:
|
||
|
// 1) Overwrite Server.prototype.addListener (and .on()) globally and not patching
|
||
|
// the http.createServer call. Or
|
||
|
// 2) Overwrite the http.createServer method and add a patched addListener to the
|
||
|
// fresh server instance. This seems more stable for possible future changes as
|
||
|
// it also covers the case where the Server might not use addListener to manage
|
||
|
// the callback internally.
|
||
|
// And also as long as the constructor uses addListener to add the handle, it is
|
||
|
// ok to patch the addListener after construction only. Because if we would patch
|
||
|
// the prototype one and the createServer method, we would wrap the handler twice
|
||
|
// in case of the constructor call.
|
||
|
var wrapServerEventHandler = function (server) {
|
||
|
var originalAddListener = server.addListener.bind(server);
|
||
|
server.addListener = function (eventType, eventHandler) {
|
||
|
switch (eventType) {
|
||
|
case 'request':
|
||
|
case 'checkContinue':
|
||
|
return originalAddListener(eventType, wrapOnRequestHandler(eventHandler));
|
||
|
default:
|
||
|
return originalAddListener(eventType, eventHandler);
|
||
|
}
|
||
|
};
|
||
|
// on is an alias to addListener only
|
||
|
server.on = server.addListener;
|
||
|
};
|
||
|
var originalHttpServer = http.createServer;
|
||
|
// options parameter was added in Node.js v9.6.0, v8.12.0
|
||
|
// function createServer(requestListener?: RequestListener): Server;
|
||
|
// function createServer(options: ServerOptions, requestListener?: RequestListener): Server;
|
||
|
http.createServer = function (param1, param2) {
|
||
|
// todo: get a pointer to the server so the IP address can be read from server.address
|
||
|
if (param2 && typeof param2 === 'function') {
|
||
|
var server = originalHttpServer(param1, wrapOnRequestHandler(param2));
|
||
|
wrapServerEventHandler(server);
|
||
|
return server;
|
||
|
}
|
||
|
else {
|
||
|
var server = originalHttpServer(wrapOnRequestHandler(param1));
|
||
|
wrapServerEventHandler(server);
|
||
|
return server;
|
||
|
}
|
||
|
};
|
||
|
var originalHttpsServer = https.createServer;
|
||
|
https.createServer = function (options, onRequest) {
|
||
|
var server = originalHttpsServer(options, wrapOnRequestHandler(onRequest));
|
||
|
wrapServerEventHandler(server);
|
||
|
return server;
|
||
|
};
|
||
|
};
|
||
|
/**
|
||
|
* Tracks a request synchronously (doesn't wait for response 'finish' event)
|
||
|
*/
|
||
|
AutoCollectHttpRequests.trackRequestSync = function (client, telemetry) {
|
||
|
if (!telemetry.request || !telemetry.response || !client) {
|
||
|
Logging.info("AutoCollectHttpRequests.trackRequestSync was called with invalid parameters: ", !telemetry.request, !telemetry.response, !client);
|
||
|
return;
|
||
|
}
|
||
|
AutoCollectHttpRequests.addResponseCorrelationIdHeader(client, telemetry.response);
|
||
|
// store data about the request
|
||
|
var correlationContext = CorrelationContextManager_1.CorrelationContextManager.getCurrentContext();
|
||
|
var requestParser = new HttpRequestParser(telemetry.request, (correlationContext && correlationContext.operation.parentId));
|
||
|
// Overwrite correlation context with request parser results
|
||
|
if (correlationContext) {
|
||
|
correlationContext.operation.id = requestParser.getOperationId(client.context.tags) || correlationContext.operation.id;
|
||
|
correlationContext.operation.name = requestParser.getOperationName(client.context.tags) || correlationContext.operation.name;
|
||
|
correlationContext.operation.parentId = requestParser.getRequestId() || correlationContext.operation.parentId;
|
||
|
correlationContext.customProperties.addHeaderData(requestParser.getCorrelationContextHeader());
|
||
|
}
|
||
|
AutoCollectHttpRequests.endRequest(client, requestParser, telemetry, telemetry.duration, telemetry.error);
|
||
|
};
|
||
|
/**
|
||
|
* Tracks a request by listening to the response 'finish' event
|
||
|
*/
|
||
|
AutoCollectHttpRequests.trackRequest = function (client, telemetry, _requestParser) {
|
||
|
if (!telemetry.request || !telemetry.response || !client) {
|
||
|
Logging.info("AutoCollectHttpRequests.trackRequest was called with invalid parameters: ", !telemetry.request, !telemetry.response, !client);
|
||
|
return;
|
||
|
}
|
||
|
// store data about the request
|
||
|
var correlationContext = CorrelationContextManager_1.CorrelationContextManager.getCurrentContext();
|
||
|
var requestParser = _requestParser || new HttpRequestParser(telemetry.request, correlationContext && correlationContext.operation.parentId);
|
||
|
if (Util.canIncludeCorrelationHeader(client, requestParser.getUrl())) {
|
||
|
AutoCollectHttpRequests.addResponseCorrelationIdHeader(client, telemetry.response);
|
||
|
}
|
||
|
// Overwrite correlation context with request parser results (if not an automatic track. we've already precalculated the correlation context in that case)
|
||
|
if (correlationContext && !_requestParser) {
|
||
|
correlationContext.operation.id = requestParser.getOperationId(client.context.tags) || correlationContext.operation.id;
|
||
|
correlationContext.operation.name = requestParser.getOperationName(client.context.tags) || correlationContext.operation.name;
|
||
|
correlationContext.operation.parentId = requestParser.getOperationParentId(client.context.tags) || correlationContext.operation.parentId;
|
||
|
correlationContext.customProperties.addHeaderData(requestParser.getCorrelationContextHeader());
|
||
|
}
|
||
|
// response listeners
|
||
|
if (telemetry.response.once) {
|
||
|
telemetry.response.once("finish", function () {
|
||
|
AutoCollectHttpRequests.endRequest(client, requestParser, telemetry, null, null);
|
||
|
});
|
||
|
}
|
||
|
// track a failed request if an error is emitted
|
||
|
if (telemetry.request.on) {
|
||
|
telemetry.request.on("error", function (error) {
|
||
|
AutoCollectHttpRequests.endRequest(client, requestParser, telemetry, null, error);
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
/**
|
||
|
* Add the target correlationId to the response headers, if not already provided.
|
||
|
*/
|
||
|
AutoCollectHttpRequests.addResponseCorrelationIdHeader = function (client, response) {
|
||
|
if (client.config && client.config.correlationId &&
|
||
|
response.getHeader && response.setHeader && !response.headersSent) {
|
||
|
var correlationHeader = response.getHeader(RequestResponseHeaders.requestContextHeader);
|
||
|
Util.safeIncludeCorrelationHeader(client, response, correlationHeader);
|
||
|
}
|
||
|
};
|
||
|
AutoCollectHttpRequests.endRequest = function (client, requestParser, telemetry, ellapsedMilliseconds, error) {
|
||
|
if (error) {
|
||
|
requestParser.onError(error, ellapsedMilliseconds);
|
||
|
}
|
||
|
else {
|
||
|
requestParser.onResponse(telemetry.response, ellapsedMilliseconds);
|
||
|
}
|
||
|
var requestTelemetry = requestParser.getRequestTelemetry(telemetry);
|
||
|
requestTelemetry.tagOverrides = requestParser.getRequestTags(client.context.tags);
|
||
|
if (telemetry.tagOverrides) {
|
||
|
for (var key in telemetry.tagOverrides) {
|
||
|
requestTelemetry.tagOverrides[key] = telemetry.tagOverrides[key];
|
||
|
}
|
||
|
}
|
||
|
var legacyRootId = requestParser.getLegacyRootId();
|
||
|
if (legacyRootId) {
|
||
|
requestTelemetry.properties["ai_legacyRootId"] = legacyRootId;
|
||
|
}
|
||
|
requestTelemetry.contextObjects = requestTelemetry.contextObjects || {};
|
||
|
requestTelemetry.contextObjects["http.ServerRequest"] = telemetry.request;
|
||
|
requestTelemetry.contextObjects["http.ServerResponse"] = telemetry.response;
|
||
|
client.trackRequest(requestTelemetry);
|
||
|
};
|
||
|
AutoCollectHttpRequests.prototype.dispose = function () {
|
||
|
AutoCollectHttpRequests.INSTANCE = null;
|
||
|
this.enable(false);
|
||
|
this._isInitialized = false;
|
||
|
CorrelationContextManager_1.CorrelationContextManager.disable();
|
||
|
this._isAutoCorrelating = false;
|
||
|
};
|
||
|
AutoCollectHttpRequests.alreadyAutoCollectedFlag = '_appInsightsAutoCollected';
|
||
|
return AutoCollectHttpRequests;
|
||
|
}());
|
||
|
module.exports = AutoCollectHttpRequests;
|
||
|
//# sourceMappingURL=HttpRequests.js.map
|