Skip to content

Commit f9a4fc3

Browse files
committed
feat(cloudflare): Improve http span data
1 parent 57893e2 commit f9a4fc3

File tree

3 files changed

+97
-27
lines changed

3 files changed

+97
-27
lines changed

packages/cloudflare/src/request.ts

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
import type { ExecutionContext, IncomingRequestCfProperties } from '@cloudflare/workers-types';
2-
import type { SpanAttributes } from '@sentry/core';
32
import {
43
captureException,
54
continueTrace,
65
flush,
7-
SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD,
8-
SEMANTIC_ATTRIBUTE_SENTRY_OP,
9-
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
10-
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
11-
SEMANTIC_ATTRIBUTE_URL_FULL,
6+
getHttpSpanDetailsFromUrlObject,
7+
parseStringToURLObject,
128
setHttpStatus,
139
startSpan,
14-
stripUrlQueryAndFragment,
1510
withIsolationScope,
1611
} from '@sentry/core';
1712
import type { CloudflareOptions } from './client';
@@ -42,29 +37,14 @@ export function wrapRequestHandler(
4237
const client = init(options);
4338
isolationScope.setClient(client);
4439

45-
const attributes: SpanAttributes = {
46-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.cloudflare',
47-
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
48-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server',
49-
[SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD]: request.method,
50-
[SEMANTIC_ATTRIBUTE_URL_FULL]: request.url,
51-
};
40+
const urlObject = parseStringToURLObject(request.url);
41+
const [name, attributes] = getHttpSpanDetailsFromUrlObject(urlObject, 'server', 'auto.http.cloudflare', request);
5242

5343
const contentLength = request.headers.get('content-length');
5444
if (contentLength) {
5545
attributes['http.request.body.size'] = parseInt(contentLength, 10);
5646
}
5747

58-
let pathname = '';
59-
try {
60-
const url = new URL(request.url);
61-
pathname = url.pathname;
62-
attributes['server.address'] = url.hostname;
63-
attributes['url.scheme'] = url.protocol.replace(':', '');
64-
} catch {
65-
// skip
66-
}
67-
6848
addCloudResourceContext(isolationScope);
6949
if (request) {
7050
addRequest(isolationScope, request);
@@ -74,8 +54,6 @@ export function wrapRequestHandler(
7454
}
7555
}
7656

77-
const routeName = `${request.method} ${pathname ? stripUrlQueryAndFragment(pathname) : '/'}`;
78-
7957
// Do not capture spans for OPTIONS and HEAD requests
8058
if (request.method === 'OPTIONS' || request.method === 'HEAD') {
8159
try {
@@ -96,7 +74,7 @@ export function wrapRequestHandler(
9674
// See: https://developers.cloudflare.com/workers/runtime-apis/performance/
9775
return startSpan(
9876
{
99-
name: routeName,
77+
name,
10078
attributes,
10179
},
10280
async span => {

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ export {
255255
parseUrl,
256256
stripUrlQueryAndFragment,
257257
parseStringToURLObject,
258+
getHttpSpanDetailsFromUrlObject,
258259
isURLObjectRelative,
259260
getSanitizedUrlStringFromUrlObject,
260261
} from './utils-hoist/url';

packages/core/src/utils-hoist/url.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
import {
2+
SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD,
3+
SEMANTIC_ATTRIBUTE_SENTRY_OP,
4+
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
5+
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
6+
SEMANTIC_ATTRIBUTE_URL_FULL,
7+
} from '../semanticAttributes';
8+
import type { SpanAttributes } from '../types-hoist/span';
9+
110
type PartialURL = {
211
host?: string;
312
path?: string;
@@ -107,6 +116,88 @@ export function getSanitizedUrlStringFromUrlObject(url: URLObject): string {
107116
return newUrl.toString();
108117
}
109118

119+
type PartialRequest = {
120+
method?: string;
121+
};
122+
123+
function getHttpSpanNameFromUrlObject(
124+
urlObject: URLObject | undefined,
125+
request?: PartialRequest,
126+
routeName?: string,
127+
): string {
128+
const method = request?.method?.toUpperCase() ?? 'GET';
129+
const route = routeName ? routeName : urlObject ? getSanitizedUrlStringFromUrlObject(urlObject) : '/';
130+
131+
return `${method} ${route}`;
132+
}
133+
134+
/**
135+
* Takes a parsed URL object and returns a set of attributes for the span
136+
* that represents the HTTP request for that url. This is used for both server
137+
* and client http spans.
138+
*
139+
* Follows https://opentelemetry.io/docs/specs/semconv/http/.
140+
*
141+
* @param urlObject - see {@link parseStringToURLObject}
142+
* @param httpType - The type of HTTP operation (server or client)
143+
* @param spanOrigin - The origin of the span
144+
* @param request - The request object, see {@link PartialRequest}
145+
* @param routeName - The name of the route, must be low cardinality
146+
* @returns The span name and attributes for the HTTP operation
147+
*/
148+
export function getHttpSpanDetailsFromUrlObject(
149+
urlObject: URLObject | undefined,
150+
httpType: 'server' | 'client',
151+
spanOrigin: string,
152+
request?: PartialRequest,
153+
routeName?: string,
154+
): [name: string, attributes: SpanAttributes] {
155+
const attributes: SpanAttributes = {
156+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: spanOrigin,
157+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
158+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: `http.${httpType}`,
159+
};
160+
161+
if (routeName) {
162+
attributes['http.route'] = routeName;
163+
attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] = 'route';
164+
}
165+
166+
if (request?.method) {
167+
attributes[SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD] = request.method.toUpperCase();
168+
}
169+
170+
if (urlObject) {
171+
if (urlObject.search) {
172+
attributes['url.query'] = urlObject.search;
173+
}
174+
if (urlObject.hash) {
175+
attributes['url.fragment'] = urlObject.hash;
176+
}
177+
if (urlObject.pathname) {
178+
attributes['url.path'] = urlObject.pathname;
179+
if (urlObject.pathname === '/') {
180+
attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] = 'route';
181+
}
182+
}
183+
184+
if (!isURLObjectRelative(urlObject)) {
185+
attributes[SEMANTIC_ATTRIBUTE_URL_FULL] = urlObject.href;
186+
if (urlObject.port) {
187+
attributes['url.port'] = urlObject.port;
188+
}
189+
if (urlObject.protocol) {
190+
attributes['url.scheme'] = urlObject.protocol;
191+
}
192+
if (urlObject.hostname) {
193+
attributes['server.address'] = urlObject.hostname;
194+
}
195+
}
196+
}
197+
198+
return [getHttpSpanNameFromUrlObject(urlObject, request, routeName), attributes];
199+
}
200+
110201
/**
111202
* Parses string form of URL into an object
112203
* // borrowed from https://tools.ietf.org/html/rfc3986#appendix-B

0 commit comments

Comments
 (0)