diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bf7de53f0072..881e1df00686 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -816,7 +816,7 @@ sdk/dnsresolver/arm-dnsresolver @qiaozha @dw511214992 @MaryGao sdk/dnsresolver/ci.mgmt.yml @qiaozha @dw511214992 @MaryGao # PRLabel: %Monitor -/sdk/monitor/ @hectorhdzg +/sdk/monitor/ @hectorhdzg @JacksonWeber /sdk/monitor/monitor-query @KarishmaGhiya # ServiceLabel: %AAD %Service Attention diff --git a/sdk/monitor/monitor-opentelemetry-exporter/review/monitor-opentelemetry-exporter.api.md b/sdk/monitor/monitor-opentelemetry-exporter/review/monitor-opentelemetry-exporter.api.md index c63683635dec..f64ccb250c02 100644 --- a/sdk/monitor/monitor-opentelemetry-exporter/review/monitor-opentelemetry-exporter.api.md +++ b/sdk/monitor/monitor-opentelemetry-exporter/review/monitor-opentelemetry-exporter.api.md @@ -6,6 +6,7 @@ import { AggregationTemporality } from '@opentelemetry/sdk-metrics-base'; import { ExportResult } from '@opentelemetry/core'; +import { InstrumentType } from '@opentelemetry/sdk-metrics-base'; import { PushMetricExporter } from '@opentelemetry/sdk-metrics-base'; import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; import { ResourceMetrics } from '@opentelemetry/sdk-metrics-base'; @@ -32,7 +33,7 @@ export class AzureMonitorMetricExporter extends AzureMonitorBaseExporter impleme constructor(options?: AzureExporterConfig); export(metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void): Promise; forceFlush(): Promise; - selectAggregationTemporality(): AggregationTemporality; + selectAggregationTemporality(_instrumentType: InstrumentType): AggregationTemporality; shutdown(): Promise; } diff --git a/sdk/monitor/monitor-opentelemetry-exporter/src/export/metric.ts b/sdk/monitor/monitor-opentelemetry-exporter/src/export/metric.ts index 660624948313..8748a21d5c41 100644 --- a/sdk/monitor/monitor-opentelemetry-exporter/src/export/metric.ts +++ b/sdk/monitor/monitor-opentelemetry-exporter/src/export/metric.ts @@ -3,10 +3,11 @@ import { diag } from "@opentelemetry/api"; import { AggregationTemporality, + InstrumentType, PushMetricExporter, ResourceMetrics, } from "@opentelemetry/sdk-metrics-base"; -import { ExportResult } from "@opentelemetry/core"; +import { ExportResult, ExportResultCode } from "@opentelemetry/core"; import { AzureMonitorBaseExporter } from "./base"; import { AzureExporterConfig } from "../config"; import { TelemetryItem as Envelope } from "../generated"; @@ -19,12 +20,22 @@ export class AzureMonitorMetricExporter extends AzureMonitorBaseExporter implements PushMetricExporter { + /** + * Flag to determine if Exporter is shutdown. + */ + private _isShutdown = false; + /** + * Aggregation temporality. + */ + private _aggregationTemporality: AggregationTemporality; + /** * Initializes a new instance of the AzureMonitorMetricExporter class. * @param AzureExporterConfig - Exporter configuration. */ constructor(options: AzureExporterConfig = {}) { super(options); + this._aggregationTemporality = AggregationTemporality.CUMULATIVE; diag.debug("AzureMonitorMetricExporter was successfully setup"); } @@ -37,6 +48,11 @@ export class AzureMonitorMetricExporter metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void ): Promise { + if (this._isShutdown) { + diag.info("Exporter shut down. Failed to export spans."); + setTimeout(() => resultCallback({ code: ExportResultCode.FAILED }), 0); + return; + } diag.info(`Exporting ${metrics.scopeMetrics.length} metrics(s). Converting to envelopes...`); let envelopes: Envelope[] = resourceMetricsToEnvelope(metrics, this._instrumentationKey); @@ -47,22 +63,22 @@ export class AzureMonitorMetricExporter * Shutdown AzureMonitorMetricExporter. */ public async shutdown(): Promise { - diag.info("Azure Monitor Trace Exporter shutting down"); + this._isShutdown = true; + diag.info("AzureMonitorMetricExporter shutting down"); return this._shutdown(); } /** * Select aggregation temporality */ - public selectAggregationTemporality() { - return AggregationTemporality.CUMULATIVE; + public selectAggregationTemporality(_instrumentType: InstrumentType): AggregationTemporality { + return this._aggregationTemporality; } /** * Force flush */ public async forceFlush() { - // TODO: https://github.com/open-telemetry/opentelemetry-js/issues/3060 - throw new Error("Method not implemented."); + return Promise.resolve(); } } diff --git a/sdk/monitor/monitor-opentelemetry-exporter/src/export/trace.ts b/sdk/monitor/monitor-opentelemetry-exporter/src/export/trace.ts index 7f7705d8b9da..7a60d97975f3 100644 --- a/sdk/monitor/monitor-opentelemetry-exporter/src/export/trace.ts +++ b/sdk/monitor/monitor-opentelemetry-exporter/src/export/trace.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { diag } from "@opentelemetry/api"; -import { ExportResult } from "@opentelemetry/core"; +import { ExportResult, ExportResultCode } from "@opentelemetry/core"; import { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base"; import { AzureMonitorBaseExporter } from "./base"; import { AzureExporterConfig } from "../config"; @@ -13,6 +13,11 @@ import { readableSpanToEnvelope, spanEventsToEnvelopes } from "../utils/spanUtil * Azure Monitor OpenTelemetry Trace Exporter. */ export class AzureMonitorTraceExporter extends AzureMonitorBaseExporter implements SpanExporter { + /** + * Flag to determine if Exporter is shutdown. + */ + private _isShutdown = false; + /** * Initializes a new instance of the AzureMonitorTraceExporter class. * @param AzureExporterConfig - Exporter configuration. @@ -31,6 +36,12 @@ export class AzureMonitorTraceExporter extends AzureMonitorBaseExporter implemen spans: ReadableSpan[], resultCallback: (result: ExportResult) => void ): Promise { + if (this._isShutdown) { + diag.info("Exporter shut down. Failed to export spans."); + setTimeout(() => resultCallback({ code: ExportResultCode.FAILED }), 0); + return; + } + diag.info(`Exporting ${spans.length} span(s). Converting to envelopes...`); let envelopes: Envelope[] = []; @@ -48,7 +59,8 @@ export class AzureMonitorTraceExporter extends AzureMonitorBaseExporter implemen * Shutdown AzureMonitorTraceExporter. */ async shutdown(): Promise { - diag.info("Azure Monitor Trace Exporter shutting down"); + this._isShutdown = true; + diag.info("AzureMonitorTraceExporter shutting down"); return this._shutdown(); } } diff --git a/sdk/monitor/monitor-opentelemetry-exporter/src/utils/metricUtils.ts b/sdk/monitor/monitor-opentelemetry-exporter/src/utils/metricUtils.ts index 1036e703ecc7..0277c40e4ab4 100644 --- a/sdk/monitor/monitor-opentelemetry-exporter/src/utils/metricUtils.ts +++ b/sdk/monitor/monitor-opentelemetry-exporter/src/utils/metricUtils.ts @@ -1,10 +1,23 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { MetricAttributes } from "@opentelemetry/api-metrics"; import { DataPointType, Histogram, ResourceMetrics } from "@opentelemetry/sdk-metrics-base"; import { TelemetryItem as Envelope, MetricsData, MetricDataPoint } from "../generated"; import { createTagsFromResource } from "./resourceUtils"; +function createPropertiesFromMetricAttributes(attributes?: MetricAttributes): { + [propertyName: string]: string; +} { + const properties: { [propertyName: string]: string } = {}; + if (attributes) { + for (const key of Object.keys(attributes)) { + properties[key] = attributes[key] as string; + } + } + return properties; +} + /** * Metric to Azure envelope parsing. * @internal @@ -16,12 +29,14 @@ export function resourceMetricsToEnvelope(metrics: ResourceMetrics, ikey: string const tags = createTagsFromResource(metrics.resource); metrics.scopeMetrics.forEach((scopeMetric) => { - let baseData: MetricsData = { - metrics: [], - version: 2, - }; scopeMetric.metrics.forEach((metric) => { metric.dataPoints.forEach((dataPoint) => { + let baseData: MetricsData = { + metrics: [], + version: 2, + properties: {}, + }; + baseData.properties = createPropertiesFromMetricAttributes(dataPoint.attributes); var metricDataPoint: MetricDataPoint = { name: metric.descriptor.name, value: 0, @@ -40,23 +55,23 @@ export function resourceMetricsToEnvelope(metrics: ResourceMetrics, ikey: string metricDataPoint.min = (dataPoint.value as Histogram).min; } baseData.metrics.push(metricDataPoint); + let envelope: Envelope = { + name: "Microsoft.ApplicationInsights.Metric", + time: time, + sampleRate: 100, + instrumentationKey: instrumentationKey, + tags: tags, + version: 1, + data: { + baseType: "MetricData", + baseData: { + ...baseData, + }, + }, + }; + envelopes.push(envelope); }); }); - let envelope: Envelope = { - name: "Microsoft.ApplicationInsights.Metric", - time: time, - sampleRate: 100, - instrumentationKey: instrumentationKey, - tags: tags, - version: 1, - data: { - baseType: "MetricData", - baseData: { - ...baseData, - }, - }, - }; - envelopes.push(envelope); }); return envelopes; diff --git a/sdk/monitor/monitor-opentelemetry-exporter/test/utils/assert.ts b/sdk/monitor/monitor-opentelemetry-exporter/test/utils/assert.ts index a4c620704fe0..053003072f61 100644 --- a/sdk/monitor/monitor-opentelemetry-exporter/test/utils/assert.ts +++ b/sdk/monitor/monitor-opentelemetry-exporter/test/utils/assert.ts @@ -133,7 +133,7 @@ export const assertTraceExpectation = (actual: Envelope[], expectations: Expecta export const assertMetricExpectation = (actual: Envelope[], expectations: Expectation[]): void => { for (const expectation of expectations) { let envelope: any = null; - if (expectation.data!.baseData!.name) { + if (expectation.data!.baseData!.metrics && expectation.data!.baseData!.metrics.length > 0) { envelope = actual.filter((e) => { return ( (e.data!.baseData as MetricsData).metrics[0].name === diff --git a/sdk/monitor/monitor-opentelemetry-exporter/test/utils/basic.ts b/sdk/monitor/monitor-opentelemetry-exporter/test/utils/basic.ts index 944c71481839..bc588fa99729 100644 --- a/sdk/monitor/monitor-opentelemetry-exporter/test/utils/basic.ts +++ b/sdk/monitor/monitor-opentelemetry-exporter/test/utils/basic.ts @@ -209,13 +209,18 @@ export class MetricBasicScenario implements Scenario { async run(): Promise { const meter = this._provider.getMeter("basic"); let counter = meter.createCounter("testCounter"); + let counter2 = meter.createCounter("testCounter2"); let histogram = meter.createHistogram("testHistogram"); + let histogram2 = meter.createHistogram("testHistogram2"); + let attributes = { testAttribute: "testValue" }; counter.add(1); counter.add(2); + counter2.add(12, attributes); histogram.record(1); histogram.record(2); histogram.record(3); histogram.record(4); + histogram2.record(12, attributes); await delay(0); } @@ -225,7 +230,6 @@ export class MetricBasicScenario implements Scenario { flush(): Promise { return delay(100); - // return metricReader.forceFlush(); } expectation: Expectation[] = [ @@ -243,6 +247,39 @@ export class MetricBasicScenario implements Scenario { count: 1, dataPointType: "Aggregation", }, + ], + } as any, + }, + children: [], + }, + { + ...COMMON_ENVELOPE_PARAMS, + name: "Microsoft.ApplicationInsights.Metric", + data: { + baseType: "MetricData", + baseData: { + version: 2, + metrics: [ + { + name: "testCounter2", + value: 12, + count: 1, + dataPointType: "Aggregation", + }, + ], + properties: { testAttribute: "testValue" }, + } as any, + }, + children: [], + }, + { + ...COMMON_ENVELOPE_PARAMS, + name: "Microsoft.ApplicationInsights.Metric", + data: { + baseType: "MetricData", + baseData: { + version: 2, + metrics: [ { name: "testHistogram", value: 10, @@ -256,5 +293,27 @@ export class MetricBasicScenario implements Scenario { }, children: [], }, + { + ...COMMON_ENVELOPE_PARAMS, + name: "Microsoft.ApplicationInsights.Metric", + data: { + baseType: "MetricData", + baseData: { + version: 2, + metrics: [ + { + name: "testHistogram2", + value: 12, + count: 1, + max: 12, + min: 12, + dataPointType: "Aggregation", + }, + ], + properties: { testAttribute: "testValue" }, + } as any, + }, + children: [], + }, ]; }