Skip to content

Commit e76b320

Browse files
authored
Propagate IS_EXTENSION into telemetry (#1574)
1 parent a844e2a commit e76b320

File tree

3 files changed

+114
-7
lines changed

3 files changed

+114
-7
lines changed

appmonitoring/ts/src/.trivyignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
#─┬ @kubernetes/[email protected]
22
# └── [email protected]
33
CVE-2025-59343
4+
5+
#─┬ @kubernetes/[email protected]
6+
# └── [email protected]
7+
CVE-2025-64718

appmonitoring/ts/src/LoggerWrapper.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@ class ClusterMetadata {
5151
private podName: string;
5252
private imageTag: string;
5353
private arch: string;
54+
private isExtension: boolean;
5455

55-
public constructor(clusterArmId: string, clusterArmRegion: string, podName: string, imageTag: string, arch: string) {
56+
public constructor(clusterArmId: string, clusterArmRegion: string, podName: string, imageTag: string, arch: string, isExtension: boolean) {
5657
this.clusterArmId = clusterArmId;
5758
this.clusterArmRegion = clusterArmRegion;
5859
this.podName = podName;
5960
this.imageTag = imageTag;
6061
this.arch = arch;
62+
this.isExtension = isExtension;
6163
}
6264
}
6365

@@ -105,10 +107,18 @@ class HeartbeatAccumulator {
105107
public logs : Map<HeartbeatLogs, Map<string, number>> = new Map<HeartbeatLogs, Map<string, number>>();
106108
}
107109

110+
export function parseIsExtension(value: string | undefined): boolean {
111+
if (!value) {
112+
return false;
113+
}
114+
const normalized = value.trim().toLowerCase();
115+
return normalized === "true" || normalized === "1" || normalized === "yes";
116+
}
117+
108118
class LocalLogger {
109-
public static Instance(clusterArmId: string, clusterArmRegion: string, podName: string, imageTag: string, arch: string) {
119+
public static Instance(clusterArmId: string, clusterArmRegion: string, podName: string, imageTag: string, arch: string, isExtension: boolean) {
110120
if (!LocalLogger.instance) {
111-
LocalLogger.instance = new LocalLogger(clusterArmId, clusterArmRegion, podName, imageTag, arch);
121+
LocalLogger.instance = new LocalLogger(clusterArmId, clusterArmRegion, podName, imageTag, arch, isExtension);
112122
}
113123

114124
return LocalLogger.instance;
@@ -147,10 +157,10 @@ class LocalLogger {
147157

148158
private heartbeatRequestMetadata = new RequestMetadata(null, null);
149159

150-
private constructor(clusterArmId: string, clusterArmRegion: string, podName: string, imageTag: string, arch: string) {
160+
private constructor(clusterArmId: string, clusterArmRegion: string, podName: string, imageTag: string, arch: string, isExtension: boolean) {
151161
this.client = telemetryClient.telemetryClient;
152162

153-
this.clusterMetadata = new ClusterMetadata(clusterArmId, clusterArmRegion, podName, imageTag, arch);
163+
this.clusterMetadata = new ClusterMetadata(clusterArmId, clusterArmRegion, podName, imageTag, arch, isExtension);
154164

155165
this.log.info(`Application Insights has been set up and started. Default telemetry client is: ${this.client}, cluster metadata: ${JSON.stringify(this.clusterMetadata)}`);
156166

@@ -386,4 +396,4 @@ class LocalLogger {
386396
}
387397
}
388398

389-
export const logger = LocalLogger.Instance(process.env.ARM_ID, process.env.ARM_REGION, process.env.POD_NAME, process.env.IMAGE_TAG, process.env.ARCH);
399+
export const logger = LocalLogger.Instance(process.env.ARM_ID, process.env.ARM_REGION, process.env.POD_NAME, process.env.IMAGE_TAG, process.env.ARCH, parseIsExtension(process.env.IS_EXTENSION));

appmonitoring/ts/src/tests/LoggerWrapper.spec.ts

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, describe, it } from "@jest/globals";
2-
import { logger, HeartbeatMetrics, HeartbeatLogs, Watchdogs } from "LoggerWrapper.js";
2+
import { logger, HeartbeatMetrics, HeartbeatLogs, Watchdogs, parseIsExtension } from "LoggerWrapper.js";
33
import * as applicationInsights from "applicationinsights";
44

55
beforeEach(() => {
@@ -10,6 +10,99 @@ afterEach(() => {
1010
jest.restoreAllMocks();
1111
});
1212

13+
describe("isExtension", () => {
14+
describe("Parsing", () => {
15+
it("Parses 'true' as true", () => {
16+
expect(parseIsExtension("true")).toBe(true);
17+
});
18+
19+
it("Parses '1' as true", () => {
20+
expect(parseIsExtension("1")).toBe(true);
21+
});
22+
23+
it("Parses 'yes' as true", () => {
24+
expect(parseIsExtension("yes")).toBe(true);
25+
});
26+
27+
it("Parses 'TRUE' (uppercase) as true", () => {
28+
expect(parseIsExtension("TRUE")).toBe(true);
29+
});
30+
31+
it("Parses 'false' as false", () => {
32+
expect(parseIsExtension("false")).toBe(false);
33+
});
34+
35+
it("Parses '0' as false", () => {
36+
expect(parseIsExtension("0")).toBe(false);
37+
});
38+
39+
it("Parses undefined as false", () => {
40+
expect(parseIsExtension(undefined)).toBe(false);
41+
});
42+
43+
it("Parses empty string as false", () => {
44+
expect(parseIsExtension("")).toBe(false);
45+
});
46+
});
47+
48+
describe("Telemetry Inclusion", () => {
49+
it("Includes isExtension in event telemetry", async () => {
50+
const eventsSent = <applicationInsights.Contracts.EventTelemetry[]>[];
51+
52+
jest.spyOn(applicationInsights.TelemetryClient.prototype, "trackEvent").mockImplementation((telemetry: applicationInsights.Contracts.EventTelemetry) => {
53+
eventsSent.push(telemetry);
54+
});
55+
56+
await logger.SendEvent("TestEvent", "test-operation-id", "test-uid", false);
57+
58+
expect(eventsSent.length).toBe(1);
59+
expect(eventsSent[0].properties.clusterMetadata).toBeDefined();
60+
61+
const clusterMetadata = JSON.parse(eventsSent[0].properties.clusterMetadata);
62+
expect(clusterMetadata.isExtension).toBeDefined();
63+
expect(typeof clusterMetadata.isExtension).toBe("boolean");
64+
});
65+
66+
it("Includes isExtension in metric telemetry", async () => {
67+
logger.setHeartbeatMetric(HeartbeatMetrics.CRCount, 5);
68+
69+
const metricsSent = <(applicationInsights.Contracts.MetricTelemetry & applicationInsights.Contracts.MetricPointTelemetry)[]>[];
70+
71+
jest.spyOn(applicationInsights.TelemetryClient.prototype, "trackMetric").mockImplementation((telemetry: applicationInsights.Contracts.MetricTelemetry & applicationInsights.Contracts.MetricPointTelemetry) => {
72+
metricsSent.push(telemetry);
73+
});
74+
75+
await logger.startHeartbeats("test-operation-id");
76+
77+
expect(metricsSent.length).toBeGreaterThan(0);
78+
expect(metricsSent[0].properties.clusterMetadata).toBeDefined();
79+
80+
const clusterMetadata = JSON.parse(metricsSent[0].properties.clusterMetadata);
81+
expect(clusterMetadata.isExtension).toBeDefined();
82+
expect(typeof clusterMetadata.isExtension).toBe("boolean");
83+
});
84+
85+
it("Includes isExtension in trace telemetry", async () => {
86+
logger.appendHeartbeatLog(HeartbeatLogs.ApiServerTopExceptionsEncountered, "test-error");
87+
88+
const tracesSent = <applicationInsights.Contracts.TraceTelemetry[]>[];
89+
90+
jest.spyOn(applicationInsights.TelemetryClient.prototype, "trackTrace").mockImplementation((telemetry: applicationInsights.Contracts.TraceTelemetry) => {
91+
tracesSent.push(telemetry);
92+
});
93+
94+
await logger.startHeartbeats("test-operation-id");
95+
96+
expect(tracesSent.length).toBeGreaterThan(0);
97+
expect(tracesSent[0].properties.clusterMetadata).toBeDefined();
98+
99+
const clusterMetadata = JSON.parse(tracesSent[0].properties.clusterMetadata);
100+
expect(clusterMetadata.isExtension).toBeDefined();
101+
expect(typeof clusterMetadata.isExtension).toBe("boolean");
102+
});
103+
});
104+
});
105+
13106
describe("Heartbeats", () => {
14107
it("Sends logs", async () => {
15108
for(let i = 0; i < 10; i++) {

0 commit comments

Comments
 (0)