From bfcc939a305ccb9d8813b7fa45e99e68faaf85a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Jun 2025 17:21:56 +0000 Subject: [PATCH 1/4] Initial plan for issue From 4dfee799e5ca879911dc46f4450998f55c004c5f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Jun 2025 17:33:39 +0000 Subject: [PATCH 2/4] Fix trackRequest HTTP method extraction and ID preservation Co-authored-by: JacksonWeber <47067795+JacksonWeber@users.noreply.github.com> --- src/shim/telemetryClient.ts | 35 +++++++- test/unitTests/shim/telemetryClient.tests.ts | 92 ++++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/src/shim/telemetryClient.ts b/src/shim/telemetryClient.ts index 289124cda..1cd07ed65 100644 --- a/src/shim/telemetryClient.ts +++ b/src/shim/telemetryClient.ts @@ -172,9 +172,18 @@ export class TelemetryClient { const attributes: Attributes = { ...telemetry.properties, }; - attributes[SEMATTRS_HTTP_METHOD] = "HTTP"; + + // Extract HTTP method from request name if it follows "METHOD path" pattern + const httpMethod = this._extractHttpMethod(telemetry.name); + attributes[SEMATTRS_HTTP_METHOD] = httpMethod; attributes[SEMATTRS_HTTP_URL] = telemetry.url; attributes[SEMATTRS_HTTP_STATUS_CODE] = telemetry.resultCode; + + // Preserve user-provided request ID + if (telemetry.id) { + attributes["request.id"] = telemetry.id; + } + const options: SpanOptions = { kind: SpanKind.SERVER, attributes: attributes, @@ -188,6 +197,30 @@ export class TelemetryClient { span.end(endTime); } + /** + * Extract HTTP method from request name if it follows "METHOD path" pattern + * @param name The request name (e.g., "GET /", "POST /api/users") + * @returns The HTTP method (e.g., "GET", "POST") or "HTTP" as fallback + */ + private _extractHttpMethod(name?: string): string { + if (!name) { + return "HTTP"; + } + + // Common HTTP methods + const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE", "CONNECT"]; + + // Check if name starts with an HTTP method followed by a space + for (const method of httpMethods) { + if (name.startsWith(method + " ")) { + return method; + } + } + + // Fallback to "HTTP" if no method pattern found + return "HTTP"; + } + /** * Log a dependency. Note that the default client will attempt to collect dependencies automatically so only use this for dependencies * that aren't automatically captured or if you've disabled automatic dependency collection. diff --git a/test/unitTests/shim/telemetryClient.tests.ts b/test/unitTests/shim/telemetryClient.tests.ts index c12486be1..82781933e 100644 --- a/test/unitTests/shim/telemetryClient.tests.ts +++ b/test/unitTests/shim/telemetryClient.tests.ts @@ -261,6 +261,98 @@ describe("shim/TelemetryClient", () => { assert.equal(spans[0].attributes["http.url"], "http://test.com"); }); + it("trackRequest with HTTP method in name", async () => { + const telemetry: RequestTelemetry = { + id: "7d2b68c6-5b3d-479d-92f9-ab680847acfd", + name: "GET /", + duration: 6, + resultCode: "304", + url: "http://localhost:4001/", + success: false, + }; + client.trackRequest(telemetry); + await tracerProvider.forceFlush(); + const spans = testProcessor.spansProcessed; + assert.equal(spans.length, 1); + assert.equal(spans[0].name, "GET /"); + assert.equal(spans[0].kind, 1, "Span Kind"); // Incoming + // HTTP method should be extracted from name, not hardcoded as "HTTP" + assert.equal(spans[0].attributes["http.method"], "GET"); + assert.equal(spans[0].attributes["http.status_code"], "304"); + assert.equal(spans[0].attributes["http.url"], "http://localhost:4001/"); + // User-provided ID should be preserved + assert.equal(spans[0].attributes["request.id"], "7d2b68c6-5b3d-479d-92f9-ab680847acfd"); + }); + + it("trackRequest with different HTTP methods", async () => { + const testCases = [ + { name: "POST /api/users", expectedMethod: "POST" }, + { name: "PUT /api/users/123", expectedMethod: "PUT" }, + { name: "DELETE /api/users/123", expectedMethod: "DELETE" }, + { name: "PATCH /api/users/123", expectedMethod: "PATCH" }, + { name: "HEAD /health", expectedMethod: "HEAD" }, + { name: "OPTIONS /api", expectedMethod: "OPTIONS" }, + ]; + + for (let i = 0; i < testCases.length; i++) { + const testCase = testCases[i]; + const telemetry: RequestTelemetry = { + id: `test-id-${i}`, + name: testCase.name, + duration: 100, + resultCode: "200", + url: "http://test.com", + success: true, + }; + client.trackRequest(telemetry); + } + + await tracerProvider.forceFlush(); + const spans = testProcessor.spansProcessed; + assert.equal(spans.length, testCases.length); + + for (let i = 0; i < testCases.length; i++) { + assert.equal(spans[i].attributes["http.method"], testCases[i].expectedMethod); + assert.equal(spans[i].attributes["request.id"], `test-id-${i}`); + } + }); + + it("trackRequest with non-HTTP method name fallback", async () => { + const telemetry: RequestTelemetry = { + id: "fallback-test", + name: "Custom Operation Name", + duration: 50, + resultCode: "200", + url: "http://test.com", + success: true, + }; + client.trackRequest(telemetry); + await tracerProvider.forceFlush(); + const spans = testProcessor.spansProcessed; + assert.equal(spans.length, 1); + assert.equal(spans[0].name, "Custom Operation Name"); + // Should fallback to "HTTP" when no method pattern found + assert.equal(spans[0].attributes["http.method"], "HTTP"); + assert.equal(spans[0].attributes["request.id"], "fallback-test"); + }); + + it("trackRequest without ID should not add request.id attribute", async () => { + const telemetry: RequestTelemetry = { + name: "GET /test", + duration: 50, + resultCode: "200", + url: "http://test.com", + success: true, + }; + client.trackRequest(telemetry); + await tracerProvider.forceFlush(); + const spans = testProcessor.spansProcessed; + assert.equal(spans.length, 1); + assert.equal(spans[0].attributes["http.method"], "GET"); + // Should not have request.id attribute when not provided + assert.equal(spans[0].attributes["request.id"], undefined); + }); + it("trackMetric", async () => { const telemetry = { name: "TestName", From 0113533455618c8bb4d8a61bdbfd634888f4dd57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Jun 2025 17:34:18 +0000 Subject: [PATCH 3/4] Apply linter fixes to telemetryClient.ts --- src/shim/telemetryClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shim/telemetryClient.ts b/src/shim/telemetryClient.ts index 1cd07ed65..2c3389f0d 100644 --- a/src/shim/telemetryClient.ts +++ b/src/shim/telemetryClient.ts @@ -212,7 +212,7 @@ export class TelemetryClient { // Check if name starts with an HTTP method followed by a space for (const method of httpMethods) { - if (name.startsWith(method + " ")) { + if (name.startsWith(`${method } `)) { return method; } } From ae6dbb5e800d8ee11dcf38f28cb094df9076c116 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Jun 2025 00:35:19 +0000 Subject: [PATCH 4/4] Address feedback: focus on HTTP method extraction, remove problematic request ID handling Co-authored-by: hectorhdzg <39923391+hectorhdzg@users.noreply.github.com> --- src/shim/telemetryClient.ts | 5 ---- test/unitTests/shim/telemetryClient.tests.ts | 24 -------------------- 2 files changed, 29 deletions(-) diff --git a/src/shim/telemetryClient.ts b/src/shim/telemetryClient.ts index 2c3389f0d..3c49e83ae 100644 --- a/src/shim/telemetryClient.ts +++ b/src/shim/telemetryClient.ts @@ -179,11 +179,6 @@ export class TelemetryClient { attributes[SEMATTRS_HTTP_URL] = telemetry.url; attributes[SEMATTRS_HTTP_STATUS_CODE] = telemetry.resultCode; - // Preserve user-provided request ID - if (telemetry.id) { - attributes["request.id"] = telemetry.id; - } - const options: SpanOptions = { kind: SpanKind.SERVER, attributes: attributes, diff --git a/test/unitTests/shim/telemetryClient.tests.ts b/test/unitTests/shim/telemetryClient.tests.ts index 82781933e..466ec3c76 100644 --- a/test/unitTests/shim/telemetryClient.tests.ts +++ b/test/unitTests/shim/telemetryClient.tests.ts @@ -263,7 +263,6 @@ describe("shim/TelemetryClient", () => { it("trackRequest with HTTP method in name", async () => { const telemetry: RequestTelemetry = { - id: "7d2b68c6-5b3d-479d-92f9-ab680847acfd", name: "GET /", duration: 6, resultCode: "304", @@ -280,8 +279,6 @@ describe("shim/TelemetryClient", () => { assert.equal(spans[0].attributes["http.method"], "GET"); assert.equal(spans[0].attributes["http.status_code"], "304"); assert.equal(spans[0].attributes["http.url"], "http://localhost:4001/"); - // User-provided ID should be preserved - assert.equal(spans[0].attributes["request.id"], "7d2b68c6-5b3d-479d-92f9-ab680847acfd"); }); it("trackRequest with different HTTP methods", async () => { @@ -297,7 +294,6 @@ describe("shim/TelemetryClient", () => { for (let i = 0; i < testCases.length; i++) { const testCase = testCases[i]; const telemetry: RequestTelemetry = { - id: `test-id-${i}`, name: testCase.name, duration: 100, resultCode: "200", @@ -313,13 +309,11 @@ describe("shim/TelemetryClient", () => { for (let i = 0; i < testCases.length; i++) { assert.equal(spans[i].attributes["http.method"], testCases[i].expectedMethod); - assert.equal(spans[i].attributes["request.id"], `test-id-${i}`); } }); it("trackRequest with non-HTTP method name fallback", async () => { const telemetry: RequestTelemetry = { - id: "fallback-test", name: "Custom Operation Name", duration: 50, resultCode: "200", @@ -333,24 +327,6 @@ describe("shim/TelemetryClient", () => { assert.equal(spans[0].name, "Custom Operation Name"); // Should fallback to "HTTP" when no method pattern found assert.equal(spans[0].attributes["http.method"], "HTTP"); - assert.equal(spans[0].attributes["request.id"], "fallback-test"); - }); - - it("trackRequest without ID should not add request.id attribute", async () => { - const telemetry: RequestTelemetry = { - name: "GET /test", - duration: 50, - resultCode: "200", - url: "http://test.com", - success: true, - }; - client.trackRequest(telemetry); - await tracerProvider.forceFlush(); - const spans = testProcessor.spansProcessed; - assert.equal(spans.length, 1); - assert.equal(spans[0].attributes["http.method"], "GET"); - // Should not have request.id attribute when not provided - assert.equal(spans[0].attributes["request.id"], undefined); }); it("trackMetric", async () => {