|
| 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 | + |
1 | 10 | type PartialURL = {
|
2 | 11 | host?: string;
|
3 | 12 | path?: string;
|
@@ -107,6 +116,88 @@ export function getSanitizedUrlStringFromUrlObject(url: URLObject): string {
|
107 | 116 | return newUrl.toString();
|
108 | 117 | }
|
109 | 118 |
|
| 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 | + |
110 | 201 | /**
|
111 | 202 | * Parses string form of URL into an object
|
112 | 203 | * // borrowed from https://tools.ietf.org/html/rfc3986#appendix-B
|
|
0 commit comments