Skip to content

Commit

Permalink
Address issue #365
Browse files Browse the repository at this point in the history
  • Loading branch information
OsvaldoRosado committed Feb 7, 2018
1 parent b324e79 commit 0ce908d
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 79 deletions.
61 changes: 35 additions & 26 deletions AutoCollection/HttpDependencies.ts
Expand Up @@ -2,6 +2,7 @@ import http = require("http");
import https = require("https");
import url = require("url");

import Contracts = require("../Declarations/Contracts");
import TelemetryClient = require("../Library/TelemetryClient");
import Logging = require("../Library/Logging");
import Util = require("../Library/Util");
Expand Down Expand Up @@ -66,7 +67,7 @@ class AutoCollectHttpDependencies {
(<any>request)[AutoCollectHttpDependencies.alreadyAutoCollectedFlag] = true;

if (request && options && shouldCollect) {
AutoCollectHttpDependencies.trackRequest(this._client, options, request);
AutoCollectHttpDependencies.trackRequest(this._client, {options: options, request: request});
}
};

Expand Down Expand Up @@ -106,14 +107,13 @@ class AutoCollectHttpDependencies {
* Tracks an outgoing request. Because it may set headers this method must be called before
* writing content to or ending the request.
*/
public static trackRequest(client: TelemetryClient, requestOptions: string | http.RequestOptions | https.RequestOptions, request: http.ClientRequest,
properties?: { [key: string]: string }) {
if (!requestOptions || !request || !client) {
Logging.info("AutoCollectHttpDependencies.trackRequest was called with invalid parameters: ", !requestOptions, !request, !client);
public static trackRequest(client: TelemetryClient, telemetry: Contracts.NodeHttpDependencyTelemetry) {
if (!telemetry.options || !telemetry.request || !client) {
Logging.info("AutoCollectHttpDependencies.trackRequest was called with invalid parameters: ", !telemetry.options, !telemetry.request, !client);
return;
}

let requestParser = new HttpDependencyParser(requestOptions, request);
let requestParser = new HttpDependencyParser(telemetry.options, telemetry.request);

const currentContext = CorrelationContextManager.getCurrentContext();
const uniqueRequestId = currentContext && currentContext.operation && (currentContext.operation.parentId + AutoCollectHttpDependencies.requestNumber++ + '.');
Expand All @@ -122,53 +122,62 @@ class AutoCollectHttpDependencies {
// The getHeader/setHeader methods aren't available on very old Node versions, and
// are not included in the v0.10 type declarations currently used. So check if the
// methods exist before invoking them.
if (Util.canIncludeCorrelationHeader(client, requestParser.getUrl()) &&
request['getHeader'] && request['setHeader']) {
if (Util.canIncludeCorrelationHeader(client, requestParser.getUrl()) && telemetry.request.getHeader && telemetry.request.setHeader) {
if (client.config && client.config.correlationId) {
const correlationHeader = request['getHeader'](RequestResponseHeaders.requestContextHeader);
const correlationHeader = telemetry.request.getHeader(RequestResponseHeaders.requestContextHeader);
if (correlationHeader) {
const components = correlationHeader.split(",");
const key = `${RequestResponseHeaders.requestContextSourceKey}=`;
const roleNameKey = `${RequestResponseHeaders.requestContextSourceRoleNameKey}=`;
if (!components.some((value) => value.substring(0,key.length) === key)) {
request['setHeader'](
telemetry.request.setHeader(
RequestResponseHeaders.requestContextHeader,
`${correlationHeader},${RequestResponseHeaders.requestContextSourceKey}=${client.config.correlationId},${RequestResponseHeaders.requestContextSourceRoleNameKey}=${client.context.tags[client.context.keys.cloudRole]}`);
}
} else {
request['setHeader'](
telemetry.request.setHeader(
RequestResponseHeaders.requestContextHeader,
`${RequestResponseHeaders.requestContextSourceKey}=${client.config.correlationId},${RequestResponseHeaders.requestContextSourceRoleNameKey}=${client.context.tags[client.context.keys.cloudRole]}`);
}
}

if (currentContext && currentContext.operation) {
request['setHeader'](RequestResponseHeaders.requestIdHeader, uniqueRequestId);
telemetry.request.setHeader(RequestResponseHeaders.requestIdHeader, uniqueRequestId);
// Also set legacy headers
request['setHeader'](RequestResponseHeaders.parentIdHeader, currentContext.operation.id);
request['setHeader'](RequestResponseHeaders.rootIdHeader, uniqueRequestId);
telemetry.request.setHeader(RequestResponseHeaders.parentIdHeader, currentContext.operation.id);
telemetry.request.setHeader(RequestResponseHeaders.rootIdHeader, uniqueRequestId);

const correlationContextHeader = (<PrivateCustomProperties>currentContext.customProperties).serializeToHeader();
if (correlationContextHeader) {
request['setHeader'](RequestResponseHeaders.correlationContextHeader, correlationContextHeader);
telemetry.request.setHeader(RequestResponseHeaders.correlationContextHeader, correlationContextHeader);
}
}
}

// Collect dependency telemetry about the request when it finishes.
if (request.on) {
request.on('response', (response: http.ClientResponse) => {
requestParser.onResponse(response, properties);
var context : { [name: string]: any; } = { "http.RequestOptions": requestOptions, "http.ClientRequest": request, "http.ClientResponse": response };
var dependencyTelemetry = requestParser.getDependencyTelemetry(uniqueRequestId);
dependencyTelemetry.contextObjects = context;
if (telemetry.request.on) {
telemetry.request.on('response', (response: http.ClientResponse) => {
requestParser.onResponse(response);

var dependencyTelemetry = requestParser.getDependencyTelemetry(telemetry, uniqueRequestId);

dependencyTelemetry.contextObjects = dependencyTelemetry.contextObjects || {};
dependencyTelemetry.contextObjects["http.RequestOptions"] = telemetry.options;
dependencyTelemetry.contextObjects["http.ClientRequest"] = telemetry.request;
dependencyTelemetry.contextObjects["http.ClientResponse"] = response;

client.trackDependency(dependencyTelemetry);
});
request.on('error', (e: Error) => {
requestParser.onError(e, properties);
var context : { [name: string]: any; } = { "http.RequestOptions": requestOptions, "http.ClientRequest": request, "Error": e };
var dependencyTelemetry = requestParser.getDependencyTelemetry(uniqueRequestId);
dependencyTelemetry.contextObjects = context;
telemetry.request.on('error', (e: Error) => {
requestParser.onError(e);

var dependencyTelemetry = requestParser.getDependencyTelemetry(telemetry, uniqueRequestId);

dependencyTelemetry.contextObjects = dependencyTelemetry.contextObjects || {};
dependencyTelemetry.contextObjects["http.RequestOptions"] = telemetry.options;
dependencyTelemetry.contextObjects["http.ClientRequest"] = telemetry.request;
dependencyTelemetry.contextObjects["Error"] = e;

client.trackDependency(dependencyTelemetry);
});
}
Expand Down
32 changes: 25 additions & 7 deletions AutoCollection/HttpDependencyParser.ts
Expand Up @@ -31,23 +31,23 @@ class HttpDependencyParser extends RequestParser {
/**
* Called when the ClientRequest emits an error event.
*/
public onError(error: Error, properties?: { [key: string]: string }) {
this._setStatus(undefined, error, properties);
public onError(error: Error) {
this._setStatus(undefined, error);
}

/**
* Called when the ClientRequest emits a response event.
*/
public onResponse(response: http.ClientResponse, properties?: { [key: string]: string }) {
this._setStatus(response.statusCode, undefined, properties);
public onResponse(response: http.ClientResponse) {
this._setStatus(response.statusCode, undefined);
this.correlationId = Util.getCorrelationContextTarget(response, RequestResponseHeaders.requestContextTargetKey);
this.targetRoleName = Util.getCorrelationContextTarget(response, RequestResponseHeaders.requestContextTargetRoleNameKey);
}

/**
* Gets a dependency data contract object for a completed ClientRequest.
*/
public getDependencyTelemetry(dependencyId?: string): Contracts.DependencyTelemetry {
public getDependencyTelemetry(baseTelemetry?: Contracts.Telemetry, dependencyId?: string): Contracts.DependencyTelemetry {
let urlObject = url.parse(this.url);
urlObject.search = undefined;
urlObject.hash = undefined;
Expand All @@ -66,7 +66,7 @@ class HttpDependencyParser extends RequestParser {
remoteDependencyType = Contracts.RemoteDependencyDataConstants.TYPE_HTTP;
}

var telemetry: Contracts.DependencyTelemetry & Contracts.Identified = {
var dependencyTelemetry: Contracts.DependencyTelemetry & Contracts.Identified = {
id: dependencyId,
name: dependencyName,
data: this.url,
Expand All @@ -78,7 +78,25 @@ class HttpDependencyParser extends RequestParser {
target: remoteDependencyTarget
};

return telemetry;
// 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 (let key in baseTelemetry) {
if (!(<any>dependencyTelemetry)[key]) {
(<any>dependencyTelemetry)[key] = (<any>baseTelemetry)[key];
}
}
// Merge properties
if (baseTelemetry.properties) {
for (let key in baseTelemetry.properties) {
dependencyTelemetry.properties[key] = baseTelemetry.properties[key];
}
}
}

return dependencyTelemetry;
}

/**
Expand Down
34 changes: 29 additions & 5 deletions AutoCollection/HttpRequestParser.ts
Expand Up @@ -43,19 +43,25 @@ class HttpRequestParser extends RequestParser {
}
}

public onError(error: Error | string, properties?: { [key: string]: string }, ellapsedMilliseconds?: number) {
this._setStatus(undefined, error, properties);
public onError(error: Error | string, ellapsedMilliseconds?: number) {
this._setStatus(undefined, error);

// This parameter is only for overrides. setStatus handles this internally for the autocollected case
if (ellapsedMilliseconds) {
this.duration = ellapsedMilliseconds;
}
}

public onResponse(response: http.ServerResponse, properties?: { [key: string]: string }, ellapsedMilliseconds?: number) {
this._setStatus(response.statusCode, undefined, properties);
public onResponse(response: http.ServerResponse, ellapsedMilliseconds?: number) {
this._setStatus(response.statusCode, undefined);

// This parameter is only for overrides. setStatus handles this internally for the autocollected case
if (ellapsedMilliseconds) {
this.duration = ellapsedMilliseconds;
}
}

public getRequestTelemetry(): Contracts.RequestTelemetry {
public getRequestTelemetry(baseTelemetry?: Contracts.Telemetry): Contracts.RequestTelemetry {
var requestTelemetry: Contracts.RequestTelemetry & Contracts.Identified = {
id: this.requestId,
name: this.method + " " + url.parse(this.url).pathname,
Expand All @@ -71,6 +77,24 @@ class HttpRequestParser extends RequestParser {
properties: this.properties
};

// 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 (let key in baseTelemetry) {
if (!(<any>requestTelemetry)[key]) {
(<any>requestTelemetry)[key] = (<any>baseTelemetry)[key];
}
}
// Merge properties
if (baseTelemetry.properties) {
for (let key in baseTelemetry.properties) {
requestTelemetry.properties[key] = baseTelemetry.properties[key];
}
}
}

return requestTelemetry;
}

Expand Down

0 comments on commit 0ce908d

Please sign in to comment.