Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Beta] Auto collection of logs aligning to OpenTelemetry #1272

Merged
merged 5 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions src/logs/console.ts → src/logs/autoCollectLogs.ts
Original file line number Diff line number Diff line change
@@ -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();
}
}
60 changes: 25 additions & 35 deletions src/logs/diagnostic-channel/console.sub.ts
Original file line number Diff line number Diff line change
@@ -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 = [];
}
99 changes: 43 additions & 56 deletions src/logs/diagnostic-channel/winston.sub.ts
Original file line number Diff line number Diff line change
@@ -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) {

Check warning on line 41 in src/logs/diagnostic-channel/winston.sub.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 16.x)

'og' is defined but never used

Check warning on line 41 in src/logs/diagnostic-channel/winston.sub.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 18.x)

'og' is defined but never used
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 = [];
}
2 changes: 1 addition & 1 deletion src/logs/exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
12 changes: 6 additions & 6 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
}
Expand All @@ -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);
}

Expand All @@ -51,7 +51,7 @@ export function useAzureMonitor(options?: AzureMonitorOpenTelemetryOptions) {
*/
export async function shutdownAzureMonitor() {
await distroShutdownAzureMonitor();
console.shutdown();
autoCollectLogs.shutdown();
exceptions?.shutdown();
perfCounters?.shutdown();
}
Expand Down
4 changes: 2 additions & 2 deletions src/logs/api.ts → src/shim/logsApi.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/shim/telemetryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
9 changes: 5 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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 };
}

/**
Expand Down
2 changes: 1 addition & 1 deletion test/unitTests/logs/api.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading