Skip to content
Open
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
32 changes: 16 additions & 16 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 16 additions & 17 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -20,38 +17,40 @@ 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
tslib: ^2.8.1
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
6 changes: 6 additions & 0 deletions sdk/monitor/monitor-opentelemetry-exporter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
10 changes: 5 additions & 5 deletions sdk/monitor/monitor-opentelemetry-exporter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<CustomerSDKStatsMetrics> {
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -21,21 +20,34 @@ export class AzureMonitorStatsbeatExporter
* Flag to determine if the Exporter is shutdown.
*/
private _isShutdown = false;
private _sender: HttpSender;
private _sender: any;
private _senderOptions: any;
Comment on lines +23 to +24
Copy link
Preview

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using any types reduces type safety. Consider importing the HttpSender type separately from the implementation, or define proper interfaces for these properties to maintain type safety.

Copilot uses AI. Check for mistakes.


/**
* Initializes a new instance of the AzureMonitorStatsbeatExporter class.
* @param options - Exporter configuration
*/
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<any> {
Copy link
Preview

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return type should be more specific than any. Consider importing the HttpSender type or defining a proper interface to maintain type safety.

Copilot uses AI. Check for mistakes.

if (!this._sender) {
const { HttpSender } = await import("../../platform/nodejs/httpSender.js");
this._sender = new HttpSender(this._senderOptions);
}
return this._sender;
}

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

Expand All @@ -88,7 +101,9 @@ export class AzureMonitorStatsbeatExporter
*/
public async shutdown(): Promise<void> {
this._isShutdown = true;
return this._sender.shutdown();
if (this._sender) {
return this._sender.shutdown();
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Copy link
Preview

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using any type reduces type safety. Consider creating a proper interface or using a union type with undefined to maintain type safety while accommodating the async initialization pattern.

Copilot uses AI. Check for mistakes.

private longIntervalStatsbeatMetrics;
private statsbeatFailureCount: number = 0;
private batchSendRetryIntervalMs: number = DEFAULT_BATCH_SEND_RETRY_INTERVAL_MS;
Expand Down Expand Up @@ -79,12 +78,24 @@ 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;
return;
})
.catch((error) => {
diag.warn("Failed to initialize customer SDK stats metrics:", error);
});
}
}
}
this.persister = new FileSystemPersist(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
},
Expand Down
Loading
Loading