From 99bcf68cc71e0d07490bbdcd39bb39a9a66a7ab9 Mon Sep 17 00:00:00 2001 From: Jimmy Thomson Date: Fri, 5 May 2017 09:39:12 -0700 Subject: [PATCH] Adding diagnostic-channel 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. --- AutoCollection/ClientRequests.ts | 9 +++++ AutoCollection/Console.ts | 8 ++++- AutoCollection/CorrelationContextManager.ts | 33 +++---------------- .../diagnostic-channel/bunyan.sub.ts | 32 ++++++++++++++++++ .../diagnostic-channel/console.sub.ts | 22 +++++++++++++ .../diagnostic-channel/initialization.ts | 8 +++++ .../diagnostic-channel/mongodb.sub.ts | 32 ++++++++++++++++++ .../diagnostic-channel/mysql.sub.ts | 32 ++++++++++++++++++ .../diagnostic-channel/redis.sub.ts | 30 +++++++++++++++++ package.json | 4 ++- 10 files changed, 180 insertions(+), 30 deletions(-) create mode 100755 AutoCollection/diagnostic-channel/bunyan.sub.ts create mode 100755 AutoCollection/diagnostic-channel/console.sub.ts create mode 100755 AutoCollection/diagnostic-channel/initialization.ts create mode 100755 AutoCollection/diagnostic-channel/mongodb.sub.ts create mode 100755 AutoCollection/diagnostic-channel/mysql.sub.ts create mode 100755 AutoCollection/diagnostic-channel/redis.sub.ts diff --git a/AutoCollection/ClientRequests.ts b/AutoCollection/ClientRequests.ts index 8e2f1d0f6..8ae7dbcc2 100644 --- a/AutoCollection/ClientRequests.ts +++ b/AutoCollection/ClientRequests.ts @@ -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'; @@ -32,6 +38,9 @@ class AutoCollectClientRequests { if (this._isEnabled && !this._isInitialized) { this._initialize(); } + enableMongodb(isEnabled); + enableMysql(isEnabled); + enableRedis(isEnabled); } public isInitialized() { diff --git a/AutoCollection/Console.ts b/AutoCollection/Console.ts index bdefb7dd7..53552c5ab 100644 --- a/AutoCollection/Console.ts +++ b/AutoCollection/Console.ts @@ -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}; @@ -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() { diff --git a/AutoCollection/CorrelationContextManager.ts b/AutoCollection/CorrelationContextManager.ts index 5a025d4dc..19b9b1a71 100644 --- a/AutoCollection/CorrelationContextManager.ts +++ b/AutoCollection/CorrelationContextManager.ts @@ -3,6 +3,8 @@ import http = require("http"); import Util = require("../Library/Util"); +import {channel} from "diagnostic-channel"; + export interface CorrelationContext { operation: { name: string; @@ -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; @@ -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[]) { diff --git a/AutoCollection/diagnostic-channel/bunyan.sub.ts b/AutoCollection/diagnostic-channel/bunyan.sub.ts new file mode 100755 index 000000000..b55d11103 --- /dev/null +++ b/AutoCollection/diagnostic-channel/bunyan.sub.ts @@ -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) => { + 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", subscriber); + } else { + channel.unsubscribe("bunyan", subscriber); + } +} \ No newline at end of file diff --git a/AutoCollection/diagnostic-channel/console.sub.ts b/AutoCollection/diagnostic-channel/console.sub.ts new file mode 100755 index 000000000..9f10a835f --- /dev/null +++ b/AutoCollection/diagnostic-channel/console.sub.ts @@ -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) => { + 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("console", subscriber); + } else { + channel.unsubscribe("console", subscriber); + } +} \ No newline at end of file diff --git a/AutoCollection/diagnostic-channel/initialization.ts b/AutoCollection/diagnostic-channel/initialization.ts new file mode 100755 index 000000000..8d978aeda --- /dev/null +++ b/AutoCollection/diagnostic-channel/initialization.ts @@ -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(); +} \ No newline at end of file diff --git a/AutoCollection/diagnostic-channel/mongodb.sub.ts b/AutoCollection/diagnostic-channel/mongodb.sub.ts new file mode 100755 index 000000000..cf307fd4a --- /dev/null +++ b/AutoCollection/diagnostic-channel/mongodb.sub.ts @@ -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) => { + 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", subscriber); + } else { + channel.unsubscribe("mongodb", subscriber); + } +} \ No newline at end of file diff --git a/AutoCollection/diagnostic-channel/mysql.sub.ts b/AutoCollection/diagnostic-channel/mysql.sub.ts new file mode 100755 index 000000000..417826faf --- /dev/null +++ b/AutoCollection/diagnostic-channel/mysql.sub.ts @@ -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) => { + 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", subscriber); + } else { + channel.unsubscribe("mysql", subscriber); + } +} \ No newline at end of file diff --git a/AutoCollection/diagnostic-channel/redis.sub.ts b/AutoCollection/diagnostic-channel/redis.sub.ts new file mode 100755 index 000000000..55ecc85ec --- /dev/null +++ b/AutoCollection/diagnostic-channel/redis.sub.ts @@ -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) => { + 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", subscriber); + } else { + channel.unsubscribe("redis", subscriber); + } +} \ No newline at end of file diff --git a/package.json b/package.json index faf660e25..b10b3d547 100644 --- a/package.json +++ b/package.json @@ -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" } }