Skip to content
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
6 changes: 3 additions & 3 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ jobs:

strategy:
matrix:
node-version: [18.x]
node-version: [20.x]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- run: openssl req -x509 -nodes -newkey rsa -keyout ./test/certs/server-key.pem -out ./test/certs/server-cert.pem -days 1 -subj "/C=CL/ST=RM/L=OpenTelemetryTest/O=Root/OU=Test/CN=ca"
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm i
Expand Down
48 changes: 16 additions & 32 deletions .github/workflows/node.js-linux-arm64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,27 @@ on:

jobs:
build:
# Use a standard Ubuntu runner instead of requesting ARM64 hardware directly
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm

strategy:
matrix:
# Using the same Node versions as the main workflow
node-version: [18, 20, 22, 24]
node-version: [20.x, 22.x, 24.x]

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up QEMU
uses: docker/setup-qemu-action@v2
with:
platforms: arm64

- name: Run tests in ARM64 Docker container
- uses: actions/checkout@v4

- name: Generate SSL Certificate
run: |
# Generate SSL certificates first (outside container)
mkdir -p ./test/certs
openssl req -x509 -nodes -newkey rsa:2048 -keyout ./test/certs/server-key.pem -out ./test/certs/server-cert.pem -days 1 -subj "/C=CL/ST=RM/L=OpenTelemetryTest/O=Root/OU=Test/CN=ca"

# Set proper permissions for the mounted volume
chmod -R 777 .

# Run the Node.js tests in ARM64 container
docker run --rm -v ${{ github.workspace }}:/app -w /app --platform linux/arm64 node:${{ matrix.node-version }}-alpine sh -c '
# Install build tools needed for native modules
apk add --no-cache python3 make g++

# Clean out directory only (preserve node_modules for fresh install)
rm -rf ./out

# Install dependencies and run tests
npm i
npm run build --if-present
npm run lint
npm test
'

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- run: npm run clean
- run: npm i
- run: npm run build --if-present
- run: npm run lint
- run: npm test
60 changes: 15 additions & 45 deletions .github/workflows/node.js-windows-arm64.yml
Original file line number Diff line number Diff line change
@@ -1,56 +1,26 @@
name: Node.js CI (Windows ARM64)

# NOTE: GitHub does not offer public Windows ARM64 runners.
# The previous version of this workflow used QEMU to emulate ARM64 Linux in
# Docker, which was neither testing Windows nor reliably passing due to
# emulation flakiness. Native ARM64 testing is now covered by the
# node.js-linux-arm64.yml workflow using ubuntu-24.04-arm runners.
#
# This workflow will be enabled once GitHub provides public Windows ARM64
# runners (or a self-hosted runner is configured).

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
# Use the Linux runner instead as it has better Docker support
placeholder:
runs-on: ubuntu-latest

strategy:
matrix:
# Using the same Node versions as the main workflow but without the .x suffix for Docker images
node-version: [18, 20, 22, 24]

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up QEMU
uses: docker/setup-qemu-action@v2
with:
platforms: arm64
# Generate certificates using Linux openssl command
- name: Generate SSL Certificate
run: |
# Create certificates directory
mkdir -p ./test/certs

# Generate SSL certificates
openssl req -x509 -nodes -newkey rsa:2048 -keyout ./test/certs/server-key.pem -out ./test/certs/server-cert.pem -days 1 -subj "/C=CL/ST=RM/L=OpenTelemetryTest/O=Root/OU=Test/CN=ca"

# Set permissions
chmod -R 777 .

- name: Run Node.js ${{ matrix.node-version }} tests in ARM64 Docker container
run: |
# Run the tests in an ARM64 container
docker run --rm -v ${{ github.workspace }}:/app -w /app --platform linux/arm64 node:${{ matrix.node-version }}-alpine sh -c '
echo "Running tests for Node.js ${{ matrix.node-version }} on ARM64 emulation (Windows-targeted tests)"

# Install build dependencies for native modules
apk add --no-cache python3 make g++

# Clean out directory only (preserve node_modules for fresh install)
rm -rf ./out

# Install dependencies and run tests
npm i
npm run build --if-present
npm run lint
npm test
'
- name: Windows ARM64 testing not yet available
run: |
echo "Skipped: GitHub does not offer public Windows ARM64 runners."
echo "ARM64 testing is covered by the Linux ARM64 workflow (ubuntu-24.04-arm)."
echo "See: https://github.com/actions/runner-images#available-images"
66 changes: 0 additions & 66 deletions .github/workflows/node.js-windows-x86.yml

This file was deleted.

56 changes: 56 additions & 0 deletions .github/workflows/node.js-windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Node.js CI (Windows)

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:

runs-on: windows-latest

strategy:
matrix:
node-version: [20.x, 22.x, 24.x]

steps:
- uses: actions/checkout@v4
- name: Generate SSL Certificate
shell: pwsh
run: |
$certsDir = ".\test\certs"
if (-not (Test-Path $certsDir)) {
New-Item -ItemType Directory -Path $certsDir
}

$cert = New-SelfSignedCertificate -Subject "CN=ca,OU=Test,O=Root,L=OpenTelemetryTest,ST=RM,C=CL" -NotAfter (Get-Date).AddDays(1)

# Export certificate to PEM format
$certBytes = $cert.Export("Cert")
$pemCert = "-----BEGIN CERTIFICATE-----`r`n" + [Convert]::ToBase64String($certBytes, [System.Base64FormattingOptions]::InsertLineBreaks) + "`r`n-----END CERTIFICATE-----"
Set-Content -Path "$certsDir\server-cert.pem" -Value $pemCert

# Export private key placeholder
$randomBytes = New-Object byte[] 32
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($randomBytes)
$randomKeyContent = [Convert]::ToBase64String($randomBytes)
Set-Content -Path "$certsDir\server-key.pem" -Value "-----BEGIN PRIVATE KEY-----`r`n$randomKeyContent`r`n-----END PRIVATE KEY-----"

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- run: npm run clean
- name: Install dependencies
run: |
npm i
if (!(Test-Path -Path node_modules/diagnostic-channel-publishers)) {
npm i diagnostic-channel-publishers --no-save
}
- run: npm run build --if-present
- run: npm run lint
- name: Run tests with mocks
run: npm run test:mocked
6 changes: 3 additions & 3 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
node-version: [18.x, 20.x, 22.x, 24.x]
node-version: [20.x, 22.x, 24.x]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- run: openssl req -x509 -nodes -newkey rsa -keyout ./test/certs/server-key.pem -out ./test/certs/server-cert.pem -days 1 -subj "/C=CL/ST=RM/L=OpenTelemetryTest/O=Root/OU=Test/CN=ca"
- name: (${{ matrix.os }}) on Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm run clean
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Release History

### 3.15.0 (Unreleased)

#### Bug Fixes

- Fix memory leak caused by process event listener accumulation when `useAzureMonitor()` is called multiple times. ([#1415](https://github.com/microsoft/ApplicationInsights-node.js/issues/1415))

### 3.14.0 (2026-02-24)

#### Other Changes
Expand Down
4 changes: 4 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export function useAzureMonitor(options?: AzureMonitorOpenTelemetryOptions) {
options.logRecordProcessors.push(otlpLogProcessor);
}

// Clean up previous instances to prevent listener accumulation on repeated calls
autoCollectLogs?.shutdown();
exceptions?.shutdown();

distroUseAzureMonitor(options);
const logApi = new LogApi(logs.getLogger("ApplicationInsightsLogger"));
autoCollectLogs = new AutoCollectLogs();
Expand Down
51 changes: 51 additions & 0 deletions test/unitTests/main.tests.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import assert from "assert";
import sinon from "sinon";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { trace, ProxyTracerProvider } from "@opentelemetry/api";
import { logs } from "@opentelemetry/api-logs";
Expand Down Expand Up @@ -154,4 +155,54 @@ describe("ApplicationInsightsClient", () => {
});
assert.ok(hasOtlpProcessor, "Should have OTLP trace processor with custom config");
});

it("repeated useAzureMonitor calls should not accumulate process event listeners", () => {
const connString = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333";
const options = { azureMonitorExporterOptions: { connectionString: connString } };

const uncaughtBefore = process.listenerCount("uncaughtException");
const rejectionBefore = process.listenerCount("unhandledRejection");

useAzureMonitor(options);
const afterFirst = {
uncaught: process.listenerCount("uncaughtException"),
rejection: process.listenerCount("unhandledRejection"),
};

shutdownAzureMonitor();
useAzureMonitor(options);
const afterSecond = {
uncaught: process.listenerCount("uncaughtException"),
rejection: process.listenerCount("unhandledRejection"),
};

assert.strictEqual(
afterSecond.uncaught,
afterFirst.uncaught,
"uncaughtException listeners should not accumulate across repeated useAzureMonitor calls"
);
assert.strictEqual(
afterSecond.rejection,
afterFirst.rejection,
"unhandledRejection listeners should not accumulate across repeated useAzureMonitor calls"
);

// Also test calling useAzureMonitor again WITHOUT shutdown in between
useAzureMonitor(options);
const afterThird = {
uncaught: process.listenerCount("uncaughtException"),
rejection: process.listenerCount("unhandledRejection"),
};

assert.strictEqual(
afterThird.uncaught,
afterFirst.uncaught,
"uncaughtException listeners should not accumulate even without explicit shutdown between calls"
);
assert.strictEqual(
afterThird.rejection,
afterFirst.rejection,
"unhandledRejection listeners should not accumulate even without explicit shutdown between calls"
);
});
});
Loading