Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion src/shim/telemetryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,13 @@ 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

telemetry name could be anything that customer provide, not necessary an HTTP method, issue associated is complaining about the code changing the value they provided, where is the actual name being overridden?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The telemetry name is not being overridden - it's preserved as-is on line 193 where telemetry.name is passed to startSpan(). We only extract the HTTP method from the name to correctly set the http.method attribute (previously hardcoded as "HTTP"). The span name stays exactly as the user provided it. The original issue was that the HTTP method attribute was always "HTTP" instead of the actual method like "GET", "POST", etc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is name passed in startSpan method changed at some point in OpenTelemetry code?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot answer the question above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the name passed to startSpan() is not changed by OpenTelemetry. The user-provided telemetry.name becomes the span name and remains unchanged throughout the OpenTelemetry processing pipeline. The Azure Monitor exporter reads span.name directly and uses it for telemetry without modification. Our implementation correctly preserves the original request name while only extracting the HTTP method from it to set the http.method attribute.

attributes[SEMATTRS_HTTP_METHOD] = httpMethod;
attributes[SEMATTRS_HTTP_URL] = telemetry.url;
attributes[SEMATTRS_HTTP_STATUS_CODE] = telemetry.resultCode;

const options: SpanOptions = {
kind: SpanKind.SERVER,
attributes: attributes,
Expand All @@ -188,6 +192,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.
Expand Down
68 changes: 68 additions & 0 deletions test/unitTests/shim/telemetryClient.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,74 @@ describe("shim/TelemetryClient", () => {
assert.equal(spans[0].attributes["http.url"], "http://test.com");
});

it("trackRequest with HTTP method in name", async () => {
const telemetry: RequestTelemetry = {
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/");
});

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 = {
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);
}
});

it("trackRequest with non-HTTP method name fallback", async () => {
const telemetry: RequestTelemetry = {
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");
});

it("trackMetric", async () => {
const telemetry = {
name: "TestName",
Expand Down
Loading