first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
import Contracts = require("../Declarations/Contracts");
import Sender = require("./Sender");
declare class Channel {
protected _lastSend: number;
protected _timeoutHandle: any;
protected _isDisabled: () => boolean;
protected _getBatchSize: () => number;
protected _getBatchIntervalMs: () => number;
_sender: Sender;
_buffer: string[];
constructor(isDisabled: () => boolean, getBatchSize: () => number, getBatchIntervalMs: () => number, sender: Sender);
/**
* Enable or disable disk-backed retry caching to cache events when client is offline (enabled by default)
* These cached events are stored in your system or user's temporary directory and access restricted to your user when possible.
* @param value if true events that occurred while client is offline will be cached on disk
* @param resendInterval The wait interval for resending cached events.
* @param maxBytesOnDisk The maximum size (in bytes) that the created temporary directory for cache events can grow to, before caching is disabled.
* @returns {Configuration} this class
*/
setUseDiskRetryCaching(value: boolean, resendInterval?: number, maxBytesOnDisk?: number): void;
/**
* Add a telemetry item to the send buffer
*/
send(envelope: Contracts.EnvelopeTelemetry): void;
/**
* Immediately send buffered data
*/
triggerSend(isNodeCrashing: boolean, callback?: (v: string) => void): void;
protected _stringify(envelope: Contracts.EnvelopeTelemetry): string;
}
export = Channel;
+98
View File
@@ -0,0 +1,98 @@
"use strict";
var Logging = require("./Logging");
var Channel = (function () {
function Channel(isDisabled, getBatchSize, getBatchIntervalMs, sender) {
this._buffer = [];
this._lastSend = 0;
this._isDisabled = isDisabled;
this._getBatchSize = getBatchSize;
this._getBatchIntervalMs = getBatchIntervalMs;
this._sender = sender;
}
/**
* Enable or disable disk-backed retry caching to cache events when client is offline (enabled by default)
* These cached events are stored in your system or user's temporary directory and access restricted to your user when possible.
* @param value if true events that occurred while client is offline will be cached on disk
* @param resendInterval The wait interval for resending cached events.
* @param maxBytesOnDisk The maximum size (in bytes) that the created temporary directory for cache events can grow to, before caching is disabled.
* @returns {Configuration} this class
*/
Channel.prototype.setUseDiskRetryCaching = function (value, resendInterval, maxBytesOnDisk) {
this._sender.setDiskRetryMode(value, resendInterval, maxBytesOnDisk);
};
/**
* Add a telemetry item to the send buffer
*/
Channel.prototype.send = function (envelope) {
var _this = this;
// if master off switch is set, don't send any data
if (this._isDisabled()) {
// Do not send/save data
return;
}
// validate input
if (!envelope) {
Logging.warn("Cannot send null/undefined telemetry");
return;
}
// check if the incoming payload is too large, truncate if necessary
var payload = this._stringify(envelope);
if (typeof payload !== "string") {
return;
}
// enqueue the payload
this._buffer.push(payload);
// flush if we would exceed the max-size limit by adding this item
if (this._buffer.length >= this._getBatchSize()) {
this.triggerSend(false);
return;
}
// ensure an invocation timeout is set if anything is in the buffer
if (!this._timeoutHandle && this._buffer.length > 0) {
this._timeoutHandle = setTimeout(function () {
_this._timeoutHandle = null;
_this.triggerSend(false);
}, this._getBatchIntervalMs());
}
};
/**
* Immediately send buffered data
*/
Channel.prototype.triggerSend = function (isNodeCrashing, callback) {
var bufferIsEmpty = this._buffer.length < 1;
if (!bufferIsEmpty) {
// compose an array of payloads
var batch = this._buffer.join("\n");
// invoke send
if (isNodeCrashing) {
this._sender.saveOnCrash(batch);
if (typeof callback === "function") {
callback("data saved on crash");
}
}
else {
this._sender.send(Buffer.from ? Buffer.from(batch) : new Buffer(batch), callback);
}
}
// update lastSend time to enable throttling
this._lastSend = +new Date;
// clear buffer
this._buffer.length = 0;
clearTimeout(this._timeoutHandle);
this._timeoutHandle = null;
if (bufferIsEmpty && typeof callback === "function") {
callback("no data to send");
}
};
Channel.prototype._stringify = function (envelope) {
try {
return JSON.stringify(envelope);
}
catch (error) {
Logging.warn("Failed to serialize payload", error, envelope);
}
};
return Channel;
}());
module.exports = Channel;
//# sourceMappingURL=Channel.js.map
File diff suppressed because one or more lines are too long
+68
View File
@@ -0,0 +1,68 @@
/// <reference types="node" />
import http = require('http');
import https = require('https');
declare class Config {
static ENV_azurePrefix: string;
static ENV_iKey: string;
static legacy_ENV_iKey: string;
static ENV_profileQueryEndpoint: string;
static ENV_quickPulseHost: string;
static ENV_connectionString: string;
static ENV_nativeMetricsDisablers: string;
static ENV_nativeMetricsDisableAll: string;
static ENV_http_proxy: string;
static ENV_https_proxy: string;
/** An identifier for your Application Insights resource */
instrumentationKey: string;
/** The id for cross-component correlation. READ ONLY. */
correlationId: string;
/** The ingestion endpoint to send telemetry payloads to */
endpointUrl: string;
/** The maximum number of telemetry items to include in a payload to the ingestion endpoint (Default 250) */
maxBatchSize: number;
/** The maximum amount of time to wait for a payload to reach maxBatchSize (Default 15000) */
maxBatchIntervalMs: number;
/** A flag indicating if telemetry transmission is disabled (Default false) */
disableAppInsights: boolean;
/** The percentage of telemetry items tracked that should be transmitted (Default 100) */
samplingPercentage: number;
/** The time to wait before retrying to retrieve the id for cross-component correlation (Default 30000) */
correlationIdRetryIntervalMs: number;
/** A list of domains to exclude from cross-component header injection */
correlationHeaderExcludedDomains: string[];
/** A proxy server for SDK HTTP traffic (Optional, Default pulled from `http_proxy` environment variable) */
proxyHttpUrl: string;
/** A proxy server for SDK HTTPS traffic (Optional, Default pulled from `https_proxy` environment variable) */
proxyHttpsUrl: string;
/** An http.Agent to use for SDK HTTP traffic (Optional, Default undefined) */
httpAgent: http.Agent;
/** An https.Agent to use for SDK HTTPS traffic (Optional, Default undefined) */
httpsAgent: https.Agent;
/** Disable including legacy headers in outgoing requests, x-ms-request-id */
ignoreLegacyHeaders?: boolean;
private endpointBase;
private setCorrelationId;
private _profileQueryEndpoint;
/** Host name for quickpulse service */
private _quickPulseHost;
constructor(setupString?: string);
profileQueryEndpoint: string;
quickPulseHost: string;
private static _getInstrumentationKey();
/**
* Validate UUID Format
* Specs taken from breeze repo
* The definition of a VALID instrumentation key is as follows:
* Not none
* Not empty
* Every character is a hex character [0-9a-f]
* 32 characters are separated into 5 sections via 4 dashes
* First section has 8 characters
* Second section has 4 characters
* Third section has 4 characters
* Fourth section has 4 characters
* Fifth section has 12 characters
*/
private static _validateInstrumentationKey(iKey);
}
export = Config;
+118
View File
@@ -0,0 +1,118 @@
"use strict";
var CorrelationIdManager = require("./CorrelationIdManager");
var ConnectionStringParser = require("./ConnectionStringParser");
var Logging = require("./Logging");
var Constants = require("../Declarations/Constants");
var url = require("url");
var Config = (function () {
function Config(setupString) {
var _this = this;
this.endpointBase = Constants.DEFAULT_BREEZE_ENDPOINT;
var connectionStringEnv = process.env[Config.ENV_connectionString];
var csCode = ConnectionStringParser.parse(setupString);
var csEnv = ConnectionStringParser.parse(connectionStringEnv);
var iKeyCode = !csCode.instrumentationkey && Object.keys(csCode).length > 0
? null // CS was valid but instrumentation key was not provided, null and grab from env var
: setupString; // CS was invalid, so it must be an ikey
this.instrumentationKey = csCode.instrumentationkey || iKeyCode /* === instrumentationKey */ || csEnv.instrumentationkey || Config._getInstrumentationKey();
// validate ikey. If fails throw a warning
if (!Config._validateInstrumentationKey(this.instrumentationKey)) {
Logging.warn("An invalid instrumentation key was provided. There may be resulting telemetry loss", this.instrumentationKey);
}
this.endpointUrl = (csCode.ingestionendpoint || csEnv.ingestionendpoint || this.endpointBase) + "/v2/track";
this.maxBatchSize = 250;
this.maxBatchIntervalMs = 15000;
this.disableAppInsights = false;
this.samplingPercentage = 100;
this.correlationIdRetryIntervalMs = 30 * 1000;
this.correlationHeaderExcludedDomains = [
"*.core.windows.net",
"*.core.chinacloudapi.cn",
"*.core.cloudapi.de",
"*.core.usgovcloudapi.net",
"*.core.microsoft.scloud",
"*.core.eaglex.ic.gov"
];
this.setCorrelationId = function (correlationId) { return _this.correlationId = correlationId; };
this.proxyHttpUrl = process.env[Config.ENV_http_proxy] || undefined;
this.proxyHttpsUrl = process.env[Config.ENV_https_proxy] || undefined;
this.httpAgent = undefined;
this.httpsAgent = undefined;
this.profileQueryEndpoint = csCode.ingestionendpoint || csEnv.ingestionendpoint || process.env[Config.ENV_profileQueryEndpoint] || this.endpointBase;
this._quickPulseHost = csCode.liveendpoint || csEnv.liveendpoint || process.env[Config.ENV_quickPulseHost] || Constants.DEFAULT_LIVEMETRICS_HOST;
// Parse quickPulseHost if it starts with http(s)://
if (this._quickPulseHost.match(/^https?:\/\//)) {
this._quickPulseHost = url.parse(this._quickPulseHost).host;
}
}
Object.defineProperty(Config.prototype, "profileQueryEndpoint", {
get: function () {
return this._profileQueryEndpoint;
},
set: function (endpoint) {
CorrelationIdManager.cancelCorrelationIdQuery(this, this.setCorrelationId);
this._profileQueryEndpoint = endpoint;
this.correlationId = CorrelationIdManager.correlationIdPrefix; // Reset the correlationId while we wait for the new query
CorrelationIdManager.queryCorrelationId(this, this.setCorrelationId);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Config.prototype, "quickPulseHost", {
get: function () {
return this._quickPulseHost;
},
set: function (host) {
this._quickPulseHost = host;
},
enumerable: true,
configurable: true
});
Config._getInstrumentationKey = function () {
// check for both the documented env variable and the azure-prefixed variable
var iKey = process.env[Config.ENV_iKey]
|| process.env[Config.ENV_azurePrefix + Config.ENV_iKey]
|| process.env[Config.legacy_ENV_iKey]
|| process.env[Config.ENV_azurePrefix + Config.legacy_ENV_iKey];
if (!iKey || iKey == "") {
throw new Error("Instrumentation key not found, pass the key in the config to this method or set the key in the environment variable APPINSIGHTS_INSTRUMENTATIONKEY before starting the server");
}
return iKey;
};
/**
* Validate UUID Format
* Specs taken from breeze repo
* The definition of a VALID instrumentation key is as follows:
* Not none
* Not empty
* Every character is a hex character [0-9a-f]
* 32 characters are separated into 5 sections via 4 dashes
* First section has 8 characters
* Second section has 4 characters
* Third section has 4 characters
* Fourth section has 4 characters
* Fifth section has 12 characters
*/
Config._validateInstrumentationKey = function (iKey) {
var UUID_Regex = '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$';
var regexp = new RegExp(UUID_Regex);
return regexp.test(iKey);
};
// Azure adds this prefix to all environment variables
Config.ENV_azurePrefix = "APPSETTING_";
// This key is provided in the readme
Config.ENV_iKey = "APPINSIGHTS_INSTRUMENTATIONKEY";
Config.legacy_ENV_iKey = "APPINSIGHTS_INSTRUMENTATION_KEY";
Config.ENV_profileQueryEndpoint = "APPINSIGHTS_PROFILE_QUERY_ENDPOINT";
Config.ENV_quickPulseHost = "APPINSIGHTS_QUICKPULSE_HOST";
// Azure Connection String
Config.ENV_connectionString = "APPLICATIONINSIGHTS_CONNECTION_STRING";
// Native Metrics Opt Outs
Config.ENV_nativeMetricsDisablers = "APPLICATION_INSIGHTS_DISABLE_EXTENDED_METRIC";
Config.ENV_nativeMetricsDisableAll = "APPLICATION_INSIGHTS_DISABLE_ALL_EXTENDED_METRICS";
Config.ENV_http_proxy = "http_proxy";
Config.ENV_https_proxy = "https_proxy";
return Config;
}());
module.exports = Config;
//# sourceMappingURL=Config.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
import { ConnectionString } from "../Declarations/Contracts";
declare class ConnectionStringParser {
private static _FIELDS_SEPARATOR;
private static _FIELD_KEY_VALUE_SEPARATOR;
static parse(connectionString?: string): ConnectionString;
}
export = ConnectionStringParser;
+39
View File
@@ -0,0 +1,39 @@
"use strict";
var Constants = require("../Declarations/Constants");
var ConnectionStringParser = (function () {
function ConnectionStringParser() {
}
ConnectionStringParser.parse = function (connectionString) {
if (!connectionString) {
return {};
}
var kvPairs = connectionString.split(ConnectionStringParser._FIELDS_SEPARATOR);
var result = kvPairs.reduce(function (fields, kv) {
var kvParts = kv.split(ConnectionStringParser._FIELD_KEY_VALUE_SEPARATOR);
if (kvParts.length === 2) {
var key = kvParts[0].toLowerCase();
var value = kvParts[1];
fields[key] = value;
}
return fields;
}, {});
if (Object.keys(result).length > 0) {
// this is a valid connection string, so parse the results
if (result.endpointsuffix) {
// use endpoint suffix where overrides are not provided
var locationPrefix = result.location ? result.location + "." : "";
result.ingestionendpoint = result.ingestionendpoint || ("https://" + locationPrefix + "dc." + result.endpointsuffix);
result.liveendpoint = result.liveendpoint || ("https://" + locationPrefix + "live." + result.endpointsuffix);
}
// apply the default endpoints
result.ingestionendpoint = result.ingestionendpoint || Constants.DEFAULT_BREEZE_ENDPOINT;
result.liveendpoint = result.liveendpoint || Constants.DEFAULT_LIVEMETRICS_ENDPOINT;
}
return result;
};
ConnectionStringParser._FIELDS_SEPARATOR = ";";
ConnectionStringParser._FIELD_KEY_VALUE_SEPARATOR = "=";
return ConnectionStringParser;
}());
module.exports = ConnectionStringParser;
//# sourceMappingURL=ConnectionStringParser.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ConnectionStringParser.js","sourceRoot":"","sources":["../../Library/ConnectionStringParser.ts"],"names":[],"mappings":";AACA,qDAAwD;AAExD;IAAA;IAuCA,CAAC;IAnCiB,4BAAK,GAAnB,UAAoB,gBAAyB;QACzC,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC;YACpB,MAAM,CAAC,EAAE,CAAC;QACd,CAAC;QAED,IAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,CAAC,sBAAsB,CAAC,iBAAiB,CAAC,CAAC;QAEjF,IAAM,MAAM,GAAqB,OAAO,CAAC,MAAM,CAAC,UAAC,MAAwB,EAAE,EAAU;YACjF,IAAM,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,sBAAsB,CAAC,0BAA0B,CAAC,CAAC;YAE5E,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;gBACvB,IAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,WAAW,EAAyB,CAAC;gBAC5D,IAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;gBACzB,MAAM,CAAC,GAAG,CAAC,GAAG,KAAe,CAAC;YAClC,CAAC;YACD,MAAM,CAAC,MAAM,CAAC;QAClB,CAAC,EAAE,EAAE,CAAC,CAAC;QAEP,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;YACjC,0DAA0D;YAE1D,EAAE,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;gBACxB,uDAAuD;gBACvD,IAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,GAAG,GAAG,GAAG,EAAE,CAAC;gBACpE,MAAM,CAAC,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,IAAI,CAAC,UAAU,GAAG,cAAc,GAAG,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;gBACrH,MAAM,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,CAAC,UAAU,GAAG,cAAc,GAAG,OAAO,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;YACjH,CAAC;YAED,8BAA8B;YAC9B,MAAM,CAAC,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,IAAI,SAAS,CAAC,uBAAuB,CAAC;YACzF,MAAM,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,SAAS,CAAC,4BAA4B,CAAC;QACxF,CAAC;QAED,MAAM,CAAC,MAAM,CAAC;IAClB,CAAC;IArCc,wCAAiB,GAAG,GAAG,CAAC;IACxB,iDAA0B,GAAG,GAAG,CAAC;IAqCpD,6BAAC;CAAA,AAvCD,IAuCC;AAED,iBAAS,sBAAsB,CAAC","sourcesContent":["import { ConnectionString, ConnectionStringKey } from \"../Declarations/Contracts\";\r\nimport Constants = require(\"../Declarations/Constants\");\r\n\r\nclass ConnectionStringParser {\r\n private static _FIELDS_SEPARATOR = \";\";\r\n private static _FIELD_KEY_VALUE_SEPARATOR = \"=\";\r\n\r\n public static parse(connectionString?: string): ConnectionString {\r\n if (!connectionString) {\r\n return {};\r\n }\r\n\r\n const kvPairs = connectionString.split(ConnectionStringParser._FIELDS_SEPARATOR);\r\n\r\n const result: ConnectionString = kvPairs.reduce((fields: ConnectionString, kv: string) => {\r\n const kvParts = kv.split(ConnectionStringParser._FIELD_KEY_VALUE_SEPARATOR);\r\n\r\n if (kvParts.length === 2) { // only save fields with valid formats\r\n const key = kvParts[0].toLowerCase() as ConnectionStringKey;\r\n const value = kvParts[1];\r\n fields[key] = value as string;\r\n }\r\n return fields;\r\n }, {});\r\n\r\n if (Object.keys(result).length > 0) {\r\n // this is a valid connection string, so parse the results\r\n\r\n if (result.endpointsuffix) {\r\n // use endpoint suffix where overrides are not provided\r\n const locationPrefix = result.location ? result.location + \".\" : \"\";\r\n result.ingestionendpoint = result.ingestionendpoint || (\"https://\" + locationPrefix + \"dc.\" + result.endpointsuffix);\r\n result.liveendpoint = result.liveendpoint || (\"https://\" + locationPrefix + \"live.\" + result.endpointsuffix);\r\n }\r\n\r\n // apply the default endpoints\r\n result.ingestionendpoint = result.ingestionendpoint || Constants.DEFAULT_BREEZE_ENDPOINT;\r\n result.liveendpoint = result.liveendpoint || Constants.DEFAULT_LIVEMETRICS_ENDPOINT;\r\n }\r\n\r\n return result;\r\n }\r\n}\r\n\r\nexport = ConnectionStringParser;\r\n"]}
+17
View File
@@ -0,0 +1,17 @@
import Contracts = require("../Declarations/Contracts");
declare class Context {
keys: Contracts.ContextTagKeys;
tags: {
[key: string]: string;
};
static DefaultRoleName: string;
static appVersion: {
[path: string]: string;
};
static sdkVersion: string;
constructor(packageJsonPath?: string);
private _loadApplicationContext(packageJsonPath?);
private _loadDeviceContext();
private _loadInternalContext();
}
export = Context;
+64
View File
@@ -0,0 +1,64 @@
"use strict";
var os = require("os");
var fs = require("fs");
var path = require("path");
var Contracts = require("../Declarations/Contracts");
var Logging = require("./Logging");
var Context = (function () {
function Context(packageJsonPath) {
this.keys = new Contracts.ContextTagKeys();
this.tags = {};
this._loadApplicationContext(packageJsonPath);
this._loadDeviceContext();
this._loadInternalContext();
}
Context.prototype._loadApplicationContext = function (packageJsonPath) {
// note: this should return the host package.json
packageJsonPath = packageJsonPath || path.resolve(__dirname, "../../../../package.json");
if (!Context.appVersion[packageJsonPath]) {
Context.appVersion[packageJsonPath] = "unknown";
try {
var packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
if (packageJson && typeof packageJson.version === "string") {
Context.appVersion[packageJsonPath] = packageJson.version;
}
}
catch (exception) {
Logging.info("unable to read app version: ", exception);
}
}
this.tags[this.keys.applicationVersion] = Context.appVersion[packageJsonPath];
};
Context.prototype._loadDeviceContext = function () {
this.tags[this.keys.deviceId] = "";
this.tags[this.keys.cloudRoleInstance] = os && os.hostname();
this.tags[this.keys.deviceOSVersion] = os && (os.type() + " " + os.release());
this.tags[this.keys.cloudRole] = Context.DefaultRoleName;
// not yet supported tags
this.tags["ai.device.osArchitecture"] = os && os.arch();
this.tags["ai.device.osPlatform"] = os && os.platform();
};
Context.prototype._loadInternalContext = function () {
// note: this should return the sdk package.json
var packageJsonPath = path.resolve(__dirname, "../../package.json");
if (!Context.sdkVersion) {
Context.sdkVersion = "unknown";
try {
var packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
if (packageJson && typeof packageJson.version === "string") {
Context.sdkVersion = packageJson.version;
}
}
catch (exception) {
Logging.info("unable to read app version: ", exception);
}
}
this.tags[this.keys.internalSdkVersion] = "node:" + Context.sdkVersion;
};
Context.DefaultRoleName = "Web";
Context.appVersion = {};
Context.sdkVersion = null;
return Context;
}());
module.exports = Context;
//# sourceMappingURL=Context.js.map
File diff suppressed because one or more lines are too long
+26
View File
@@ -0,0 +1,26 @@
import Config = require("./Config");
declare class CorrelationIdManager {
private static TAG;
static correlationIdPrefix: string;
static w3cEnabled: boolean;
private static pendingLookups;
private static completedLookups;
private static requestIdMaxLength;
private static currentRootId;
static queryCorrelationId(config: Config, callback: (correlationId: string) => void): void;
static cancelCorrelationIdQuery(config: Config, callback: (correlationId: string) => void): void;
/**
* Generate a request Id according to https://github.com/lmolkova/correlation/blob/master/hierarchical_request_id.md
* @param parentId
*/
static generateRequestId(parentId: string): string;
/**
* Given a hierarchical identifier of the form |X.*
* return the root identifier X
* @param id
*/
static getRootId(id: string): string;
private static generateRootId();
private static appendSuffix(parentId, suffix, delimiter);
}
export = CorrelationIdManager;
+153
View File
@@ -0,0 +1,153 @@
"use strict";
var Util = require("./Util");
var Logging = require("./Logging");
var CorrelationIdManager = (function () {
function CorrelationIdManager() {
}
CorrelationIdManager.queryCorrelationId = function (config, callback) {
// GET request to `${this.endpointBase}/api/profiles/${this.instrumentationKey}/appId`
// If it 404s, the iKey is bad and we should give up
// If it fails otherwise, try again later
var appIdUrlString = config.profileQueryEndpoint + "/api/profiles/" + config.instrumentationKey + "/appId";
if (CorrelationIdManager.completedLookups.hasOwnProperty(appIdUrlString)) {
callback(CorrelationIdManager.completedLookups[appIdUrlString]);
return;
}
else if (CorrelationIdManager.pendingLookups[appIdUrlString]) {
CorrelationIdManager.pendingLookups[appIdUrlString].push(callback);
return;
}
CorrelationIdManager.pendingLookups[appIdUrlString] = [callback];
var fetchAppId = function () {
if (!CorrelationIdManager.pendingLookups[appIdUrlString]) {
// This query has been cancelled.
return;
}
var requestOptions = {
method: 'GET',
// Ensure this request is not captured by auto-collection.
// Note: we don't refer to the property in HttpDependencyParser because that would cause a cyclical dependency
disableAppInsightsAutoCollection: true
};
Logging.info(CorrelationIdManager.TAG, requestOptions);
var req = Util.makeRequest(config, appIdUrlString, requestOptions, function (res) {
if (res.statusCode === 200) {
// Success; extract the appId from the body
var appId_1 = "";
res.setEncoding("utf-8");
res.on('data', function (data) {
appId_1 += data;
});
res.on('end', function () {
Logging.info(CorrelationIdManager.TAG, appId_1);
var result = CorrelationIdManager.correlationIdPrefix + appId_1;
CorrelationIdManager.completedLookups[appIdUrlString] = result;
if (CorrelationIdManager.pendingLookups[appIdUrlString]) {
CorrelationIdManager.pendingLookups[appIdUrlString].forEach(function (cb) { return cb(result); });
}
delete CorrelationIdManager.pendingLookups[appIdUrlString];
});
}
else if (res.statusCode >= 400 && res.statusCode < 500) {
// Not found, probably a bad key. Do not try again.
CorrelationIdManager.completedLookups[appIdUrlString] = undefined;
delete CorrelationIdManager.pendingLookups[appIdUrlString];
}
else {
// Retry after timeout.
setTimeout(fetchAppId, config.correlationIdRetryIntervalMs);
}
});
if (req) {
req.on('error', function (error) {
// Unable to contact endpoint.
// Do nothing for now.
Logging.warn(CorrelationIdManager.TAG, error);
});
req.end();
}
};
setTimeout(fetchAppId, 0);
};
CorrelationIdManager.cancelCorrelationIdQuery = function (config, callback) {
var appIdUrlString = config.profileQueryEndpoint + "/api/profiles/" + config.instrumentationKey + "/appId";
var pendingLookups = CorrelationIdManager.pendingLookups[appIdUrlString];
if (pendingLookups) {
CorrelationIdManager.pendingLookups[appIdUrlString] = pendingLookups.filter(function (cb) { return cb != callback; });
if (CorrelationIdManager.pendingLookups[appIdUrlString].length == 0) {
delete CorrelationIdManager.pendingLookups[appIdUrlString];
}
}
};
/**
* Generate a request Id according to https://github.com/lmolkova/correlation/blob/master/hierarchical_request_id.md
* @param parentId
*/
CorrelationIdManager.generateRequestId = function (parentId) {
if (parentId) {
parentId = parentId[0] == '|' ? parentId : '|' + parentId;
if (parentId[parentId.length - 1] !== '.') {
parentId += '.';
}
var suffix = (CorrelationIdManager.currentRootId++).toString(16);
return CorrelationIdManager.appendSuffix(parentId, suffix, '_');
}
else {
return CorrelationIdManager.generateRootId();
}
};
/**
* Given a hierarchical identifier of the form |X.*
* return the root identifier X
* @param id
*/
CorrelationIdManager.getRootId = function (id) {
var endIndex = id.indexOf('.');
if (endIndex < 0) {
endIndex = id.length;
}
var startIndex = id[0] === '|' ? 1 : 0;
return id.substring(startIndex, endIndex);
};
CorrelationIdManager.generateRootId = function () {
return '|' + Util.w3cTraceId() + '.';
};
CorrelationIdManager.appendSuffix = function (parentId, suffix, delimiter) {
if (parentId.length + suffix.length < CorrelationIdManager.requestIdMaxLength) {
return parentId + suffix + delimiter;
}
// Combined identifier would be too long, so we must truncate it.
// We need 9 characters of space: 8 for the overflow ID, 1 for the
// overflow delimiter '#'
var trimPosition = CorrelationIdManager.requestIdMaxLength - 9;
if (parentId.length > trimPosition) {
for (; trimPosition > 1; --trimPosition) {
var c = parentId[trimPosition - 1];
if (c === '.' || c === '_') {
break;
}
}
}
if (trimPosition <= 1) {
// parentId is not a valid ID
return CorrelationIdManager.generateRootId();
}
suffix = Util.randomu32().toString(16);
while (suffix.length < 8) {
suffix = '0' + suffix;
}
return parentId.substring(0, trimPosition) + suffix + '#';
};
CorrelationIdManager.TAG = "CorrelationIdManager";
CorrelationIdManager.correlationIdPrefix = "cid-v1:";
CorrelationIdManager.w3cEnabled = true;
// To avoid extraneous HTTP requests, we maintain a queue of callbacks waiting on a particular appId lookup,
// as well as a cache of completed lookups so future requests can be resolved immediately.
CorrelationIdManager.pendingLookups = {};
CorrelationIdManager.completedLookups = {};
CorrelationIdManager.requestIdMaxLength = 1024;
CorrelationIdManager.currentRootId = Util.randomu32();
return CorrelationIdManager;
}());
module.exports = CorrelationIdManager;
//# sourceMappingURL=CorrelationIdManager.js.map
File diff suppressed because one or more lines are too long
+30
View File
@@ -0,0 +1,30 @@
import Contracts = require("../Declarations/Contracts");
import Config = require("./Config");
import Context = require("./Context");
/**
* Manages the logic of creating envelopes from Telemetry objects
*/
declare class EnvelopeFactory {
/**
* Creates envelope ready to be sent by Channel
* @param telemetry Telemetry data
* @param telemetryType Type of telemetry
* @param commonProperties Bag of custom common properties to be added to the envelope
* @param context Client context
* @param config Client configuration
*/
static createEnvelope(telemetry: Contracts.Telemetry, telemetryType: Contracts.TelemetryType, commonProperties?: {
[key: string]: string;
}, context?: Context, config?: Config): Contracts.Envelope;
private static createTraceData(telemetry);
private static createDependencyData(telemetry);
private static createEventData(telemetry);
private static createExceptionData(telemetry);
private static createRequestData(telemetry);
private static createMetricData(telemetry);
private static createAvailabilityData(telemetry);
private static createPageViewData(telemetry);
private static getTags(context, tagOverrides?);
private static parseStack(stack);
}
export = EnvelopeFactory;
+324
View File
@@ -0,0 +1,324 @@
"use strict";
var Contracts = require("../Declarations/Contracts");
var Util = require("./Util");
var CorrelationContextManager_1 = require("../AutoCollection/CorrelationContextManager");
/**
* Manages the logic of creating envelopes from Telemetry objects
*/
var EnvelopeFactory = (function () {
function EnvelopeFactory() {
}
/**
* Creates envelope ready to be sent by Channel
* @param telemetry Telemetry data
* @param telemetryType Type of telemetry
* @param commonProperties Bag of custom common properties to be added to the envelope
* @param context Client context
* @param config Client configuration
*/
EnvelopeFactory.createEnvelope = function (telemetry, telemetryType, commonProperties, context, config) {
var data = null;
switch (telemetryType) {
case Contracts.TelemetryType.Trace:
data = EnvelopeFactory.createTraceData(telemetry);
break;
case Contracts.TelemetryType.Dependency:
data = EnvelopeFactory.createDependencyData(telemetry);
break;
case Contracts.TelemetryType.Event:
data = EnvelopeFactory.createEventData(telemetry);
break;
case Contracts.TelemetryType.Exception:
data = EnvelopeFactory.createExceptionData(telemetry);
break;
case Contracts.TelemetryType.Request:
data = EnvelopeFactory.createRequestData(telemetry);
break;
case Contracts.TelemetryType.Metric:
data = EnvelopeFactory.createMetricData(telemetry);
break;
case Contracts.TelemetryType.Availability:
data = EnvelopeFactory.createAvailabilityData(telemetry);
break;
case Contracts.TelemetryType.PageView:
data = EnvelopeFactory.createPageViewData(telemetry);
break;
}
if (commonProperties && Contracts.domainSupportsProperties(data.baseData)) {
if (data && data.baseData) {
// if no properties are specified just add the common ones
if (!data.baseData.properties) {
data.baseData.properties = commonProperties;
}
else {
// otherwise, check each of the common ones
for (var name in commonProperties) {
// only override if the property `name` has not been set on this item
if (!data.baseData.properties[name]) {
data.baseData.properties[name] = commonProperties[name];
}
}
}
}
// sanitize properties
data.baseData.properties = Util.validateStringMap(data.baseData.properties);
}
var iKey = config ? config.instrumentationKey || "" : "";
var envelope = new Contracts.Envelope();
envelope.data = data;
envelope.iKey = iKey;
// this is kind of a hack, but the envelope name is always the same as the data name sans the chars "data"
envelope.name =
"Microsoft.ApplicationInsights." +
iKey.replace(/-/g, "") +
"." +
data.baseType.substr(0, data.baseType.length - 4);
envelope.tags = this.getTags(context, telemetry.tagOverrides);
envelope.time = (new Date()).toISOString();
envelope.ver = 1;
envelope.sampleRate = config ? config.samplingPercentage : 100;
// Exclude metrics from sampling by default
if (telemetryType === Contracts.TelemetryType.Metric) {
envelope.sampleRate = 100;
}
return envelope;
};
EnvelopeFactory.createTraceData = function (telemetry) {
var trace = new Contracts.MessageData();
trace.message = telemetry.message;
trace.properties = telemetry.properties;
if (!isNaN(telemetry.severity)) {
trace.severityLevel = telemetry.severity;
}
else {
trace.severityLevel = Contracts.SeverityLevel.Information;
}
var data = new Contracts.Data();
data.baseType = Contracts.telemetryTypeToBaseType(Contracts.TelemetryType.Trace);
data.baseData = trace;
return data;
};
EnvelopeFactory.createDependencyData = function (telemetry) {
var remoteDependency = new Contracts.RemoteDependencyData();
if (typeof telemetry.name === "string") {
remoteDependency.name = telemetry.name.length > 1024 ? telemetry.name.slice(0, 1021) + '...' : telemetry.name;
}
remoteDependency.data = telemetry.data;
remoteDependency.target = telemetry.target;
remoteDependency.duration = Util.msToTimeSpan(telemetry.duration);
remoteDependency.success = telemetry.success;
remoteDependency.type = telemetry.dependencyTypeName;
remoteDependency.properties = telemetry.properties;
remoteDependency.resultCode = (telemetry.resultCode ? telemetry.resultCode + '' : '');
if (telemetry.id) {
remoteDependency.id = telemetry.id;
}
else {
remoteDependency.id = Util.w3cTraceId();
}
var data = new Contracts.Data();
data.baseType = Contracts.telemetryTypeToBaseType(Contracts.TelemetryType.Dependency);
data.baseData = remoteDependency;
return data;
};
EnvelopeFactory.createEventData = function (telemetry) {
var event = new Contracts.EventData();
event.name = telemetry.name;
event.properties = telemetry.properties;
event.measurements = telemetry.measurements;
var data = new Contracts.Data();
data.baseType = Contracts.telemetryTypeToBaseType(Contracts.TelemetryType.Event);
data.baseData = event;
return data;
};
EnvelopeFactory.createExceptionData = function (telemetry) {
var exception = new Contracts.ExceptionData();
exception.properties = telemetry.properties;
if (!isNaN(telemetry.severity)) {
exception.severityLevel = telemetry.severity;
}
else {
exception.severityLevel = Contracts.SeverityLevel.Error;
}
exception.measurements = telemetry.measurements;
exception.exceptions = [];
var stack = telemetry.exception["stack"];
var exceptionDetails = new Contracts.ExceptionDetails();
exceptionDetails.message = telemetry.exception.message;
exceptionDetails.typeName = telemetry.exception.name;
exceptionDetails.parsedStack = this.parseStack(stack);
exceptionDetails.hasFullStack = Util.isArray(exceptionDetails.parsedStack) && exceptionDetails.parsedStack.length > 0;
exception.exceptions.push(exceptionDetails);
var data = new Contracts.Data();
data.baseType = Contracts.telemetryTypeToBaseType(Contracts.TelemetryType.Exception);
data.baseData = exception;
return data;
};
EnvelopeFactory.createRequestData = function (telemetry) {
var requestData = new Contracts.RequestData();
if (telemetry.id) {
requestData.id = telemetry.id;
}
else {
requestData.id = Util.w3cTraceId();
}
requestData.name = telemetry.name;
requestData.url = telemetry.url;
requestData.source = telemetry.source;
requestData.duration = Util.msToTimeSpan(telemetry.duration);
requestData.responseCode = (telemetry.resultCode ? telemetry.resultCode + '' : '');
requestData.success = telemetry.success;
requestData.properties = telemetry.properties;
var data = new Contracts.Data();
data.baseType = Contracts.telemetryTypeToBaseType(Contracts.TelemetryType.Request);
data.baseData = requestData;
return data;
};
EnvelopeFactory.createMetricData = function (telemetry) {
var metrics = new Contracts.MetricData(); // todo: enable client-batching of these
metrics.metrics = [];
var metric = new Contracts.DataPoint();
metric.count = !isNaN(telemetry.count) ? telemetry.count : 1;
metric.kind = Contracts.DataPointType.Aggregation;
metric.max = !isNaN(telemetry.max) ? telemetry.max : telemetry.value;
metric.min = !isNaN(telemetry.min) ? telemetry.min : telemetry.value;
metric.name = telemetry.name;
metric.stdDev = !isNaN(telemetry.stdDev) ? telemetry.stdDev : 0;
metric.value = telemetry.value;
metrics.metrics.push(metric);
metrics.properties = telemetry.properties;
var data = new Contracts.Data();
data.baseType = Contracts.telemetryTypeToBaseType(Contracts.TelemetryType.Metric);
data.baseData = metrics;
return data;
};
EnvelopeFactory.createAvailabilityData = function (telemetry) {
var availabilityData = new Contracts.AvailabilityData();
if (telemetry.id) {
availabilityData.id = telemetry.id;
}
else {
availabilityData.id = Util.w3cTraceId();
}
availabilityData.name = telemetry.name;
availabilityData.duration = Util.msToTimeSpan(telemetry.duration);
availabilityData.success = telemetry.success;
availabilityData.runLocation = telemetry.runLocation;
availabilityData.message = telemetry.message;
availabilityData.measurements = telemetry.measurements;
availabilityData.properties = telemetry.properties;
var data = new Contracts.Data();
data.baseType = Contracts.telemetryTypeToBaseType(Contracts.TelemetryType.Availability);
data.baseData = availabilityData;
return data;
};
EnvelopeFactory.createPageViewData = function (telemetry) {
var pageViewData = new Contracts.PageViewData();
pageViewData.name = telemetry.name;
pageViewData.duration = Util.msToTimeSpan(telemetry.duration);
pageViewData.url = telemetry.url;
pageViewData.measurements = telemetry.measurements;
pageViewData.properties = telemetry.properties;
var data = new Contracts.Data();
data.baseType = Contracts.telemetryTypeToBaseType(Contracts.TelemetryType.PageView);
data.baseData = pageViewData;
return data;
};
EnvelopeFactory.getTags = function (context, tagOverrides) {
var correlationContext = CorrelationContextManager_1.CorrelationContextManager.getCurrentContext();
// Make a copy of context tags so we don't alter the actual object
// Also perform tag overriding
var newTags = {};
if (context && context.tags) {
for (var key in context.tags) {
newTags[key] = context.tags[key];
}
}
if (tagOverrides) {
for (var key in tagOverrides) {
newTags[key] = tagOverrides[key];
}
}
// Fill in internally-populated values if not already set
if (correlationContext) {
newTags[context.keys.operationId] = newTags[context.keys.operationId] || correlationContext.operation.id;
newTags[context.keys.operationName] = newTags[context.keys.operationName] || correlationContext.operation.name;
newTags[context.keys.operationParentId] = newTags[context.keys.operationParentId] || correlationContext.operation.parentId;
}
return newTags;
};
EnvelopeFactory.parseStack = function (stack) {
var parsedStack = undefined;
if (typeof stack === "string") {
var frames = stack.split("\n");
parsedStack = [];
var level = 0;
var totalSizeInBytes = 0;
for (var i = 0; i <= frames.length; i++) {
var frame = frames[i];
if (_StackFrame.regex.test(frame)) {
var parsedFrame = new _StackFrame(frames[i], level++);
totalSizeInBytes += parsedFrame.sizeInBytes;
parsedStack.push(parsedFrame);
}
}
// DP Constraint - exception parsed stack must be < 32KB
// remove frames from the middle to meet the threshold
var exceptionParsedStackThreshold = 32 * 1024;
if (totalSizeInBytes > exceptionParsedStackThreshold) {
var left = 0;
var right = parsedStack.length - 1;
var size = 0;
var acceptedLeft = left;
var acceptedRight = right;
while (left < right) {
// check size
var lSize = parsedStack[left].sizeInBytes;
var rSize = parsedStack[right].sizeInBytes;
size += lSize + rSize;
if (size > exceptionParsedStackThreshold) {
// remove extra frames from the middle
var howMany = acceptedRight - acceptedLeft + 1;
parsedStack.splice(acceptedLeft, howMany);
break;
}
// update pointers
acceptedLeft = left;
acceptedRight = right;
left++;
right--;
}
}
}
return parsedStack;
};
return EnvelopeFactory;
}());
var _StackFrame = (function () {
function _StackFrame(frame, level) {
this.sizeInBytes = 0;
this.level = level;
this.method = "<no_method>";
this.assembly = Util.trim(frame);
var matches = frame.match(_StackFrame.regex);
if (matches && matches.length >= 5) {
this.method = Util.trim(matches[2]) || this.method;
this.fileName = Util.trim(matches[4]) || "<no_filename>";
this.line = parseInt(matches[5]) || 0;
}
this.sizeInBytes += this.method.length;
this.sizeInBytes += this.fileName.length;
this.sizeInBytes += this.assembly.length;
// todo: these might need to be removed depending on how the back-end settles on their size calculation
this.sizeInBytes += _StackFrame.baseSize;
this.sizeInBytes += this.level.toString().length;
this.sizeInBytes += this.line.toString().length;
}
// regex to match stack frames from ie/chrome/ff
// methodName=$2, fileName=$4, lineNo=$5, column=$6
_StackFrame.regex = /^(\s+at)?(.*?)(\@|\s\(|\s)([^\(\n]+):(\d+):(\d+)(\)?)$/;
_StackFrame.baseSize = 58; //'{"method":"","level":,"assembly":"","fileName":"","line":}'.length
return _StackFrame;
}());
module.exports = EnvelopeFactory;
//# sourceMappingURL=EnvelopeFactory.js.map
File diff suppressed because one or more lines are too long
+16
View File
@@ -0,0 +1,16 @@
/**
* Encapsulates options passed into client.flush() function
*/
interface FlushOptions {
/**
* Flag indicating whether application is crashing. When this flag is set to true
* and storing data locally is enabled, Node.JS SDK will attempt to store data on disk
*/
isAppCrashing?: boolean;
/**
* Callback that will be invoked with the response from server, in case of isAppCrashing set to true,
* with immediate notification that data was stored
*/
callback?: (v: string) => void;
}
export = FlushOptions;
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=FlushOptions.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"FlushOptions.js","sourceRoot":"","sources":["../../Library/FlushOptions.ts"],"names":[],"mappings":"","sourcesContent":["/**\r\n * Encapsulates options passed into client.flush() function\r\n */\r\ninterface FlushOptions\r\n{\r\n /**\r\n * Flag indicating whether application is crashing. When this flag is set to true\r\n * and storing data locally is enabled, Node.JS SDK will attempt to store data on disk\r\n */\r\n isAppCrashing?:boolean\r\n \r\n /**\r\n * Callback that will be invoked with the response from server, in case of isAppCrashing set to true,\r\n * with immediate notification that data was stored\r\n */\r\n callback?: (v: string) => void;\r\n}\r\nexport = FlushOptions;"]}
+31
View File
@@ -0,0 +1,31 @@
/**
* The context object can be used for writing logs, reading data from bindings, setting outputs and using
* the context.done callback when your exported function is synchronous. A context object is passed
* to your function from the Azure Functions runtime on function invocation.
*/
export interface Context {
traceContext: TraceContext;
}
/**
* HTTP request object. Provided to your function when using HTTP Bindings.
*/
export interface HttpRequest {
method: string | null;
url: string;
headers: {
[key: string]: string;
};
}
/**
* TraceContext information to enable distributed tracing scenarios.
*/
export interface TraceContext {
/** Describes the position of the incoming request in its trace graph in a portable, fixed-length format. */
traceparent: string | null | undefined;
/** Extends traceparent with vendor-specific data. */
tracestate: string | null | undefined;
/** Holds additional properties being sent as part of request telemetry. */
attributes: {
[k: string]: string;
} | null | undefined;
}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=Functions.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"Functions.js","sourceRoot":"","sources":["../../Library/Functions.ts"],"names":[],"mappings":"","sourcesContent":["/**\r\n * The context object can be used for writing logs, reading data from bindings, setting outputs and using\r\n * the context.done callback when your exported function is synchronous. A context object is passed\r\n * to your function from the Azure Functions runtime on function invocation.\r\n */\r\nexport interface Context {\r\n traceContext: TraceContext\r\n}\r\n\r\n/**\r\n * HTTP request object. Provided to your function when using HTTP Bindings.\r\n */\r\nexport interface HttpRequest {\r\n method: string | null;\r\n url: string;\r\n headers: {\r\n [key: string]: string;\r\n };\r\n}\r\n\r\n/**\r\n * TraceContext information to enable distributed tracing scenarios.\r\n */\r\nexport interface TraceContext {\r\n /** Describes the position of the incoming request in its trace graph in a portable, fixed-length format. */\r\n traceparent: string | null | undefined;\r\n /** Extends traceparent with vendor-specific data. */\r\n tracestate: string | null | undefined;\r\n /** Holds additional properties being sent as part of request telemetry. */\r\n attributes: {\r\n [k: string]: string;\r\n } | null | undefined;\r\n}\r\n"]}
+9
View File
@@ -0,0 +1,9 @@
declare class Logging {
static enableDebug: boolean;
static disableWarnings: boolean;
static disableErrors: boolean;
private static TAG;
static info(message?: any, ...optionalParams: any[]): void;
static warn(message?: any, ...optionalParams: any[]): void;
}
export = Logging;
+30
View File
@@ -0,0 +1,30 @@
"use strict";
var Logging = (function () {
function Logging() {
}
Logging.info = function (message) {
var optionalParams = [];
for (var _i = 1; _i < arguments.length; _i++) {
optionalParams[_i - 1] = arguments[_i];
}
if (Logging.enableDebug) {
console.info(Logging.TAG + message, optionalParams);
}
};
Logging.warn = function (message) {
var optionalParams = [];
for (var _i = 1; _i < arguments.length; _i++) {
optionalParams[_i - 1] = arguments[_i];
}
if (!Logging.disableWarnings) {
console.warn(Logging.TAG + message, optionalParams);
}
};
Logging.enableDebug = false;
Logging.disableWarnings = false;
Logging.disableErrors = false;
Logging.TAG = "ApplicationInsights:";
return Logging;
}());
module.exports = Logging;
//# sourceMappingURL=Logging.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"Logging.js","sourceRoot":"","sources":["../../Library/Logging.ts"],"names":[],"mappings":";AAAA;IAAA;IAkBA,CAAC;IAXiB,YAAI,GAAlB,UAAmB,OAAa;QAAE,wBAAwB;aAAxB,UAAwB,EAAxB,qBAAwB,EAAxB,IAAwB;YAAxB,uCAAwB;;QACtD,EAAE,CAAA,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;YACrB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,GAAG,OAAO,EAAE,cAAc,CAAC,CAAC;QACxD,CAAC;IACL,CAAC;IAEa,YAAI,GAAlB,UAAmB,OAAa;QAAE,wBAAwB;aAAxB,UAAwB,EAAxB,qBAAwB,EAAxB,IAAwB;YAAxB,uCAAwB;;QACtD,EAAE,CAAA,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,GAAG,OAAO,EAAE,cAAc,CAAC,CAAC;QACxD,CAAC;IACL,CAAC;IAhBa,mBAAW,GAAG,KAAK,CAAC;IACpB,uBAAe,GAAG,KAAK,CAAC;IACxB,qBAAa,GAAG,KAAK,CAAC;IAErB,WAAG,GAAG,sBAAsB,CAAC;IAahD,cAAC;CAAA,AAlBD,IAkBC;AAED,iBAAS,OAAO,CAAC","sourcesContent":["class Logging {\r\n public static enableDebug = false;\r\n public static disableWarnings = false;\r\n public static disableErrors = false;\r\n\r\n private static TAG = \"ApplicationInsights:\";\r\n\r\n public static info(message?: any, ...optionalParams: any[]) {\r\n if(Logging.enableDebug) {\r\n console.info(Logging.TAG + message, optionalParams);\r\n }\r\n }\r\n\r\n public static warn(message?: any, ...optionalParams: any[]) {\r\n if(!Logging.disableWarnings) {\r\n console.warn(Logging.TAG + message, optionalParams);\r\n }\r\n }\r\n}\r\n\r\nexport = Logging;"]}
+30
View File
@@ -0,0 +1,30 @@
import TelemetryClient = require("./TelemetryClient");
import Contracts = require("../Declarations/Contracts");
/**
* Application Insights Telemetry Client for Node.JS. Provides the Application Insights TelemetryClient API
* in addition to Node-specific helper functions.
* Construct a new TelemetryClient to have an instance with a different configuration than the default client.
* In most cases, `appInsights.defaultClient` should be used instead.
*/
declare class NodeClient extends TelemetryClient {
/**
* Log RequestTelemetry from HTTP request and response. This method will log immediately without waiting for request completion
* and it requires duration parameter to be specified on NodeHttpRequestTelemetry object.
* Use trackNodeHttpRequest function to log the telemetry after request completion
* @param telemetry Object encapsulating incoming request, response and duration information
*/
trackNodeHttpRequestSync(telemetry: Contracts.NodeHttpRequestTelemetry): void;
/**
* Log RequestTelemetry from HTTP request and response. This method will `follow` the request to completion.
* Use trackNodeHttpRequestSync function to log telemetry immediately without waiting for request completion
* @param telemetry Object encapsulating incoming request and response information
*/
trackNodeHttpRequest(telemetry: Contracts.NodeHttpRequestTelemetry): void;
/**
* Log DependencyTelemetry from outgoing HTTP request. This method will instrument the outgoing request and append
* the specified headers and will log the telemetry when outgoing request is complete
* @param telemetry Object encapsulating outgoing request information
*/
trackNodeHttpDependency(telemetry: Contracts.NodeHttpDependencyTelemetry): void;
}
export = NodeClient;
+73
View File
@@ -0,0 +1,73 @@
"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 TelemetryClient = require("./TelemetryClient");
var ServerRequestTracking = require("../AutoCollection/HttpRequests");
var ClientRequestTracking = require("../AutoCollection/HttpDependencies");
var Logging = require("./Logging");
/**
* Application Insights Telemetry Client for Node.JS. Provides the Application Insights TelemetryClient API
* in addition to Node-specific helper functions.
* Construct a new TelemetryClient to have an instance with a different configuration than the default client.
* In most cases, `appInsights.defaultClient` should be used instead.
*/
var NodeClient = (function (_super) {
__extends(NodeClient, _super);
function NodeClient() {
return _super !== null && _super.apply(this, arguments) || this;
}
/**
* Log RequestTelemetry from HTTP request and response. This method will log immediately without waiting for request completion
* and it requires duration parameter to be specified on NodeHttpRequestTelemetry object.
* Use trackNodeHttpRequest function to log the telemetry after request completion
* @param telemetry Object encapsulating incoming request, response and duration information
*/
NodeClient.prototype.trackNodeHttpRequestSync = function (telemetry) {
if (telemetry && telemetry.request && telemetry.response && telemetry.duration) {
ServerRequestTracking.trackRequestSync(this, telemetry);
}
else {
Logging.warn("trackNodeHttpRequestSync requires NodeHttpRequestTelemetry object with request, response and duration specified.");
}
};
/**
* Log RequestTelemetry from HTTP request and response. This method will `follow` the request to completion.
* Use trackNodeHttpRequestSync function to log telemetry immediately without waiting for request completion
* @param telemetry Object encapsulating incoming request and response information
*/
NodeClient.prototype.trackNodeHttpRequest = function (telemetry) {
if (telemetry.duration || telemetry.error) {
Logging.warn("trackNodeHttpRequest will ignore supplied duration and error parameters. These values are collected from the request and response objects.");
}
if (telemetry && telemetry.request && telemetry.response) {
ServerRequestTracking.trackRequest(this, telemetry);
}
else {
Logging.warn("trackNodeHttpRequest requires NodeHttpRequestTelemetry object with request and response specified.");
}
};
/**
* Log DependencyTelemetry from outgoing HTTP request. This method will instrument the outgoing request and append
* the specified headers and will log the telemetry when outgoing request is complete
* @param telemetry Object encapsulating outgoing request information
*/
NodeClient.prototype.trackNodeHttpDependency = function (telemetry) {
if (telemetry && telemetry.request) {
ClientRequestTracking.trackRequest(this, telemetry);
}
else {
Logging.warn("trackNodeHttpDependency requires NodeHttpDependencyTelemetry object with request specified.");
}
};
return NodeClient;
}(TelemetryClient));
module.exports = NodeClient;
//# sourceMappingURL=NodeClient.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"NodeClient.js","sourceRoot":"","sources":["../../Library/NodeClient.ts"],"names":[],"mappings":";;;;;;;;;;;AAEA,mDAAsD;AACtD,sEAAyE;AACzE,0EAA6E;AAC7E,mCAAsC;AAGtC;;;;;GAKG;AACH;IAAyB,8BAAe;IAAxC;;IA6CA,CAAC;IA3CG;;;;;OAKG;IACI,6CAAwB,GAA/B,UAAgC,SAA6C;QACzE,EAAE,CAAC,CAAC,SAAS,IAAI,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC7E,qBAAqB,CAAC,gBAAgB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC5D,CAAC;QAAC,IAAI,CAAC,CAAC;YACJ,OAAO,CAAC,IAAI,CAAC,kHAAkH,CAAC,CAAC;QACrI,CAAC;IACL,CAAC;IAED;;;;OAIG;IACI,yCAAoB,GAA3B,UAA4B,SAA6C;QACrE,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;YACxC,OAAO,CAAC,IAAI,CAAC,4IAA4I,CAAC,CAAC;QAC/J,CAAC;QACD,EAAE,CAAC,CAAC,SAAS,IAAI,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;YACvD,qBAAqB,CAAC,YAAY,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACxD,CAAC;QAAC,IAAI,CAAC,CAAC;YACJ,OAAO,CAAC,IAAI,CAAC,oGAAoG,CAAC,CAAC;QACvH,CAAC;IACL,CAAC;IAED;;;;OAIG;IACI,4CAAuB,GAA9B,UAA+B,SAAgD;QAC3E,EAAE,CAAC,CAAC,SAAS,IAAI,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;YACjC,qBAAqB,CAAC,YAAY,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACxD,CAAC;QACD,IAAI,CAAC,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,6FAA6F,CAAC,CAAC;QAChH,CAAC;IACL,CAAC;IACL,iBAAC;AAAD,CAAC,AA7CD,CAAyB,eAAe,GA6CvC;AAED,iBAAS,UAAU,CAAA","sourcesContent":["import http = require(\"http\");\r\nimport https = require(\"https\");\r\nimport TelemetryClient = require(\"./TelemetryClient\");\r\nimport ServerRequestTracking = require(\"../AutoCollection/HttpRequests\");\r\nimport ClientRequestTracking = require(\"../AutoCollection/HttpDependencies\");\r\nimport Logging = require(\"./Logging\");\r\nimport Contracts = require(\"../Declarations/Contracts\");\r\n\r\n/**\r\n * Application Insights Telemetry Client for Node.JS. Provides the Application Insights TelemetryClient API\r\n * in addition to Node-specific helper functions.\r\n * Construct a new TelemetryClient to have an instance with a different configuration than the default client.\r\n * In most cases, `appInsights.defaultClient` should be used instead.\r\n */\r\nclass NodeClient extends TelemetryClient {\r\n\r\n /**\r\n * Log RequestTelemetry from HTTP request and response. This method will log immediately without waiting for request completion\r\n * and it requires duration parameter to be specified on NodeHttpRequestTelemetry object.\r\n * Use trackNodeHttpRequest function to log the telemetry after request completion\r\n * @param telemetry Object encapsulating incoming request, response and duration information \r\n */\r\n public trackNodeHttpRequestSync(telemetry: Contracts.NodeHttpRequestTelemetry) {\r\n if (telemetry && telemetry.request && telemetry.response && telemetry.duration) {\r\n ServerRequestTracking.trackRequestSync(this, telemetry);\r\n } else {\r\n Logging.warn(\"trackNodeHttpRequestSync requires NodeHttpRequestTelemetry object with request, response and duration specified.\");\r\n }\r\n }\r\n\r\n /**\r\n * Log RequestTelemetry from HTTP request and response. This method will `follow` the request to completion.\r\n * Use trackNodeHttpRequestSync function to log telemetry immediately without waiting for request completion\r\n * @param telemetry Object encapsulating incoming request and response information\r\n */\r\n public trackNodeHttpRequest(telemetry: Contracts.NodeHttpRequestTelemetry) {\r\n if (telemetry.duration || telemetry.error) {\r\n Logging.warn(\"trackNodeHttpRequest will ignore supplied duration and error parameters. These values are collected from the request and response objects.\");\r\n }\r\n if (telemetry && telemetry.request && telemetry.response) {\r\n ServerRequestTracking.trackRequest(this, telemetry);\r\n } else {\r\n Logging.warn(\"trackNodeHttpRequest requires NodeHttpRequestTelemetry object with request and response specified.\");\r\n }\r\n }\r\n\r\n /**\r\n * Log DependencyTelemetry from outgoing HTTP request. This method will instrument the outgoing request and append\r\n * the specified headers and will log the telemetry when outgoing request is complete\r\n * @param telemetry Object encapsulating outgoing request information\r\n */\r\n public trackNodeHttpDependency(telemetry: Contracts.NodeHttpDependencyTelemetry) {\r\n if (telemetry && telemetry.request) {\r\n ClientRequestTracking.trackRequest(this, telemetry);\r\n }\r\n else {\r\n Logging.warn(\"trackNodeHttpDependency requires NodeHttpDependencyTelemetry object with request specified.\");\r\n }\r\n }\r\n}\r\n\r\nexport = NodeClient"]}
@@ -0,0 +1,17 @@
import Contracts = require("../Declarations/Contracts");
import Config = require("./Config");
import Context = require("./Context");
declare class QuickPulseEnvelopeFactory {
private static keys;
static createQuickPulseEnvelope(metrics: Contracts.MetricQuickPulse[], documents: Contracts.DocumentQuickPulse[], config: Config, context: Context): Contracts.EnvelopeQuickPulse;
static createQuickPulseMetric(telemetry: Contracts.MetricTelemetry): Contracts.MetricQuickPulse;
static telemetryEnvelopeToQuickPulseDocument(envelope: Contracts.Envelope): Contracts.DocumentQuickPulse;
private static createQuickPulseEventDocument(envelope);
private static createQuickPulseTraceDocument(envelope);
private static createQuickPulseExceptionDocument(envelope);
private static createQuickPulseRequestDocument(envelope);
private static createQuickPulseDependencyDocument(envelope);
private static createQuickPulseDocument(envelope);
private static aggregateProperties(envelope);
}
export = QuickPulseEnvelopeFactory;
@@ -0,0 +1,161 @@
"use strict";
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
var os = require("os");
var Contracts = require("../Declarations/Contracts");
var Constants = require("../Declarations/Constants");
var Util = require("./Util");
var Logging = require("./Logging");
var StreamId = Util.w3cTraceId(); // Create a guid
var QuickPulseEnvelopeFactory = (function () {
function QuickPulseEnvelopeFactory() {
}
QuickPulseEnvelopeFactory.createQuickPulseEnvelope = function (metrics, documents, config, context) {
var machineName = (os && typeof os.hostname === "function"
&& os.hostname()) || "Unknown"; // Note: os.hostname() was added in node v0.3.3
var instance = (context.tags
&& context.keys
&& context.keys.cloudRoleInstance
&& context.tags[context.keys.cloudRoleInstance]) || machineName;
var envelope = {
Documents: documents.length > 0 ? documents : null,
InstrumentationKey: config.instrumentationKey || "",
Metrics: metrics.length > 0 ? metrics : null,
InvariantVersion: 1,
Timestamp: "/Date(" + Date.now() + ")/",
Version: context.tags[context.keys.internalSdkVersion],
StreamId: StreamId,
MachineName: machineName,
Instance: instance
};
return envelope;
};
QuickPulseEnvelopeFactory.createQuickPulseMetric = function (telemetry) {
var data;
data = {
Name: telemetry.name,
Value: telemetry.value,
Weight: telemetry.count || 1
};
return data;
};
QuickPulseEnvelopeFactory.telemetryEnvelopeToQuickPulseDocument = function (envelope) {
switch (envelope.data.baseType) {
case Contracts.TelemetryTypeString.Event:
return QuickPulseEnvelopeFactory.createQuickPulseEventDocument(envelope);
case Contracts.TelemetryTypeString.Exception:
return QuickPulseEnvelopeFactory.createQuickPulseExceptionDocument(envelope);
case Contracts.TelemetryTypeString.Trace:
return QuickPulseEnvelopeFactory.createQuickPulseTraceDocument(envelope);
case Contracts.TelemetryTypeString.Dependency:
return QuickPulseEnvelopeFactory.createQuickPulseDependencyDocument(envelope);
case Contracts.TelemetryTypeString.Request:
return QuickPulseEnvelopeFactory.createQuickPulseRequestDocument(envelope);
}
return null;
};
QuickPulseEnvelopeFactory.createQuickPulseEventDocument = function (envelope) {
var document = QuickPulseEnvelopeFactory.createQuickPulseDocument(envelope);
var name = envelope.data.baseData.name;
var eventDocument = __assign({}, document, { Name: name });
return eventDocument;
};
QuickPulseEnvelopeFactory.createQuickPulseTraceDocument = function (envelope) {
var document = QuickPulseEnvelopeFactory.createQuickPulseDocument(envelope);
var severityLevel = envelope.data.baseData.severityLevel || 0;
var traceDocument = __assign({}, document, { Message: envelope.data.baseData.message, SeverityLevel: Contracts.SeverityLevel[severityLevel] });
return traceDocument;
};
QuickPulseEnvelopeFactory.createQuickPulseExceptionDocument = function (envelope) {
var document = QuickPulseEnvelopeFactory.createQuickPulseDocument(envelope);
var exceptionDetails = envelope.data.baseData.exceptions;
var exception = '';
var exceptionMessage = '';
var exceptionType = '';
// Try to fill exception information from first error only
if (exceptionDetails && exceptionDetails.length > 0) {
// Try to grab the stack from parsedStack or stack
if (exceptionDetails[0].parsedStack && exceptionDetails[0].parsedStack.length > 0) {
exceptionDetails[0].parsedStack.forEach(function (err) {
exception += err.assembly + "\n";
});
}
else if (exceptionDetails[0].stack && exceptionDetails[0].stack.length > 0) {
exception = exceptionDetails[0].stack;
}
exceptionMessage = exceptionDetails[0].message;
exceptionType = exceptionDetails[0].typeName;
}
var exceptionDocument = __assign({}, document, { Exception: exception, ExceptionMessage: exceptionMessage, ExceptionType: exceptionType });
return exceptionDocument;
};
QuickPulseEnvelopeFactory.createQuickPulseRequestDocument = function (envelope) {
var document = QuickPulseEnvelopeFactory.createQuickPulseDocument(envelope);
var baseData = envelope.data.baseData;
var requestDocument = __assign({}, document, { Name: baseData.name, Success: baseData.success, Duration: baseData.duration, ResponseCode: baseData.responseCode, OperationName: baseData.name // TODO: is this correct?
});
return requestDocument;
};
QuickPulseEnvelopeFactory.createQuickPulseDependencyDocument = function (envelope) {
var document = QuickPulseEnvelopeFactory.createQuickPulseDocument(envelope);
var baseData = envelope.data.baseData;
var dependencyDocument = __assign({}, document, { Name: baseData.name, Target: baseData.target, Success: baseData.success, Duration: baseData.duration, ResultCode: baseData.resultCode, CommandName: baseData.data, OperationName: document.OperationId, DependencyTypeName: baseData.type });
return dependencyDocument;
};
QuickPulseEnvelopeFactory.createQuickPulseDocument = function (envelope) {
var documentType;
var __type;
var operationId, properties;
if (envelope.data.baseType) {
__type = Constants.TelemetryTypeStringToQuickPulseType[envelope.data.baseType];
documentType = Constants.TelemetryTypeStringToQuickPulseDocumentType[envelope.data.baseType];
}
else {
// Remark: This should never be hit because createQuickPulseDocument is only called within
// valid baseType values
Logging.warn("Document type invalid; not sending live metric document", envelope.data.baseType);
}
operationId = envelope.tags[QuickPulseEnvelopeFactory.keys.operationId];
properties = QuickPulseEnvelopeFactory.aggregateProperties(envelope);
var document = {
DocumentType: documentType,
__type: __type,
OperationId: operationId,
Version: "1.0",
Properties: properties
};
return document;
};
QuickPulseEnvelopeFactory.aggregateProperties = function (envelope) {
var properties = [];
// Collect measurements
var meas = (envelope.data.baseData).measurements || {};
for (var key in meas) {
if (meas.hasOwnProperty(key)) {
var value = meas[key];
var property = { key: key, value: value };
properties.push(property);
}
}
// Collect properties
var props = (envelope.data.baseData).properties || {};
for (var key in props) {
if (props.hasOwnProperty(key)) {
var value = props[key];
var property = { key: key, value: value };
properties.push(property);
}
}
return properties;
};
QuickPulseEnvelopeFactory.keys = new Contracts.ContextTagKeys();
return QuickPulseEnvelopeFactory;
}());
module.exports = QuickPulseEnvelopeFactory;
//# sourceMappingURL=QuickPulseEnvelopeFactory.js.map
File diff suppressed because one or more lines are too long
+15
View File
@@ -0,0 +1,15 @@
/// <reference types="node" />
import Config = require("./Config");
import * as http from "http";
import * as Contracts from "../Declarations/Contracts";
declare class QuickPulseSender {
private static TAG;
private static MAX_QPS_FAILURES_BEFORE_WARN;
private _config;
private _consecutiveErrors;
constructor(config: Config);
ping(envelope: Contracts.EnvelopeQuickPulse, done: (shouldPOST?: boolean, res?: http.IncomingMessage) => void): void;
post(envelope: Contracts.EnvelopeQuickPulse, done: (shouldPOST?: boolean, res?: http.IncomingMessage) => void): void;
private _submitData(envelope, done, postOrPing);
}
export = QuickPulseSender;
+77
View File
@@ -0,0 +1,77 @@
"use strict";
var https = require("https");
var AutoCollectHttpDependencies = require("../AutoCollection/HttpDependencies");
var Logging = require("./Logging");
var QuickPulseUtil = require("./QuickPulseUtil");
var Util = require("./Util");
var QuickPulseConfig = {
method: "POST",
time: "x-ms-qps-transmission-time",
subscribed: "x-ms-qps-subscribed"
};
var QuickPulseSender = (function () {
function QuickPulseSender(config) {
this._config = config;
this._consecutiveErrors = 0;
}
QuickPulseSender.prototype.ping = function (envelope, done) {
this._submitData(envelope, done, "ping");
};
QuickPulseSender.prototype.post = function (envelope, done) {
// Important: When POSTing data, envelope must be an array
this._submitData([envelope], done, "post");
};
QuickPulseSender.prototype._submitData = function (envelope, done, postOrPing) {
var _this = this;
var payload = JSON.stringify(envelope);
var options = (_a = {},
_a[AutoCollectHttpDependencies.disableCollectionRequestOption] = true,
_a.host = this._config.quickPulseHost,
_a.method = QuickPulseConfig.method,
_a.path = "/QuickPulseService.svc/" + postOrPing + "?ikey=" + this._config.instrumentationKey,
_a.headers = (_b = {
'Expect': '100-continue'
},
_b[QuickPulseConfig.time] = QuickPulseUtil.getTransmissionTime(),
_b['Content-Type'] = 'application\/json',
_b['Content-Length'] = Buffer.byteLength(payload),
_b),
_a);
// HTTPS only
if (this._config.httpsAgent) {
options.agent = this._config.httpsAgent;
}
else {
options.agent = Util.tlsRestrictedAgent;
}
var req = https.request(options, function (res) {
var shouldPOSTData = res.headers[QuickPulseConfig.subscribed] === "true";
_this._consecutiveErrors = 0;
done(shouldPOSTData, res);
});
req.on("error", function (error) {
// Unable to contact qps endpoint.
// Do nothing for now.
_this._consecutiveErrors++;
// LOG every error, but WARN instead when X number of consecutive errors occur
var notice = "Transient error connecting to the Live Metrics endpoint. This packet will not appear in your Live Metrics Stream. Error:";
if (_this._consecutiveErrors % QuickPulseSender.MAX_QPS_FAILURES_BEFORE_WARN === 0) {
notice = "Live Metrics endpoint could not be reached " + _this._consecutiveErrors + " consecutive times. Most recent error:";
Logging.warn(QuickPulseSender.TAG, notice, error);
}
else {
// Potentially transient error, do not change the ping/post state yet.
Logging.info(QuickPulseSender.TAG, notice, error);
}
done();
});
req.write(payload);
req.end();
var _a, _b;
};
QuickPulseSender.TAG = "QuickPulseSender";
QuickPulseSender.MAX_QPS_FAILURES_BEFORE_WARN = 25;
return QuickPulseSender;
}());
module.exports = QuickPulseSender;
//# sourceMappingURL=QuickPulseSender.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,61 @@
import Config = require("./Config");
import Context = require("./Context");
import * as Contracts from "../Declarations/Contracts";
/** State Container for sending to the QuickPulse Service */
declare class QuickPulseStateManager {
config: Config;
context: Context;
private static MAX_POST_WAIT_TIME;
private static MAX_PING_WAIT_TIME;
private static FALLBACK_INTERVAL;
private static PING_INTERVAL;
private static POST_INTERVAL;
private _isCollectingData;
private _sender;
private _isEnabled;
private _lastSuccessTime;
private _lastSendSucceeded;
private _handle;
private _metrics;
private _documents;
private _collectors;
constructor(iKey?: string, context?: Context);
/**
*
* @param collector
*/
addCollector(collector: any): void;
/**
* Override of TelemetryClient.trackMetric
*/
trackMetric(telemetry: Contracts.MetricTelemetry): void;
/**
* Add a document to the current buffer
* @param envelope
*/
addDocument(envelope: Contracts.Envelope): void;
/**
* Enable or disable communication with QuickPulseService
* @param isEnabled
*/
enable(isEnabled: boolean): void;
/**
* Enable or disable all collectors in this instance
* @param enable
*/
private enableCollectors(enable);
/**
* Add the metric to this buffer. If same metric already exists in this buffer, add weight to it
* @param telemetry
*/
private _addMetric(telemetry);
private _resetQuickPulseBuffer();
private _goQuickPulse();
private _ping(envelope);
private _post(envelope);
/**
* Change the current QPS send state. (shouldPOST == undefined) --> error, but do not change the state yet.
*/
private _quickPulseDone(shouldPOST?, res?);
}
export = QuickPulseStateManager;
+159
View File
@@ -0,0 +1,159 @@
"use strict";
var Logging = require("./Logging");
var Config = require("./Config");
var QuickPulseEnvelopeFactory = require("./QuickPulseEnvelopeFactory");
var QuickPulseSender = require("./QuickPulseSender");
var Constants = require("../Declarations/Constants");
var Context = require("./Context");
/** State Container for sending to the QuickPulse Service */
var QuickPulseStateManager = (function () {
function QuickPulseStateManager(iKey, context) {
this._isCollectingData = false;
this._lastSuccessTime = Date.now();
this._lastSendSucceeded = true;
this._metrics = {};
this._documents = [];
this._collectors = [];
this.config = new Config(iKey);
this.context = context || new Context();
this._sender = new QuickPulseSender(this.config);
this._isEnabled = false;
}
/**
*
* @param collector
*/
QuickPulseStateManager.prototype.addCollector = function (collector) {
this._collectors.push(collector);
};
/**
* Override of TelemetryClient.trackMetric
*/
QuickPulseStateManager.prototype.trackMetric = function (telemetry) {
this._addMetric(telemetry);
};
/**
* Add a document to the current buffer
* @param envelope
*/
QuickPulseStateManager.prototype.addDocument = function (envelope) {
var document = QuickPulseEnvelopeFactory.telemetryEnvelopeToQuickPulseDocument(envelope);
if (document) {
this._documents.push(document);
}
};
/**
* Enable or disable communication with QuickPulseService
* @param isEnabled
*/
QuickPulseStateManager.prototype.enable = function (isEnabled) {
if (isEnabled && !this._isEnabled) {
this._isEnabled = true;
this._goQuickPulse();
}
else if (!isEnabled && this._isEnabled) {
this._isEnabled = false;
clearTimeout(this._handle);
this._handle = undefined;
}
};
/**
* Enable or disable all collectors in this instance
* @param enable
*/
QuickPulseStateManager.prototype.enableCollectors = function (enable) {
this._collectors.forEach(function (collector) {
collector.enable(enable);
});
};
/**
* Add the metric to this buffer. If same metric already exists in this buffer, add weight to it
* @param telemetry
*/
QuickPulseStateManager.prototype._addMetric = function (telemetry) {
var value = telemetry.value;
var count = telemetry.count || 1;
var name = Constants.PerformanceToQuickPulseCounter[telemetry.name];
if (name) {
if (this._metrics[name]) {
this._metrics[name].Value = (this._metrics[name].Value * this._metrics[name].Weight + value * count) / (this._metrics[name].Weight + count);
this._metrics[name].Weight += count;
}
else {
this._metrics[name] = QuickPulseEnvelopeFactory.createQuickPulseMetric(telemetry);
this._metrics[name].Name = name;
this._metrics[name].Weight = 1;
}
}
};
QuickPulseStateManager.prototype._resetQuickPulseBuffer = function () {
delete this._metrics;
this._metrics = {};
this._documents.length = 0;
};
QuickPulseStateManager.prototype._goQuickPulse = function () {
var _this = this;
// Create envelope from Documents and Metrics
var metrics = Object.keys(this._metrics).map(function (k) { return _this._metrics[k]; });
var envelope = QuickPulseEnvelopeFactory.createQuickPulseEnvelope(metrics, this._documents.slice(), this.config, this.context);
// Clear this document, metric buffer
this._resetQuickPulseBuffer();
// Send it to QuickPulseService, if collecting
if (this._isCollectingData) {
this._post(envelope);
}
else {
this._ping(envelope);
}
var currentTimeout = this._isCollectingData ? QuickPulseStateManager.POST_INTERVAL : QuickPulseStateManager.PING_INTERVAL;
if (this._isCollectingData && Date.now() - this._lastSuccessTime >= QuickPulseStateManager.MAX_POST_WAIT_TIME && !this._lastSendSucceeded) {
// Haven't posted successfully in 20 seconds, so wait 60 seconds and ping
this._isCollectingData = false;
currentTimeout = QuickPulseStateManager.FALLBACK_INTERVAL;
}
else if (!this._isCollectingData && Date.now() - this._lastSuccessTime >= QuickPulseStateManager.MAX_PING_WAIT_TIME && !this._lastSendSucceeded) {
// Haven't pinged successfully in 60 seconds, so wait another 60 seconds
currentTimeout = QuickPulseStateManager.FALLBACK_INTERVAL;
}
this._lastSendSucceeded = null;
this._handle = setTimeout(this._goQuickPulse.bind(this), currentTimeout);
this._handle.unref(); // Don't block apps from terminating
};
QuickPulseStateManager.prototype._ping = function (envelope) {
this._sender.ping(envelope, this._quickPulseDone.bind(this));
};
QuickPulseStateManager.prototype._post = function (envelope) {
this._sender.post(envelope, this._quickPulseDone.bind(this));
};
/**
* Change the current QPS send state. (shouldPOST == undefined) --> error, but do not change the state yet.
*/
QuickPulseStateManager.prototype._quickPulseDone = function (shouldPOST, res) {
if (shouldPOST != undefined) {
if (this._isCollectingData !== shouldPOST) {
Logging.info("Live Metrics sending data", shouldPOST);
this.enableCollectors(shouldPOST);
}
this._isCollectingData = shouldPOST;
if (res && res.statusCode < 300 && res.statusCode >= 200) {
this._lastSuccessTime = Date.now();
this._lastSendSucceeded = true;
}
else {
this._lastSendSucceeded = false;
}
}
else {
// Received an error, keep the state as is
this._lastSendSucceeded = false;
}
};
QuickPulseStateManager.MAX_POST_WAIT_TIME = 20000;
QuickPulseStateManager.MAX_PING_WAIT_TIME = 60000;
QuickPulseStateManager.FALLBACK_INTERVAL = 60000;
QuickPulseStateManager.PING_INTERVAL = 5000;
QuickPulseStateManager.POST_INTERVAL = 1000;
return QuickPulseStateManager;
}());
module.exports = QuickPulseStateManager;
//# sourceMappingURL=QuickPulseStateManager.js.map
File diff suppressed because one or more lines are too long
+4
View File
@@ -0,0 +1,4 @@
declare const _default: {
getTransmissionTime: () => number;
};
export = _default;
+15
View File
@@ -0,0 +1,15 @@
"use strict";
/**
* @description UTC time the request was made. Expressed as the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight on January 1, 0001. This is used for clock skew calculations, so the value can never be stale (cached).
*
* @example
* 8/5/2020 10:15:00 PM UTC => 637322625000000000
* 8/5/2020 10:15:01 PM UTC => 637322625010000000
*
* @returns {number}
*/
var getTransmissionTime = function () {
return (Date.now() + 62135596800000) * 10000;
};
module.exports = { getTransmissionTime: getTransmissionTime };
//# sourceMappingURL=QuickPulseUtil.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"QuickPulseUtil.js","sourceRoot":"","sources":["../../Library/QuickPulseUtil.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;AACH,IAAM,mBAAmB,GAAG;IACxB,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,CAAC,GAAG,KAAK,CAAC;AACjD,CAAC,CAAA;AAED,iBAAS,EAAC,mBAAmB,qBAAA,EAAC,CAAC","sourcesContent":["/**\r\n * @description UTC time the request was made. Expressed as the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight on January 1, 0001. This is used for clock skew calculations, so the value can never be stale (cached).\r\n *\r\n * @example\r\n * 8/5/2020 10:15:00 PM UTC => 637322625000000000\r\n * 8/5/2020 10:15:01 PM UTC => 637322625010000000\r\n *\r\n * @returns {number}\r\n */\r\nconst getTransmissionTime = (): number => {\r\n return (Date.now() + 62135596800000) * 10000;\r\n}\r\n\r\nexport = {getTransmissionTime};\r\n"]}
@@ -0,0 +1,12 @@
declare const _default: {
requestContextHeader: string;
requestContextSourceKey: string;
requestContextTargetKey: string;
requestIdHeader: string;
parentIdHeader: string;
rootIdHeader: string;
correlationContextHeader: string;
traceparentHeader: string;
traceStateHeader: string;
};
export = _default;
+45
View File
@@ -0,0 +1,45 @@
"use strict";
module.exports = {
/**
* Request-Context header
*/
requestContextHeader: "request-context",
/**
* Source instrumentation header that is added by an application while making http
* requests and retrieved by the other application when processing incoming requests.
*/
requestContextSourceKey: "appId",
/**
* Target instrumentation header that is added to the response and retrieved by the
* calling application when processing incoming responses.
*/
requestContextTargetKey: "appId",
/**
* Request-Id header
*/
requestIdHeader: "request-id",
/**
* Legacy Header containing the id of the immediate caller
*/
parentIdHeader: "x-ms-request-id",
/**
* Legacy Header containing the correlation id that kept the same for every telemetry item
* across transactions
*/
rootIdHeader: "x-ms-request-root-id",
/**
* Correlation-Context header
*
* Not currently actively used, but the contents should be passed from incoming to outgoing requests
*/
correlationContextHeader: "correlation-context",
/**
* W3C distributed tracing protocol header
*/
traceparentHeader: "traceparent",
/**
* W3C distributed tracing protocol state header
*/
traceStateHeader: "tracestate"
};
//# sourceMappingURL=RequestResponseHeaders.js.map
@@ -0,0 +1 @@
{"version":3,"file":"RequestResponseHeaders.js","sourceRoot":"","sources":["../../Library/RequestResponseHeaders.ts"],"names":[],"mappings":";AAAA,iBAAS;IAEL;;OAEG;IACH,oBAAoB,EAAE,iBAAiB;IAEvC;;;OAGG;IACH,uBAAuB,EAAE,OAAO;IAEhC;;;OAGG;IACH,uBAAuB,EAAE,OAAO;IAEhC;;OAEG;IACH,eAAe,EAAE,YAAY;IAE7B;;OAEG;IACH,cAAc,EAAE,iBAAiB;IAEjC;;;OAGG;IACH,YAAY,EAAE,sBAAsB;IAEpC;;;;OAIG;IACH,wBAAwB,EAAE,qBAAqB;IAE/C;;OAEG;IACH,iBAAiB,EAAE,aAAa;IAEhC;;OAEG;IACH,gBAAgB,EAAE,YAAY;CACjC,CAAA","sourcesContent":["export = {\r\n\r\n /**\r\n * Request-Context header\r\n */\r\n requestContextHeader: \"request-context\",\r\n\r\n /**\r\n * Source instrumentation header that is added by an application while making http\r\n * requests and retrieved by the other application when processing incoming requests.\r\n */\r\n requestContextSourceKey: \"appId\",\r\n\r\n /**\r\n * Target instrumentation header that is added to the response and retrieved by the\r\n * calling application when processing incoming responses.\r\n */\r\n requestContextTargetKey: \"appId\",\r\n\r\n /**\r\n * Request-Id header\r\n */\r\n requestIdHeader: \"request-id\",\r\n\r\n /**\r\n * Legacy Header containing the id of the immediate caller\r\n */\r\n parentIdHeader: \"x-ms-request-id\",\r\n\r\n /**\r\n * Legacy Header containing the correlation id that kept the same for every telemetry item\r\n * across transactions\r\n */\r\n rootIdHeader: \"x-ms-request-root-id\",\r\n\r\n /**\r\n * Correlation-Context header\r\n *\r\n * Not currently actively used, but the contents should be passed from incoming to outgoing requests\r\n */\r\n correlationContextHeader: \"correlation-context\",\r\n\r\n /**\r\n * W3C distributed tracing protocol header\r\n */\r\n traceparentHeader: \"traceparent\",\r\n\r\n /**\r\n * W3C distributed tracing protocol state header\r\n */\r\n traceStateHeader: \"tracestate\"\r\n}\r\n"]}
+63
View File
@@ -0,0 +1,63 @@
/// <reference types="node" />
import Config = require("./Config");
declare class Sender {
private static TAG;
private static ICACLS_PATH;
private static POWERSHELL_PATH;
private static ACLED_DIRECTORIES;
private static ACL_IDENTITY;
static WAIT_BETWEEN_RESEND: number;
static MAX_BYTES_ON_DISK: number;
static MAX_CONNECTION_FAILURES_BEFORE_WARN: number;
static TEMPDIR_PREFIX: string;
static OS_PROVIDES_FILE_PROTECTION: boolean;
static USE_ICACLS: boolean;
private _config;
private _storageDirectory;
private _onSuccess;
private _onError;
private _enableDiskRetryMode;
private _numConsecutiveFailures;
private _resendTimer;
protected _resendInterval: number;
protected _maxBytesOnDisk: number;
constructor(config: Config, onSuccess?: (response: string) => void, onError?: (error: Error) => void);
/**
* Enable or disable offline mode
*/
setDiskRetryMode(value: boolean, resendInterval?: number, maxBytesOnDisk?: number): void;
send(payload: Buffer, callback?: (v: string) => void): void;
saveOnCrash(payload: string): void;
private _runICACLS(args, callback);
private _runICACLSSync(args);
private _getACLIdentity(callback);
private _getACLIdentitySync();
private _getACLArguments(directory, identity);
private _applyACLRules(directory, callback);
private _applyACLRulesSync(directory);
private _confirmDirExists(directory, callback);
/**
* Computes the size (in bytes) of all files in a directory at the root level. Asynchronously.
*/
private _getShallowDirectorySize(directory, callback);
/**
* Computes the size (in bytes) of all files in a directory at the root level. Synchronously.
*/
private _getShallowDirectorySizeSync(directory);
/**
* Stores the payload as a json file on disk in the temp directory
*/
private _storeToDisk(payload);
/**
* Stores the payload as a json file on disk using sync file operations
* this is used when storing data before crashes
*/
private _storeToDiskSync(payload);
/**
* Check for temp telemetry files
* reads the first file if exist, deletes it and tries to send its load
*/
private _sendFirstFileOnDisk();
private _onErrorHelper(error);
}
export = Sender;
+472
View File
@@ -0,0 +1,472 @@
"use strict";
var fs = require("fs");
var os = require("os");
var path = require("path");
var zlib = require("zlib");
var child_process = require("child_process");
var Logging = require("./Logging");
var AutoCollectHttpDependencies = require("../AutoCollection/HttpDependencies");
var Util = require("./Util");
var Sender = (function () {
function Sender(config, onSuccess, onError) {
this._config = config;
this._onSuccess = onSuccess;
this._onError = onError;
this._enableDiskRetryMode = false;
this._resendInterval = Sender.WAIT_BETWEEN_RESEND;
this._maxBytesOnDisk = Sender.MAX_BYTES_ON_DISK;
this._numConsecutiveFailures = 0;
this._resendTimer = null;
if (!Sender.OS_PROVIDES_FILE_PROTECTION) {
// Node's chmod levels do not appropriately restrict file access on Windows
// Use the built-in command line tool ICACLS on Windows to properly restrict
// access to the temporary directory used for disk retry mode.
if (Sender.USE_ICACLS) {
// This should be async - but it's currently safer to have this synchronous
// This guarantees we can immediately fail setDiskRetryMode if we need to
try {
Sender.OS_PROVIDES_FILE_PROTECTION = fs.existsSync(Sender.ICACLS_PATH);
}
catch (e) { }
if (!Sender.OS_PROVIDES_FILE_PROTECTION) {
Logging.warn(Sender.TAG, "Could not find ICACLS in expected location! This is necessary to use disk retry mode on Windows.");
}
}
else {
// chmod works everywhere else
Sender.OS_PROVIDES_FILE_PROTECTION = true;
}
}
}
/**
* Enable or disable offline mode
*/
Sender.prototype.setDiskRetryMode = function (value, resendInterval, maxBytesOnDisk) {
this._enableDiskRetryMode = Sender.OS_PROVIDES_FILE_PROTECTION && value;
if (typeof resendInterval === 'number' && resendInterval >= 0) {
this._resendInterval = Math.floor(resendInterval);
}
if (typeof maxBytesOnDisk === 'number' && maxBytesOnDisk >= 0) {
this._maxBytesOnDisk = Math.floor(maxBytesOnDisk);
}
if (value && !Sender.OS_PROVIDES_FILE_PROTECTION) {
this._enableDiskRetryMode = false;
Logging.warn(Sender.TAG, "Ignoring request to enable disk retry mode. Sufficient file protection capabilities were not detected.");
}
};
Sender.prototype.send = function (payload, callback) {
var _this = this;
var endpointUrl = this._config.endpointUrl;
// todo: investigate specifying an agent here: https://nodejs.org/api/http.html#http_class_http_agent
var options = {
method: "POST",
withCredentials: false,
headers: {
"Content-Type": "application/x-json-stream"
}
};
zlib.gzip(payload, function (err, buffer) {
var dataToSend = buffer;
if (err) {
Logging.warn(err);
dataToSend = payload; // something went wrong so send without gzip
options.headers["Content-Length"] = payload.length.toString();
}
else {
options.headers["Content-Encoding"] = "gzip";
options.headers["Content-Length"] = buffer.length;
}
Logging.info(Sender.TAG, options);
// Ensure this request is not captured by auto-collection.
options[AutoCollectHttpDependencies.disableCollectionRequestOption] = true;
var requestCallback = function (res) {
res.setEncoding("utf-8");
//returns empty if the data is accepted
var responseString = "";
res.on("data", function (data) {
responseString += data;
});
res.on("end", function () {
_this._numConsecutiveFailures = 0;
Logging.info(Sender.TAG, responseString);
if (typeof _this._onSuccess === "function") {
_this._onSuccess(responseString);
}
if (typeof callback === "function") {
callback(responseString);
}
if (_this._enableDiskRetryMode) {
// try to send any cached events if the user is back online
if (res.statusCode === 200) {
if (!_this._resendTimer) {
_this._resendTimer = setTimeout(function () {
_this._resendTimer = null;
_this._sendFirstFileOnDisk();
}, _this._resendInterval);
_this._resendTimer.unref();
}
// store to disk in case of burst throttling
}
else if (res.statusCode === 408 ||
res.statusCode === 429 ||
res.statusCode === 439 ||
res.statusCode === 500 ||
res.statusCode === 503) {
// TODO: Do not support partial success (206) until _sendFirstFileOnDisk checks payload age
_this._storeToDisk(payload);
}
}
});
};
var req = Util.makeRequest(_this._config, endpointUrl, options, requestCallback);
req.on("error", function (error) {
// todo: handle error codes better (group to recoverable/non-recoverable and persist)
_this._numConsecutiveFailures++;
// Only use warn level if retries are disabled or we've had some number of consecutive failures sending data
// This is because warn level is printed in the console by default, and we don't want to be noisy for transient and self-recovering errors
// Continue informing on each failure if verbose logging is being used
if (!_this._enableDiskRetryMode || _this._numConsecutiveFailures > 0 && _this._numConsecutiveFailures % Sender.MAX_CONNECTION_FAILURES_BEFORE_WARN === 0) {
var notice = "Ingestion endpoint could not be reached. This batch of telemetry items has been lost. Use Disk Retry Caching to enable resending of failed telemetry. Error:";
if (_this._enableDiskRetryMode) {
notice = "Ingestion endpoint could not be reached " + _this._numConsecutiveFailures + " consecutive times. There may be resulting telemetry loss. Most recent error:";
}
Logging.warn(Sender.TAG, notice, error);
}
else {
var notice = "Transient failure to reach ingestion endpoint. This batch of telemetry items will be retried. Error:";
Logging.info(Sender.TAG, notice, error);
}
_this._onErrorHelper(error);
if (typeof callback === "function") {
var errorMessage = "error sending telemetry";
if (error && (typeof error.toString === "function")) {
errorMessage = error.toString();
}
callback(errorMessage);
}
if (_this._enableDiskRetryMode) {
_this._storeToDisk(payload);
}
});
req.write(dataToSend);
req.end();
});
};
Sender.prototype.saveOnCrash = function (payload) {
if (this._enableDiskRetryMode) {
this._storeToDiskSync(payload);
}
};
Sender.prototype._runICACLS = function (args, callback) {
var aclProc = child_process.spawn(Sender.ICACLS_PATH, args, { windowsHide: true });
aclProc.on("error", function (e) { return callback(e); });
aclProc.on("close", function (code, signal) {
return callback(code === 0 ? null : new Error("Setting ACL restrictions did not succeed (ICACLS returned code " + code + ")"));
});
};
Sender.prototype._runICACLSSync = function (args) {
// Some very old versions of Node (< 0.11) don't have this
if (child_process.spawnSync) {
var aclProc = child_process.spawnSync(Sender.ICACLS_PATH, args, { windowsHide: true });
if (aclProc.error) {
throw aclProc.error;
}
else if (aclProc.status !== 0) {
throw new Error("Setting ACL restrictions did not succeed (ICACLS returned code " + aclProc.status + ")");
}
}
else {
throw new Error("Could not synchronously call ICACLS under current version of Node.js");
}
};
Sender.prototype._getACLIdentity = function (callback) {
if (Sender.ACL_IDENTITY) {
return callback(null, Sender.ACL_IDENTITY);
}
var psProc = child_process.spawn(Sender.POWERSHELL_PATH, ["-Command", "[System.Security.Principal.WindowsIdentity]::GetCurrent().Name"], {
windowsHide: true,
stdio: ['ignore', 'pipe', 'pipe'] // Needed to prevent hanging on Win 7
});
var data = "";
psProc.stdout.on("data", function (d) { return data += d; });
psProc.on("error", function (e) { return callback(e, null); });
psProc.on("close", function (code, signal) {
Sender.ACL_IDENTITY = data && data.trim();
return callback(code === 0 ? null : new Error("Getting ACL identity did not succeed (PS returned code " + code + ")"), Sender.ACL_IDENTITY);
});
};
Sender.prototype._getACLIdentitySync = function () {
if (Sender.ACL_IDENTITY) {
return Sender.ACL_IDENTITY;
}
// Some very old versions of Node (< 0.11) don't have this
if (child_process.spawnSync) {
var psProc = child_process.spawnSync(Sender.POWERSHELL_PATH, ["-Command", "[System.Security.Principal.WindowsIdentity]::GetCurrent().Name"], {
windowsHide: true,
stdio: ['ignore', 'pipe', 'pipe'] // Needed to prevent hanging on Win 7
});
if (psProc.error) {
throw psProc.error;
}
else if (psProc.status !== 0) {
throw new Error("Getting ACL identity did not succeed (PS returned code " + psProc.status + ")");
}
Sender.ACL_IDENTITY = psProc.stdout && psProc.stdout.toString().trim();
return Sender.ACL_IDENTITY;
}
else {
throw new Error("Could not synchronously get ACL identity under current version of Node.js");
}
};
Sender.prototype._getACLArguments = function (directory, identity) {
return [directory,
"/grant", "*S-1-5-32-544:(OI)(CI)F",
"/grant", identity + ":(OI)(CI)F",
"/inheritance:r"]; // Remove all inherited permissions
};
Sender.prototype._applyACLRules = function (directory, callback) {
var _this = this;
if (!Sender.USE_ICACLS) {
return callback(null);
}
// For performance, only run ACL rules if we haven't already during this session
if (Sender.ACLED_DIRECTORIES[directory] === undefined) {
// Avoid multiple calls race condition by setting ACLED_DIRECTORIES to false for this directory immediately
// If batches are being failed faster than the processes spawned below return, some data won't be stored to disk
// This is better than the alternative of potentially infinitely spawned processes
Sender.ACLED_DIRECTORIES[directory] = false;
// Restrict this directory to only current user and administrator access
this._getACLIdentity(function (err, identity) {
if (err) {
Sender.ACLED_DIRECTORIES[directory] = false; // false is used to cache failed (vs undefined which is "not yet tried")
return callback(err);
}
else {
_this._runICACLS(_this._getACLArguments(directory, identity), function (err) {
Sender.ACLED_DIRECTORIES[directory] = !err;
return callback(err);
});
}
});
}
else {
return callback(Sender.ACLED_DIRECTORIES[directory] ? null :
new Error("Setting ACL restrictions did not succeed (cached result)"));
}
};
Sender.prototype._applyACLRulesSync = function (directory) {
if (Sender.USE_ICACLS) {
// For performance, only run ACL rules if we haven't already during this session
if (Sender.ACLED_DIRECTORIES[directory] === undefined) {
this._runICACLSSync(this._getACLArguments(directory, this._getACLIdentitySync()));
Sender.ACLED_DIRECTORIES[directory] = true; // If we get here, it succeeded. _runIACLSSync will throw on failures
return;
}
else if (!Sender.ACLED_DIRECTORIES[directory]) {
throw new Error("Setting ACL restrictions did not succeed (cached result)");
}
}
};
Sender.prototype._confirmDirExists = function (directory, callback) {
var _this = this;
fs.lstat(directory, function (err, stats) {
if (err && err.code === 'ENOENT') {
fs.mkdir(directory, function (err) {
if (err && err.code !== 'EEXIST') {
callback(err);
}
else {
_this._applyACLRules(directory, callback);
}
});
}
else if (!err && stats.isDirectory()) {
_this._applyACLRules(directory, callback);
}
else {
callback(err || new Error("Path existed but was not a directory"));
}
});
};
/**
* Computes the size (in bytes) of all files in a directory at the root level. Asynchronously.
*/
Sender.prototype._getShallowDirectorySize = function (directory, callback) {
// Get the directory listing
fs.readdir(directory, function (err, files) {
if (err) {
return callback(err, -1);
}
var error = null;
var totalSize = 0;
var count = 0;
if (files.length === 0) {
callback(null, 0);
return;
}
// Query all file sizes
for (var i = 0; i < files.length; i++) {
fs.stat(path.join(directory, files[i]), function (err, fileStats) {
count++;
if (err) {
error = err;
}
else {
if (fileStats.isFile()) {
totalSize += fileStats.size;
}
}
if (count === files.length) {
// Did we get an error?
if (error) {
callback(error, -1);
}
else {
callback(error, totalSize);
}
}
});
}
});
};
/**
* Computes the size (in bytes) of all files in a directory at the root level. Synchronously.
*/
Sender.prototype._getShallowDirectorySizeSync = function (directory) {
var files = fs.readdirSync(directory);
var totalSize = 0;
for (var i = 0; i < files.length; i++) {
totalSize += fs.statSync(path.join(directory, files[i])).size;
}
return totalSize;
};
/**
* Stores the payload as a json file on disk in the temp directory
*/
Sender.prototype._storeToDisk = function (payload) {
var _this = this;
// tmpdir is /tmp for *nix and USERDIR/AppData/Local/Temp for Windows
var directory = path.join(os.tmpdir(), Sender.TEMPDIR_PREFIX + this._config.instrumentationKey);
// This will create the dir if it does not exist
// Default permissions on *nix are directory listing from other users but no file creations
Logging.info(Sender.TAG, "Checking existence of data storage directory: " + directory);
this._confirmDirExists(directory, function (error) {
if (error) {
Logging.warn(Sender.TAG, "Error while checking/creating directory: " + (error && error.message));
_this._onErrorHelper(error);
return;
}
_this._getShallowDirectorySize(directory, function (err, size) {
if (err || size < 0) {
Logging.warn(Sender.TAG, "Error while checking directory size: " + (err && err.message));
_this._onErrorHelper(err);
return;
}
else if (size > _this._maxBytesOnDisk) {
Logging.warn(Sender.TAG, "Not saving data due to max size limit being met. Directory size in bytes is: " + size);
return;
}
//create file - file name for now is the timestamp, a better approach would be a UUID but that
//would require an external dependency
var fileName = new Date().getTime() + ".ai.json";
var fileFullPath = path.join(directory, fileName);
// Mode 600 is w/r for creator and no read access for others (only applies on *nix)
// For Windows, ACL rules are applied to the entire directory (see logic in _confirmDirExists and _applyACLRules)
Logging.info(Sender.TAG, "saving data to disk at: " + fileFullPath);
fs.writeFile(fileFullPath, payload, { mode: 384 }, function (error) { return _this._onErrorHelper(error); });
});
});
};
/**
* Stores the payload as a json file on disk using sync file operations
* this is used when storing data before crashes
*/
Sender.prototype._storeToDiskSync = function (payload) {
// tmpdir is /tmp for *nix and USERDIR/AppData/Local/Temp for Windows
var directory = path.join(os.tmpdir(), Sender.TEMPDIR_PREFIX + this._config.instrumentationKey);
try {
Logging.info(Sender.TAG, "Checking existence of data storage directory: " + directory);
if (!fs.existsSync(directory)) {
fs.mkdirSync(directory);
}
// Make sure permissions are valid
this._applyACLRulesSync(directory);
var dirSize = this._getShallowDirectorySizeSync(directory);
if (dirSize > this._maxBytesOnDisk) {
Logging.info(Sender.TAG, "Not saving data due to max size limit being met. Directory size in bytes is: " + dirSize);
return;
}
//create file - file name for now is the timestamp, a better approach would be a UUID but that
//would require an external dependency
var fileName = new Date().getTime() + ".ai.json";
var fileFullPath = path.join(directory, fileName);
// Mode 600 is w/r for creator and no access for anyone else (only applies on *nix)
Logging.info(Sender.TAG, "saving data before crash to disk at: " + fileFullPath);
fs.writeFileSync(fileFullPath, payload, { mode: 384 });
}
catch (error) {
Logging.warn(Sender.TAG, "Error while saving data to disk: " + (error && error.message));
this._onErrorHelper(error);
}
};
/**
* Check for temp telemetry files
* reads the first file if exist, deletes it and tries to send its load
*/
Sender.prototype._sendFirstFileOnDisk = function () {
var _this = this;
var tempDir = path.join(os.tmpdir(), Sender.TEMPDIR_PREFIX + this._config.instrumentationKey);
fs.exists(tempDir, function (exists) {
if (exists) {
fs.readdir(tempDir, function (error, files) {
if (!error) {
files = files.filter(function (f) { return path.basename(f).indexOf(".ai.json") > -1; });
if (files.length > 0) {
var firstFile = files[0];
var filePath = path.join(tempDir, firstFile);
fs.readFile(filePath, function (error, payload) {
if (!error) {
// delete the file first to prevent double sending
fs.unlink(filePath, function (error) {
if (!error) {
_this.send(payload);
}
else {
_this._onErrorHelper(error);
}
});
}
else {
_this._onErrorHelper(error);
}
});
}
}
else {
_this._onErrorHelper(error);
}
});
}
});
};
Sender.prototype._onErrorHelper = function (error) {
if (typeof this._onError === "function") {
this._onError(error);
}
};
Sender.TAG = "Sender";
Sender.ICACLS_PATH = process.env.systemdrive + "/windows/system32/icacls.exe";
Sender.POWERSHELL_PATH = process.env.systemdrive + "/windows/system32/windowspowershell/v1.0/powershell.exe";
Sender.ACLED_DIRECTORIES = {};
Sender.ACL_IDENTITY = null;
// the amount of time the SDK will wait between resending cached data, this buffer is to avoid any throttling from the service side
Sender.WAIT_BETWEEN_RESEND = 60 * 1000;
Sender.MAX_BYTES_ON_DISK = 50 * 1000 * 1000;
Sender.MAX_CONNECTION_FAILURES_BEFORE_WARN = 5;
Sender.TEMPDIR_PREFIX = "appInsights-node";
Sender.OS_PROVIDES_FILE_PROTECTION = false;
Sender.USE_ICACLS = os.type() === "Windows_NT";
return Sender;
}());
module.exports = Sender;
//# sourceMappingURL=Sender.js.map
File diff suppressed because one or more lines are too long
+101
View File
@@ -0,0 +1,101 @@
import Config = require("./Config");
import Context = require("./Context");
import Contracts = require("../Declarations/Contracts");
import Channel = require("./Channel");
import FlushOptions = require("./FlushOptions");
import QuickPulseStateManager = require("./QuickPulseStateManager");
/**
* Application Insights telemetry client provides interface to track telemetry items, register telemetry initializers and
* and manually trigger immediate sending (flushing)
*/
declare class TelemetryClient {
private _telemetryProcessors;
private _enableAzureProperties;
config: Config;
context: Context;
commonProperties: {
[key: string]: string;
};
channel: Channel;
quickPulseClient: QuickPulseStateManager;
/**
* Constructs a new client of the client
* @param setupString the Connection String or Instrumentation Key to use (read from environment variable if not specified)
*/
constructor(setupString?: string);
/**
* Log information about availability of an application
* @param telemetry Object encapsulating tracking options
*/
trackAvailability(telemetry: Contracts.AvailabilityTelemetry): void;
/**
* Log a page view
* @param telemetry Object encapsulating tracking options
*/
trackPageView(telemetry: Contracts.PageViewTelemetry): void;
/**
* Log a trace message
* @param telemetry Object encapsulating tracking options
*/
trackTrace(telemetry: Contracts.TraceTelemetry): void;
/**
* Log a numeric value that is not associated with a specific event. Typically used to send regular reports of performance indicators.
* To send a single measurement, use just the first two parameters. If you take measurements very frequently, you can reduce the
* telemetry bandwidth by aggregating multiple measurements and sending the resulting average at intervals.
* @param telemetry Object encapsulating tracking options
*/
trackMetric(telemetry: Contracts.MetricTelemetry): void;
/**
* Log an exception
* @param telemetry Object encapsulating tracking options
*/
trackException(telemetry: Contracts.ExceptionTelemetry): void;
/**
* Log a user action or other occurrence.
* @param telemetry Object encapsulating tracking options
*/
trackEvent(telemetry: Contracts.EventTelemetry): void;
/**
* Log a request. Note that the default client will attempt to collect HTTP requests automatically so only use this for requests
* that aren't automatically captured or if you've disabled automatic request collection.
*
* @param telemetry Object encapsulating tracking options
*/
trackRequest(telemetry: Contracts.RequestTelemetry & Contracts.Identified): void;
/**
* Log a dependency. Note that the default client will attempt to collect dependencies automatically so only use this for dependencies
* that aren't automatically captured or if you've disabled automatic dependency collection.
*
* @param telemetry Object encapsulating tracking option
* */
trackDependency(telemetry: Contracts.DependencyTelemetry & Contracts.Identified): void;
/**
* Immediately send all queued telemetry.
* @param options Flush options, including indicator whether app is crashing and callback
*/
flush(options?: FlushOptions): void;
/**
* Generic track method for all telemetry types
* @param data the telemetry to send
* @param telemetryType specify the type of telemetry you are tracking from the list of Contracts.DataTypes
*/
track(telemetry: Contracts.Telemetry, telemetryType: Contracts.TelemetryType): void;
/**
* Automatically populate telemetry properties like RoleName when running in Azure
*
* @param value if true properties will be populated
*/
setAutoPopulateAzureProperties(value: boolean): void;
/**
* Adds telemetry processor to the collection. Telemetry processors will be called one by one
* before telemetry item is pushed for sending and in the order they were added.
*
* @param telemetryProcessor function, takes Envelope, and optional context object and returns boolean
*/
addTelemetryProcessor(telemetryProcessor: (envelope: Contracts.EnvelopeTelemetry, contextObjects?: {
[name: string]: any;
}) => boolean): void;
clearTelemetryProcessors(): void;
private runTelemetryProcessors(envelope, contextObjects);
}
export = TelemetryClient;
+189
View File
@@ -0,0 +1,189 @@
"use strict";
var url = require("url");
var Config = require("./Config");
var Context = require("./Context");
var Contracts = require("../Declarations/Contracts");
var Channel = require("./Channel");
var TelemetryProcessors = require("../TelemetryProcessors");
var CorrelationContextManager_1 = require("../AutoCollection/CorrelationContextManager");
var Sender = require("./Sender");
var Util = require("./Util");
var Logging = require("./Logging");
var EnvelopeFactory = require("./EnvelopeFactory");
/**
* Application Insights telemetry client provides interface to track telemetry items, register telemetry initializers and
* and manually trigger immediate sending (flushing)
*/
var TelemetryClient = (function () {
/**
* Constructs a new client of the client
* @param setupString the Connection String or Instrumentation Key to use (read from environment variable if not specified)
*/
function TelemetryClient(setupString) {
this._telemetryProcessors = [];
this._enableAzureProperties = false;
var config = new Config(setupString);
this.config = config;
this.context = new Context();
this.commonProperties = {};
var sender = new Sender(this.config);
this.channel = new Channel(function () { return config.disableAppInsights; }, function () { return config.maxBatchSize; }, function () { return config.maxBatchIntervalMs; }, sender);
}
/**
* Log information about availability of an application
* @param telemetry Object encapsulating tracking options
*/
TelemetryClient.prototype.trackAvailability = function (telemetry) {
this.track(telemetry, Contracts.TelemetryType.Availability);
};
/**
* Log a page view
* @param telemetry Object encapsulating tracking options
*/
TelemetryClient.prototype.trackPageView = function (telemetry) {
this.track(telemetry, Contracts.TelemetryType.PageView);
};
/**
* Log a trace message
* @param telemetry Object encapsulating tracking options
*/
TelemetryClient.prototype.trackTrace = function (telemetry) {
this.track(telemetry, Contracts.TelemetryType.Trace);
};
/**
* Log a numeric value that is not associated with a specific event. Typically used to send regular reports of performance indicators.
* To send a single measurement, use just the first two parameters. If you take measurements very frequently, you can reduce the
* telemetry bandwidth by aggregating multiple measurements and sending the resulting average at intervals.
* @param telemetry Object encapsulating tracking options
*/
TelemetryClient.prototype.trackMetric = function (telemetry) {
this.track(telemetry, Contracts.TelemetryType.Metric);
};
/**
* Log an exception
* @param telemetry Object encapsulating tracking options
*/
TelemetryClient.prototype.trackException = function (telemetry) {
if (telemetry && telemetry.exception && !Util.isError(telemetry.exception)) {
telemetry.exception = new Error(telemetry.exception.toString());
}
this.track(telemetry, Contracts.TelemetryType.Exception);
};
/**
* Log a user action or other occurrence.
* @param telemetry Object encapsulating tracking options
*/
TelemetryClient.prototype.trackEvent = function (telemetry) {
this.track(telemetry, Contracts.TelemetryType.Event);
};
/**
* Log a request. Note that the default client will attempt to collect HTTP requests automatically so only use this for requests
* that aren't automatically captured or if you've disabled automatic request collection.
*
* @param telemetry Object encapsulating tracking options
*/
TelemetryClient.prototype.trackRequest = function (telemetry) {
this.track(telemetry, Contracts.TelemetryType.Request);
};
/**
* Log a dependency. Note that the default client will attempt to collect dependencies automatically so only use this for dependencies
* that aren't automatically captured or if you've disabled automatic dependency collection.
*
* @param telemetry Object encapsulating tracking option
* */
TelemetryClient.prototype.trackDependency = function (telemetry) {
if (telemetry && !telemetry.target && telemetry.data) {
// url.parse().host returns null for non-urls,
// making this essentially a no-op in those cases
// If this logic is moved, update jsdoc in DependencyTelemetry.target
telemetry.target = url.parse(telemetry.data).host;
}
this.track(telemetry, Contracts.TelemetryType.Dependency);
};
/**
* Immediately send all queued telemetry.
* @param options Flush options, including indicator whether app is crashing and callback
*/
TelemetryClient.prototype.flush = function (options) {
this.channel.triggerSend(options ? !!options.isAppCrashing : false, options ? options.callback : undefined);
};
/**
* Generic track method for all telemetry types
* @param data the telemetry to send
* @param telemetryType specify the type of telemetry you are tracking from the list of Contracts.DataTypes
*/
TelemetryClient.prototype.track = function (telemetry, telemetryType) {
if (telemetry && Contracts.telemetryTypeToBaseType(telemetryType)) {
var envelope = EnvelopeFactory.createEnvelope(telemetry, telemetryType, this.commonProperties, this.context, this.config);
// Set time on the envelope if it was set on the telemetry item
if (telemetry.time) {
envelope.time = telemetry.time.toISOString();
}
if (this._enableAzureProperties) {
TelemetryProcessors.azureRoleEnvironmentTelemetryProcessor(envelope, this.context);
}
var accepted = this.runTelemetryProcessors(envelope, telemetry.contextObjects);
// Ideally we would have a central place for "internal" telemetry processors and users can configure which ones are in use.
// This will do for now. Otherwise clearTelemetryProcessors() would be problematic.
accepted = accepted && TelemetryProcessors.samplingTelemetryProcessor(envelope, { correlationContext: CorrelationContextManager_1.CorrelationContextManager.getCurrentContext() });
if (accepted) {
TelemetryProcessors.performanceMetricsTelemetryProcessor(envelope, this.quickPulseClient);
this.channel.send(envelope);
}
}
else {
Logging.warn("track() requires telemetry object and telemetryType to be specified.");
}
};
/**
* Automatically populate telemetry properties like RoleName when running in Azure
*
* @param value if true properties will be populated
*/
TelemetryClient.prototype.setAutoPopulateAzureProperties = function (value) {
this._enableAzureProperties = value;
};
/**
* Adds telemetry processor to the collection. Telemetry processors will be called one by one
* before telemetry item is pushed for sending and in the order they were added.
*
* @param telemetryProcessor function, takes Envelope, and optional context object and returns boolean
*/
TelemetryClient.prototype.addTelemetryProcessor = function (telemetryProcessor) {
this._telemetryProcessors.push(telemetryProcessor);
};
/*
* Removes all telemetry processors
*/
TelemetryClient.prototype.clearTelemetryProcessors = function () {
this._telemetryProcessors = [];
};
TelemetryClient.prototype.runTelemetryProcessors = function (envelope, contextObjects) {
var accepted = true;
var telemetryProcessorsCount = this._telemetryProcessors.length;
if (telemetryProcessorsCount === 0) {
return accepted;
}
contextObjects = contextObjects || {};
contextObjects['correlationContext'] = CorrelationContextManager_1.CorrelationContextManager.getCurrentContext();
for (var i = 0; i < telemetryProcessorsCount; ++i) {
try {
var processor = this._telemetryProcessors[i];
if (processor) {
if (processor.apply(null, [envelope, contextObjects]) === false) {
accepted = false;
break;
}
}
}
catch (error) {
accepted = true;
Logging.warn("One of telemetry processors failed, telemetry item will be sent.", error, envelope);
}
}
return accepted;
};
return TelemetryClient;
}());
module.exports = TelemetryClient;
//# sourceMappingURL=TelemetryClient.js.map
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
/**
* Helper class to manage parsing and validation of traceparent header. Also handles hierarchical
* back-compatibility headers generated from traceparent. W3C traceparent spec is documented at
* https://www.w3.org/TR/trace-context/#traceparent-field
*/
declare class Traceparent {
static DEFAULT_TRACE_FLAG: string;
static DEFAULT_VERSION: string;
legacyRootId: string;
parentId: string;
spanId: string;
traceFlag: string;
traceId: string;
version: string;
constructor(traceparent?: string, parentId?: string);
static isValidTraceId(id: string): boolean;
static isValidSpanId(id: string): boolean;
getBackCompatRequestId(): string;
toString(): string;
updateSpanId(): void;
}
export = Traceparent;
+108
View File
@@ -0,0 +1,108 @@
"use strict";
var Util = require("./Util");
var CorrelationIdManager = require("./CorrelationIdManager");
/**
* Helper class to manage parsing and validation of traceparent header. Also handles hierarchical
* back-compatibility headers generated from traceparent. W3C traceparent spec is documented at
* https://www.w3.org/TR/trace-context/#traceparent-field
*/
var Traceparent = (function () {
function Traceparent(traceparent, parentId) {
this.traceFlag = Traceparent.DEFAULT_TRACE_FLAG;
this.version = Traceparent.DEFAULT_VERSION;
if (traceparent && typeof traceparent === "string") {
// If incoming request contains traceparent: parse it, set operation, parent and telemetry id accordingly. traceparent should be injected into outgoing requests. request-id should be injected in back-compat format |traceId.spanId. so that older SDKs could understand it.
if (traceparent.split(",").length > 1) {
this.traceId = Util.w3cTraceId();
this.spanId = Util.w3cTraceId().substr(0, 16);
}
else {
var traceparentArr = traceparent.trim().split("-");
var len = traceparentArr.length;
if (len >= 4) {
this.version = traceparentArr[0];
this.traceId = traceparentArr[1];
this.spanId = traceparentArr[2];
this.traceFlag = traceparentArr[3];
}
else {
this.traceId = Util.w3cTraceId();
this.spanId = Util.w3cTraceId().substr(0, 16);
}
// Version validation
if (!this.version.match(/^[0-9a-f]{2}$/g)) {
this.version = Traceparent.DEFAULT_VERSION;
this.traceId = Util.w3cTraceId();
}
if (this.version === "00" && len !== 4) {
this.traceId = Util.w3cTraceId();
this.spanId = Util.w3cTraceId().substr(0, 16);
}
if (this.version === "ff") {
this.version = Traceparent.DEFAULT_VERSION;
this.traceId = Util.w3cTraceId();
this.spanId = Util.w3cTraceId().substr(0, 16);
}
if (!this.version.match(/^0[0-9a-f]$/g)) {
this.version = Traceparent.DEFAULT_VERSION;
}
// TraceFlag validation
if (!this.traceFlag.match(/^[0-9a-f]{2}$/g)) {
this.traceFlag = Traceparent.DEFAULT_TRACE_FLAG;
this.traceId = Util.w3cTraceId();
}
// Validate TraceId, regenerate new traceid if invalid
if (!Traceparent.isValidTraceId(this.traceId)) {
this.traceId = Util.w3cTraceId();
}
// Validate Span Id, discard entire traceparent if invalid
if (!Traceparent.isValidSpanId(this.spanId)) {
this.spanId = Util.w3cTraceId().substr(0, 16);
this.traceId = Util.w3cTraceId();
}
// Save backCompat parentId
this.parentId = this.getBackCompatRequestId();
}
}
else if (parentId) {
// If incoming request contains only request-id, new traceid and spanid should be started, request-id value should be used as a parent. Root part of request-id should be stored in custom dimension on the request telemetry if root part is different from traceid. On the outgoing request side, request-id should be emitted in the |traceId.spanId. format.
this.parentId = parentId.slice(); // copy
var operationId = CorrelationIdManager.getRootId(parentId);
if (!Traceparent.isValidTraceId(operationId)) {
this.legacyRootId = operationId;
operationId = Util.w3cTraceId();
}
if (parentId.indexOf("|") !== -1) {
parentId = parentId.substring(1 + parentId.substring(0, parentId.length - 1).lastIndexOf("."), parentId.length - 1);
}
this.traceId = operationId;
this.spanId = parentId;
}
else {
// Fallback default constructor
// if request does not contain any correlation headers, see case p2
this.traceId = Util.w3cTraceId();
this.spanId = Util.w3cTraceId().substr(0, 16);
}
}
Traceparent.isValidTraceId = function (id) {
return id.match(/^[0-9a-f]{32}$/) && id !== "00000000000000000000000000000000";
};
Traceparent.isValidSpanId = function (id) {
return id.match(/^[0-9a-f]{16}$/) && id !== "0000000000000000";
};
Traceparent.prototype.getBackCompatRequestId = function () {
return "|" + this.traceId + "." + this.spanId + ".";
};
Traceparent.prototype.toString = function () {
return this.version + "-" + this.traceId + "-" + this.spanId + "-" + this.traceFlag;
};
Traceparent.prototype.updateSpanId = function () {
this.spanId = Util.w3cTraceId().substr(0, 16);
};
Traceparent.DEFAULT_TRACE_FLAG = "01";
Traceparent.DEFAULT_VERSION = "00";
return Traceparent;
}());
module.exports = Traceparent;
//# sourceMappingURL=Traceparent.js.map
File diff suppressed because one or more lines are too long
+14
View File
@@ -0,0 +1,14 @@
/**
* Helper class to manage parsing and strict-validation of tracestate header. W3C tracestate spec
* is documented at https://www.w3.org/TR/trace-context/#header-value
* @class Tracestate
*/
declare class Tracestate {
static strict: boolean;
fieldmap: string[];
constructor(id?: string);
toString(): string;
private static validateKeyChars(key);
private parseHeader(id);
}
export = Tracestate;
+76
View File
@@ -0,0 +1,76 @@
"use strict";
/**
* Helper class to manage parsing and strict-validation of tracestate header. W3C tracestate spec
* is documented at https://www.w3.org/TR/trace-context/#header-value
* @class Tracestate
*/
var Tracestate = (function () {
// if true, performs strict tracestate header checking, else just passes it along
function Tracestate(id) {
this.fieldmap = [];
if (!id) {
return;
}
this.fieldmap = this.parseHeader(id);
}
Tracestate.prototype.toString = function () {
var fieldarr = this.fieldmap;
if (!fieldarr || fieldarr.length == 0) {
return null;
}
return fieldarr.join(", ");
};
Tracestate.validateKeyChars = function (key) {
var keyParts = key.split("@");
if (keyParts.length == 2) {
// Parse for tenant@vendor format
var tenant = keyParts[0].trim();
var vendor = keyParts[1].trim();
var tenantValid = Boolean(tenant.match(/^[\ ]?[a-z0-9\*\-\_/]{1,241}$/));
var vendorValid = Boolean(vendor.match(/^[\ ]?[a-z0-9\*\-\_/]{1,14}$/));
return tenantValid && vendorValid;
}
else if (keyParts.length == 1) {
// Parse for standard key format
return Boolean(key.match(/^[\ ]?[a-z0-9\*\-\_/]{1,256}$/));
}
return false;
};
Tracestate.prototype.parseHeader = function (id) {
var res = [];
var keydeduper = {};
var parts = id.split(",");
if (parts.length > 32)
return null;
for (var _i = 0, parts_1 = parts; _i < parts_1.length; _i++) {
var rawPart = parts_1[_i];
var part = rawPart.trim(); // trim out whitespace
if (part.length === 0) {
continue; // Discard empty pairs, but keep the rest of this tracestate
}
var pair = part.split("=");
// pair should contain exactly one "="
if (pair.length !== 2) {
return null; // invalid pair: discard entire tracestate
}
// Validate length and charset of this key
if (!Tracestate.validateKeyChars(pair[0])) {
return null;
}
// Assert uniqueness of this key
if (keydeduper[pair[0]]) {
return null; // duplicate key: discard entire tracestate
}
else {
keydeduper[pair[0]] = true;
}
// All checks passed -- add this part
res.push(part);
}
return res;
};
Tracestate.strict = true;
return Tracestate;
}());
module.exports = Tracestate;
//# sourceMappingURL=Tracestate.js.map
File diff suppressed because one or more lines are too long
+99
View File
@@ -0,0 +1,99 @@
/// <reference types="node" />
import http = require("http");
import https = require("https");
import Config = require("./Config");
import TelemetryClient = require("../Library/TelemetryClient");
import { HttpRequest } from "../Library/Functions";
declare class Util {
static MAX_PROPERTY_LENGTH: number;
static tlsRestrictedAgent: https.Agent;
/**
* helper method to access userId and sessionId cookie
*/
static getCookie(name: string, cookie: string): string;
/**
* helper method to trim strings (IE8 does not implement String.prototype.trim)
*/
static trim(str: string): string;
/**
* Convert an array of int32 to Base64 (no '==' at the end).
* MSB first.
*/
static int32ArrayToBase64(array: number[]): string;
/**
* generate a random 32bit number (-0x80000000..0x7FFFFFFF).
*/
static random32(): number;
/**
* generate a random 32bit number (0x00000000..0xFFFFFFFF).
*/
static randomu32(): number;
/**
* generate W3C-compatible trace id
* https://github.com/w3c/distributed-tracing/blob/master/trace_context/HTTP_HEADER_FORMAT.md#trace-id
*/
static w3cTraceId(): string;
static w3cSpanId(): string;
static isValidW3CId(id: string): boolean;
/**
* Check if an object is of type Array
*/
static isArray(obj: any): boolean;
/**
* Check if an object is of type Error
*/
static isError(obj: any): boolean;
static isPrimitive(input: any): boolean;
/**
* Check if an object is of type Date
*/
static isDate(obj: any): boolean;
/**
* Convert ms to c# time span format
*/
static msToTimeSpan(totalms: number): string;
/**
* Using JSON.stringify, by default Errors do not serialize to something useful:
* Simplify a generic Node Error into a simpler map for customDimensions
* Custom errors can still implement toJSON to override this functionality
*/
protected static extractError(err: Error): {
message: string;
code: string;
};
/**
* Manually call toJSON if available to pre-convert the value.
* If a primitive is returned, then the consumer of this function can skip JSON.stringify.
* This avoids double escaping of quotes for Date objects, for example.
*/
protected static extractObject(origProperty: any): any;
/**
* Validate that an object is of type { [key: string]: string }
*/
static validateStringMap(obj: any): {
[key: string]: string;
};
/**
* Checks if a request url is not on a excluded domain list
* and if it is safe to add correlation headers
*/
static canIncludeCorrelationHeader(client: TelemetryClient, requestUrl: string): boolean;
static getCorrelationContextTarget(response: http.ClientResponse | http.ServerRequest | HttpRequest, key: string): any;
/**
* Generate request
*
* Proxify the request creation to handle proxy http
*
* @param {string} requestUrl url endpoint
* @param {Object} requestOptions Request option
* @param {Function} requestCallback callback on request
* @returns {http.ClientRequest} request object
*/
static makeRequest(config: Config, requestUrl: string, requestOptions: http.RequestOptions | https.RequestOptions, requestCallback: (res: http.IncomingMessage) => void): http.ClientRequest;
/**
* Parse standard <string | string[] | number> request-context header
*/
static safeIncludeCorrelationHeader(client: TelemetryClient, request: http.ClientRequest | http.ServerResponse, correlationHeader: any): void;
private static addCorrelationIdHeaderFromString(client, response, correlationHeader);
}
export = Util;
+344
View File
@@ -0,0 +1,344 @@
"use strict";
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
var http = require("http");
var https = require("https");
var url = require("url");
var constants = require("constants");
var Logging = require("./Logging");
var RequestResponseHeaders = require("./RequestResponseHeaders");
var Util = (function () {
function Util() {
}
/**
* helper method to access userId and sessionId cookie
*/
Util.getCookie = function (name, cookie) {
var value = "";
if (name && name.length && typeof cookie === "string") {
var cookieName = name + "=";
var cookies = cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i];
cookie = Util.trim(cookie);
if (cookie && cookie.indexOf(cookieName) === 0) {
value = cookie.substring(cookieName.length, cookies[i].length);
break;
}
}
}
return value;
};
/**
* helper method to trim strings (IE8 does not implement String.prototype.trim)
*/
Util.trim = function (str) {
if (typeof str === "string") {
return str.replace(/^\s+|\s+$/g, "");
}
else {
return "";
}
};
/**
* Convert an array of int32 to Base64 (no '==' at the end).
* MSB first.
*/
Util.int32ArrayToBase64 = function (array) {
var toChar = function (v, i) {
return String.fromCharCode((v >> i) & 0xFF);
};
var int32AsString = function (v) {
return toChar(v, 24) + toChar(v, 16) + toChar(v, 8) + toChar(v, 0);
};
var x = array.map(int32AsString).join("");
var b = Buffer.from ? Buffer.from(x, "binary") : new Buffer(x, "binary");
var s = b.toString("base64");
return s.substr(0, s.indexOf("="));
};
/**
* generate a random 32bit number (-0x80000000..0x7FFFFFFF).
*/
Util.random32 = function () {
return (0x100000000 * Math.random()) | 0;
};
/**
* generate a random 32bit number (0x00000000..0xFFFFFFFF).
*/
Util.randomu32 = function () {
return Util.random32() + 0x80000000;
};
/**
* generate W3C-compatible trace id
* https://github.com/w3c/distributed-tracing/blob/master/trace_context/HTTP_HEADER_FORMAT.md#trace-id
*/
Util.w3cTraceId = function () {
var hexValues = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"];
// rfc4122 version 4 UUID without dashes and with lowercase letters
var oct = "", tmp;
for (var a = 0; a < 4; a++) {
tmp = Util.random32();
oct +=
hexValues[tmp & 0xF] +
hexValues[tmp >> 4 & 0xF] +
hexValues[tmp >> 8 & 0xF] +
hexValues[tmp >> 12 & 0xF] +
hexValues[tmp >> 16 & 0xF] +
hexValues[tmp >> 20 & 0xF] +
hexValues[tmp >> 24 & 0xF] +
hexValues[tmp >> 28 & 0xF];
}
// "Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively"
var clockSequenceHi = hexValues[8 + (Math.random() * 4) | 0];
return oct.substr(0, 8) + oct.substr(9, 4) + "4" + oct.substr(13, 3) + clockSequenceHi + oct.substr(16, 3) + oct.substr(19, 12);
};
Util.w3cSpanId = function () {
return Util.w3cTraceId().substring(16);
};
Util.isValidW3CId = function (id) {
return id.length === 32 && id !== "00000000000000000000000000000000";
};
/**
* Check if an object is of type Array
*/
Util.isArray = function (obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
};
/**
* Check if an object is of type Error
*/
Util.isError = function (obj) {
return obj instanceof Error;
};
Util.isPrimitive = function (input) {
var propType = typeof input;
return propType === "string" || propType === "number" || propType === "boolean";
};
/**
* Check if an object is of type Date
*/
Util.isDate = function (obj) {
return Object.prototype.toString.call(obj) === "[object Date]";
};
/**
* Convert ms to c# time span format
*/
Util.msToTimeSpan = function (totalms) {
if (isNaN(totalms) || totalms < 0) {
totalms = 0;
}
var sec = ((totalms / 1000) % 60).toFixed(7).replace(/0{0,4}$/, "");
var min = "" + Math.floor(totalms / (1000 * 60)) % 60;
var hour = "" + Math.floor(totalms / (1000 * 60 * 60)) % 24;
var days = Math.floor(totalms / (1000 * 60 * 60 * 24));
sec = sec.indexOf(".") < 2 ? "0" + sec : sec;
min = min.length < 2 ? "0" + min : min;
hour = hour.length < 2 ? "0" + hour : hour;
var daysText = days > 0 ? days + "." : "";
return daysText + hour + ":" + min + ":" + sec;
};
/**
* Using JSON.stringify, by default Errors do not serialize to something useful:
* Simplify a generic Node Error into a simpler map for customDimensions
* Custom errors can still implement toJSON to override this functionality
*/
Util.extractError = function (err) {
// Error is often subclassed so may have code OR id properties:
// https://nodejs.org/api/errors.html#errors_error_code
var looseError = err;
return {
message: err.message,
code: looseError.code || looseError.id || "",
};
};
/**
* Manually call toJSON if available to pre-convert the value.
* If a primitive is returned, then the consumer of this function can skip JSON.stringify.
* This avoids double escaping of quotes for Date objects, for example.
*/
Util.extractObject = function (origProperty) {
if (origProperty instanceof Error) {
return Util.extractError(origProperty);
}
if (typeof origProperty.toJSON === "function") {
return origProperty.toJSON();
}
return origProperty;
};
/**
* Validate that an object is of type { [key: string]: string }
*/
Util.validateStringMap = function (obj) {
if (typeof obj !== "object") {
Logging.info("Invalid properties dropped from payload");
return;
}
var map = {};
for (var field in obj) {
var property = '';
var origProperty = obj[field];
var propType = typeof origProperty;
if (Util.isPrimitive(origProperty)) {
property = origProperty.toString();
}
else if (origProperty === null || propType === "undefined") {
property = "";
}
else if (propType === "function") {
Logging.info("key: " + field + " was function; will not serialize");
continue;
}
else {
var stringTarget = Util.isArray(origProperty) ? origProperty : Util.extractObject(origProperty);
try {
if (Util.isPrimitive(stringTarget)) {
property = stringTarget;
}
else {
property = JSON.stringify(stringTarget);
}
}
catch (e) {
property = origProperty.constructor.name.toString() + " (Error: " + e.message + ")";
Logging.info("key: " + field + ", could not be serialized");
}
}
map[field] = property.substring(0, Util.MAX_PROPERTY_LENGTH);
}
return map;
};
/**
* Checks if a request url is not on a excluded domain list
* and if it is safe to add correlation headers
*/
Util.canIncludeCorrelationHeader = function (client, requestUrl) {
var excludedDomains = client && client.config && client.config.correlationHeaderExcludedDomains;
if (!excludedDomains || excludedDomains.length == 0 || !requestUrl) {
return true;
}
for (var i = 0; i < excludedDomains.length; i++) {
var regex = new RegExp(excludedDomains[i].replace(/\./g, "\.").replace(/\*/g, ".*"));
if (regex.test(url.parse(requestUrl).hostname)) {
return false;
}
}
return true;
};
Util.getCorrelationContextTarget = function (response, key) {
var contextHeaders = response.headers && response.headers[RequestResponseHeaders.requestContextHeader];
if (contextHeaders) {
var keyValues = contextHeaders.split(",");
for (var i = 0; i < keyValues.length; ++i) {
var keyValue = keyValues[i].split("=");
if (keyValue.length == 2 && keyValue[0] == key) {
return keyValue[1];
}
}
}
};
/**
* Generate request
*
* Proxify the request creation to handle proxy http
*
* @param {string} requestUrl url endpoint
* @param {Object} requestOptions Request option
* @param {Function} requestCallback callback on request
* @returns {http.ClientRequest} request object
*/
Util.makeRequest = function (config, requestUrl, requestOptions, requestCallback) {
if (requestUrl && requestUrl.indexOf('//') === 0) {
requestUrl = 'https:' + requestUrl;
}
var requestUrlParsed = url.parse(requestUrl);
var options = __assign({}, requestOptions, { host: requestUrlParsed.hostname, port: requestUrlParsed.port, path: requestUrlParsed.pathname });
var proxyUrl = undefined;
if (requestUrlParsed.protocol === 'https:') {
proxyUrl = config.proxyHttpsUrl || undefined;
}
if (requestUrlParsed.protocol === 'http:') {
proxyUrl = config.proxyHttpUrl || undefined;
}
if (proxyUrl) {
if (proxyUrl.indexOf('//') === 0) {
proxyUrl = 'http:' + proxyUrl;
}
var proxyUrlParsed = url.parse(proxyUrl);
// https is not supported at the moment
if (proxyUrlParsed.protocol === 'https:') {
Logging.info("Proxies that use HTTPS are not supported");
proxyUrl = undefined;
}
else {
options = __assign({}, options, { host: proxyUrlParsed.hostname, port: proxyUrlParsed.port || "80", path: requestUrl, headers: __assign({}, options.headers, { Host: requestUrlParsed.hostname }) });
}
}
var isHttps = requestUrlParsed.protocol === 'https:' && !proxyUrl;
if (isHttps && config.httpsAgent !== undefined) {
options.agent = config.httpsAgent;
}
else if (!isHttps && config.httpAgent !== undefined) {
options.agent = config.httpAgent;
}
else if (isHttps) {
// HTTPS without a passed in agent. Use one that enforces our TLS rules
options.agent = Util.tlsRestrictedAgent;
}
if (isHttps) {
return https.request(options, requestCallback);
}
else {
return http.request(options, requestCallback);
}
};
;
/**
* Parse standard <string | string[] | number> request-context header
*/
Util.safeIncludeCorrelationHeader = function (client, request, correlationHeader) {
var header; // attempt to cast correlationHeader to string
if (typeof correlationHeader === "string") {
header = correlationHeader;
}
else if (correlationHeader instanceof Array) {
header = correlationHeader.join(",");
}
else if (correlationHeader && typeof correlationHeader.toString === "function") {
// best effort attempt: requires well-defined toString
try {
header = correlationHeader.toString();
}
catch (err) {
Logging.warn("Outgoing request-context header could not be read. Correlation of requests may be lost.", err, correlationHeader);
}
}
if (header) {
Util.addCorrelationIdHeaderFromString(client, request, header);
}
else {
request.setHeader(RequestResponseHeaders.requestContextHeader, RequestResponseHeaders.requestContextSourceKey + "=" + client.config.correlationId);
}
};
Util.addCorrelationIdHeaderFromString = function (client, response, correlationHeader) {
var components = correlationHeader.split(",");
var key = RequestResponseHeaders.requestContextSourceKey + "=";
var found = components.some(function (value) { return value.substring(0, key.length) === key; });
if (!found) {
response.setHeader(RequestResponseHeaders.requestContextHeader, correlationHeader + "," + RequestResponseHeaders.requestContextSourceKey + "=" + client.config.correlationId);
}
};
Util.MAX_PROPERTY_LENGTH = 8192;
Util.tlsRestrictedAgent = new https.Agent({
secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 |
constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1
});
return Util;
}());
module.exports = Util;
//# sourceMappingURL=Util.js.map
File diff suppressed because one or more lines are too long