Skip to content

Commit bd77d5a

Browse files
committed
feat(bun): Set http response header attributes instead of response context headers
1 parent 0067c64 commit bd77d5a

3 files changed

Lines changed: 66 additions & 15 deletions

File tree

packages/bun/src/integrations/bunserver.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,9 @@ function wrapRequestHandler<T extends RouteHandler = RouteHandler>(
207207
routeName = route;
208208
}
209209

210-
Object.assign(
211-
attributes,
212-
httpHeadersToSpanAttributes(request.headers.toJSON(), getClient()?.getOptions().sendDefaultPii ?? false),
213-
);
210+
const sendDefaultPii = getClient()?.getOptions().sendDefaultPii ?? false;
211+
212+
Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON(), sendDefaultPii));
214213

215214
isolationScope.setSDKProcessingMetadata({
216215
normalizedRequest: {
@@ -238,10 +237,12 @@ function wrapRequestHandler<T extends RouteHandler = RouteHandler>(
238237
const response = (await target.apply(thisArg, args)) as Response | undefined;
239238
if (response?.status) {
240239
setHttpStatus(span, response.status);
240+
241241
isolationScope.setContext('response', {
242-
headers: response.headers.toJSON(),
243242
status_code: response.status,
244243
});
244+
245+
span.setAttributes(httpHeadersToSpanAttributes(response.headers.toJSON(), sendDefaultPii, 'response'));
245246
}
246247
return response;
247248
} catch (e) {

packages/core/src/utils/request.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,15 +152,22 @@ const SENSITIVE_HEADER_SNIPPETS = [
152152
const PII_HEADER_SNIPPETS = ['x-forwarded-', '-user'];
153153

154154
/**
155-
* Converts incoming HTTP request headers to OpenTelemetry span attributes following semantic conventions.
156-
* Header names are converted to the format: http.request.header.<key>
155+
* Converts incoming HTTP request or response headers to OpenTelemetry span attributes following semantic conventions.
156+
* Header names are converted to the format: http.<request|response>.header.<key>
157157
* where <key> is the header name in lowercase with dashes converted to underscores.
158158
*
159+
* @param lifecycle - The lifecycle of the headers, either 'request' or 'response'
160+
*
159161
* @see https://opentelemetry.io/docs/specs/semconv/registry/attributes/http/#http-request-header
162+
* @see https://opentelemetry.io/docs/specs/semconv/registry/attributes/http/#http-response-header
163+
*
164+
* @see https://getsentry.github.io/sentry-conventions/attributes/http/#http-request-header-key
165+
* @see https://getsentry.github.io/sentry-conventions/attributes/http/#http-response-header-key
160166
*/
161167
export function httpHeadersToSpanAttributes(
162168
headers: Record<string, string | string[] | undefined>,
163169
sendDefaultPii: boolean = false,
170+
lifecycle: 'request' | 'response' = 'request',
164171
): Record<string, string> {
165172
const spanAttributes: Record<string, string> = {};
166173

@@ -189,10 +196,17 @@ export function httpHeadersToSpanAttributes(
189196

190197
const lowerCasedCookieKey = cookieKey.toLowerCase();
191198

192-
addSpanAttribute(spanAttributes, lowerCasedHeaderKey, lowerCasedCookieKey, cookieValue, sendDefaultPii);
199+
addSpanAttribute(
200+
spanAttributes,
201+
lowerCasedHeaderKey,
202+
lowerCasedCookieKey,
203+
cookieValue,
204+
sendDefaultPii,
205+
lifecycle,
206+
);
193207
}
194208
} else {
195-
addSpanAttribute(spanAttributes, lowerCasedHeaderKey, '', value, sendDefaultPii);
209+
addSpanAttribute(spanAttributes, lowerCasedHeaderKey, '', value, sendDefaultPii, lifecycle);
196210
}
197211
});
198212
} catch {
@@ -212,15 +226,15 @@ function addSpanAttribute(
212226
cookieKey: string,
213227
value: string | string[] | undefined,
214228
sendPii: boolean,
229+
lifecycle: 'request' | 'response',
215230
): void {
216-
const normalizedKey = cookieKey
217-
? `http.request.header.${normalizeAttributeKey(headerKey)}.${normalizeAttributeKey(cookieKey)}`
218-
: `http.request.header.${normalizeAttributeKey(headerKey)}`;
219-
220231
const headerValue = handleHttpHeader(cookieKey || headerKey, value, sendPii);
221-
if (headerValue !== undefined) {
222-
spanAttributes[normalizedKey] = headerValue;
232+
if (headerValue == null) {
233+
return;
223234
}
235+
236+
const normalizedKey = `http.${lifecycle}.header.${normalizeAttributeKey(headerKey)}${cookieKey ? `.${normalizeAttributeKey(cookieKey)}` : ''}`;
237+
spanAttributes[normalizedKey] = headerValue;
224238
}
225239

226240
function handleHttpHeader(

packages/core/test/lib/utils/request.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,42 @@ describe('request utils', () => {
780780
'http.request.header.x_saml_token': '[Filtered]',
781781
});
782782
});
783+
784+
it('returns response header attributes if `lifecycle` is "response"', () => {
785+
const headers = {
786+
Host: 'example.com',
787+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
788+
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
789+
'Accept-Language': 'en-US,en;q=0.5',
790+
'Accept-Encoding': 'gzip, deflate',
791+
Connection: 'keep-alive',
792+
'Upgrade-Insecure-Requests': '1',
793+
'Cache-Control': 'no-cache',
794+
'X-Forwarded-For': '192.168.1.1',
795+
Authorization: '[Filtered]',
796+
'x-bearer-token': 'bearer',
797+
'x-sso-token': 'sso',
798+
'x-saml-token': 'saml',
799+
};
800+
801+
const result = httpHeadersToSpanAttributes(headers, false, 'response');
802+
803+
expect(result).toEqual({
804+
'http.response.header.host': 'example.com',
805+
'http.response.header.user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
806+
'http.response.header.accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
807+
'http.response.header.accept_language': 'en-US,en;q=0.5',
808+
'http.response.header.accept_encoding': 'gzip, deflate',
809+
'http.response.header.connection': 'keep-alive',
810+
'http.response.header.upgrade_insecure_requests': '1',
811+
'http.response.header.cache_control': 'no-cache',
812+
'http.response.header.x_forwarded_for': '[Filtered]',
813+
'http.response.header.authorization': '[Filtered]',
814+
'http.response.header.x_bearer_token': '[Filtered]',
815+
'http.response.header.x_saml_token': '[Filtered]',
816+
'http.response.header.x_sso_token': '[Filtered]',
817+
});
818+
});
783819
});
784820
});
785821
});

0 commit comments

Comments
 (0)