Repository: microsoft/ApplicationInsights-node.js
Pull request #1272 (2024-02-03 00:27:43)

[Beta] Auto collection of logs aligning to OpenTelemetry
Allow configuration of log level for console and winston auto instrumentations
Generate OTel logs to mantain same format as bunyan currently available in OTel
Use OTel severity values


Patch:
diff --git a/src/logs/console.ts b/src/logs/autoCollectLogs.ts
similarity index 61%
rename from src/logs/console.ts
rename to src/logs/autoCollectLogs.ts
index 94ceaa3..c0ce083 100644
--- a/src/logs/console.ts
+++ b/src/logs/autoCollectLogs.ts
@@ -1,26 +1,20 @@
 import { InstrumentationOptions } from "../types";
-import { LogApi } from "./api";
 import { enablePublishers } from "./diagnostic-channel/initialization";
 enablePublishers();
 
-export class AutoCollectConsole {
-    private _client: LogApi;
-
-    constructor(client: LogApi) {
-        this._client = client;
-    }
+export class AutoCollectLogs {
 
     public enable(options: InstrumentationOptions) {
         // eslint-disable-next-line @typescript-eslint/no-var-requires
-        require("./diagnostic-channel/console.sub").enable(options.console?.enabled, this._client);
+        require("./diagnostic-channel/console.sub").enable(options.console);
         // eslint-disable-next-line @typescript-eslint/no-var-requires
-        require("./diagnostic-channel/winston.sub").enable(options.winston?.enabled, this._client);
+        require("./diagnostic-channel/winston.sub").enable(options.winston);
     }
 
     public shutdown() {
         // eslint-disable-next-line @typescript-eslint/no-var-requires
-        require("./diagnostic-channel/console.sub").enable(false, this._client);
+        require("./diagnostic-channel/console.sub").dispose();
         // eslint-disable-next-line @typescript-eslint/no-var-requires
-        require("./diagnostic-channel/winston.sub").enable(false, this._client);
+        require("./diagnostic-channel/winston.sub").dispose();
     }
 }
diff --git a/src/logs/diagnostic-channel/console.sub.ts b/src/logs/diagnostic-channel/console.sub.ts
index 7850404..0e23a4a 100644
--- a/src/logs/diagnostic-channel/console.sub.ts
+++ b/src/logs/diagnostic-channel/console.sub.ts
@@ -1,51 +1,41 @@
 // Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT license. See LICENSE file in the project root for details.
+
+import { Logger, LogRecord, SeverityNumber, logs } from "@opentelemetry/api-logs";
+import { InstrumentationConfig } from "@opentelemetry/instrumentation";
 import { channel, IStandardEvent, trueFilter } from "diagnostic-channel";
 import { console as consolePub } from "diagnostic-channel-publishers";
-import { KnownSeverityLevel } from "../../declarations/generated";
-import { LogApi } from "../api";
 
-let clients: LogApi[] = [];
+
+let logger: Logger;
+let logSendingLevel: SeverityNumber;
 
 const subscriber = (event: IStandardEvent<consolePub.IConsoleData>) => {
-    let message = event.data.message as Error | string;
-    clients.forEach((client) => {
-        if (message instanceof Error) {
-            client.trackException({ exception: message });
-        } else {
-            // Message can have a trailing newline
-            if (message.lastIndexOf("\n") === message.length - 1) {
-                message = message.substring(0, message.length - 1);
-            }
-            client.trackTrace({
-                message: message,
-                severity: event.data.stderr
-                    ? KnownSeverityLevel.Warning
-                    : KnownSeverityLevel.Information,
-            });
+    const severity = (event.data.message as string | Error) instanceof Error ? SeverityNumber.ERROR : (event.data.stderr
+        ? SeverityNumber.WARN
+        : SeverityNumber.INFO);
+    if (logSendingLevel <= severity) {
+        let message = event.data.message.toString();
+        // Message can have a trailing newline
+        if (message.lastIndexOf("\n") === message.length - 1) {
+            message = message.substring(0, message.length - 1);
         }
-    });
+        const logRecord: LogRecord = {
+            body: message,
+            severityNumber: severity
+        };
+        logger.emit(logRecord);
+    }
 };
 
-export function enable(enabled: boolean, client: LogApi) {
-    if (enabled) {
-        const handlerFound = clients.find((c) => c === client);
-        if (handlerFound) {
-            return;
-        }
-        if (clients.length === 0) {
-            channel.subscribe<consolePub.IConsoleData>("console", subscriber, trueFilter);
-        }
-        clients.push(client);
-    } else {
-        clients = clients.filter((c) => c !== client);
-        if (clients.length === 0) {
-            channel.unsubscribe("console", subscriber);
-        }
+export function enable(config?: InstrumentationConfig & { logSendingLevel?: SeverityNumber }) {
+    if (config?.enabled) {
+        logger = logs.getLogger("ApplicationInsightsConsoleLogger");
+        logSendingLevel = config.logSendingLevel || SeverityNumber.UNSPECIFIED;
+        channel.subscribe<consolePub.IConsoleData>("console", subscriber, trueFilter);
     }
 }
 
 export function dispose() {
     channel.unsubscribe("console", subscriber);
-    clients = [];
 }
diff --git a/src/logs/diagnostic-channel/winston.sub.ts b/src/logs/diagnostic-channel/winston.sub.ts
index 0fd22fc..07f364f 100644
--- a/src/logs/diagnostic-channel/winston.sub.ts
+++ b/src/logs/diagnostic-channel/winston.sub.ts
@@ -1,82 +1,69 @@
 // Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT license. See LICENSE file in the project root for details.
+
+import { Logger, LogRecord, logs, SeverityNumber } from "@opentelemetry/api-logs";
 import { channel, IStandardEvent, trueFilter } from "diagnostic-channel";
 import { winston } from "diagnostic-channel-publishers";
-import { KnownSeverityLevel } from "../../declarations/generated";
-import { LogApi } from "../api";
+import { InstrumentationConfig } from "@opentelemetry/instrumentation";
+
 
-let clients: LogApi[] = [];
+let logger: Logger;
+let logSendingLevel: SeverityNumber;
 
-const winstonToAILevelMap: { [key: string]: (og: string) => string } = {
+const winstonToAILevelMap: { [key: string]: (og: string) => number } = {
     syslog(og: string) {
-        const map: { [key: string]: string } = {
-            emerg: KnownSeverityLevel.Critical,
-            alert: KnownSeverityLevel.Critical,
-            crit: KnownSeverityLevel.Critical,
-            error: KnownSeverityLevel.Error,
-            warning: KnownSeverityLevel.Warning,
-            notice: KnownSeverityLevel.Information,
-            info: KnownSeverityLevel.Information,
-            debug: KnownSeverityLevel.Verbose,
+        const map: { [key: string]: number } = {
+            emerg: SeverityNumber.FATAL3,
+            alert: SeverityNumber.FATAL2,
+            crit: SeverityNumber.FATAL,
+            error: SeverityNumber.ERROR,
+            warning: SeverityNumber.WARN,
+            notice: SeverityNumber.INFO2,
+            info: SeverityNumber.INFO,
+            debug: SeverityNumber.DEBUG,
         };
 
-        return map[og] === undefined ? KnownSeverityLevel.Information : map[og];
+        return map[og] === undefined ? SeverityNumber.INFO : map[og];
     },
     npm(og: string) {
-        const map: { [key: string]: string } = {
-            error: KnownSeverityLevel.Error,
-            warn: KnownSeverityLevel.Warning,
-            info: KnownSeverityLevel.Information,
-            verbose: KnownSeverityLevel.Verbose,
-            debug: KnownSeverityLevel.Verbose,
-            silly: KnownSeverityLevel.Verbose,
+        const map: { [key: string]: number } = {
+            error: SeverityNumber.ERROR,
+            warn: SeverityNumber.WARN,
+            info: SeverityNumber.INFO,
+            http: SeverityNumber.DEBUG3,
+            verbose: SeverityNumber.DEBUG2,
+            debug: SeverityNumber.DEBUG,
+            silly: SeverityNumber.TRACE,
         };
 
-        return map[og] === undefined ? KnownSeverityLevel.Information : map[og];
+        return map[og] === undefined ? SeverityNumber.INFO : map[og];
     },
     unknown(og: string) {
-        return KnownSeverityLevel.Information;
+        return SeverityNumber.INFO;
     },
 };
 
-const subscriber = (event: IStandardEvent<winston.IWinstonData>) => {
-    const message = event.data.message as Error | string;
-    clients.forEach((client) => {
-        if (message instanceof Error) {
-            client.trackException({
-                exception: message,
-                properties: event.data.meta,
-            });
-        } else {
-            const AIlevel = winstonToAILevelMap[event.data.levelKind](event.data.level);
-            client.trackTrace({
-                message: message,
-                severity: AIlevel,
-                properties: event.data.meta,
-            });
-        }
-    });
+const subscriber = (event: IStandardEvent<winston.IWinstonData>) => {  
+    const severity = winstonToAILevelMap[event.data.levelKind](event.data.level);
+    if (logSendingLevel <= severity) {
+        const message = event.data.message.toString();
+        let logRecord: LogRecord = {
+            body: message,
+            severityNumber: severity,
+            attributes: event.data.meta
+        };
+        logger.emit(logRecord);
+    }
 };
 
-export function enable(enabled: boolean, client: LogApi) {
-    if (enabled) {
-        const handlerFound = clients.find((c) => c === client);
-        if (handlerFound) {
-            return;
-        }
-        if (clients.length === 0) {
-            channel.subscribe<winston.IWinstonData>("winston", subscriber, trueFilter);
-        }
-        clients.push(client);
-    } else {
-        clients = clients.filter((c) => c !== client);
-        if (clients.length === 0) {
-            channel.unsubscribe("winston", subscriber);
-        }
+export function enable(config?: InstrumentationConfig & { logSendingLevel?: SeverityNumber }) {
+    if (config?.enabled) {
+        logger = logs.getLogger("ApplicationInsightsConsoleLogger");
+        logSendingLevel = config.logSendingLevel || SeverityNumber.UNSPECIFIED;
+        channel.subscribe<winston.IWinstonData>("winston", subscriber, trueFilter);
     }
 }
 
 export function dispose() {
     channel.unsubscribe("winston", subscriber);
-    clients = [];
 }
diff --git a/src/logs/exceptions.ts b/src/logs/exceptions.ts
index 3e4970f..91ba17d 100644
--- a/src/logs/exceptions.ts
+++ b/src/logs/exceptions.ts
@@ -2,7 +2,7 @@
 // Licensed under the MIT license. See LICENSE file in the project root for details.
 import { logs } from "@opentelemetry/api-logs";
 import { Util } from "../shared/util";
-import { LogApi } from "./api";
+import { LogApi } from "../shim/logsApi";
 import { LoggerProvider } from "@opentelemetry/sdk-logs";
 
 type ExceptionHandle = "uncaughtExceptionMonitor" | "uncaughtException" | "unhandledRejection";
diff --git a/src/main.ts b/src/main.ts
index f8833a2..0fabbfd 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -11,15 +11,15 @@ import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
 import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
 import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
 
-import { AutoCollectConsole } from "./logs/console";
+import { AutoCollectLogs } from "./logs/autoCollectLogs";
 import { AutoCollectExceptions } from "./logs/exceptions";
 import { AzureMonitorOpenTelemetryOptions } from "./types";
 import { ApplicationInsightsConfig } from "./shared/configuration/config";
-import { LogApi } from "./logs/api";
+import { LogApi } from "./shim/logsApi";
 import { PerformanceCounterMetrics } from "./metrics/performanceCounters";
 import { AzureMonitorSpanProcessor } from "./traces/spanProcessor";
 
-let console: AutoCollectConsole;
+let autoCollectLogs: AutoCollectLogs;
 let exceptions: AutoCollectExceptions;
 let perfCounters: PerformanceCounterMetrics;
 
@@ -31,7 +31,7 @@ export function useAzureMonitor(options?: AzureMonitorOpenTelemetryOptions) {
     distroUseAzureMonitor(options);
     const internalConfig = new ApplicationInsightsConfig(options);
     const logApi = new LogApi(logs.getLogger("ApplicationInsightsLogger"));
-    console = new AutoCollectConsole(logApi);
+    autoCollectLogs = new AutoCollectLogs();
     if (internalConfig.enableAutoCollectExceptions) {
         exceptions = new AutoCollectExceptions(logApi);
     }
@@ -42,7 +42,7 @@ export function useAzureMonitor(options?: AzureMonitorOpenTelemetryOptions) {
             (trace.getTracerProvider() as BasicTracerProvider).addSpanProcessor(new AzureMonitorSpanProcessor(perfCounters));
         }
     }
-    console.enable(internalConfig.instrumentationOptions);
+    autoCollectLogs.enable(internalConfig.instrumentationOptions);
     _addOtlpExporters(internalConfig);
 }
 
@@ -51,7 +51,7 @@ export function useAzureMonitor(options?: AzureMonitorOpenTelemetryOptions) {
 */
 export async function shutdownAzureMonitor() {
     await distroShutdownAzureMonitor();
-    console.shutdown();
+    autoCollectLogs.shutdown();
     exceptions?.shutdown();
     perfCounters?.shutdown();
 }
diff --git a/src/logs/api.ts b/src/shim/logsApi.ts
similarity index 98%
rename from src/logs/api.ts
rename to src/shim/logsApi.ts
index ab76de1..eff478a 100644
--- a/src/logs/api.ts
+++ b/src/shim/logsApi.ts
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT license.
 
-import { Logger as OtelLogger, LogRecord, logs } from "@opentelemetry/api-logs";
+import { Logger as OtelLogger, LogRecord } from "@opentelemetry/api-logs";
 import { LogRecord as SDKLogRecord } from "@opentelemetry/sdk-logs";
 import { Attributes, diag } from "@opentelemetry/api";
 import { IdGenerator, RandomIdGenerator } from "@opentelemetry/sdk-trace-base";
@@ -18,7 +18,7 @@ import {
     TelemetryExceptionDetails
 } from "../declarations/generated";
 import { Util } from "../shared/util";
-import { parseStack } from "./exceptions";
+import { parseStack } from "../logs/exceptions";
 
 /**
  * Log manual API to generate Application Insights telemetry
diff --git a/src/shim/telemetryClient.ts b/src/shim/telemetryClient.ts
index 58a5960..0be98ee 100644
--- a/src/shim/telemetryClient.ts
+++ b/src/shim/telemetryClient.ts
@@ -13,7 +13,7 @@ import Config = require("./shim-config");
 import { AttributeSpanProcessor } from "../shared/util/attributeSpanProcessor";
 import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
 import { AttributeLogProcessor } from "../shared/util/attributeLogRecordProcessor";
-import { LogApi } from "../logs/api";
+import { LogApi } from "./logsApi";
 import { flushAzureMonitor, shutdownAzureMonitor, useAzureMonitor } from "../main";
 import { AzureMonitorOpenTelemetryOptions } from "../types";
 
diff --git a/src/types.ts b/src/types.ts
index 9cd0e97..b72aee4 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -2,6 +2,7 @@
 // Licensed under the MIT license.
 
 import { AzureMonitorOpenTelemetryOptions as DistroOptions, InstrumentationOptions as DistroInstrumentationOptions } from "@azure/monitor-opentelemetry";
+import { SeverityNumber } from "@opentelemetry/api-logs";
 import { InstrumentationConfig } from "@opentelemetry/instrumentation";
 import { OTLPExporterNodeConfigBase } from "@opentelemetry/otlp-exporter-base";
 
@@ -37,10 +38,10 @@ export interface AzureMonitorOpenTelemetryOptions extends DistroOptions {
 }
 
 export interface InstrumentationOptions extends DistroInstrumentationOptions {
-    /** Console Instrumentation Config */
-    console?: InstrumentationConfig;
-    /** Winston Instrumentation Config */
-    winston?: InstrumentationConfig;
+  /** Console Instrumentation Config */
+  console?: InstrumentationConfig & { logSendingLevel?: SeverityNumber };
+  /** Winston Instrumentation Config */
+  winston?: InstrumentationConfig & { logSendingLevel?: SeverityNumber };
 }
 
 /**


Test patch:
diff --git a/test/unitTests/logs/api.tests.ts b/test/unitTests/logs/api.tests.ts
index 0348741..7a3dbb9 100644
--- a/test/unitTests/logs/api.tests.ts
+++ b/test/unitTests/logs/api.tests.ts
@@ -13,7 +13,7 @@ import {
     TraceTelemetry
 } from "../../../src/declarations/contracts";
 import { AvailabilityData, MessageData, MonitorDomain, PageViewData, TelemetryEventData, TelemetryExceptionData } from "../../../src/declarations/generated";
-import { LogApi } from "../../../src/logs/api";
+import { LogApi } from "../../../src/shim/logsApi";
 
 describe("logs/API", () => {
     let sandbox: sinon.SinonSandbox;
diff --git a/test/unitTests/logs/console.tests.ts b/test/unitTests/logs/console.tests.ts
index 717b294..8801524 100644
--- a/test/unitTests/logs/console.tests.ts
+++ b/test/unitTests/logs/console.tests.ts
@@ -1,76 +1,84 @@
 import * as assert from "assert";
-import * as sinon from "sinon";
 import { channel } from "diagnostic-channel";
 import { console } from "diagnostic-channel-publishers";
-import { logs } from "@opentelemetry/api-logs";
-import { enable, dispose } from "../../../src/logs/diagnostic-channel/console.sub";
-import { LogApi } from "../../../src/logs/api";
-import { AutoCollectConsole } from "../../../src/logs/console";
+import { SeverityNumber, logs } from '@opentelemetry/api-logs';
+import {
+    LoggerProvider,
+    SimpleLogRecordProcessor,
+    InMemoryLogRecordExporter,
+} from '@opentelemetry/sdk-logs';
+
+import { dispose } from "../../../src/logs/diagnostic-channel/console.sub";
+import { AutoCollectLogs } from "../../../src/logs/autoCollectLogs";
 
 
 describe("AutoCollection/Console", () => {
-    let sandbox: sinon.SinonSandbox;
+    let memoryLogExporter: InMemoryLogRecordExporter;
 
     before(() => {
-        sandbox = sinon.createSandbox();
+        logs.disable();
+        const loggerProvider = new LoggerProvider();
+        memoryLogExporter = new InMemoryLogRecordExporter();
+        loggerProvider.addLogRecordProcessor(
+            new SimpleLogRecordProcessor(memoryLogExporter)
+        );
+        logs.setGlobalLoggerProvider(loggerProvider);
+    });
+
+    beforeEach(() => {
+        memoryLogExporter.getFinishedLogRecords().length = 0; // clear
     });
 
     afterEach(() => {
-        sandbox.restore();
         dispose();
     });
 
     describe("#log and #error()", () => {
-        it("should call trackException for errors", () => {
-            let logApi = new LogApi(logs.getLogger("testLogger"));
-            let autoCollect = new AutoCollectConsole(logApi);
+        it("should log event for errors", () => {
+            let autoCollect = new AutoCollectLogs();
             autoCollect.enable({
                 console: { enabled: true }
             });
-            const stub = sandbox.stub(logApi, "trackException");
             const dummyError = new Error("test error");
             const errorEvent: console.IConsoleData = {
                 message: dummyError as any,
-                stderr: false, // log() should still log as ExceptionData
+                stderr: false,
             };
-
             channel.publish("console", errorEvent);
-            assert.ok(stub.calledOnce);
-            assert.deepEqual(stub.args[0][0].exception, dummyError);
+            const logRecords = memoryLogExporter.getFinishedLogRecords();
+            assert.strictEqual(logRecords.length, 1);
+            assert.strictEqual(logRecords[0].body, "Error: test error");
+            assert.strictEqual(logRecords[0].severityNumber, SeverityNumber.ERROR);
         });
 
-        it("should call trackTrace for logs", () => {
-            let logApi = new LogApi(logs.getLogger("testLogger"));
-            let autoCollect = new AutoCollectConsole(logApi);
+        it("should log event for logs", () => {
+            let autoCollect = new AutoCollectLogs();
             autoCollect.enable({
                 console: { enabled: true }
             });
-            const stub = sandbox.stub(logApi, "trackTrace");
             const logEvent: console.IConsoleData = {
                 message: "test log",
-                stderr: true, // should log as MessageData regardless of this setting
+                stderr: true,
             };
             channel.publish("console", logEvent);
-            assert.ok(stub.calledOnce);
-            assert.deepEqual(stub.args[0][0].message, "test log");
+            const logRecords = memoryLogExporter.getFinishedLogRecords();
+            assert.strictEqual(logRecords.length, 1);
+            assert.strictEqual(logRecords[0].body, "test log");
+            assert.strictEqual(logRecords[0].severityNumber, SeverityNumber.WARN);
         });
 
-        it("should notify multiple handlers", () => {
-            let logApi = new LogApi(logs.getLogger("testLogger"));
-            let secondLogApi = new LogApi(logs.getLogger("testLogger"));
-            const stub = sandbox.stub(logApi, "trackTrace");
-            const secondStub = sandbox.stub(secondLogApi, "trackTrace");
-            enable(true, logApi);
-            enable(true, secondLogApi);
+        it("severityLevel", () => {
+            let autoCollect = new AutoCollectLogs();
+            autoCollect.enable({
+                console: { enabled: true, logSendingLevel: SeverityNumber.ERROR }
+            });
             const logEvent: console.IConsoleData = {
                 message: "test log",
-                stderr: true, // should log as MessageData regardless of this setting
+                stderr: true,
             };
             channel.publish("console", logEvent);
-            assert.ok(stub.calledOnce);
-            assert.deepEqual(stub.args[0][0].message, "test log");
-            assert.ok(secondStub.calledOnce);
-            assert.deepEqual(secondStub.args[0][0].message, "test log");
+            const logRecords = memoryLogExporter.getFinishedLogRecords();
+            assert.strictEqual(logRecords.length, 0);
         });
     });
 });
diff --git a/test/unitTests/logs/winston.tests.ts b/test/unitTests/logs/winston.tests.ts
index 1a27376..0f6b7a8 100644
--- a/test/unitTests/logs/winston.tests.ts
+++ b/test/unitTests/logs/winston.tests.ts
@@ -1,79 +1,88 @@
 import * as assert from "assert";
-import * as sinon from "sinon";
 import { channel } from "diagnostic-channel";
 import { winston } from "diagnostic-channel-publishers";
-import { logs } from "@opentelemetry/api-logs";
-import { enable, dispose } from "../../../src/logs/diagnostic-channel/winston.sub";
-import { LogApi } from "../../../src/logs/api";
-import { AutoCollectConsole } from "../../../src/logs/console";
+import { SeverityNumber, logs } from '@opentelemetry/api-logs';
+import {
+    LoggerProvider,
+    SimpleLogRecordProcessor,
+    InMemoryLogRecordExporter,
+} from '@opentelemetry/sdk-logs';
+import { dispose } from "../../../src/logs/diagnostic-channel/winston.sub";
+import { AutoCollectLogs } from "../../../src/logs/autoCollectLogs";
 
 
 describe("diagnostic-channel/winston", () => {
-    let sandbox: sinon.SinonSandbox;
+    let memoryLogExporter: InMemoryLogRecordExporter;
 
     before(() => {
-        sandbox = sinon.createSandbox();
+        logs.disable();
+        const loggerProvider = new LoggerProvider();
+        memoryLogExporter = new InMemoryLogRecordExporter();
+        loggerProvider.addLogRecordProcessor(
+            new SimpleLogRecordProcessor(memoryLogExporter)
+        );
+        logs.setGlobalLoggerProvider(loggerProvider);
+    });
+
+    beforeEach(() => {
+        memoryLogExporter.getFinishedLogRecords().length = 0; // clear
     });
 
     afterEach(() => {
-        sandbox.restore();
         dispose();
     });
 
-    it("should call trackException for errors", () => {
-        let logApi = new LogApi(logs.getLogger("testLogger"));
-        let autoCollect = new AutoCollectConsole(logApi);
+    it("should emit log for errors", () => {
+        let autoCollect = new AutoCollectLogs();
         autoCollect.enable({
             winston: { enabled: true }
         });
-        const stub = sandbox.stub(logApi, "trackException");
         const dummyError = new Error("test error");
         const errorEvent: winston.IWinstonData = {
             message: dummyError as any,
             meta: {},
-            level: "foo",
+            level: "error",
             levelKind: "npm",
         };
         channel.publish("winston", errorEvent);
-        assert.ok(stub.calledOnce);
-        assert.deepEqual(stub.args[0][0].exception, dummyError);
+        const logRecords = memoryLogExporter.getFinishedLogRecords();
+        assert.strictEqual(logRecords.length, 1);
+        assert.strictEqual(logRecords[0].body, "Error: test error");
+        assert.strictEqual(logRecords[0].severityNumber, SeverityNumber.ERROR);
     });
 
-    it("should call trackTrace for logs", () => {
-        let logApi = new LogApi(logs.getLogger("testLogger"));
-        let autoCollect = new AutoCollectConsole(logApi);
+    it("should emit log for winston log", () => {
+        let autoCollect = new AutoCollectLogs();
         autoCollect.enable({
             winston: { enabled: true }
         });
-        const stub = sandbox.stub(logApi, "trackTrace");
         const logEvent: winston.IWinstonData = {
             message: "test log",
-            meta: {},
-            level: "foo",
+            meta: { "test1": "testValue" },
+            level: "debug",
             levelKind: "npm",
         };
         channel.publish("winston", logEvent);
-        assert.ok(stub.calledOnce);
-        assert.deepEqual(stub.args[0][0].message, "test log");
+        const logRecords = memoryLogExporter.getFinishedLogRecords();
+        assert.strictEqual(logRecords.length, 1);
+        assert.strictEqual(logRecords[0].body, "test log");
+        assert.strictEqual(logRecords[0].severityNumber, SeverityNumber.DEBUG);
+        assert.strictEqual(logRecords[0].attributes["test1"], "testValue");
     });
 
-    it("should notify multiple handlers", () => {
-        let logApi = new LogApi(logs.getLogger("testLogger"));
-        let secondLogApi = new LogApi(logs.getLogger("testLogger"));
-        const stub = sandbox.stub(logApi, "trackTrace");
-        const secondStub = sandbox.stub(secondLogApi, "trackTrace");
-        enable(true, logApi);
-        enable(true, secondLogApi);
+    it("severityLevel", () => {
+        let autoCollect = new AutoCollectLogs();
+        autoCollect.enable({
+            console: { enabled: true, logSendingLevel: SeverityNumber.ERROR }
+        });
         const logEvent: winston.IWinstonData = {
             message: "test log",
             meta: {},
-            level: "foo",
+            level: "debug",
             levelKind: "npm",
         };
         channel.publish("winston", logEvent);
-        assert.ok(stub.calledOnce);
-        assert.deepEqual(stub.args[0][0].message, "test log");
-        assert.ok(secondStub.calledOnce);
-        assert.deepEqual(secondStub.args[0][0].message, "test log");
+        const logRecords = memoryLogExporter.getFinishedLogRecords();
+        assert.strictEqual(logRecords.length, 0);
     });
 });