Skip to content

Commit e3e5686

Browse files
authored
Add OTEL_METRICS_EXPORTER environment variable (#1523)
* Add OTEL_METRICS_EXPORTER environment variable * feat: add OpenTelemetry metrics support to Node.js test app - Updated package.json to include OpenTelemetry dependencies for metrics and logging. - Integrated OpenTelemetry metrics in server.js to track the total number of cows sold. - Added a counter for cows sold in the /call-target and root endpoints. * chore(deps): update OpenTelemetry dependencies to latest versions - Updated @opentelemetry/api-logs from ^0.202.0 to ^0.205.0 - Updated @opentelemetry/instrumentation from ^0.202.0 to ^0.204.0 - Updated @opentelemetry/instrumentation-http from ^0.202.0 to ^0.204.0 - Updated @opentelemetry/resources from ^1.27.0 to ^2.1.0 - Updated @opentelemetry/sdk-logs from ^0.202.0 to ^0.205.0 - Updated @opentelemetry/sdk-node from ^0.202.0 to ^0.204.0 - Updated @opentelemetry/semantic-conventions from ^1.28.0 to ^1.37.0 * chore: update OpenTelemetry dependencies in Node.js app - Removed outdated dependencies: @opentelemetry/api-events, @opentelemetry/sdk-events - Updated @opentelemetry/instrumentation to version 0.205.0 - Updated @opentelemetry/instrumentation-express to version 0.54.0 - Updated @opentelemetry/instrumentation-http to version 0.205.0 - Updated @opentelemetry/sdk-node to version 0.205.0 - Updated @opentelemetry/sdk-trace-node to version 2.1.0 * feat: integrate OpenTelemetry metrics in Java test app and add configuration class * Update OTEL_METRICS_EXPORTER to include azure_monitor * Temporarily ignore CVE-2025-59343 while @kubernetes/client-node is not up-to-date * Update validation chart with OpenTelemetry environment variables * Node test app: update TARGET_URL to use 'https://bing.com' for consistency * Update Java image to 3.7.5-aks for OTEL support * Update Node.js agent image tag to 3.3.1 for OTEL support * Update Node.js agent image tag to 3.3.2 for OTEL support
1 parent 07fe2f3 commit e3e5686

File tree

13 files changed

+1576
-18
lines changed

13 files changed

+1576
-18
lines changed

appmonitoring/ts/src/.trivyignore

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
# @kubernetes/client-node@1.0.0
2-
# │ └─┬ tar@7.4.3
3-
# │ └─┬ minizlib@3.0.1
4-
# │ └─┬ rimraf@5.0.10
5-
# │ └─┬ glob@10.4.5
6-
# │ └─┬ foreground-child@3.3.0
7-
# │ └── cross-spawn@7.0.3 deduped
8-
#CVE-2024-21538
1+
#─┬ @kubernetes/client-node@1.3.0
2+
# └── tar-fs@3.0.9
3+
CVE-2025-59343

appmonitoring/ts/src/Mutations.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ export class Mutations {
1515

1616
private static agentImageJava = {
1717
repositoryPath: "auto-instrumentation/java",
18-
imageTag: "3.7.2-aks" // https://mcr.microsoft.com/v2/applicationinsights/auto-instrumentation/java/tags/list
18+
imageTag: "3.7.5-aks" // https://mcr.microsoft.com/v2/applicationinsights/auto-instrumentation/java/tags/list
1919
};
2020
private static agentImageNodeJs = {
2121
repositoryPath: "opentelemetry-auto-instrumentation/nodejs",
22-
imageTag: "3.2.7" // https://mcr.microsoft.com/v2/applicationinsights/opentelemetry-auto-instrumentation/nodejs/tags/list
22+
imageTag: "3.3.2" // https://mcr.microsoft.com/v2/applicationinsights/opentelemetry-auto-instrumentation/nodejs/tags/list
2323
};
2424
private static agentImagePython = {
2525
repositoryPath: "auto-instrumentation/python",
@@ -264,25 +264,35 @@ export class Mutations {
264264

265265
if (otelParams.logsEnabled) {
266266
returnValue.push(
267+
// not setting this to ensure Microsoft distros don't send OTLP traces. For OSS SDKs this defaults to "otlp" anyway, so no impact
268+
// {
269+
// name: "OTEL_TRACES_EXPORTER",
270+
// value: `otlp`
271+
// },
267272
{
268273
name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", //!!! http -> https
269274
value: `http://$(OTEL_ENDPOINT_NODE_IP):${otelParams.logsPortHttpProtobuf}/v1/traces`
270275
},
271276
{
272-
name: "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", //!!!
277+
name: "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL",
273278
value: "http/protobuf"
274279
},
275280
{
276281
name: "OTEL_EXPORTER_OTLP_TRACES_INSECURE", //!!!
277282
value: "true"
278283
},
279284

285+
// not setting this to ensure Microsoft distros don't send OTLP logs. For OSS SDKs this defaults to "otlp" anyway, so no impact
286+
// {
287+
// name: "OTEL_LOGS_EXPORTER",
288+
// value: `otlp`
289+
// },
280290
{
281291
name: "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", //!!! http -> https
282292
value: `http://$(OTEL_ENDPOINT_NODE_IP):${otelParams.logsPortHttpProtobuf}/v1/logs`
283293
},
284294
{
285-
name: "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL", //!!!
295+
name: "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL",
286296
value: "http/protobuf"
287297
},
288298
{
@@ -294,12 +304,17 @@ export class Mutations {
294304

295305
if (otelParams.metricsEnabled) {
296306
returnValue.push(
307+
// setting this to ensure Microsoft distros do send OTLP metrics (forked, sent to Breeze and OTLP endpoint). For OSS SDKs this defaults to "otlp" anyway, so no impact
308+
{
309+
name: "OTEL_METRICS_EXPORTER",
310+
value: `otlp,azure_monitor`
311+
},
297312
{
298313
name: "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", //!!! http -> https
299314
value: `http://$(OTEL_ENDPOINT_NODE_IP):${otelParams.metricsPortHttpProtobuf}/v1/metrics`
300315
},
301316
{
302-
name: "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", //!!!
317+
name: "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL",
303318
value: "http/protobuf"
304319
},
305320
{

appmonitoring/ts/src/tests/Mutations.spec.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,99 @@ describe("OTEL Resource Attributes Merging", () => {
113113
expect(otelResourceAttributes!.value).toContain("k8s.cluster.name=test-cluster");
114114
});
115115
});
116+
117+
describe("OTEL Metrics Exporter Environment Variable", () => {
118+
const podInfo = new PodInfo();
119+
podInfo.ownerKind = "Deployment";
120+
podInfo.ownerName = "test-app";
121+
podInfo.ownerUid = "uid-123";
122+
podInfo.onlyContainerName = "main-container";
123+
124+
it("should include OTEL_METRICS_EXPORTER when metrics are enabled", () => {
125+
const testOtelParams: OtelParams = {
126+
logsEnabled: false,
127+
metricsEnabled: true,
128+
logsPortHttpProtobuf: 4318,
129+
metricsPortHttpProtobuf: 4319
130+
};
131+
132+
const generatedEnvVars = Mutations.GenerateEnvironmentVariables(
133+
podInfo,
134+
[AutoInstrumentationPlatforms.Java],
135+
false,
136+
"InstrumentationKey=test-key",
137+
"/subscriptions/test/resourceGroups/test-rg",
138+
"eastus",
139+
"test-cluster",
140+
testOtelParams,
141+
undefined
142+
);
143+
144+
const otelMetricsExporter = generatedEnvVars.find(env => env.name === "OTEL_METRICS_EXPORTER");
145+
expect(otelMetricsExporter).toBeDefined();
146+
expect(otelMetricsExporter!.value).toBe("otlp,azure_monitor");
147+
});
148+
149+
it("should not include OTEL_METRICS_EXPORTER when metrics are disabled", () => {
150+
const testOtelParams: OtelParams = {
151+
logsEnabled: true,
152+
metricsEnabled: false,
153+
logsPortHttpProtobuf: 4318,
154+
metricsPortHttpProtobuf: 4319
155+
};
156+
157+
const generatedEnvVars = Mutations.GenerateEnvironmentVariables(
158+
podInfo,
159+
[AutoInstrumentationPlatforms.Java],
160+
false,
161+
"InstrumentationKey=test-key",
162+
"/subscriptions/test/resourceGroups/test-rg",
163+
"eastus",
164+
"test-cluster",
165+
testOtelParams,
166+
undefined
167+
);
168+
169+
const otelMetricsExporter = generatedEnvVars.find(env => env.name === "OTEL_METRICS_EXPORTER");
170+
expect(otelMetricsExporter).toBeUndefined();
171+
});
172+
173+
it("should include OTEL_METRICS_EXPORTER with other metrics environment variables when both logs and metrics are enabled", () => {
174+
const testOtelParams: OtelParams = {
175+
logsEnabled: true,
176+
metricsEnabled: true,
177+
logsPortHttpProtobuf: 4318,
178+
metricsPortHttpProtobuf: 4319
179+
};
180+
181+
const generatedEnvVars = Mutations.GenerateEnvironmentVariables(
182+
podInfo,
183+
[AutoInstrumentationPlatforms.Java],
184+
false,
185+
"InstrumentationKey=test-key",
186+
"/subscriptions/test/resourceGroups/test-rg",
187+
"eastus",
188+
"test-cluster",
189+
testOtelParams,
190+
undefined
191+
);
192+
193+
// Should have OTEL_METRICS_EXPORTER
194+
const otelMetricsExporter = generatedEnvVars.find(env => env.name === "OTEL_METRICS_EXPORTER");
195+
expect(otelMetricsExporter).toBeDefined();
196+
expect(otelMetricsExporter!.value).toBe("otlp,azure_monitor");
197+
198+
// Should also have other metrics-related variables
199+
const otelMetricsEndpoint = generatedEnvVars.find(env => env.name === "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT");
200+
expect(otelMetricsEndpoint).toBeDefined();
201+
expect(otelMetricsEndpoint!.value).toBe("http://$(OTEL_ENDPOINT_NODE_IP):4319/v1/metrics");
202+
203+
const otelMetricsProtocol = generatedEnvVars.find(env => env.name === "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL");
204+
expect(otelMetricsProtocol).toBeDefined();
205+
expect(otelMetricsProtocol!.value).toBe("http/protobuf");
206+
207+
const otelMetricsInsecure = generatedEnvVars.find(env => env.name === "OTEL_EXPORTER_OTLP_METRICS_INSECURE");
208+
expect(otelMetricsInsecure).toBeDefined();
209+
expect(otelMetricsInsecure!.value).toBe("true");
210+
});
211+
});

appmonitoring/ts/src/tests/Mutator.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,10 @@ describe("Mutator", () => {
11761176
const otelMetricsProtocol = container.env.find(env => env.name === "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL");
11771177
expect(otelMetricsProtocol).toBeUndefined();
11781178

1179+
// Verify OTEL_METRICS_EXPORTER is NOT present when metrics are disabled
1180+
const otelMetricsExporter = container.env.find(env => env.name === "OTEL_METRICS_EXPORTER");
1181+
expect(otelMetricsExporter).toBeUndefined();
1182+
11791183
// Verify common environment variables are still present
11801184
const otelResourceAttrsEnv = container.env.find(env => env.name === "OTEL_RESOURCE_ATTRIBUTES");
11811185
expect(otelResourceAttrsEnv.value).toContain("microsoft.applicationId=partial-app-id");
@@ -1245,6 +1249,11 @@ describe("Mutator", () => {
12451249
const otelMetricsProtocol = container.env.find(env => env.name === "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL");
12461250
expect(otelMetricsProtocol.value).toBe("http/protobuf");
12471251

1252+
// Verify OTEL_METRICS_EXPORTER is present and set to "otlp"
1253+
const otelMetricsExporter = container.env.find(env => env.name === "OTEL_METRICS_EXPORTER");
1254+
expect(otelMetricsExporter).toBeDefined();
1255+
expect(otelMetricsExporter.value).toBe("otlp,azure_monitor");
1256+
12481257
// Verify logs-related OTEL environment variables are NOT present
12491258
const otelTracesEndpoint = container.env.find(env => env.name === "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT");
12501259
expect(otelTracesEndpoint).toBeUndefined();

appmonitoring/ts/src/tests/Patcher.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,7 @@ describe("Patcher", () => {
726726
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT")).toBeDefined();
727727
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL")).toBeDefined();
728728
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_EXPORTER_OTLP_METRICS_INSECURE")).toBeDefined();
729+
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_METRICS_EXPORTER")).toBeDefined();
729730

730731
// Verify correct port values
731732
const tracesEndpointEnv = containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT");
@@ -736,6 +737,9 @@ describe("Patcher", () => {
736737

737738
const metricsEndpointEnv = containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT");
738739
expect(metricsEndpointEnv.value).toBe("http://$(OTEL_ENDPOINT_NODE_IP):4319/v1/metrics");
740+
741+
const metricsExporterEnv = containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_METRICS_EXPORTER");
742+
expect(metricsExporterEnv.value).toBe("otlp,azure_monitor");
739743
});
740744

741745
it("OTEL environment variables - includes only logs variables when only logsEnabled is true", async () => {
@@ -783,6 +787,7 @@ describe("Patcher", () => {
783787
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT")).toBeUndefined();
784788
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL")).toBeUndefined();
785789
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_EXPORTER_OTLP_METRICS_INSECURE")).toBeUndefined();
790+
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_METRICS_EXPORTER")).toBeUndefined();
786791
});
787792

788793
it("OTEL environment variables - includes only metrics variables when only metricsEnabled is true", async () => {
@@ -830,6 +835,10 @@ describe("Patcher", () => {
830835
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT")).toBeDefined();
831836
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL")).toBeDefined();
832837
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_EXPORTER_OTLP_METRICS_INSECURE")).toBeDefined();
838+
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_METRICS_EXPORTER")).toBeDefined();
839+
840+
const metricsExporterEnv = containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_METRICS_EXPORTER");
841+
expect(metricsExporterEnv.value).toBe("otlp,azure_monitor");
833842
});
834843

835844
it("OTEL environment variables - excludes all OTEL variables when both logsEnabled and metricsEnabled are false", async () => {
@@ -875,6 +884,7 @@ describe("Patcher", () => {
875884
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT")).toBeUndefined();
876885
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL")).toBeUndefined();
877886
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_EXPORTER_OTLP_METRICS_INSECURE")).toBeUndefined();
887+
expect(containerEnv.find((ev: IEnvironmentVariable) => ev.name === "OTEL_METRICS_EXPORTER")).toBeUndefined();
878888
});
879889

880890
describe("OTEL Resource Attributes Merging", () => {

appmonitoring/validation-helm/app-monitoring-addon/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
"name": "app-monitoring-addon",
44
"description": "app-monitoring addon helm chart",
55
"type": "application",
6-
"version": "1.0.0-beta.7"
6+
"version": "1.0.0-beta.8"
77
}

appmonitoring/validation-helm/app-monitoring-addon/templates/app-monitoring-agent.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,18 @@ spec:
308308
value: "{{ .Values.global.commonGlobals.Customer.AzureResourceID }}"
309309
- name: ARM_REGION
310310
value: "{{ .Values.global.commonGlobals.Region }}"
311+
{{- if .Values.AppmonitoringAgent.isOtelToggleEnabled }}
312+
- name: OTEL_TOGGLE
313+
value: "true"
314+
{{- end }}
315+
- name: OTEL_LOGS_ENABLED
316+
value: "{{ .Values.AppmonitoringAgent.isOpenTelemetryLogsEnabled }}"
317+
- name: OTEL_LOGS_PORT_HTTPPROTOBUF
318+
value: "{{ .Values.AppmonitoringAgent.openTelemetryLogsPort | default 28331 }}"
319+
- name: OTEL_METRICS_ENABLED
320+
value: "{{ .Values.AppmonitoringAgent.isOpenTelemetryMetricsEnabled }}"
321+
- name: OTEL_METRICS_PORT_HTTPPROTOBUF
322+
value: "{{ .Values.AppmonitoringAgent.openTelemetryMetricsPort | default 28333 }}"
311323
ports:
312324
- containerPort: 1337
313325
- containerPort: 4000

appmonitoring/validation-helm/test-apps/java/pom.xml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,47 @@
99
<properties>
1010
<java.version>17</java.version>
1111
<spring-boot.version>3.2.5</spring-boot.version>
12+
<opentelemetry.version>1.36.0</opentelemetry.version>
1213
</properties>
14+
15+
<dependencyManagement>
16+
<dependencies>
17+
<dependency>
18+
<groupId>io.opentelemetry</groupId>
19+
<artifactId>opentelemetry-bom</artifactId>
20+
<version>${opentelemetry.version}</version>
21+
<type>pom</type>
22+
<scope>import</scope>
23+
</dependency>
24+
</dependencies>
25+
</dependencyManagement>
26+
1327
<dependencies>
1428
<dependency>
1529
<groupId>org.springframework.boot</groupId>
1630
<artifactId>spring-boot-starter-web</artifactId>
1731
<version>${spring-boot.version}</version>
1832
</dependency>
33+
<dependency>
34+
<groupId>org.springframework.boot</groupId>
35+
<artifactId>spring-boot-starter-actuator</artifactId>
36+
<version>${spring-boot.version}</version>
37+
</dependency>
38+
<dependency>
39+
<groupId>io.micrometer</groupId>
40+
<artifactId>micrometer-core</artifactId>
41+
<version>1.12.5</version>
42+
</dependency>
43+
<dependency>
44+
<groupId>io.micrometer</groupId>
45+
<artifactId>micrometer-registry-prometheus</artifactId>
46+
<version>1.12.5</version>
47+
</dependency>
48+
<!-- OpenTelemetry API -->
49+
<dependency>
50+
<groupId>io.opentelemetry</groupId>
51+
<artifactId>opentelemetry-api</artifactId>
52+
</dependency>
1953
</dependencies>
2054
<build>
2155
<plugins>

appmonitoring/validation-helm/test-apps/java/src/main/java/com/example/HelloController.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,55 @@
66
import org.springframework.web.client.RestTemplate;
77
import org.slf4j.Logger;
88
import org.slf4j.LoggerFactory;
9+
//import io.micrometer.core.instrument.Counter;
10+
//import io.micrometer.core.instrument.MeterRegistry;
11+
12+
import io.opentelemetry.api.GlobalOpenTelemetry;
13+
import io.opentelemetry.api.common.AttributeKey;
14+
import io.opentelemetry.api.common.Attributes;
15+
import io.opentelemetry.api.metrics.LongCounter;
16+
import io.opentelemetry.api.metrics.Meter;
917

1018
import java.util.Random;
1119

1220
@RestController
1321
public class HelloController {
1422
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
15-
@Value("${TARGET_URL:}")
16-
private String targetUrl;
23+
Meter meter = GlobalOpenTelemetry.getMeter("OTEL.AzureMonitor.Demo");
24+
private String targetUrl = "https://bing.com";
1725

1826
private final RestTemplate restTemplate = new RestTemplate();
1927
private final Random random = new Random();
28+
private final LongCounter cowsSoldCounter;
29+
30+
public HelloController(/*MeterRegistry meterRegistry*/) {
31+
this.cowsSoldCounter = meter
32+
.counterBuilder("cows_sold_total")
33+
.build();
34+
}
2035

2136
@GetMapping("/")
2237
public String hello() {
2338
logger.info("Received request at root endpoint '/'");
2439
logger.debug("Responding with static hello message");
40+
41+
// Increment cows sold counter
42+
cowsSoldCounter.add(1, Attributes.of(AttributeKey.stringKey("name"), "cow", AttributeKey.stringKey("color"), "white"));
43+
logger.debug("Incremented cows_sold_total metric");
44+
2545
return "Hello from Java test app!";
2646
}
2747

2848
@GetMapping("/call-target")
2949
public String callTarget() {
3050
logger.info("Received request at '/call-target' endpoint");
3151
logger.debug("TARGET_URL value: {}", targetUrl);
52+
53+
// Increment cows sold counter
54+
cowsSoldCounter.add(1, Attributes.of(AttributeKey.stringKey("name"), "cow", AttributeKey.stringKey("color"), "white"));
55+
56+
logger.debug("Incremented cows_sold_total metric");
57+
3258
// Occasionally throw an error
3359
if (random.nextInt(10) >= 7) { // 20% chance
3460
logger.error("Simulated error at '/call-target' endpoint");

appmonitoring/validation-helm/test-apps/java/src/main/java/com/example/OpenTelemetryConfig.java

Whitespace-only changes.

0 commit comments

Comments
 (0)