Skip to content

Commit

Permalink
Merge pull request #281 from Microsoft/develop
Browse files Browse the repository at this point in the history
Merge develop into master for v0.21.0
  • Loading branch information
OsvaldoRosado authored Jun 14, 2017
2 parents 287ed57 + 45fd0ed commit 4325e12
Show file tree
Hide file tree
Showing 28 changed files with 366 additions and 124 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
language: node_js
node_js:
- "node"
- "8"
- "7"
- "6"
- "5"
- "5.1"
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"files.exclude": {
"**/*.js": { "when": "$(basename).ts"},
"**/*.js.map": true,
"**/*.d.ts": true
"**/*.d.ts": true,
"out/": true
},
"typescript.tsdk": "./node_modules/typescript/lib"
}
3 changes: 2 additions & 1 deletion AutoCollection/ClientRequestParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class ClientRequestParser extends RequestParser {
/**
* Gets a dependency data contract object for a completed ClientRequest.
*/
public getDependencyData(): Contracts.Data<Contracts.RemoteDependencyData> {
public getDependencyData(dependencyId?: string): Contracts.Data<Contracts.RemoteDependencyData> {
let urlObject = url.parse(this.url);
urlObject.search = undefined;
urlObject.hash = undefined;
Expand All @@ -64,6 +64,7 @@ class ClientRequestParser extends RequestParser {
remoteDependency.type = Contracts.RemoteDependencyDataConstants.TYPE_HTTP;
}

remoteDependency.id = dependencyId;
remoteDependency.name = dependencyName;
remoteDependency.data = this.url;
remoteDependency.duration = Util.msToTimeSpan(this.duration);
Expand Down
29 changes: 20 additions & 9 deletions AutoCollection/ClientRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Logging = require("../Library/Logging");
import Util = require("../Library/Util");
import RequestResponseHeaders = require("../Library/RequestResponseHeaders");
import ClientRequestParser = require("./ClientRequestParser");
import { CorrelationContextManager, CorrelationContext } from "./CorrelationContextManager";
import { CorrelationContextManager, CorrelationContext, PrivateCustomProperties } from "./CorrelationContextManager";

import {enable as enableMongodb} from "./diagnostic-channel/mongodb.sub";
import {enable as enableMysql} from "./diagnostic-channel/mysql.sub";
Expand All @@ -20,6 +20,8 @@ class AutoCollectClientRequests {

public static INSTANCE: AutoCollectClientRequests;

private static requestNumber = 1;

private _client: Client;
private _isEnabled: boolean;
private _isInitialized: boolean;
Expand Down Expand Up @@ -51,10 +53,10 @@ class AutoCollectClientRequests {
this._isInitialized = true;

const originalRequest = http.request;
http.request = (options, ...requestArgs) => {
http.request = (options, ...requestArgs: any[]) => {
const request: http.ClientRequest = originalRequest.call(
http, options, ...requestArgs);
if (request && options && !options[AutoCollectClientRequests.disableCollectionRequestOption]) {
if (request && options && !(<any>options)[AutoCollectClientRequests.disableCollectionRequestOption]) {
AutoCollectClientRequests.trackRequest(this._client, options, request);
}
return request;
Expand All @@ -65,10 +67,10 @@ class AutoCollectClientRequests {
// The regex matches versions < 0.11.12 (avoiding a semver package dependency).
if (/^0\.([0-9]\.)|(10\.)|(11\.([0-9]|10|11)$)/.test(process.versions.node)) {
const originalHttpsRequest = https.request;
https.request = (options, ...requestArgs) => {
https.request = (options, ...requestArgs: any[]) => {
const request: http.ClientRequest = originalHttpsRequest.call(
https, options, ...requestArgs);
if (request && options && !options[AutoCollectClientRequests.disableCollectionRequestOption]) {
if (request && options && !(<any>options)[AutoCollectClientRequests.disableCollectionRequestOption]) {
AutoCollectClientRequests.trackRequest(this._client, options, request);
}
return request;
Expand All @@ -89,6 +91,9 @@ class AutoCollectClientRequests {

let requestParser = new ClientRequestParser(requestOptions, request);

const currentContext = CorrelationContextManager.getCurrentContext();
const uniqueRequestId = currentContext && currentContext.operation && (currentContext.operation.parentId + AutoCollectClientRequests.requestNumber++ + '.');

// Add the source correlationId to the request headers, if a value was not already provided.
// 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
Expand All @@ -108,10 +113,16 @@ class AutoCollectClientRequests {
}
}

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

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

Expand All @@ -120,12 +131,12 @@ class AutoCollectClientRequests {
request.on('response', (response: http.ClientResponse) => {
requestParser.onResponse(response, properties);
var context : { [name: string]: any; } = { "http.RequestOptions": requestOptions, "http.ClientRequest": request, "http.ClientResponse": response };
client.track(requestParser.getDependencyData(), null, context);
client.track(requestParser.getDependencyData(uniqueRequestId), null, context);
});
request.on('error', (e: Error) => {
requestParser.onError(e, properties);
var context : { [name: string]: any; } = { "http.RequestOptions": requestOptions, "http.ClientRequest": request, "Error": e };
client.track(requestParser.getDependencyData(), null, context);
client.track(requestParser.getDependencyData(uniqueRequestId), null, context);
});
}
}
Expand Down
92 changes: 81 additions & 11 deletions AutoCollection/CorrelationContextManager.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
import http = require("http");
import Util = require("../Library/Util");
import Logging = require("../Library/Logging");

import {channel} from "diagnostic-channel";

export interface CustomProperties {
/**
* Get a custom property from the correlation context
*/
getProperty(key: string): string;
/**
* Store a custom property in the correlation context.
* Do not store sensitive information here.
* Properties stored here are exposed via outgoing HTTP headers for correlating data cross-component.
* The characters ',' and '=' are disallowed within keys or values.
*/
setProperty(key: string, value: string): void;
}

export interface PrivateCustomProperties extends CustomProperties {
addHeaderData(header: string): void;
serializeToHeader(): string;
}

export interface CorrelationContext {
operation: {
name: string;
Expand All @@ -11,9 +31,9 @@ export interface CorrelationContext {
};

/** Do not store sensitive information here.
* Properties here can be exposed in a future SDK release via outgoing HTTP headers for correlating data cross-component.
* Properties here are exposed via outgoing HTTP headers for correlating data cross-component.
*/
customProperties: { [id: string]: string };
customProperties: CustomProperties
}

export class CorrelationContextManager {
Expand All @@ -35,18 +55,17 @@ export class CorrelationContextManager {
/**
* A helper to generate objects conforming to the CorrelationContext interface
*/
public static generateContextObject(parentId?: string, operationName?: string, operationId?: string): CorrelationContext {
operationId = operationId || Util.newGuid();
public static generateContextObject(operationId: string, parentId?: string, operationName?: string, correlationContextHeader?: string): CorrelationContext {
parentId = parentId || operationId;

if (this.enabled) {
return {
operation: {
name: operationName,
id: operationId,
parentId: parentId
},
customProperties: {}
customProperties: new CustomPropertiesImpl(correlationContextHeader)
};
}

Expand Down Expand Up @@ -142,8 +161,8 @@ export class CorrelationContextManager {
// This fixes that.
private static patchTimers(methodNames: string[]) {
methodNames.forEach(methodName => {
var orig = global[methodName];
global[methodName] = function() {
var orig = (<any>global)[methodName];
(<any>global)[methodName] = function() {
var ret = orig.apply(this, arguments);
ret.toString = function(){
if (this.data && typeof this.data.handleId !== 'undefined') {
Expand Down Expand Up @@ -174,7 +193,7 @@ export class CorrelationContextManager {
if ((<any>orig).prepareStackTrace) {
(<any>orig).stackRewrite= false;
var stackTrace = (<any>orig).prepareStackTrace;
(<any>orig).prepareStackTrace = (e, s) => {
(<any>orig).prepareStackTrace = (e: any, s: any) => {
// Remove some AI and Zone methods from the stack trace
// Otherwise we leave side-effects

Expand Down Expand Up @@ -216,7 +235,7 @@ export class CorrelationContextManager {
// We need to proactively make those not enumerable as well as the currently visible properties
for(var i=0; i < props.length; i++) {
var propertyName = props[i];
var hiddenPropertyName = Zone['__symbol__'](propertyName);
var hiddenPropertyName = (<any>Zone)['__symbol__'](propertyName);
Object.defineProperty(this, propertyName, { enumerable: false });
Object.defineProperty(this, hiddenPropertyName, { enumerable: false, writable: true });
}
Expand All @@ -231,7 +250,7 @@ export class CorrelationContextManager {
var props = Object.getOwnPropertyNames(orig);
for(var i=0; i < props.length; i++) {
var propertyName = props[i];
if (!AppInsightsAsyncCorrelatedErrorWrapper[propertyName]) {
if (!(<any>AppInsightsAsyncCorrelatedErrorWrapper)[propertyName]) {
Object.defineProperty(AppInsightsAsyncCorrelatedErrorWrapper, propertyName, Object.getOwnPropertyDescriptor(orig, propertyName));
}
}
Expand All @@ -241,3 +260,54 @@ export class CorrelationContextManager {
global.Error = <any>AppInsightsAsyncCorrelatedErrorWrapper;
}
}

class CustomPropertiesImpl implements PrivateCustomProperties {
private static bannedCharacters = /[,=]/;
private props: {key: string, value:string}[] = [];

public constructor(header: string) {
this.addHeaderData(header);
}

public addHeaderData(header?: string) {
const keyvals = header ? header.split(", ") : [];
this.props = keyvals.map((keyval) => {
const parts = keyval.split("=");
return {key: parts[0], value: parts[1]};
}).concat(this.props);
}

public serializeToHeader() {
return this.props.map((keyval) => {
return `${keyval.key}=${keyval.value}`
}).join(", ");
}

public getProperty(prop: string) {
for(let i = 0; i < this.props.length; ++i) {
const keyval = this.props[i]
if (keyval.key === prop) {
return keyval.value;
}
}
return;
}

// TODO: Strictly according to the spec, properties which are recieved from
// an incoming request should be left untouched, while we may add our own new
// properties. The logic here will need to change to track that.
public setProperty(prop: string, val: string) {
if (CustomPropertiesImpl.bannedCharacters.test(prop) || CustomPropertiesImpl.bannedCharacters.test(val)) {
Logging.warn("Correlation context property keys and values must not contain ',' or '='. setProperty was called with key: " + prop + " and value: "+ val);
return;
}
for (let i = 0; i < this.props.length; ++i) {
const keyval = this.props[i];
if (keyval.key === prop) {
keyval.value = val;
return;
}
}
this.props.push({key: prop, value: val});
}
}
16 changes: 8 additions & 8 deletions AutoCollection/Exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ class AutoCollectExceptions {

public static INSTANCE: AutoCollectExceptions = null;

private _exceptionListenerHandle;
private _rejectionListenerHandle;
private _exceptionListenerHandle: (reThrow: boolean, error: Error) => void;
private _rejectionListenerHandle: (reThrow: boolean, error: Error) => void;
private _client: Client;
private _isInitialized: boolean;

Expand Down Expand Up @@ -88,7 +88,7 @@ class AutoCollectExceptions {
return data;
}

private static parseStack(stack): _StackFrame[] {
private static parseStack(stack: any): _StackFrame[] {
var parsedStack: _StackFrame[] = undefined;
if (typeof stack === "string") {
var frames = stack.split("\n");
Expand Down Expand Up @@ -155,11 +155,11 @@ class _StackFrame {
public static regex = /^([\s]+at)?(.*?)(\@|\s\(|\s)([^\(\@\n]+):([0-9]+):([0-9]+)(\)?)$/;
public static baseSize = 58; //'{"method":"","level":,"assembly":"","fileName":"","line":}'.length
public sizeInBytes = 0;
public level;
public method;
public assembly;
public fileName;
public line;
public level: number;
public method: string;
public assembly: string;
public fileName: string;
public line: number;

constructor(frame: string, level: number) {
this.level = level;
Expand Down
2 changes: 1 addition & 1 deletion AutoCollection/RequestParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ abstract class RequestParser {
properties["error"] = error.message;
} else if (typeof error === "object") {
for (var key in <any>error) {
properties[key] = error[key] && error[key].toString && error[key].toString();
properties[key] = (<any>error)[key] && (<any>error)[key].toString && (<any>error)[key].toString();
}
}
}
Expand Down
Loading

0 comments on commit 4325e12

Please sign in to comment.