Skip to content
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## [Unreleased]

### Bugs Fixed
- Fix duplicate Bunyan logs, missing HTTP duration metrics, duplicate request filtering, and incorrect performance-counter values. [#212](https://github.com/microsoft/opentelemetry-distro-javascript/pull/212)

## [1.3.0] - 2026-08-03

### Features Added
Expand Down
35 changes: 0 additions & 35 deletions src/azureMonitor/logs/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@
// Licensed under the MIT License.

import { AzureMonitorLogExporter } from "@azure/monitor-opentelemetry-exporter";
import type { Instrumentation } from "@opentelemetry/instrumentation";
import { BunyanInstrumentation } from "@opentelemetry/instrumentation-bunyan";
import { WinstonInstrumentation } from "@opentelemetry/instrumentation-winston";
import type { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
import type { InternalConfig } from "../../shared/config.js";
import type { MetricHandler } from "../metrics/handler.js";
import { AzureLogRecordProcessor } from "./logRecordProcessor.js";
import { AzureBatchLogRecordProcessor } from "./batchLogRecordProcessor.js";
import { logLevelToSeverityNumber } from "../utils/logUtils.js";

/**
* Azure Monitor OpenTelemetry Log Handler
Expand All @@ -21,7 +17,6 @@ export class LogHandler {
private _azureBatchLogRecordProcessor: AzureBatchLogRecordProcessor;
private _metricHandler: MetricHandler;
private _config: InternalConfig;
private _instrumentations: Instrumentation[];

/**
* Initializes a new instance of the LogHandler class.
Expand All @@ -36,8 +31,6 @@ export class LogHandler {
enableTraceBasedSamplingForLogs: this._config.enableTraceBasedSamplingForLogs,
});
this._azureLogRecordProcessor = new AzureLogRecordProcessor(this._metricHandler);
this._instrumentations = [];
this._initializeInstrumentations();
}

public getAzureLogRecordProcessor(): AzureLogRecordProcessor {
Expand All @@ -47,32 +40,4 @@ export class LogHandler {
public getBatchLogRecordProcessor(): BatchLogRecordProcessor {
return this._azureBatchLogRecordProcessor;
}

public getInstrumentations(): Instrumentation[] {
return this._instrumentations;
}

/**
* Start auto collection of telemetry
*/
private _initializeInstrumentations(): void {
const logLevelEnv = process.env.APPLICATIONINSIGHTS_INSTRUMENTATION_LOGGING_LEVEL;

if (this._config.instrumentationOptions.bunyan?.enabled) {
this._instrumentations.push(
new BunyanInstrumentation({
...this._config.instrumentationOptions.bunyan,
logSeverity: logLevelEnv ? logLevelToSeverityNumber(logLevelEnv) : undefined,
}),
);
}
if (this._config.instrumentationOptions.winston?.enabled) {
this._instrumentations.push(
new WinstonInstrumentation({
...this._config.instrumentationOptions.winston,
logSeverity: logLevelEnv ? logLevelToSeverityNumber(logLevelEnv) : undefined,
}),
);
}
}
}
40 changes: 27 additions & 13 deletions src/azureMonitor/metrics/performanceCounters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class PerformanceCounterMetrics {
private processTimeGaugeCallback: ObservableCallback;
private exceptionCountGauge: ObservableGauge;
private exceptionCountGaugeCallback: ObservableCallback;
private lastExceptionRate: { count: number; time: number } = { count: 0, time: 0 };
private lastExceptionRate: { count: number; time: number };
private totalCount: number = 0;
private intervalExecutionTime = 0;
private lastRequestRate: { count: number; time: number; executionInterval: number };
Expand All @@ -66,6 +66,14 @@ export class PerformanceCounterMetrics {
speed: number;
times: { user: number; nice: number; sys: number; idle: number; irq: number };
}[];
// Both process CPU callbacks need independent sampling state.
private lastAppCpuUsageNormalized: { user: number; system: number };
private lastHrtimeNormalized: number[];
private lastCpusProcessNormalized: {
model: string;
speed: number;
times: { user: number; nice: number; sys: number; idle: number; irq: number };
}[];
private totalExceptionCount: number = 0;

/**
Expand All @@ -79,12 +87,16 @@ export class PerformanceCounterMetrics {
this.lastCpusProcess = os.cpus();
this.lastAppCpuUsage = process.cpuUsage();
this.lastHrtime = process.hrtime();
this.lastCpusProcessNormalized = os.cpus();
this.lastAppCpuUsageNormalized = process.cpuUsage();
this.lastHrtimeNormalized = process.hrtime();

this.lastRequestRate = {
count: this.totalCount,
time: +new Date(),
executionInterval: this.intervalExecutionTime,
};
this.lastExceptionRate = { count: this.totalExceptionCount, time: +new Date() };

this.azureExporter = new AzureMonitorMetricExporter(
this.internalConfig.azureMonitorExporterOptions,
Expand All @@ -100,8 +112,6 @@ export class PerformanceCounterMetrics {
this.meterProvider = new MeterProvider(meterProviderConfig);
this.meter = this.meterProvider.getMeter("AzureMonitorPerformanceCountersMeter");

this.lastRequestRate = { count: 0, time: 0, executionInterval: 0 };

// Create Instruments
this.requestDurationHistogram = this.meter.createHistogram(
PerformanceCounterMetricNames.REQUEST_DURATION,
Expand Down Expand Up @@ -314,35 +324,39 @@ export class PerformanceCounterMetrics {
if (
cpus &&
cpus.length &&
this.lastCpusProcess &&
cpus.length === this.lastCpusProcess.length
this.lastCpusProcessNormalized &&
cpus.length === this.lastCpusProcessNormalized.length
) {
// Calculate % of total cpu time (user + system) this App Process used (Only supported by node v6.1.0+)
let appCpuPercent: number | undefined = undefined;
const appCpuUsage = process.cpuUsage();
const hrtime = process.hrtime();
const totalApp =
appCpuUsage.user -
this.lastAppCpuUsage.user +
(appCpuUsage.system - this.lastAppCpuUsage.system) || 0;
this.lastAppCpuUsageNormalized.user +
(appCpuUsage.system - this.lastAppCpuUsageNormalized.system) || 0;

if (typeof this.lastHrtime !== "undefined" && this.lastHrtime.length === 2) {
if (
typeof this.lastHrtimeNormalized !== "undefined" &&
this.lastHrtimeNormalized.length === 2
) {
const elapsedTime =
(hrtime[0] - this.lastHrtime[0]) * 1e6 + (hrtime[1] - this.lastHrtime[1]) / 1e3 || 0; // convert to microseconds
(hrtime[0] - this.lastHrtimeNormalized[0]) * 1e6 +
(hrtime[1] - this.lastHrtimeNormalized[1]) / 1e3 || 0; // convert to microseconds

appCpuPercent = (100 * totalApp) / (elapsedTime * cpus.length);
}
// Set previous
this.lastAppCpuUsage = appCpuUsage;
this.lastHrtime = hrtime;
const cpuTotals = this.getTotalCombinedCpu(cpus, this.lastCpusProcess);
this.lastAppCpuUsageNormalized = appCpuUsage;
this.lastHrtimeNormalized = hrtime;
const cpuTotals = this.getTotalCombinedCpu(cpus, this.lastCpusProcessNormalized);
const value =
appCpuPercent !== undefined
? appCpuPercent
: (cpuTotals.totalUser / cpuTotals.combinedTotal) * 100;
observableResult.observe(value);
}
this.lastCpusProcess = cpus;
this.lastCpusProcessNormalized = cpus;
}

private getProcessTime(observableResult: ObservableResult): void {
Expand Down
81 changes: 2 additions & 79 deletions src/azureMonitor/traces/handler.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import type { RequestOptions } from "node:http";
import { createAzureSdkInstrumentation } from "@azure/opentelemetry-instrumentation-azure-sdk";
import { AzureMonitorTraceExporter } from "@azure/monitor-opentelemetry-exporter";
import type { BufferConfig } from "@opentelemetry/sdk-trace-base";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
import type {
HttpInstrumentationConfig,
IgnoreOutgoingRequestFunction,
} from "@opentelemetry/instrumentation-http";
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
import { MongoDBInstrumentation } from "@opentelemetry/instrumentation-mongodb";
import { MySQLInstrumentation } from "@opentelemetry/instrumentation-mysql";
import { PgInstrumentation } from "@opentelemetry/instrumentation-pg";
import { RedisInstrumentation } from "@opentelemetry/instrumentation-redis";

import type { InternalConfig } from "../../shared/config.js";
import type { MetricHandler } from "../metrics/handler.js";
import { ignoreOutgoingRequestHook } from "../utils/common.js";
import { AzureMonitorSpanProcessor } from "./spanProcessor.js";
import type { Instrumentation } from "@opentelemetry/instrumentation";

/**
* Azure Monitor OpenTelemetry Trace Handler
Expand All @@ -29,19 +16,17 @@ export class TraceHandler {
private _batchSpanProcessor: BatchSpanProcessor;
private _azureSpanProcessor: AzureMonitorSpanProcessor;
private _azureExporter: AzureMonitorTraceExporter;
private _instrumentations: Instrumentation[];
private _config: InternalConfig;
private _metricHandler: MetricHandler;

/**
* Initializes a new instance of the TraceHandler class.
* @param _config - Configuration.
* @param _metricHandler - MetricHandler.
* @param config - Configuration.
* @param metricHandler - MetricHandler.
*/
constructor(config: InternalConfig, metricHandler: MetricHandler) {
this._config = config;
this._metricHandler = metricHandler;
this._instrumentations = [];
this._azureExporter = new AzureMonitorTraceExporter(this._config.azureMonitorExporterOptions);
const bufferConfig: BufferConfig = {
maxExportBatchSize: 512,
Expand All @@ -51,7 +36,6 @@ export class TraceHandler {
};
this._batchSpanProcessor = new BatchSpanProcessor(this._azureExporter, bufferConfig);
this._azureSpanProcessor = new AzureMonitorSpanProcessor(this._metricHandler);
this._initializeInstrumentations();
}

public getBatchSpanProcessor(): BatchSpanProcessor {
Expand All @@ -62,10 +46,6 @@ export class TraceHandler {
return this._azureSpanProcessor;
}

public getInstrumentations(): Instrumentation[] {
return this._instrumentations;
}

/**
* Shutdown handler
*/
Expand All @@ -74,61 +54,4 @@ export class TraceHandler {
await this._azureSpanProcessor.shutdown();
await this._azureExporter.shutdown();
}

/**
* Start auto collection of telemetry
*/
private _initializeInstrumentations(): void {
if (this._config.instrumentationOptions.http?.enabled) {
const httpinstrumentationOptions = this._config.instrumentationOptions
.http as HttpInstrumentationConfig;
const providedIgnoreOutgoingRequestHook =
httpinstrumentationOptions.ignoreOutgoingRequestHook;
const mergedIgnoreOutgoingRequestHook: IgnoreOutgoingRequestFunction = (
request: RequestOptions,
) => {
const result = ignoreOutgoingRequestHook(request);
Comment thread
JacksonWeber marked this conversation as resolved.
if (!result) {
// Not internal call
if (providedIgnoreOutgoingRequestHook) {
// Provided hook in config
return providedIgnoreOutgoingRequestHook(request);
}
}
return result;
};
httpinstrumentationOptions.ignoreOutgoingRequestHook = mergedIgnoreOutgoingRequestHook;
this._instrumentations.push(
new HttpInstrumentation(this._config.instrumentationOptions.http),
);
}
if (this._config.instrumentationOptions.azureSdk?.enabled) {
this._instrumentations.push(
createAzureSdkInstrumentation(this._config.instrumentationOptions.azureSdk),
);
}
if (this._config.instrumentationOptions.mongoDb?.enabled) {
this._instrumentations.push(
new MongoDBInstrumentation(this._config.instrumentationOptions.mongoDb),
);
}
if (this._config.instrumentationOptions.mySql?.enabled) {
this._instrumentations.push(
new MySQLInstrumentation(this._config.instrumentationOptions.mySql),
);
}
if (this._config.instrumentationOptions.postgreSql?.enabled) {
this._instrumentations.push(
new PgInstrumentation(this._config.instrumentationOptions.postgreSql),
);
}
if (
this._config.instrumentationOptions.redis?.enabled ||
this._config.instrumentationOptions.redis4?.enabled
) {
this._instrumentations.push(
new RedisInstrumentation(this._config.instrumentationOptions.redis),
);
}
}
}
Loading
Loading