Skip to content

Commit

Permalink
[Azure Monitor OpenTelemetry Exporter] Add OTel resource metric envel…
Browse files Browse the repository at this point in the history
…ope (#25929)

Added envelope sent to Breeze endpoint including OpenTelemetry resource
attributes
  • Loading branch information
hectorhdzg authored May 22, 2023
1 parent c2ab033 commit 9d828fc
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 16 deletions.
28 changes: 20 additions & 8 deletions sdk/monitor/monitor-opentelemetry-exporter/src/export/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { AzureMonitorBaseExporter } from "./base";
import { AzureMonitorExporterOptions } from "../config";
import { TelemetryItem as Envelope } from "../generated";
import { readableSpanToEnvelope, spanEventsToEnvelopes } from "../utils/spanUtils";
import { createResourceMetricEnvelope } from "../utils/common";

/**
* Azure Monitor OpenTelemetry Trace Exporter.
Expand Down Expand Up @@ -44,15 +45,26 @@ export class AzureMonitorTraceExporter extends AzureMonitorBaseExporter implemen

diag.info(`Exporting ${spans.length} span(s). Converting to envelopes...`);

let envelopes: Envelope[] = [];
spans.forEach((span) => {
envelopes.push(readableSpanToEnvelope(span, this._instrumentationKey));
let spanEventEnvelopes = spanEventsToEnvelopes(span, this._instrumentationKey);
if (spanEventEnvelopes.length > 0) {
envelopes.push(...spanEventEnvelopes);
if (spans.length > 0) {
let envelopes: Envelope[] = [];
const resourceMetricEnvelope = createResourceMetricEnvelope(
spans[0].resource?.attributes,
this._instrumentationKey
);
if (resourceMetricEnvelope) {
envelopes.push(resourceMetricEnvelope);
}
});
resultCallback(await this._exportEnvelopes(envelopes));
spans.forEach((span) => {
envelopes.push(readableSpanToEnvelope(span, this._instrumentationKey));
let spanEventEnvelopes = spanEventsToEnvelopes(span, this._instrumentationKey);
if (spanEventEnvelopes.length > 0) {
envelopes.push(...spanEventEnvelopes);
}
});
resultCallback(await this._exportEnvelopes(envelopes));
}
// No data to export
resultCallback({ code: ExportResultCode.SUCCESS });
}

/**
Expand Down
42 changes: 41 additions & 1 deletion sdk/monitor/monitor-opentelemetry-exporter/src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "@opentelemetry/semantic-conventions";
import { Tags } from "../types";
import { getInstance } from "../platform";
import { KnownContextTagKeys } from "../generated";
import { KnownContextTagKeys, TelemetryItem as Envelope } from "../generated";
import { Resource } from "@opentelemetry/resources";
import { Attributes } from "@opentelemetry/api";

Expand Down Expand Up @@ -112,3 +112,43 @@ export function getDependencyTarget(attributes: Attributes): string {
}
return "";
}

export function createResourceMetricEnvelope(
attributes: Attributes,
instrumentationKey: string
): Envelope | undefined {
if (attributes) {
const resourceAttributes: { [propertyName: string]: string } = {};
for (const key of Object.keys(attributes)) {
// Avoid duplication ignoring fields already mapped.
if (
!(
key.startsWith("_MS.") ||
key == SemanticResourceAttributes.TELEMETRY_SDK_VERSION ||
key == SemanticResourceAttributes.TELEMETRY_SDK_LANGUAGE ||
key == SemanticResourceAttributes.TELEMETRY_SDK_NAME
)
) {
resourceAttributes[key] = attributes[key] as string;
}
}

let envelope: Envelope = {
name: "_APPRESOURCEPREVIEW_",
time: new Date(),
sampleRate: 100, // Metrics are never sampled
instrumentationKey: instrumentationKey,
version: 1,
data: {
baseType: "MetricData",
baseData: {
version: 2,
properties: resourceAttributes,
},
},
tags: {},
};
return envelope;
}
return;
}
16 changes: 10 additions & 6 deletions sdk/monitor/monitor-opentelemetry-exporter/test/utils/assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
RequestData,
TelemetryItem as Envelope,
KnownContextTagKeys,
MonitorDomain,
} from "../../src/generated";
import { TelemetryItem as EnvelopeMapper } from "../../src/generated/models/mappers";

Expand Down Expand Up @@ -85,11 +86,12 @@ export const assertCount = (actual: Envelope[], expectations: Expectation[]): vo
export const assertTraceExpectation = (actual: Envelope[], expectations: Expectation[]): void => {
for (const expectation of expectations) {
let envelope: any = null;
if (expectation.data!.baseData!.name) {

if (expectation.data?.baseData?.name) {
envelope = actual.filter((e) => {
return (
(e.data!.baseData as RequestData).name ===
(expectation.data!.baseData as RequestData).name
(e.data!.baseData as MonitorDomain).name ===
(expectation.data!.baseData as MonitorDomain).name
);
});
} else {
Expand All @@ -101,7 +103,7 @@ export const assertTraceExpectation = (actual: Envelope[], expectations: Expecta
assert.ok(
false,
`assertExpectation: could not find exported envelope: ${
(expectation.data?.baseData as RequestData).name
(expectation.data?.baseData as MonitorDomain).name
}`
);
}
Expand All @@ -110,8 +112,10 @@ export const assertTraceExpectation = (actual: Envelope[], expectations: Expecta
const serializedKey = EnvelopeMapper.type.modelProperties![key]?.serializedName ?? undefined;
switch (key) {
case "children":
assertTrace(actual, expectation);
assertTraceExpectation(actual, expectation.children);
if (expectation.children.length > 0) {
assertTrace(actual, expectation);
assertTraceExpectation(actual, expectation.children);
}
break;
case "data":
if (envelope[0].data) {
Expand Down
27 changes: 26 additions & 1 deletion sdk/monitor/monitor-opentelemetry-exporter/test/utils/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,14 @@ export class TraceBasicScenario implements Scenario {
connectionString: `instrumentationkey=${COMMON_ENVELOPE_PARAMS.instrumentationKey}`,
});
this._processor = new FlushSpanProcessor(exporter);
const provider = new BasicTracerProvider();
const resource = new Resource({
"service.name": "testServiceName",
"k8s.cluster.name": "testClusterName",
"k8s.node.name": "testNodeName",
"k8s.namespace.name": "testNamespaceName",
"k8s.pod.name": "testPodName",
});
const provider = new BasicTracerProvider({ resource: resource });
provider.addSpanProcessor(this._processor);
provider.register();
}
Expand Down Expand Up @@ -92,6 +99,24 @@ export class TraceBasicScenario implements Scenario {
}

expectation: Expectation[] = [
{
...COMMON_ENVELOPE_PARAMS,
name: "_APPRESOURCEPREVIEW_",
data: {
baseType: "MetricData",
baseData: {
version: 2,
properties: {
"service.name": "testServiceName",
"k8s.cluster.name": "testClusterName",
"k8s.namespace.name": "testNamespaceName",
"k8s.node.name": "testNodeName",
"k8s.pod.name": "testPodName",
},
} as any,
},
children: [],
},
{
...COMMON_ENVELOPE_PARAMS,
name: "Microsoft.ApplicationInsights.Request",
Expand Down

0 comments on commit 9d828fc

Please sign in to comment.