Skip to content

Commit

Permalink
Adding diagnostic-channel
Browse files Browse the repository at this point in the history
By using diagnostic-channel and diagnostic-channel-publishers, we can support context tracking through third-party libraries as well as getting additional telemetry for those dependencies.
  • Loading branch information
MSLaguana committed May 5, 2017
1 parent 6f8d4a2 commit 99bcf68
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 30 deletions.
9 changes: 9 additions & 0 deletions AutoCollection/ClientRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import RequestResponseHeaders = require("../Library/RequestResponseHeaders");
import ClientRequestParser = require("./ClientRequestParser");
import { CorrelationContextManager, CorrelationContext } from "./CorrelationContextManager";

import {enable as enableMongodb} from "./diagnostic-channel/mongodb.sub";
import {enable as enableMysql} from "./diagnostic-channel/mysql.sub";
import {enable as enableRedis} from "./diagnostic-channel/redis.sub";

import "./diagnostic-channel/initialization";

class AutoCollectClientRequests {
public static disableCollectionRequestOption = 'disableAppInsightsAutoCollection';

Expand All @@ -32,6 +38,9 @@ class AutoCollectClientRequests {
if (this._isEnabled && !this._isInitialized) {
this._initialize();
}
enableMongodb(isEnabled);
enableMysql(isEnabled);
enableRedis(isEnabled);
}

public isInitialized() {
Expand Down
8 changes: 7 additions & 1 deletion AutoCollection/Console.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import Client = require("../Library/Client");
import Logging = require("../Library/Logging");

import {enable as enableConsole} from "./diagnostic-channel/console.sub";
import {enable as enableBunyan} from "./diagnostic-channel/bunyan.sub";

import "./diagnostic-channel/initialization";

class AutoCollectConsole {
public static originalMethods: {[name: string]: (message?: any, ...optionalParams: any[]) => void};

Expand All @@ -20,7 +25,8 @@ class AutoCollectConsole {
}

public enable(isEnabled: boolean) {
// todo: investigate feasibility/utility of this; does it make sense to have a logging adapter in node?
enableConsole(isEnabled);
enableBunyan(isEnabled);
}

public isInitialized() {
Expand Down
33 changes: 5 additions & 28 deletions AutoCollection/CorrelationContextManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import http = require("http");
import Util = require("../Library/Util");

import {channel} from "diagnostic-channel";

export interface CorrelationContext {
operation: {
name: string;
Expand Down Expand Up @@ -100,9 +102,11 @@ export class CorrelationContextManager {
// Run patches for Zone.js
if (!this.hasEverEnabled) {
this.hasEverEnabled = true;
channel.addContextPreservation((cb) => {
return Zone.current.wrap(cb, "AI-ContextPreservation");
})
this.patchError();
this.patchTimers(["setTimeout", "setInterval"]);
this.patchRedis();
}

this.enabled = true;
Expand Down Expand Up @@ -136,33 +140,6 @@ export class CorrelationContextManager {
return req;
}

// A good example of patching a third party library to respect context.
// send_command is always used in this library to send data out.
// By overwriting the function to capture the callback provided to it,
// and wrapping that callback, we ensure that consumers of this library
// will have context persisted.
private static patchRedis() {
var redis = this.requireForPatch("redis");

if (redis && redis.RedisClient) {
var orig = redis.RedisClient.prototype.send_command;
redis.RedisClient.prototype.send_command = function() {
var args = Array.prototype.slice.call(arguments);
var lastArg = args[args.length - 1];

if (typeof lastArg === "function") {
args[args.length - 1] = Zone.current.wrap(lastArg, "AI.CCM.patchRedis");
} else if (lastArg instanceof Array && typeof lastArg[lastArg.length - 1] === "function") {
// The last argument can be an array!
var lastIndexLastArg = lastArg[lastArg.length - 1];
lastArg[lastArg.length - 1] = Zone.current.wrap(lastIndexLastArg, "AI.CCM.patchRedis");
}

return orig.apply(this, args);
};
}
}

// Zone.js breaks concatenation of timer return values.
// This fixes that.
private static patchTimers(methodNames: string[]) {
Expand Down
32 changes: 32 additions & 0 deletions AutoCollection/diagnostic-channel/bunyan.sub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import ApplicationInsights = require("../../applicationinsights");
import {SeverityLevel} from "../../Declarations/Contracts";

import {channel, IStandardEvent} from "diagnostic-channel";

import {bunyan} from "diagnostic-channel-publishers";

// Mapping from bunyan levels defined at /~https://github.com/trentm/node-bunyan/blob/master/lib/bunyan.js#L256
const bunyanToAILevelMap = {};
bunyanToAILevelMap[10] = SeverityLevel.Verbose;
bunyanToAILevelMap[20] = SeverityLevel.Verbose;
bunyanToAILevelMap[30] = SeverityLevel.Information;
bunyanToAILevelMap[40] = SeverityLevel.Warning;
bunyanToAILevelMap[50] = SeverityLevel.Error;
bunyanToAILevelMap[60] = SeverityLevel.Critical;

const subscriber = (event: IStandardEvent<bunyan.IBunyanData>) => {
if (ApplicationInsights.client) {
const AIlevel = bunyanToAILevelMap[event.data.level]
ApplicationInsights.client.trackTrace(event.data.result, AIlevel)
}
};

export function enable(enabled: boolean) {
if (enabled) {
channel.subscribe<bunyan.IBunyanData>("bunyan", subscriber);
} else {
channel.unsubscribe("bunyan", subscriber);
}
}
22 changes: 22 additions & 0 deletions AutoCollection/diagnostic-channel/console.sub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import ApplicationInsights = require("../../applicationinsights");
import {SeverityLevel} from "../../Declarations/Contracts";

import {channel, IStandardEvent} from "diagnostic-channel";

import {console as consolePub} from "diagnostic-channel-publishers";

const subscriber = (event: IStandardEvent<consolePub.IConsoleData>) => {
if (ApplicationInsights.client) {
ApplicationInsights.client.trackTrace(event.data.message, event.data.stderr ? SeverityLevel.Warning : SeverityLevel.Information);
}
};

export function enable(enabled: boolean) {
if (enabled) {
channel.subscribe<consolePub.IConsoleData>("console", subscriber);
} else {
channel.unsubscribe("console", subscriber);
}
}
8 changes: 8 additions & 0 deletions AutoCollection/diagnostic-channel/initialization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

import {enable} from "diagnostic-channel-publishers";

if (!process.env["APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL"]) {
enable();
}
32 changes: 32 additions & 0 deletions AutoCollection/diagnostic-channel/mongodb.sub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import ApplicationInsights = require("../../applicationinsights");
import {channel, IStandardEvent} from "diagnostic-channel";

import {mongodb} from "diagnostic-channel-publishers";

export const subscriber = (event: IStandardEvent<mongodb.IMongoData>) => {
if (ApplicationInsights.client) {
const dbName = (event.data.startedData && event.data.startedData.databaseName) || "Unknown database";
ApplicationInsights.client
.trackDependency(
dbName,
event.data.event.commandName,
event.data.event.duration,
event.data.succeeded,
'mongodb');

if (!event.data.succeeded) {
ApplicationInsights.client
.trackException(new Error(event.data.event.failure));
}
}
};

export function enable(enabled: boolean) {
if (enabled) {
channel.subscribe<mongodb.IMongoData>("mongodb", subscriber);
} else {
channel.unsubscribe("mongodb", subscriber);
}
}
32 changes: 32 additions & 0 deletions AutoCollection/diagnostic-channel/mysql.sub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import ApplicationInsights = require("../../applicationinsights");
import {channel, IStandardEvent} from "diagnostic-channel";

import {mysql} from "diagnostic-channel-publishers";

export const subscriber = (event: IStandardEvent<mysql.IMysqlData>) => {
if (ApplicationInsights.client) {
const queryObj = event.data.query || {};
const sqlString = queryObj.sql || "Unknown query";
const success = !event.data.err;

const connection = queryObj._connection || {};
const connectionConfig = connection.config || {};
const dbName = connectionConfig.socketPath ? connectionConfig.socketPath : `${connectionConfig.host || "localhost"}:${connectionConfig.port}`;
ApplicationInsights.client.trackDependency(
dbName,
sqlString,
event.data.duration | 0,
success,
"mysql");
}
};

export function enable(enabled: boolean) {
if (enabled) {
channel.subscribe<mysql.IMysqlData>("mysql", subscriber);
} else {
channel.unsubscribe("mysql", subscriber);
}
}
30 changes: 30 additions & 0 deletions AutoCollection/diagnostic-channel/redis.sub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import ApplicationInsights = require("../../applicationinsights");
import {channel, IStandardEvent} from "diagnostic-channel";

import {redis} from "diagnostic-channel-publishers";

export const subscriber = (event: IStandardEvent<redis.IRedisData>) => {
if (ApplicationInsights.client) {
if (event.data.commandObj.command === "info") {
// We don't want to report 'info', it's irrelevant
return;
}
ApplicationInsights.client.trackDependency(
event.data.address,
event.data.commandObj.command,
event.data.duration,
!event.data.err,
"redis"
);
}
};

export function enable(enabled: boolean) {
if (enabled) {
channel.subscribe<redis.IRedisData>("redis", subscriber);
} else {
channel.unsubscribe("redis", subscriber);
}
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
"sinon": "1.17.6"
},
"dependencies": {
"zone.js": "0.7.6"
"zone.js": "0.7.6",
"diagnostic-channel": "0.1.0",
"diagnostic-channel-publishers": "0.1.1"
}
}

0 comments on commit 99bcf68

Please sign in to comment.