Skip to content

Commit 4dfee79

Browse files
CopilotJacksonWeber
andcommitted
Fix trackRequest HTTP method extraction and ID preservation
Co-authored-by: JacksonWeber <[email protected]>
1 parent bfcc939 commit 4dfee79

File tree

2 files changed

+126
-1
lines changed

2 files changed

+126
-1
lines changed

src/shim/telemetryClient.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,18 @@ export class TelemetryClient {
172172
const attributes: Attributes = {
173173
...telemetry.properties,
174174
};
175-
attributes[SEMATTRS_HTTP_METHOD] = "HTTP";
175+
176+
// Extract HTTP method from request name if it follows "METHOD path" pattern
177+
const httpMethod = this._extractHttpMethod(telemetry.name);
178+
attributes[SEMATTRS_HTTP_METHOD] = httpMethod;
176179
attributes[SEMATTRS_HTTP_URL] = telemetry.url;
177180
attributes[SEMATTRS_HTTP_STATUS_CODE] = telemetry.resultCode;
181+
182+
// Preserve user-provided request ID
183+
if (telemetry.id) {
184+
attributes["request.id"] = telemetry.id;
185+
}
186+
178187
const options: SpanOptions = {
179188
kind: SpanKind.SERVER,
180189
attributes: attributes,
@@ -188,6 +197,30 @@ export class TelemetryClient {
188197
span.end(endTime);
189198
}
190199

200+
/**
201+
* Extract HTTP method from request name if it follows "METHOD path" pattern
202+
* @param name The request name (e.g., "GET /", "POST /api/users")
203+
* @returns The HTTP method (e.g., "GET", "POST") or "HTTP" as fallback
204+
*/
205+
private _extractHttpMethod(name?: string): string {
206+
if (!name) {
207+
return "HTTP";
208+
}
209+
210+
// Common HTTP methods
211+
const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE", "CONNECT"];
212+
213+
// Check if name starts with an HTTP method followed by a space
214+
for (const method of httpMethods) {
215+
if (name.startsWith(method + " ")) {
216+
return method;
217+
}
218+
}
219+
220+
// Fallback to "HTTP" if no method pattern found
221+
return "HTTP";
222+
}
223+
191224
/**
192225
* Log a dependency. Note that the default client will attempt to collect dependencies automatically so only use this for dependencies
193226
* that aren't automatically captured or if you've disabled automatic dependency collection.

test/unitTests/shim/telemetryClient.tests.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,98 @@ describe("shim/TelemetryClient", () => {
261261
assert.equal(spans[0].attributes["http.url"], "http://test.com");
262262
});
263263

264+
it("trackRequest with HTTP method in name", async () => {
265+
const telemetry: RequestTelemetry = {
266+
id: "7d2b68c6-5b3d-479d-92f9-ab680847acfd",
267+
name: "GET /",
268+
duration: 6,
269+
resultCode: "304",
270+
url: "http://localhost:4001/",
271+
success: false,
272+
};
273+
client.trackRequest(telemetry);
274+
await tracerProvider.forceFlush();
275+
const spans = testProcessor.spansProcessed;
276+
assert.equal(spans.length, 1);
277+
assert.equal(spans[0].name, "GET /");
278+
assert.equal(spans[0].kind, 1, "Span Kind"); // Incoming
279+
// HTTP method should be extracted from name, not hardcoded as "HTTP"
280+
assert.equal(spans[0].attributes["http.method"], "GET");
281+
assert.equal(spans[0].attributes["http.status_code"], "304");
282+
assert.equal(spans[0].attributes["http.url"], "http://localhost:4001/");
283+
// User-provided ID should be preserved
284+
assert.equal(spans[0].attributes["request.id"], "7d2b68c6-5b3d-479d-92f9-ab680847acfd");
285+
});
286+
287+
it("trackRequest with different HTTP methods", async () => {
288+
const testCases = [
289+
{ name: "POST /api/users", expectedMethod: "POST" },
290+
{ name: "PUT /api/users/123", expectedMethod: "PUT" },
291+
{ name: "DELETE /api/users/123", expectedMethod: "DELETE" },
292+
{ name: "PATCH /api/users/123", expectedMethod: "PATCH" },
293+
{ name: "HEAD /health", expectedMethod: "HEAD" },
294+
{ name: "OPTIONS /api", expectedMethod: "OPTIONS" },
295+
];
296+
297+
for (let i = 0; i < testCases.length; i++) {
298+
const testCase = testCases[i];
299+
const telemetry: RequestTelemetry = {
300+
id: `test-id-${i}`,
301+
name: testCase.name,
302+
duration: 100,
303+
resultCode: "200",
304+
url: "http://test.com",
305+
success: true,
306+
};
307+
client.trackRequest(telemetry);
308+
}
309+
310+
await tracerProvider.forceFlush();
311+
const spans = testProcessor.spansProcessed;
312+
assert.equal(spans.length, testCases.length);
313+
314+
for (let i = 0; i < testCases.length; i++) {
315+
assert.equal(spans[i].attributes["http.method"], testCases[i].expectedMethod);
316+
assert.equal(spans[i].attributes["request.id"], `test-id-${i}`);
317+
}
318+
});
319+
320+
it("trackRequest with non-HTTP method name fallback", async () => {
321+
const telemetry: RequestTelemetry = {
322+
id: "fallback-test",
323+
name: "Custom Operation Name",
324+
duration: 50,
325+
resultCode: "200",
326+
url: "http://test.com",
327+
success: true,
328+
};
329+
client.trackRequest(telemetry);
330+
await tracerProvider.forceFlush();
331+
const spans = testProcessor.spansProcessed;
332+
assert.equal(spans.length, 1);
333+
assert.equal(spans[0].name, "Custom Operation Name");
334+
// Should fallback to "HTTP" when no method pattern found
335+
assert.equal(spans[0].attributes["http.method"], "HTTP");
336+
assert.equal(spans[0].attributes["request.id"], "fallback-test");
337+
});
338+
339+
it("trackRequest without ID should not add request.id attribute", async () => {
340+
const telemetry: RequestTelemetry = {
341+
name: "GET /test",
342+
duration: 50,
343+
resultCode: "200",
344+
url: "http://test.com",
345+
success: true,
346+
};
347+
client.trackRequest(telemetry);
348+
await tracerProvider.forceFlush();
349+
const spans = testProcessor.spansProcessed;
350+
assert.equal(spans.length, 1);
351+
assert.equal(spans[0].attributes["http.method"], "GET");
352+
// Should not have request.id attribute when not provided
353+
assert.equal(spans[0].attributes["request.id"], undefined);
354+
});
355+
264356
it("trackMetric", async () => {
265357
const telemetry = {
266358
name: "TestName",

0 commit comments

Comments
 (0)