Repository: microsoft/ApplicationInsights-node.js
Pull request #1137 (2023-05-05 21:24:37)

[Beta] Align with SDK versions used by all telemetry
Updated tests to shutdown handler whenever is possible


Patch:
diff --git a/src/logs/logHandler.ts b/src/logs/logHandler.ts
index f666df3..1dfdf16 100644
--- a/src/logs/logHandler.ts
+++ b/src/logs/logHandler.ts
@@ -1,6 +1,8 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT license.
+import { ApplicationInsightsSampler } from "@azure/monitor-opentelemetry-exporter";
 import { context, trace } from "@opentelemetry/api";
+import { SDK_INFO } from "@opentelemetry/core";
 import { IdGenerator, RandomIdGenerator, SamplingDecision, SamplingResult } from "@opentelemetry/sdk-trace-base";
 import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
 
@@ -36,7 +38,7 @@ import {
 import { Logger } from "../shared/logging";
 import { IStandardMetricBaseDimensions, IMetricTraceDimensions } from "../metrics/types";
 import { MetricHandler } from "../metrics/metricHandler";
-import { ApplicationInsightsSampler } from "@azure/monitor-opentelemetry-exporter";
+import { AZURE_MONITOR_DISTRO_VERSION } from "../declarations/constants";
 
 export class LogHandler {
     // Statsbeat is instantiated here such that it can be accessed by the diagnostic-channel.
@@ -50,6 +52,7 @@ export class LogHandler {
     private _metricHandler: MetricHandler;
     private _aiSampler: ApplicationInsightsSampler;
     private _instrumentationKey: string;
+    private _aiInternalSdkVersion: string;
 
     constructor(config: ApplicationInsightsConfig, metricHandler?: MetricHandler, statsbeat?: Statsbeat) {
         this._config = config;
@@ -69,6 +72,14 @@ export class LogHandler {
         const parsedConnectionString = parser.parse(this._config.azureMonitorExporterConfig.connectionString);
         this._instrumentationKey = parsedConnectionString.instrumentationkey;
         this._console.enable(this._config.logInstrumentations);
+
+        const { node } = process.versions;
+        let nodeVersion = node.split(".");
+        let opentelemetryVersion = SDK_INFO[SemanticResourceAttributes.TELEMETRY_SDK_VERSION];
+        let prefix = process.env["AZURE_MONITOR_AGENT_PREFIX"]
+            ? process.env["AZURE_MONITOR_AGENT_PREFIX"]
+            : "";
+        this._aiInternalSdkVersion = `${prefix}node${nodeVersion}:otel${opentelemetryVersion}:dst${AZURE_MONITOR_DISTRO_VERSION}`;
     }
 
     /** 
@@ -349,9 +360,7 @@ export class LogHandler {
         }
         const serviceInstanceId = attributes[SemanticResourceAttributes.SERVICE_INSTANCE_ID];
         tags[KnownContextTagKeys.AiCloudRoleInstance] = String(serviceInstanceId);
-        tags[KnownContextTagKeys.AiInternalSdkVersion] = String(
-            attributes[SemanticResourceAttributes.TELEMETRY_SDK_VERSION]
-        );
+        tags[KnownContextTagKeys.AiInternalSdkVersion] = this._aiInternalSdkVersion;
 
         // Add Correlation headers
         const spanContext = trace.getSpanContext(context.active());
diff --git a/src/metrics/collection/nativePerformanceMetrics.ts b/src/metrics/collection/nativePerformanceMetrics.ts
index 681ae6e..6ea84c6 100644
--- a/src/metrics/collection/nativePerformanceMetrics.ts
+++ b/src/metrics/collection/nativePerformanceMetrics.ts
@@ -37,38 +37,38 @@ export class NativePerformanceMetrics {
             NativeMetricsCounter.MEMORY_USAGE_NON_HEAP
         );
 
-        // Try to require in the native-metrics library. If it's found initialize it, else do nothing and never try again.
-        try {
-            // eslint-disable-next-line @typescript-eslint/no-var-requires
-            const NativeMetricsEmitter = require("applicationinsights-native-metrics");
-            this._emitter = new NativeMetricsEmitter();
-            Logger.getInstance().info("Native metrics module successfully loaded!");
-        } catch (err) {
-            // Package not available.
-            return;
-        }
-        // Enable the emitter if we were able to construct one
-        if (this._emitter) {
-            try {
-                // enable self
-                this._emitter.enable(true, this._collectionInterval);
-            } catch (err) {
-                Logger.getInstance().error("Native metrics enable failed", err);
-            }
+        // // Try to require in the native-metrics library. If it's found initialize it, else do nothing and never try again.
+        // try {
+        //     // eslint-disable-next-line @typescript-eslint/no-var-requires
+        //     const NativeMetricsEmitter = require("applicationinsights-native-metrics");
+        //     this._emitter = new NativeMetricsEmitter();
+        //     Logger.getInstance().info("Native metrics module successfully loaded!");
+        // } catch (err) {
+        //     // Package not available.
+        //     return;
+        // }
+        // // Enable the emitter if we were able to construct one
+        // if (this._emitter) {
+        //     try {
+        //         // enable self
+        //         this._emitter.enable(true, this._collectionInterval);
+        //     } catch (err) {
+        //         Logger.getInstance().error("Native metrics enable failed", err);
+        //     }
 
-            // Add histogram data collection
-            if (!this._handle) {
-                this._handle = setInterval(
-                    () => this._collectHistogramData(),
-                    this._collectionInterval
-                );
-                this._handle.unref();
-            }
-            // Add observable callbacks
-            this._heapMemoryTotalGauge.addCallback(this._getHeapTotal.bind(this));
-            this._heapMemoryUsageGauge.addCallback(this._getHeapUsage.bind(this));
-            this._memoryUsageNonHeapGauge.addCallback(this._getNonHeapUsage.bind(this));
-        }
+        //     // Add histogram data collection
+        //     if (!this._handle) {
+        //         this._handle = setInterval(
+        //             () => this._collectHistogramData(),
+        //             this._collectionInterval
+        //         );
+        //         this._handle.unref();
+        //     }
+        //     // Add observable callbacks
+        //     this._heapMemoryTotalGauge.addCallback(this._getHeapTotal.bind(this));
+        //     this._heapMemoryUsageGauge.addCallback(this._getHeapUsage.bind(this));
+        //     this._memoryUsageNonHeapGauge.addCallback(this._getNonHeapUsage.bind(this));
+        // }
     }
 
     /** 
diff --git a/src/metrics/handlers/heartBeatHandler.ts b/src/metrics/handlers/heartBeatHandler.ts
index d8fe04a..2a9a611 100644
--- a/src/metrics/handlers/heartBeatHandler.ts
+++ b/src/metrics/handlers/heartBeatHandler.ts
@@ -60,6 +60,7 @@ export class HeartBeatHandler {
     }
 
     public async shutdown(): Promise<void> {
+        this._metricGauge.removeCallback(this._metricGaugeCallback);
         await this._meterProvider.shutdown();
     }
 
diff --git a/src/metrics/handlers/performanceCounterMetricsHandler.ts b/src/metrics/handlers/performanceCounterMetricsHandler.ts
index 83537d6..f8bb3e8 100644
--- a/src/metrics/handlers/performanceCounterMetricsHandler.ts
+++ b/src/metrics/handlers/performanceCounterMetricsHandler.ts
@@ -59,9 +59,9 @@ export class PerformanceCounterMetricsHandler {
     }
 
     public shutdown() {
-        this._meterProvider.shutdown();
         this._processMetrics.shutdown();
         this._requestMetrics.shutdown();
+        this._meterProvider.shutdown();
     }
 
     public recordSpan(span: ReadableSpan): void {
diff --git a/src/metrics/handlers/standardMetricsHandler.ts b/src/metrics/handlers/standardMetricsHandler.ts
index 6248eff..57adb8d 100644
--- a/src/metrics/handlers/standardMetricsHandler.ts
+++ b/src/metrics/handlers/standardMetricsHandler.ts
@@ -62,7 +62,6 @@ export class StandardMetricsHandler {
     }
 
     public shutdown() {
-        this._meterProvider.shutdown();
         this._dependencyMetrics.shutdown();
         this._exceptionMetrics.shutdown();
         this._traceMetrics.shutdown();
diff --git a/src/metrics/metricHandler.ts b/src/metrics/metricHandler.ts
index b1dcb0a..9a9761f 100644
--- a/src/metrics/metricHandler.ts
+++ b/src/metrics/metricHandler.ts
@@ -54,6 +54,9 @@ export class MetricHandler {
         await this._perfCounterMetricsHandler?.flush();
     }
 
+    /** 
+   * @deprecated This should not be used
+   */
     public getConfig(): ApplicationInsightsConfig {
         return this._config;
     }


Test patch:
diff --git a/test/unitTests/logs/logHandler.tests.ts b/test/unitTests/logs/logHandler.tests.ts
index 223d05c..7f8f77c 100644
--- a/test/unitTests/logs/logHandler.tests.ts
+++ b/test/unitTests/logs/logHandler.tests.ts
@@ -21,6 +21,10 @@ import { ApplicationInsightsClient } from "../../../src";
 
 describe("Library/LogHandler", () => {
     let sandbox: sinon.SinonSandbox;
+    let handler: LogHandler;
+    let traceHandler: TraceHandler;
+    let stub: sinon.SinonStub;
+    let metricHandler: MetricHandler;
     const _config = new ApplicationInsightsConfig();
     _config.connectionString = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;IngestionEndpoint=https://westus.in.applicationinsights.azure.com/;LiveEndpoint=https://west.live.monitor.azure.com/";
 
@@ -30,19 +34,39 @@ describe("Library/LogHandler", () => {
 
     afterEach(() => {
         sandbox.restore();
+        handler.shutdown();
+        if (traceHandler) {
+            traceHandler.shutdown();
+        }
+        if (metricHandler) {
+            metricHandler.shutdown();
+        }
     });
 
+    function createLogHandler(config: ApplicationInsightsConfig, metricHandler?: MetricHandler) {
+        handler = new LogHandler(config, metricHandler);
+        stub = sinon.stub(handler["_exporter"], "export").callsFake(
+            (envelopes: any, resultCallback: any) =>
+                new Promise((resolve, reject) => {
+                    resultCallback({
+                        code: ExportResultCode.SUCCESS,
+                    });
+                    resolve();
+                })
+        );
+    }
+
     describe("#autoCollect", () => {
         it("exception enablement during start", () => {
             _config.enableAutoCollectExceptions = true;
-            const handler = new LogHandler(_config);
+            createLogHandler(_config);
             assert.ok(handler["_exceptions"], "Exceptions not enabled");
         });
     });
 
     describe("#manual track APIs", () => {
         it("_logToEnvelope", () => {
-            const handler = new LogHandler(_config);
+            createLogHandler(_config);
             const telemetry: Telemetry = {};
             const data: MonitorDomain = {};
             const envelope = handler["_logToEnvelope"](
@@ -64,16 +88,24 @@ describe("Library/LogHandler", () => {
             assert.equal(envelope.tags["ai.cloud.role"], "Web");
             assert.equal(envelope.tags["ai.cloud.roleInstance"], os.hostname());
             assert.ok(
-                envelope.tags["ai.internal.sdkVersion"].indexOf("node") == 0,
+                envelope.tags["ai.internal.sdkVersion"].indexOf("node") > 0,
+                "Incorrect SDK version"
+            );
+            assert.ok(
+                envelope.tags["ai.internal.sdkVersion"].indexOf(":otel") > 0,
+                "Incorrect SDK version"
+            );
+            assert.ok(
+                envelope.tags["ai.internal.sdkVersion"].indexOf(":dst") > 0,
                 "Incorrect SDK version"
             );
         });
 
         it("tracing", () => {
-            const logHandler = new LogHandler(_config);
-            const traceHandler = new TraceHandler(_config);
+            createLogHandler(_config);
+            traceHandler = new TraceHandler(_config);
             traceHandler["_tracer"].startActiveSpan("test", () => {
-                const envelope = logHandler["_logToEnvelope"]({}, "", {});
+                const envelope = handler["_logToEnvelope"]({}, "", {});
                 const spanContext = trace.getSpanContext(context.active());
                 assert.ok(isValidTraceId(envelope.tags["ai.operation.id"]), "Valid operation Id");
                 assert.ok(
@@ -89,8 +121,8 @@ describe("Library/LogHandler", () => {
             let otherConfig = new ApplicationInsightsConfig();
             otherConfig.connectionString = _config.connectionString;
             otherConfig.samplingRatio = 0;
-            const logHandler = new LogHandler(otherConfig);
-            const stub = sinon.stub(logHandler["_batchProcessor"], "send");
+            createLogHandler(otherConfig);
+            const stub = sinon.stub(handler["_batchProcessor"], "send");
             const telemetry: AvailabilityTelemetry = {
                 name: "TestName",
                 duration: 2000, //2 seconds
@@ -99,22 +131,13 @@ describe("Library/LogHandler", () => {
                 message: "testMessage",
                 success: false,
             };
-            logHandler.trackAvailability(telemetry);
+            handler.trackAvailability(telemetry);
             assert.ok(stub.notCalled);
         });
 
 
         it("trackAvailability", (done) => {
-            const handler = new LogHandler(_config);
-            const stub = sinon.stub(handler["_exporter"], "export").callsFake(
-                (envelopes: any, resultCallback: any) =>
-                    new Promise((resolve, reject) => {
-                        resultCallback({
-                            code: ExportResultCode.SUCCESS,
-                        });
-                        resolve();
-                    })
-            );
+            createLogHandler(_config);
             const telemetry: AvailabilityTelemetry = {
                 name: "TestName",
                 duration: 2000, //2 seconds
@@ -156,16 +179,7 @@ describe("Library/LogHandler", () => {
         });
 
         it("trackPageView", (done) => {
-            const handler = new LogHandler(_config);
-            const stub = sinon.stub(handler["_exporter"], "export").callsFake(
-                (envelopes: any, resultCallback: any) =>
-                    new Promise((resolve, reject) => {
-                        resultCallback({
-                            code: ExportResultCode.SUCCESS,
-                        });
-                        resolve();
-                    })
-            );
+            createLogHandler(_config);
             const telemetry: PageViewTelemetry = {
                 name: "TestName",
                 duration: 2000, //2 seconds
@@ -205,16 +219,7 @@ describe("Library/LogHandler", () => {
         });
 
         it("trackTrace", (done) => {
-            const handler = new LogHandler(_config);
-            const stub = sinon.stub(handler["_exporter"], "export").callsFake(
-                (envelopes: any, resultCallback: any) =>
-                    new Promise((resolve, reject) => {
-                        resultCallback({
-                            code: ExportResultCode.SUCCESS,
-                        });
-                        resolve();
-                    })
-            );
+            createLogHandler(_config);
             const telemetry: TraceTelemetry = {
                 message: "testMessage",
                 severity: "Information",
@@ -249,16 +254,7 @@ describe("Library/LogHandler", () => {
         });
 
         it("trackException", (done) => {
-            const handler = new LogHandler(_config);
-            const stub = sinon.stub(handler["_exporter"], "export").callsFake(
-                (envelopes: any, resultCallback: any) =>
-                    new Promise((resolve, reject) => {
-                        resultCallback({
-                            code: ExportResultCode.SUCCESS,
-                        });
-                        resolve();
-                    })
-            );
+            createLogHandler(_config);
             const measurements: { [key: string]: number } = {};
             measurements["test"] = 123;
             const telemetry: ExceptionTelemetry = {
@@ -304,16 +300,7 @@ describe("Library/LogHandler", () => {
         });
 
         it("trackEvent", (done) => {
-            const handler = new LogHandler(_config);
-            const stub = sinon.stub(handler["_exporter"], "export").callsFake(
-                (envelopes: any, resultCallback: any) =>
-                    new Promise((resolve, reject) => {
-                        resultCallback({
-                            code: ExportResultCode.SUCCESS,
-                        });
-                        resolve();
-                    })
-            );
+            createLogHandler(_config);
             const measurements: { [key: string]: number } = {};
             measurements["test"] = 123;
             const telemetry: EventTelemetry = {
@@ -351,17 +338,9 @@ describe("Library/LogHandler", () => {
 
         it("Exception standard metrics processed", (done) => {
             _config.enableAutoCollectStandardMetrics = true;
-            const metricHandler = new MetricHandler(_config);
-            const handler = new LogHandler(_config, metricHandler);
-            const stub = sinon.stub(handler["_exporter"], "export").callsFake(
-                (envelopes: any, resultCallback: any) =>
-                    new Promise((resolve, reject) => {
-                        resultCallback({
-                            code: ExportResultCode.SUCCESS,
-                        });
-                        resolve();
-                    })
-            );
+            metricHandler = new MetricHandler(_config);
+            createLogHandler(_config, metricHandler);
+
             const telemetry: ExceptionTelemetry = {
                 exception: new Error("TestError"),
                 severity: "Critical",
@@ -385,17 +364,8 @@ describe("Library/LogHandler", () => {
 
         it("Trace standard metrics processed", (done) => {
             _config.enableAutoCollectStandardMetrics = true;
-            const metricHandler = new MetricHandler(_config);
-            const handler = new LogHandler(_config, metricHandler);
-            const stub = sinon.stub(handler["_exporter"], "export").callsFake(
-                (envelopes: any, resultCallback: any) =>
-                    new Promise((resolve, reject) => {
-                        resultCallback({
-                            code: ExportResultCode.SUCCESS,
-                        });
-                        resolve();
-                    })
-            );
+            metricHandler = new MetricHandler(_config);
+            createLogHandler(_config, metricHandler);
             const telemetry: TraceTelemetry = {
                 message: "testMessage",
                 severity: "Information",
@@ -424,6 +394,7 @@ describe("Library/LogHandler", () => {
             const logsStatsbeatCollection = appInsights["_logHandler"]["_exporter"]["_statsbeatMetrics"]["_networkStatsbeatCollection"];
             assert.strictEqual(logsStatsbeatCollection[0].totalSuccesfulRequestCount, 1);
             assert.strictEqual(logsStatsbeatCollection[0].intervalRequestExecutionTime, 100);
+            appInsights.shutdown();
         });
     });
 });
diff --git a/test/unitTests/metrics/customMetricsHandler.tests.ts b/test/unitTests/metrics/customMetricsHandler.tests.ts
index 8eff37e..1995d1c 100644
--- a/test/unitTests/metrics/customMetricsHandler.tests.ts
+++ b/test/unitTests/metrics/customMetricsHandler.tests.ts
@@ -3,25 +3,34 @@ import * as sinon from "sinon";
 
 import { CustomMetricsHandler } from "../../../src/metrics/handlers/customMetricsHandler";
 import { ApplicationInsightsConfig } from "../../../src/shared";
+import { ExportResultCode } from "@opentelemetry/core";
 
 describe("#CustomMetricsHandler", () => {
-    let sandbox: sinon.SinonSandbox;
     let autoCollect: CustomMetricsHandler;
+    let exportStub: sinon.SinonStub;
 
     before(() => {
-        sandbox = sinon.createSandbox();
         const config = new ApplicationInsightsConfig();
-        config.connectionString = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;";
+        config.azureMonitorExporterConfig.connectionString = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;";
         autoCollect = new CustomMetricsHandler(config, { collectionInterval: 100 });
-        sandbox.stub(autoCollect["_metricReader"]["_exporter"], "export");
+        exportStub = sinon.stub(autoCollect["_azureExporter"], "export").callsFake(
+            (spans: any, resultCallback: any) =>
+                new Promise((resolve, reject) => {
+                    resultCallback({
+                        code: ExportResultCode.SUCCESS,
+                    });
+                    resolve();
+                })
+        );
     });
 
     afterEach(() => {
-        sandbox.restore();
+        exportStub.resetHistory();
     });
 
     after(() => {
         autoCollect.shutdown();
+        exportStub.restore();
     });
 
     it("should create a meter", () => {
@@ -29,11 +38,10 @@ describe("#CustomMetricsHandler", () => {
     });
 
     it("should observe instruments during collection", async () => {
-        const mockExport = sandbox.stub(autoCollect["_azureExporter"], "export");
         autoCollect.getMeter().createCounter("testCounter", { description: "testDescription" });
         await new Promise((resolve) => setTimeout(resolve, 120));
-        assert.ok(mockExport.called);
-        const resourceMetrics = mockExport.args[0][0];
+        assert.ok(exportStub.called);
+        const resourceMetrics = exportStub.args[0][0];
         const scopeMetrics = resourceMetrics.scopeMetrics;
         assert.strictEqual(scopeMetrics.length, 1, "scopeMetrics count");
         const metrics = scopeMetrics[0].metrics;
@@ -43,10 +51,9 @@ describe("#CustomMetricsHandler", () => {
     });
 
     it("should not collect when disabled", async () => {
-        const mockExport = sandbox.stub(autoCollect["_azureExporter"], "export");
         autoCollect.getMeter().createCounter("testCounter", { description: "testDescription" });
         autoCollect.shutdown();
         await new Promise((resolve) => setTimeout(resolve, 120));
-        assert.ok(mockExport.notCalled);
+        assert.ok(exportStub.notCalled);
     });
 });
diff --git a/test/unitTests/metrics/heartbeat.tests.ts b/test/unitTests/metrics/heartbeat.tests.ts
index 5d3f8d7..24d9b3e 100644
--- a/test/unitTests/metrics/heartbeat.tests.ts
+++ b/test/unitTests/metrics/heartbeat.tests.ts
@@ -5,19 +5,27 @@ import * as os from "os";
 import { HeartBeatHandler } from "../../../src/metrics/handlers/heartBeatHandler";
 import { ApplicationInsightsConfig } from "../../../src/shared";
 import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
+import { ExportResultCode } from "@opentelemetry/core";
 
 describe("AutoCollection/HeartBeat", () => {
-    let sandbox: sinon.SinonSandbox;
     let originalEnv: NodeJS.ProcessEnv;
     let config: ApplicationInsightsConfig;
     let heartbeat: HeartBeatHandler;
+    let exportStub: sinon.SinonStub;
 
     before(() => {
-        sandbox = sinon.createSandbox();
         config = new ApplicationInsightsConfig();
-        config.connectionString = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;";
+        config.azureMonitorExporterConfig.connectionString = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;";
         heartbeat = new HeartBeatHandler(config, { collectionInterval: 100 });
-        sandbox.stub(heartbeat["_metricReader"]["_exporter"], "export");
+        exportStub = sinon.stub(heartbeat["_azureExporter"], "export").callsFake(
+            (spans: any, resultCallback: any) =>
+                new Promise((resolve, reject) => {
+                    resultCallback({
+                        code: ExportResultCode.SUCCESS,
+                    });
+                    resolve();
+                })
+        );
     });
 
     beforeEach(() => {
@@ -26,7 +34,12 @@ describe("AutoCollection/HeartBeat", () => {
 
     afterEach(() => {
         process.env = originalEnv;
-        sandbox.restore();
+        exportStub.resetHistory();
+    });
+
+    after(() => {
+        exportStub.restore();
+        heartbeat.shutdown();
     });
 
     describe("#Metrics", () => {
@@ -35,10 +48,9 @@ describe("AutoCollection/HeartBeat", () => {
         });
 
         it("should observe instruments during collection", async () => {
-            const mockExport = sandbox.stub(heartbeat["_azureExporter"], "export");
             await new Promise((resolve) => setTimeout(resolve, 120));
-            assert.ok(mockExport.called);
-            const resourceMetrics = mockExport.args[0][0];
+            assert.ok(exportStub.called);
+            const resourceMetrics = exportStub.args[0][0];
             const scopeMetrics = resourceMetrics.scopeMetrics;
             assert.strictEqual(scopeMetrics.length, 1, "scopeMetrics count");
             const metrics = scopeMetrics[0].metrics;
@@ -47,10 +59,9 @@ describe("AutoCollection/HeartBeat", () => {
         });
 
         it("should not collect when shutdown", async () => {
-            const mockExport = sandbox.stub(heartbeat["_azureExporter"], "export");
             heartbeat.shutdown();
             await new Promise((resolve) => setTimeout(resolve, 120));
-            assert.ok(mockExport.notCalled);
+            assert.ok(exportStub.notCalled);
         });
     });
 
diff --git a/test/unitTests/metrics/metricHandler.tests.ts b/test/unitTests/metrics/metricHandler.tests.ts
index a25fe48..b6da6b7 100644
--- a/test/unitTests/metrics/metricHandler.tests.ts
+++ b/test/unitTests/metrics/metricHandler.tests.ts
@@ -2,38 +2,71 @@ import * as assert from "assert";
 import * as sinon from "sinon";
 import { MetricHandler } from "../../../src/metrics";
 import { ApplicationInsightsConfig } from "../../../src/shared";
+import { ExportResultCode } from "@opentelemetry/core";
 
 describe("Library/MetricHandler", () => {
-    let sandbox: sinon.SinonSandbox;
+    let exportStub: sinon.SinonStub;
     let _config: ApplicationInsightsConfig;
+    let handler: MetricHandler;
 
     before(() => {
         _config = new ApplicationInsightsConfig();
-        _config.connectionString = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;";
-        sandbox = sinon.createSandbox();
+        _config.azureMonitorExporterConfig.connectionString = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;";
     });
 
     afterEach(() => {
-        sandbox.restore();
+        exportStub.resetHistory();
+        handler.shutdown();
+    });
+
+    after(() => {
+        exportStub.restore();
     });
 
     describe("#autoCollect", () => {
         it("performance enablement during start", () => {
             _config.enableAutoCollectPerformance = true;
-            const handler = new MetricHandler(_config);
+            handler = new MetricHandler(_config);
+            exportStub = sinon.stub(handler["_perfCounterMetricsHandler"]["_azureExporter"], "export").callsFake(
+                (spans: any, resultCallback: any) =>
+                    new Promise((resolve, reject) => {
+                        resultCallback({
+                            code: ExportResultCode.SUCCESS,
+                        });
+                        resolve();
+                    })
+            );
             assert.ok(handler["_perfCounterMetricsHandler"], "Performance counters not loaded");
         });
 
         it("preAggregated metrics enablement during start", () => {
             _config.enableAutoCollectStandardMetrics = true;
-            const handler = new MetricHandler(_config);
+            handler = new MetricHandler(_config);
+            exportStub = sinon.stub(handler["_standardMetricsHandler"]["_azureExporter"], "export").callsFake(
+                (spans: any, resultCallback: any) =>
+                    new Promise((resolve, reject) => {
+                        resultCallback({
+                            code: ExportResultCode.SUCCESS,
+                        });
+                        resolve();
+                    })
+            );
             assert.ok(handler["_standardMetricsHandler"], "preAggregated metrics not loaded");
         });
 
         it("heartbeat metrics enablement during start", () => {
             _config.enableAutoCollectHeartbeat = true;
-            const handler = new MetricHandler(_config);
-            assert.ok(handler["_standardMetricsHandler"], "Heartbeat metrics not loaded");
+            handler = new MetricHandler(_config);
+            exportStub = sinon.stub(handler["_heartbeatHandler"]["_azureExporter"], "export").callsFake(
+                (spans: any, resultCallback: any) =>
+                    new Promise((resolve, reject) => {
+                        resultCallback({
+                            code: ExportResultCode.SUCCESS,
+                        });
+                        resolve();
+                    })
+            );
+            assert.ok(handler["_heartbeatHandler"], "Heartbeat metrics not loaded");
         });
     });
 });
diff --git a/test/unitTests/metrics/performance.tests.ts b/test/unitTests/metrics/performance.tests.ts
index 7bf24ed..60b859b 100644
--- a/test/unitTests/metrics/performance.tests.ts
+++ b/test/unitTests/metrics/performance.tests.ts
@@ -4,28 +4,46 @@ import * as sinon from "sinon";
 import { PerformanceCounterMetricsHandler } from "../../../src/metrics/handlers";
 import { NativeMetricsCounter, PerformanceCounter } from "../../../src/metrics/types";
 import { ApplicationInsightsConfig } from "../../../src/shared";
+import { ExportResultCode } from "@opentelemetry/core";
 
 describe("PerformanceCounterMetricsHandler", () => {
-    let sandbox: sinon.SinonSandbox;
     let autoCollect: PerformanceCounterMetricsHandler;
+    let config: ApplicationInsightsConfig;
+    let exportStub: sinon.SinonStub;
 
     before(() => {
-        sandbox = sinon.createSandbox();
-        const config = new ApplicationInsightsConfig();
-        config.connectionString = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;";
+        config = new ApplicationInsightsConfig();
+        config.azureMonitorExporterConfig.connectionString = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;";
         config.extendedMetrics.heap = true;
         config.extendedMetrics.loop = true;
         config.extendedMetrics.gc = true;
-        autoCollect = new PerformanceCounterMetricsHandler(config, { collectionInterval: 100 });
-        sandbox.stub(autoCollect["_metricReader"]["_exporter"], "export");
     });
 
     afterEach(() => {
-        sandbox.restore();
+        exportStub.resetHistory();
+        autoCollect.shutdown();
     });
 
+    after(() => {
+        exportStub.restore();
+    });
+
+    function createAutoCollect(customConfig?: ApplicationInsightsConfig) {
+        autoCollect = new PerformanceCounterMetricsHandler(customConfig || config, { collectionInterval: 100 });
+        exportStub = sinon.stub(autoCollect["_azureExporter"], "export").callsFake(
+            (spans: any, resultCallback: any) =>
+                new Promise((resolve, reject) => {
+                    resultCallback({
+                        code: ExportResultCode.SUCCESS,
+                    });
+                    resolve();
+                })
+        );
+    }
+
     describe("#Metrics", () => {
         it("should create instruments", () => {
+            createAutoCollect();
             assert.ok(
                 autoCollect["_processMetrics"]["_memoryPrivateBytesGauge"],
                 "_memoryPrivateBytesGauge not available"
@@ -49,10 +67,10 @@ describe("PerformanceCounterMetricsHandler", () => {
         });
 
         it("should observe instruments during collection", async () => {
-            const mockExport = sandbox.stub(autoCollect["_azureExporter"], "export");
+            createAutoCollect();
             await new Promise((resolve) => setTimeout(resolve, 120));
-            assert.ok(mockExport.called);
-            const resourceMetrics = mockExport.args[0][0];
+            assert.ok(exportStub.called);
+            const resourceMetrics = exportStub.args[0][0];
             const scopeMetrics = resourceMetrics.scopeMetrics;
             assert.strictEqual(scopeMetrics.length, 1, "scopeMetrics count");
             let metrics = scopeMetrics[0].metrics;
@@ -66,10 +84,10 @@ describe("PerformanceCounterMetricsHandler", () => {
         });
 
         it("should not collect when disabled", async () => {
-            const mockExport = sandbox.stub(autoCollect["_azureExporter"], "export");
+            createAutoCollect();
             autoCollect.shutdown();
             await new Promise((resolve) => setTimeout(resolve, 120));
-            assert.ok(mockExport.notCalled);
+            assert.ok(exportStub.notCalled);
         });
 
         it("should add correct views", () => {
@@ -78,7 +96,7 @@ describe("PerformanceCounterMetricsHandler", () => {
             config.extendedMetrics.heap = false;
             config.extendedMetrics.loop = false;
             config.extendedMetrics.gc = false;
-            const autoCollect = new PerformanceCounterMetricsHandler(config);
+            createAutoCollect(config);
             let views = autoCollect["_getViews"]();
             assert.equal(views.length, 11);
         });
diff --git a/test/unitTests/metrics/standardMetrics.tests.ts b/test/unitTests/metrics/standardMetrics.tests.ts
index f69228b..627ad24 100644
--- a/test/unitTests/metrics/standardMetrics.tests.ts
+++ b/test/unitTests/metrics/standardMetrics.tests.ts
@@ -7,24 +7,33 @@ import * as sinon from "sinon";
 import { StandardMetricsHandler } from "../../../src/metrics/handlers/standardMetricsHandler";
 import { IStandardMetricBaseDimensions, StandardMetric } from "../../../src/metrics/types";
 import { ApplicationInsightsConfig } from "../../../src/shared";
+import { ExportResultCode } from "@opentelemetry/core";
 
 describe("#StandardMetricsHandler", () => {
-    let sandbox: sinon.SinonSandbox;
+    let exportStub: sinon.SinonStub;
     let autoCollect: StandardMetricsHandler;
 
     before(() => {
-        sandbox = sinon.createSandbox();
         const config = new ApplicationInsightsConfig();
         config.azureMonitorExporterConfig.connectionString = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;";
         autoCollect = new StandardMetricsHandler(config, { collectionInterval: 100 });
-        sandbox.stub(autoCollect["_metricReader"]["_exporter"], "export");
+        exportStub = sinon.stub(autoCollect["_azureExporter"], "export").callsFake(
+            (spans: any, resultCallback: any) =>
+                new Promise((resolve, reject) => {
+                    resultCallback({
+                        code: ExportResultCode.SUCCESS,
+                    });
+                    resolve();
+                })
+        );
     });
 
     afterEach(() => {
-        sandbox.restore();
+        exportStub.resetHistory();
     });
 
     after(() => {
+        exportStub.restore();
         autoCollect.shutdown();
     });
 
@@ -40,7 +49,6 @@ describe("#StandardMetricsHandler", () => {
     });
 
     it("should observe instruments during collection", async () => {
-        const mockExport = sandbox.stub(autoCollect["_azureExporter"], "export");
         let resource = {
             attributes: {} as any
         };
@@ -98,8 +106,8 @@ describe("#StandardMetricsHandler", () => {
         }
 
         await new Promise((resolve) => setTimeout(resolve, 120));
-        assert.ok(mockExport.called);
-        const resourceMetrics = mockExport.args[0][0];
+        assert.ok(exportStub.called);
+        const resourceMetrics = exportStub.args[0][0];
         const scopeMetrics = resourceMetrics.scopeMetrics;
         assert.strictEqual(scopeMetrics.length, 1, "scopeMetrics count");
         const metrics = scopeMetrics[0].metrics;
@@ -110,7 +118,7 @@ describe("#StandardMetricsHandler", () => {
         assert.equal(metrics[3].descriptor.name, StandardMetric.TRACE_COUNT);
 
         // Requests
-        
+
         assert.strictEqual(metrics[0].dataPoints.length, 3, "dataPoints count");
         assert.strictEqual((metrics[0].dataPoints[0].value as Histogram).count, 1, "dataPoint count");
         assert.strictEqual((metrics[0].dataPoints[0].value as Histogram).min, 654321, "dataPoint min");
@@ -219,9 +227,8 @@ describe("#StandardMetricsHandler", () => {
     });
 
     it("should not collect when disabled", async () => {
-        const mockExport = sandbox.stub(autoCollect["_azureExporter"], "export");
         autoCollect.shutdown();
         await new Promise((resolve) => setTimeout(resolve, 120));
-        assert.ok(mockExport.notCalled);
+        assert.ok(exportStub.notCalled);
     });
 });
diff --git a/test/unitTests/traces/traceHandler.tests.ts b/test/unitTests/traces/traceHandler.tests.ts
index 143225b..ea67f26 100644
--- a/test/unitTests/traces/traceHandler.tests.ts
+++ b/test/unitTests/traces/traceHandler.tests.ts
@@ -71,6 +71,8 @@ describe("Library/TraceHandler", () => {
             exportStub.restore();
             mockHttpServer.close();
             mockHttpsServer.close();
+            metricHandler.shutdown();
+            handler.shutdown();
         });
 
         function createMockServers() {