259 lines
13 KiB
JavaScript
259 lines
13 KiB
JavaScript
"use strict";
|
|
var __extends = (this && this.__extends) || (function () {
|
|
var extendStatics = Object.setPrototypeOf ||
|
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
|
return function (d, b) {
|
|
extendStatics(d, b);
|
|
function __() { this.constructor = d; }
|
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
};
|
|
})();
|
|
var url = require("url");
|
|
var Contracts = require("../Declarations/Contracts");
|
|
var Util = require("../Library/Util");
|
|
var RequestResponseHeaders = require("../Library/RequestResponseHeaders");
|
|
var RequestParser = require("./RequestParser");
|
|
var CorrelationIdManager = require("../Library/CorrelationIdManager");
|
|
var Tracestate = require("../Library/Tracestate");
|
|
var Traceparent = require("../Library/Traceparent");
|
|
/**
|
|
* Helper class to read data from the request/response objects and convert them into the telemetry contract
|
|
*/
|
|
var HttpRequestParser = (function (_super) {
|
|
__extends(HttpRequestParser, _super);
|
|
function HttpRequestParser(request, requestId) {
|
|
var _this = _super.call(this) || this;
|
|
if (request) {
|
|
_this.method = request.method;
|
|
_this.url = _this._getAbsoluteUrl(request);
|
|
_this.startTime = +new Date();
|
|
_this.socketRemoteAddress = request.socket && request.socket.remoteAddress;
|
|
_this.parseHeaders(request, requestId);
|
|
if (request.connection) {
|
|
_this.connectionRemoteAddress = request.connection.remoteAddress;
|
|
_this.legacySocketRemoteAddress = request.connection["socket"] && request.connection["socket"].remoteAddress;
|
|
}
|
|
}
|
|
return _this;
|
|
}
|
|
HttpRequestParser.prototype.onError = function (error, ellapsedMilliseconds) {
|
|
this._setStatus(undefined, error);
|
|
// This parameter is only for overrides. setStatus handles this internally for the autocollected case
|
|
if (ellapsedMilliseconds) {
|
|
this.duration = ellapsedMilliseconds;
|
|
}
|
|
};
|
|
HttpRequestParser.prototype.onResponse = function (response, ellapsedMilliseconds) {
|
|
this._setStatus(response.statusCode, undefined);
|
|
// This parameter is only for overrides. setStatus handles this internally for the autocollected case
|
|
if (ellapsedMilliseconds) {
|
|
this.duration = ellapsedMilliseconds;
|
|
}
|
|
};
|
|
HttpRequestParser.prototype.getRequestTelemetry = function (baseTelemetry) {
|
|
var requestTelemetry = {
|
|
id: this.requestId,
|
|
name: this.method + " " + url.parse(this.url).pathname,
|
|
url: this.url,
|
|
/*
|
|
See https://github.com/microsoft/ApplicationInsights-dotnet-server/blob/25d695e6a906fbe977f67be3966d25dbf1c50a79/Src/Web/Web.Shared.Net/RequestTrackingTelemetryModule.cs#L250
|
|
for reference
|
|
*/
|
|
source: this.sourceCorrelationId,
|
|
duration: this.duration,
|
|
resultCode: this.statusCode ? this.statusCode.toString() : null,
|
|
success: this._isSuccess(),
|
|
properties: this.properties
|
|
};
|
|
if (baseTelemetry && baseTelemetry.time) {
|
|
requestTelemetry.time = baseTelemetry.time;
|
|
}
|
|
else if (this.startTime) {
|
|
requestTelemetry.time = new Date(this.startTime);
|
|
}
|
|
// We should keep any parameters the user passed in
|
|
// Except the fields defined above in requestTelemetry, which take priority
|
|
// Except the properties field, where they're merged instead, with baseTelemetry taking priority
|
|
if (baseTelemetry) {
|
|
// Copy missing fields
|
|
for (var key in baseTelemetry) {
|
|
if (!requestTelemetry[key]) {
|
|
requestTelemetry[key] = baseTelemetry[key];
|
|
}
|
|
}
|
|
// Merge properties
|
|
if (baseTelemetry.properties) {
|
|
for (var key in baseTelemetry.properties) {
|
|
requestTelemetry.properties[key] = baseTelemetry.properties[key];
|
|
}
|
|
}
|
|
}
|
|
return requestTelemetry;
|
|
};
|
|
HttpRequestParser.prototype.getRequestTags = function (tags) {
|
|
// create a copy of the context for requests since client info will be used here
|
|
var newTags = {};
|
|
for (var key in tags) {
|
|
newTags[key] = tags[key];
|
|
}
|
|
// don't override tags if they are already set
|
|
newTags[HttpRequestParser.keys.locationIp] = tags[HttpRequestParser.keys.locationIp] || this._getIp();
|
|
newTags[HttpRequestParser.keys.sessionId] = tags[HttpRequestParser.keys.sessionId] || this._getId("ai_session");
|
|
newTags[HttpRequestParser.keys.userId] = tags[HttpRequestParser.keys.userId] || this._getId("ai_user");
|
|
newTags[HttpRequestParser.keys.userAuthUserId] = tags[HttpRequestParser.keys.userAuthUserId] || this._getId("ai_authUser");
|
|
newTags[HttpRequestParser.keys.operationName] = this.getOperationName(tags);
|
|
newTags[HttpRequestParser.keys.operationParentId] = this.getOperationParentId(tags);
|
|
newTags[HttpRequestParser.keys.operationId] = this.getOperationId(tags);
|
|
return newTags;
|
|
};
|
|
HttpRequestParser.prototype.getOperationId = function (tags) {
|
|
return tags[HttpRequestParser.keys.operationId] || this.operationId;
|
|
};
|
|
HttpRequestParser.prototype.getOperationParentId = function (tags) {
|
|
return tags[HttpRequestParser.keys.operationParentId] || this.parentId || this.getOperationId(tags);
|
|
};
|
|
HttpRequestParser.prototype.getOperationName = function (tags) {
|
|
return tags[HttpRequestParser.keys.operationName] || this.method + " " + url.parse(this.url).pathname;
|
|
};
|
|
HttpRequestParser.prototype.getRequestId = function () {
|
|
return this.requestId;
|
|
};
|
|
HttpRequestParser.prototype.getCorrelationContextHeader = function () {
|
|
return this.correlationContextHeader;
|
|
};
|
|
HttpRequestParser.prototype.getTraceparent = function () {
|
|
return this.traceparent;
|
|
};
|
|
HttpRequestParser.prototype.getTracestate = function () {
|
|
return this.tracestate;
|
|
};
|
|
HttpRequestParser.prototype.getLegacyRootId = function () {
|
|
return this.legacyRootId;
|
|
};
|
|
HttpRequestParser.prototype._getAbsoluteUrl = function (request) {
|
|
if (!request.headers) {
|
|
return request.url;
|
|
}
|
|
var encrypted = request.connection ? request.connection.encrypted : null;
|
|
var requestUrl = url.parse(request.url);
|
|
var pathName = requestUrl.pathname;
|
|
var search = requestUrl.search;
|
|
var absoluteUrl = url.format({
|
|
protocol: encrypted ? "https" : "http",
|
|
host: request.headers.host,
|
|
pathname: pathName,
|
|
search: search
|
|
});
|
|
return absoluteUrl;
|
|
};
|
|
HttpRequestParser.prototype._getIp = function () {
|
|
// regex to match ipv4 without port
|
|
// Note: including the port would cause the payload to be rejected by the data collector
|
|
var ipMatch = /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/;
|
|
var check = function (str) {
|
|
var results = ipMatch.exec(str);
|
|
if (results) {
|
|
return results[0];
|
|
}
|
|
};
|
|
var ip = check(this.rawHeaders["x-forwarded-for"])
|
|
|| check(this.rawHeaders["x-client-ip"])
|
|
|| check(this.rawHeaders["x-real-ip"])
|
|
|| check(this.connectionRemoteAddress)
|
|
|| check(this.socketRemoteAddress)
|
|
|| check(this.legacySocketRemoteAddress);
|
|
// node v12 returns this if the address is "localhost"
|
|
if (!ip
|
|
&& this.connectionRemoteAddress
|
|
&& this.connectionRemoteAddress.substr
|
|
&& this.connectionRemoteAddress.substr(0, 2) === "::") {
|
|
ip = "127.0.0.1";
|
|
}
|
|
return ip;
|
|
};
|
|
HttpRequestParser.prototype._getId = function (name) {
|
|
var cookie = (this.rawHeaders && this.rawHeaders["cookie"] &&
|
|
typeof this.rawHeaders["cookie"] === 'string' && this.rawHeaders["cookie"]) || "";
|
|
var value = HttpRequestParser.parseId(Util.getCookie(name, cookie));
|
|
return value;
|
|
};
|
|
/**
|
|
* Sets this operation's operationId, parentId, requestId (and legacyRootId, if necessary) based on this operation's traceparent
|
|
*/
|
|
HttpRequestParser.prototype.setBackCompatFromThisTraceContext = function () {
|
|
// Set operationId
|
|
this.operationId = this.traceparent.traceId;
|
|
if (this.traceparent.legacyRootId) {
|
|
this.legacyRootId = this.traceparent.legacyRootId;
|
|
}
|
|
// Set parentId with existing spanId
|
|
this.parentId = this.traceparent.parentId;
|
|
// Update the spanId and set the current requestId
|
|
this.traceparent.updateSpanId();
|
|
this.requestId = this.traceparent.getBackCompatRequestId();
|
|
};
|
|
HttpRequestParser.prototype.parseHeaders = function (request, requestId) {
|
|
this.rawHeaders = request.headers || request.rawHeaders;
|
|
this.userAgent = request.headers && request.headers["user-agent"];
|
|
this.sourceCorrelationId = Util.getCorrelationContextTarget(request, RequestResponseHeaders.requestContextSourceKey);
|
|
if (request.headers) {
|
|
var tracestateHeader = request.headers[RequestResponseHeaders.traceStateHeader]; // w3c header
|
|
var traceparentHeader = request.headers[RequestResponseHeaders.traceparentHeader]; // w3c header
|
|
var requestIdHeader = request.headers[RequestResponseHeaders.requestIdHeader]; // default AI header
|
|
var legacy_parentId = request.headers[RequestResponseHeaders.parentIdHeader]; // legacy AI header
|
|
var legacy_rootId = request.headers[RequestResponseHeaders.rootIdHeader]; // legacy AI header
|
|
this.correlationContextHeader = request.headers[RequestResponseHeaders.correlationContextHeader];
|
|
if (CorrelationIdManager.w3cEnabled && (traceparentHeader || tracestateHeader)) {
|
|
// Parse W3C Trace Context headers
|
|
this.traceparent = new Traceparent(traceparentHeader); // new traceparent is always created from this
|
|
this.tracestate = traceparentHeader && tracestateHeader && new Tracestate(tracestateHeader); // discard tracestate if no traceparent is present
|
|
this.setBackCompatFromThisTraceContext();
|
|
}
|
|
else if (requestIdHeader) {
|
|
// Parse AI headers
|
|
if (CorrelationIdManager.w3cEnabled) {
|
|
this.traceparent = new Traceparent(null, requestIdHeader);
|
|
this.setBackCompatFromThisTraceContext();
|
|
}
|
|
else {
|
|
this.parentId = requestIdHeader;
|
|
this.requestId = CorrelationIdManager.generateRequestId(this.parentId);
|
|
this.operationId = CorrelationIdManager.getRootId(this.requestId);
|
|
}
|
|
}
|
|
else {
|
|
// Legacy fallback
|
|
if (CorrelationIdManager.w3cEnabled) {
|
|
this.traceparent = new Traceparent();
|
|
this.traceparent.parentId = legacy_parentId;
|
|
this.traceparent.legacyRootId = legacy_rootId || legacy_parentId;
|
|
this.setBackCompatFromThisTraceContext();
|
|
}
|
|
else {
|
|
this.parentId = legacy_parentId;
|
|
this.requestId = CorrelationIdManager.generateRequestId(legacy_rootId || this.parentId);
|
|
this.correlationContextHeader = null;
|
|
this.operationId = CorrelationIdManager.getRootId(this.requestId);
|
|
}
|
|
}
|
|
if (requestId) {
|
|
// For the scenarios that don't guarantee an AI-created context,
|
|
// override the requestId with the provided one.
|
|
this.requestId = requestId;
|
|
this.operationId = CorrelationIdManager.getRootId(this.requestId);
|
|
}
|
|
}
|
|
};
|
|
HttpRequestParser.parseId = function (cookieValue) {
|
|
var cookieParts = cookieValue.split("|");
|
|
if (cookieParts.length > 0) {
|
|
return cookieParts[0];
|
|
}
|
|
return ""; // old behavior was to return "" for incorrect parsing
|
|
};
|
|
HttpRequestParser.keys = new Contracts.ContextTagKeys();
|
|
return HttpRequestParser;
|
|
}(RequestParser));
|
|
module.exports = HttpRequestParser;
|
|
//# sourceMappingURL=HttpRequestParser.js.map
|