From c89a1fe78f419a8472121d9c07ddd497cffa0923 Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Tue, 30 Sep 2025 14:41:13 -0700 Subject: [PATCH 1/3] Fix dynamic import. --- .../src/export/statsbeat/customerSDKStats.ts | 26 ++++++++++-------- .../src/export/statsbeat/statsbeatExporter.ts | 27 ++++++++++++++----- .../src/platform/nodejs/baseSender.ts | 26 ++++++++++++------ .../test/internal/baseSender.spec.ts | 2 +- .../test/internal/customerSDKStats.spec.ts | 6 ++--- 5 files changed, 58 insertions(+), 29 deletions(-) diff --git a/sdk/monitor/monitor-opentelemetry-exporter/src/export/statsbeat/customerSDKStats.ts b/sdk/monitor/monitor-opentelemetry-exporter/src/export/statsbeat/customerSDKStats.ts index a9a0182637f0..ccd8acec061b 100644 --- a/sdk/monitor/monitor-opentelemetry-exporter/src/export/statsbeat/customerSDKStats.ts +++ b/sdk/monitor/monitor-opentelemetry-exporter/src/export/statsbeat/customerSDKStats.ts @@ -5,9 +5,9 @@ import type { BatchObservableResult, Meter, ObservableGauge } from "@opentelemet import { diag } from "@opentelemetry/api"; import type { PeriodicExportingMetricReaderOptions } from "@opentelemetry/sdk-metrics"; import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"; -import type { AzureMonitorExporterOptions } from "../../index.js"; import * as ai from "../../utils/constants/applicationinsights.js"; import { StatsbeatMetrics } from "./statsbeatMetrics.js"; +import type { AzureMonitorStatsbeatExporter } from "./statsbeatExporter.js"; import type { CustomerSDKStatsProperties, StatsbeatOptions } from "./types.js"; import { CustomerSDKStats, @@ -19,7 +19,6 @@ import { } from "./types.js"; import { CustomSDKStatsCounter, STATSBEAT_LANGUAGE, TelemetryType } from "./types.js"; import { getAttachType } from "../../utils/metricUtils.js"; -import { AzureMonitorStatsbeatExporter } from "./statsbeatExporter.js"; import { BreezePerformanceCounterNames } from "../../types.js"; import type { MetricsData, RemoteDependencyData, RequestData } from "../../generated/index.js"; import type { TelemetryItem as Envelope } from "../../generated/index.js"; @@ -55,13 +54,12 @@ export class CustomerSDKStatsMetrics extends StatsbeatMetrics { // Customer SDK Stats properties private customerProperties: CustomerSDKStatsProperties; - private constructor(options: StatsbeatOptions) { + private constructor( + options: StatsbeatOptions, + exporter: AzureMonitorStatsbeatExporter + ) { super(); - const exporterConfig: AzureMonitorExporterOptions = { - connectionString: `InstrumentationKey=${options.instrumentationKey};IngestionEndpoint=${options.endpointUrl}`, - }; - - this.customerSDKStatsExporter = new AzureMonitorStatsbeatExporter(exporterConfig); + this.customerSDKStatsExporter = exporter; // Exports Customer SDK Stats every 15 minutes const customerMetricReaderOptions: PeriodicExportingMetricReaderOptions = { exporter: this.customerSDKStatsExporter, @@ -109,11 +107,17 @@ export class CustomerSDKStatsMetrics extends StatsbeatMetrics { /** * Get singleton instance of CustomerSDKStatsMetrics * @param options - Configuration options for customer SDK Stats metrics - * @returns The singleton instance + * @returns Promise of the singleton instance */ - public static getInstance(options: StatsbeatOptions): CustomerSDKStatsMetrics { + public static async getInstance(options: StatsbeatOptions): Promise { if (!CustomerSDKStatsMetrics._instance) { - CustomerSDKStatsMetrics._instance = new CustomerSDKStatsMetrics(options); + // Use dynamic import to break circular dependency + const { AzureMonitorStatsbeatExporter } = await import("./statsbeatExporter.js"); + const customerStatsExporterConfig = { + connectionString: `InstrumentationKey=${options.instrumentationKey};IngestionEndpoint=${options.endpointUrl}`, + }; + const exporter = new AzureMonitorStatsbeatExporter(customerStatsExporterConfig); + CustomerSDKStatsMetrics._instance = new CustomerSDKStatsMetrics(options, exporter); } return CustomerSDKStatsMetrics._instance; } diff --git a/sdk/monitor/monitor-opentelemetry-exporter/src/export/statsbeat/statsbeatExporter.ts b/sdk/monitor/monitor-opentelemetry-exporter/src/export/statsbeat/statsbeatExporter.ts index abbb6caecf2a..fbd50a733175 100644 --- a/sdk/monitor/monitor-opentelemetry-exporter/src/export/statsbeat/statsbeatExporter.ts +++ b/sdk/monitor/monitor-opentelemetry-exporter/src/export/statsbeat/statsbeatExporter.ts @@ -8,7 +8,6 @@ import type { AzureMonitorExporterOptions } from "../../config.js"; import type { TelemetryItem as Envelope } from "../../generated/index.js"; import { resourceMetricsToEnvelope } from "../../utils/metricUtils.js"; import { AzureMonitorBaseExporter } from "../base.js"; -import { HttpSender } from "../../platform/index.js"; /** * Azure Monitor Statsbeat Exporter @@ -21,7 +20,8 @@ export class AzureMonitorStatsbeatExporter * Flag to determine if the Exporter is shutdown. */ private _isShutdown = false; - private _sender: HttpSender; + private _sender: any; + private _senderOptions: any; /** * Initializes a new instance of the AzureMonitorStatsbeatExporter class. @@ -29,13 +29,25 @@ export class AzureMonitorStatsbeatExporter */ constructor(options: AzureMonitorExporterOptions) { super(options, true); - this._sender = new HttpSender({ + // Store sender options for lazy initialization to avoid circular dependency + this._senderOptions = { endpointUrl: this.endpointUrl, instrumentationKey: this.instrumentationKey, trackStatsbeat: this.trackStatsbeat, exporterOptions: options, isStatsbeatSender: true, - }); + }; + } + + /** + * Lazily initialize the sender to avoid circular dependency + */ + private async _getSender(): Promise { + if (!this._sender) { + const { HttpSender } = await import("../../platform/nodejs/httpSender.js"); + this._sender = new HttpSender(this._senderOptions); + } + return this._sender; } /** @@ -79,7 +91,8 @@ export class AzureMonitorStatsbeatExporter // Supress tracing until OpenTelemetry Metrics SDK support it context.with(suppressTracing(context.active()), async () => { - resultCallback(await this._sender.exportEnvelopes(filteredEnvelopes)); + const sender = await this._getSender(); + resultCallback(await sender.exportEnvelopes(filteredEnvelopes)); }); } @@ -88,7 +101,9 @@ export class AzureMonitorStatsbeatExporter */ public async shutdown(): Promise { this._isShutdown = true; - return this._sender.shutdown(); + if (this._sender) { + return this._sender.shutdown(); + } } /** diff --git a/sdk/monitor/monitor-opentelemetry-exporter/src/platform/nodejs/baseSender.ts b/sdk/monitor/monitor-opentelemetry-exporter/src/platform/nodejs/baseSender.ts index c626099b45c8..95f8cc5e84b2 100644 --- a/sdk/monitor/monitor-opentelemetry-exporter/src/platform/nodejs/baseSender.ts +++ b/sdk/monitor/monitor-opentelemetry-exporter/src/platform/nodejs/baseSender.ts @@ -25,7 +25,6 @@ import { ENV_APPLICATIONINSIGHTS_SDKSTATS_EXPORT_INTERVAL, RetriableRestErrorTypes, } from "../../Declarations/Constants.js"; -import { CustomerSDKStatsMetrics } from "../../export/statsbeat/customerSDKStats.js"; const DEFAULT_BATCH_SEND_RETRY_INTERVAL_MS = 60_000; @@ -38,7 +37,7 @@ export abstract class BaseSender { private numConsecutiveRedirects: number; private retryTimer: NodeJS.Timeout | null; private networkStatsbeatMetrics: NetworkStatsbeatMetrics | undefined; - private customerSDKStatsMetrics: CustomerSDKStatsMetrics | undefined; + private customerSDKStatsMetrics: any; private longIntervalStatsbeatMetrics; private statsbeatFailureCount: number = 0; private batchSendRetryIntervalMs: number = DEFAULT_BATCH_SEND_RETRY_INTERVAL_MS; @@ -79,12 +78,23 @@ export abstract class BaseSender { ); } } - this.customerSDKStatsMetrics = CustomerSDKStatsMetrics.getInstance({ - instrumentationKey: options.instrumentationKey, - endpointUrl: options.endpointUrl, - disableOfflineStorage: this.disableOfflineStorage, - networkCollectionInterval: exportInterval, - }); + // Initialize customer SDK stats metrics asynchronously to avoid circular dependency + // Only initialize if not already set (e.g., by tests) + if (!this.customerSDKStatsMetrics) { + import("../../export/statsbeat/customerSDKStats.js") + .then(({ CustomerSDKStatsMetrics }) => CustomerSDKStatsMetrics.getInstance({ + instrumentationKey: options.instrumentationKey, + endpointUrl: options.endpointUrl, + disableOfflineStorage: this.disableOfflineStorage, + networkCollectionInterval: exportInterval, + })) + .then((metrics) => { + this.customerSDKStatsMetrics = metrics; + }) + .catch((error) => { + diag.warn("Failed to initialize customer SDK stats metrics:", error); + }); + } } } this.persister = new FileSystemPersist( diff --git a/sdk/monitor/monitor-opentelemetry-exporter/test/internal/baseSender.spec.ts b/sdk/monitor/monitor-opentelemetry-exporter/test/internal/baseSender.spec.ts index bb4662ecad76..79f8c2bee52c 100644 --- a/sdk/monitor/monitor-opentelemetry-exporter/test/internal/baseSender.spec.ts +++ b/sdk/monitor/monitor-opentelemetry-exporter/test/internal/baseSender.spec.ts @@ -106,7 +106,7 @@ vi.mock("../../src/export/statsbeat/customerSDKStats.js", () => { return { CustomerSDKStatsMetrics: { getInstance: vi.fn().mockImplementation(() => { - return mockCustomerSDKStatsMetrics; + return Promise.resolve(mockCustomerSDKStatsMetrics); }), shutdown: vi.fn(), }, diff --git a/sdk/monitor/monitor-opentelemetry-exporter/test/internal/customerSDKStats.spec.ts b/sdk/monitor/monitor-opentelemetry-exporter/test/internal/customerSDKStats.spec.ts index 57626f55f558..076528998e78 100644 --- a/sdk/monitor/monitor-opentelemetry-exporter/test/internal/customerSDKStats.spec.ts +++ b/sdk/monitor/monitor-opentelemetry-exporter/test/internal/customerSDKStats.spec.ts @@ -77,9 +77,9 @@ describe("CustomerSDKStatsMetrics", () => { endpointUrl: "https://test.endpoint.com", }; - beforeEach(() => { - // Use getInstance to get the singleton - customerSDKStatsMetrics = CustomerSDKStatsMetrics.getInstance(mockOptions); + beforeEach(async () => { + // Use getInstance to get the singleton (now async) + customerSDKStatsMetrics = await CustomerSDKStatsMetrics.getInstance(mockOptions); }); afterEach(async () => { From 2a8f4011ef5e2cf2eadc6cdbedf40c263bf2eda9 Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Tue, 30 Sep 2025 14:44:08 -0700 Subject: [PATCH 2/3] Update CHANGELOG.md --- sdk/monitor/monitor-opentelemetry-exporter/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sdk/monitor/monitor-opentelemetry-exporter/CHANGELOG.md b/sdk/monitor/monitor-opentelemetry-exporter/CHANGELOG.md index 1c76e7969f69..02b48df60c9a 100644 --- a/sdk/monitor/monitor-opentelemetry-exporter/CHANGELOG.md +++ b/sdk/monitor/monitor-opentelemetry-exporter/CHANGELOG.md @@ -1,5 +1,11 @@ # Release History +## 1.0.0-beta.36 () + +### Bugs Fixed + +- Fixed dynamic import of the exporter package. + ## 1.0.0-beta.35 (2025-09-16) ### Other Changes From d5cfdaa0969028437d85826fef5abd1111b3c8c7 Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Mon, 6 Oct 2025 12:29:41 -0700 Subject: [PATCH 3/3] Fix lint. --- pnpm-lock.yaml | 32 +++++++++--------- pnpm-workspace.yaml | 33 +++++++++---------- .../package.json | 10 +++--- .../src/platform/nodejs/baseSender.ts | 1 + 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b808ad5fb19..c18787474b03 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,13 +17,13 @@ catalogs: version: 1.2.0 default: '@types/node': - specifier: ^20.19.0 + specifier: ^20.19.19 version: 20.19.19 autorest: specifier: latest version: 3.7.2 eslint: - specifier: ^9.33.0 + specifier: ^9.37.0 version: 9.37.0 tslib: specifier: ^2.8.1 @@ -43,10 +43,10 @@ catalogs: specifier: ^8.0.2 version: 8.0.2 '@vitest/browser': - specifier: ^3.2.3 + specifier: ^3.2.4 version: 3.2.4 '@vitest/coverage-istanbul': - specifier: ^3.2.3 + specifier: ^3.2.4 version: 3.2.4 '@vitest/expect': specifier: ^3.2.3 @@ -61,16 +61,16 @@ catalogs: specifier: ^3.0.0 version: 3.0.1 dotenv: - specifier: ^16.0.0 + specifier: ^16.6.1 version: 16.6.1 nock: - specifier: ^13.5.4 + specifier: ^13.5.6 version: 13.5.6 playwright: - specifier: ^1.50.1 + specifier: ^1.55.1 version: 1.55.1 vitest: - specifier: ^3.2.3 + specifier: ^3.2.4 version: 3.2.4 overrides: @@ -17831,13 +17831,13 @@ importers: sdk/monitor/monitor-opentelemetry-exporter: dependencies: '@azure/core-auth': - specifier: ^1.9.0 + specifier: workspace:^ version: link:../../core/core-auth '@azure/core-client': - specifier: ^1.9.2 + specifier: workspace:^ version: link:../../core/core-client '@azure/core-rest-pipeline': - specifier: ^1.19.0 + specifier: workspace:^ version: link:../../core/core-rest-pipeline '@opentelemetry/api': specifier: ^1.9.0 @@ -17880,7 +17880,7 @@ importers: specifier: workspace:^ version: link:../../../common/tools/eslint-plugin-azure-sdk '@azure/logger': - specifier: ^1.1.4 + specifier: workspace:^ version: link:../../core/logger '@opentelemetry/instrumentation': specifier: ^0.205.0 @@ -30666,8 +30666,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001747: - resolution: {integrity: sha512-mzFa2DGIhuc5490Nd/G31xN1pnBnYMadtkyTjefPI7wzypqgCEpeWu9bJr0OnDsyKrW75zA9ZAt7pbQFmwLsQg==} + caniuse-lite@1.0.30001748: + resolution: {integrity: sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==} catharsis@0.9.0: resolution: {integrity: sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==} @@ -36243,7 +36243,7 @@ snapshots: browserslist@4.26.3: dependencies: baseline-browser-mapping: 2.8.12 - caniuse-lite: 1.0.30001747 + caniuse-lite: 1.0.30001748 electron-to-chromium: 1.5.230 node-releases: 2.0.23 update-browserslist-db: 1.1.3(browserslist@4.26.3) @@ -36301,7 +36301,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001747: {} + caniuse-lite@1.0.30001748: {} catharsis@0.9.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ccef5d76ffae..9f5e16066bcb 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,8 +1,5 @@ -linkWorkspacePackages: true -minimumReleaseAge: 1440 - packages: - - 'sdk/**' + - sdk/** - common/tools/dev-tool - common/tools/eslint-plugin-azure-sdk - eng/containers/turborepo-remote-cache @@ -20,12 +17,11 @@ packages: - '!sdk/servicebus/service-bus/test/stress/**' - '!sdk/eventhub/event-hubs/test/stress/**' -# Main catalog catalog: - '@types/node': ^20.19.0 + '@types/node': ^20.19.19 autorest: latest cross-env: ^7.0.3 - eslint: ^9.33.0 + eslint: ^9.37.0 mkdirp: ^3.0.1 prettier: ^3.6.2 rimraf: ^6.0.1 @@ -33,25 +29,28 @@ catalog: tsx: ^4.20.4 typescript: ~5.8.3 -# Named catalogs catalogs: arm: - '@azure/arm-storage': 18.5.0 '@azure/arm-cognitiveservices': 7.6.0 + '@azure/arm-storage': 18.5.0 '@azure/arm-webpubsub': 1.2.0 + internal: + '@azure/identity': 4.11.1 testing: '@rollup/plugin-inject': ^5.0.5 '@types/chai': ^5.2.1 '@types/chai-as-promised': ^8.0.2 - '@vitest/browser': ^3.2.3 - '@vitest/coverage-istanbul': ^3.2.3 + '@vitest/browser': ^3.2.4 + '@vitest/coverage-istanbul': ^3.2.4 '@vitest/expect': ^3.2.3 chai: ^5.2.0 chai-as-promised: ^8.0.1 chai-exclude: ^3.0.0 - dotenv: ^16.0.0 - nock: ^13.5.4 - playwright: ^1.50.1 - vitest: ^3.2.3 - internal: - '@azure/identity': 4.11.1 + dotenv: ^16.6.1 + nock: ^13.5.6 + playwright: ^1.55.1 + vitest: ^3.2.4 + +linkWorkspacePackages: true + +minimumReleaseAge: 1440 diff --git a/sdk/monitor/monitor-opentelemetry-exporter/package.json b/sdk/monitor/monitor-opentelemetry-exporter/package.json index 1121310e2ae0..dc5506f8f90c 100644 --- a/sdk/monitor/monitor-opentelemetry-exporter/package.json +++ b/sdk/monitor/monitor-opentelemetry-exporter/package.json @@ -72,7 +72,7 @@ "@azure/core-util": "workspace:^", "@azure/dev-tool": "workspace:^", "@azure/eslint-plugin-azure-sdk": "workspace:^", - "@azure/logger": "^1.1.4", + "@azure/logger": "workspace:^", "@opentelemetry/instrumentation": "^0.205.0", "@opentelemetry/instrumentation-http": "^0.205.0", "@opentelemetry/sdk-trace-node": "^2.1.0", @@ -83,13 +83,13 @@ "eslint": "catalog:", "nock": "catalog:testing", "playwright": "catalog:testing", - "typescript": "catalog:", + "typescript": "~5.8.3", "vitest": "catalog:testing" }, "dependencies": { - "@azure/core-auth": "^1.9.0", - "@azure/core-client": "^1.9.2", - "@azure/core-rest-pipeline": "^1.19.0", + "@azure/core-auth": "workspace:^", + "@azure/core-client": "workspace:^", + "@azure/core-rest-pipeline": "workspace:^", "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.205.0", "@opentelemetry/core": "^2.1.0", diff --git a/sdk/monitor/monitor-opentelemetry-exporter/src/platform/nodejs/baseSender.ts b/sdk/monitor/monitor-opentelemetry-exporter/src/platform/nodejs/baseSender.ts index 95f8cc5e84b2..5ae8b379f45f 100644 --- a/sdk/monitor/monitor-opentelemetry-exporter/src/platform/nodejs/baseSender.ts +++ b/sdk/monitor/monitor-opentelemetry-exporter/src/platform/nodejs/baseSender.ts @@ -90,6 +90,7 @@ export abstract class BaseSender { })) .then((metrics) => { this.customerSDKStatsMetrics = metrics; + return; }) .catch((error) => { diag.warn("Failed to initialize customer SDK stats metrics:", error);