Skip to content

Commit 5f0b92e

Browse files
committed
apply request data to segment span
1 parent 7b16758 commit 5f0b92e

File tree

4 files changed

+96
-9
lines changed

4 files changed

+96
-9
lines changed

packages/core/src/client.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { IntegrationIndex } from './integration';
88
import { afterSetupIntegrations, setupIntegration, setupIntegrations } from './integration';
99
import { _INTERNAL_flushLogsBuffer } from './logs/internal';
1010
import { _INTERNAL_flushMetricsBuffer } from './metrics/internal';
11-
import type { Scope } from './scope';
11+
import type { Scope, ScopeData } from './scope';
1212
import { updateSession } from './session';
1313
import { getDynamicSamplingContextFromScope } from './tracing/dynamicSamplingContext';
1414
import { DEFAULT_TRANSPORT_BUFFER_SIZE } from './transports/base';
@@ -629,10 +629,16 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
629629
*/
630630
public on(hook: 'enqueueSpan', callback: (spanJSON: SpanV2JSONWithSegmentRef) => void): () => void;
631631
/**
632-
* Register a callback for when a span JSON is processed, to add some attributes to the span JSON.
632+
* Register a callback for when a span JSON is processed, to add some data to the span JSON.
633633
*/
634634
public on(hook: 'processSpan', callback: (spanJSON: SpanV2JSON, hint: { readOnlySpan: Span }) => void): () => void;
635-
635+
/**
636+
* Register a callback for when a segment span JSON is processed, to add some data to the segment span JSON.
637+
*/
638+
public on(
639+
hook: 'processSegmentSpan',
640+
callback: (spanJSON: SpanV2JSON, hint: { scopeData: ScopeData }) => void,
641+
): () => void;
636642
/**
637643
* Register a callback for when an idle span is allowed to auto-finish.
638644
* @returns {() => void} A function that, when executed, removes the registered callback.
@@ -910,6 +916,8 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
910916
public emit(hook: 'afterSpanEnd', span: Span): void;
911917
/** Fire a hook after a span is processed, to add some attributes to the span JSON. */
912918
public emit(hook: 'processSpan', spanJSON: SpanV2JSON, hint: { readOnlySpan: Span }): void;
919+
/** Fire a hook after a span is processed, to add some attributes to the span JSON. */
920+
public emit(hook: 'processSegmentSpan', spanJSON: SpanV2JSON, hint: { scopeData: ScopeData }): void;
913921
/** Fire a hook after the `segmentSpanEnd` hook is fired. */
914922
public emit(hook: 'afterSegmentSpanEnd', span: Span): void;
915923
/** Fire a hook after a span ready to be enqueued into the span buffer. */

packages/core/src/integrations/requestdata.ts

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
1+
import type { Client } from '../client';
12
import { defineIntegration } from '../integration';
3+
import {
4+
SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD,
5+
SEMANTIC_ATTRIBUTE_URL_FULL,
6+
SEMANTIC_ATTRIBUTE_URL_QUERY,
7+
SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS,
8+
} from '../semanticAttributes';
9+
import { safeSetSpanJSONAttributes } from '../spans/spanFirstUtils';
210
import type { Event } from '../types-hoist/event';
311
import type { IntegrationFn } from '../types-hoist/integration';
12+
import type { ClientOptions } from '../types-hoist/options';
413
import type { RequestEventData } from '../types-hoist/request';
14+
import type { BaseTransportOptions } from '../types-hoist/transport';
515
import { parseCookie } from '../utils/cookie';
16+
import { httpHeadersToSpanAttributes } from '../utils/request';
617
import { getClientIPAddress, ipHeaderNames } from '../vendor/getIpAddress';
718

819
interface RequestDataIncludeOptions {
@@ -40,16 +51,67 @@ const _requestDataIntegration = ((options: RequestDataIntegrationOptions = {}) =
4051

4152
return {
4253
name: INTEGRATION_NAME,
54+
setup(client) {
55+
client.on('processSegmentSpan', (spanJSON, { scopeData }) => {
56+
const { sdkProcessingMetadata = {} } = scopeData;
57+
const { normalizedRequest, ipAddress } = sdkProcessingMetadata;
58+
59+
if (!normalizedRequest) {
60+
return;
61+
}
62+
63+
const includeWithDefaultPiiApplied: RequestDataIncludeOptions = getIncludeWithDefaultPiiApplied(
64+
include,
65+
client,
66+
);
67+
68+
// no need to check for include after calling `extractNormalizedRequestData`
69+
// because it already internally only return what's permitted by `include`
70+
const { method, url, query_string, headers, data, env } = extractNormalizedRequestData(
71+
normalizedRequest,
72+
includeWithDefaultPiiApplied,
73+
);
74+
75+
safeSetSpanJSONAttributes(spanJSON, {
76+
...(method ? { [SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD]: method } : {}),
77+
...(url ? { [SEMANTIC_ATTRIBUTE_URL_FULL]: url } : {}),
78+
...(query_string ? { [SEMANTIC_ATTRIBUTE_URL_QUERY]: query_string } : {}),
79+
...(headers ? httpHeadersToSpanAttributes(headers, client.getOptions().sendDefaultPii) : {}),
80+
// TODO: Apparently, Relay still needs Pii rule updates, so I'm leaving this out for now
81+
// ...(cookies
82+
// ? Object.keys(cookies).reduce(
83+
// (acc, cookieName) => ({
84+
// ...acc,
85+
// [`http.request.header.cookie.${cookieName}`]: cookies[cookieName] ?? '',
86+
// }),
87+
// {} as Record<string, string>,
88+
// )
89+
// : {}),
90+
...(include.ip
91+
? {
92+
[SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS]:
93+
(normalizedRequest.headers && getClientIPAddress(normalizedRequest.headers)) || ipAddress,
94+
}
95+
: {}),
96+
...(data ? { 'http.request.body.content': data } : {}),
97+
...(env
98+
? {
99+
'http.request.env': Object.keys(env).reduce(
100+
(acc, key) => ({ ...acc, [key]: env[key] ?? '' }),
101+
{} as Record<string, string>,
102+
),
103+
}
104+
: {}),
105+
});
106+
});
107+
},
43108
// TODO (span-streaming): probably fine to leave as-is for errors.
44109
// For spans, we go through global context -> attribute conversion or omit this completely (TBD)
45110
processEvent(event, _hint, client) {
46111
const { sdkProcessingMetadata = {} } = event;
47112
const { normalizedRequest, ipAddress } = sdkProcessingMetadata;
48113

49-
const includeWithDefaultPiiApplied: RequestDataIncludeOptions = {
50-
...include,
51-
ip: include.ip ?? client.getOptions().sendDefaultPii,
52-
};
114+
const includeWithDefaultPiiApplied: RequestDataIncludeOptions = getIncludeWithDefaultPiiApplied(include, client);
53115

54116
if (normalizedRequest) {
55117
addNormalizedRequestDataToEvent(event, normalizedRequest, { ipAddress }, includeWithDefaultPiiApplied);
@@ -66,6 +128,21 @@ const _requestDataIntegration = ((options: RequestDataIntegrationOptions = {}) =
66128
*/
67129
export const requestDataIntegration = defineIntegration(_requestDataIntegration);
68130

131+
const getIncludeWithDefaultPiiApplied = (
132+
include: {
133+
cookies?: boolean;
134+
data?: boolean;
135+
headers?: boolean;
136+
ip?: boolean;
137+
query_string?: boolean;
138+
url?: boolean;
139+
},
140+
client: Client<ClientOptions<BaseTransportOptions>>,
141+
): RequestDataIncludeOptions => ({
142+
...include,
143+
ip: include.ip ?? client.getOptions().sendDefaultPii,
144+
});
145+
69146
/**
70147
* Add already normalized request data to an event.
71148
* This mutates the passed in event.
@@ -105,14 +182,14 @@ function extractNormalizedRequestData(
105182

106183
// Remove the Cookie header in case cookie data should not be included in the event
107184
if (!include.cookies) {
108-
delete (headers as { cookie?: string }).cookie;
185+
delete headers.cookie;
109186
}
110187

111188
// Remove IP headers in case IP data should not be included in the event
112189
if (!include.ip) {
113190
ipHeaderNames.forEach(ipHeaderName => {
114191
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
115-
delete (headers as Record<string, unknown>)[ipHeaderName];
192+
delete headers[ipHeaderName];
116193
});
117194
}
118195
}

packages/core/src/semanticAttributes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const SEMANTIC_ATTRIBUTE_CACHE_ITEM_SIZE = 'cache.item_size';
6565
/** TODO: Remove these once we update to latest semantic conventions */
6666
export const SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD = 'http.request.method';
6767
export const SEMANTIC_ATTRIBUTE_URL_FULL = 'url.full';
68+
export const SEMANTIC_ATTRIBUTE_URL_QUERY = 'url.query';
6869

6970
/**
7071
* A span link attribute to mark the link as a special span link.

packages/core/src/spans/captureSpan.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export function captureSpan(span: Span, client = getClient()): void {
4747

4848
if (span === segmentSpan) {
4949
applyScopeToSegmentSpan(spanJSON, finalScopeData);
50+
client.emit('processSegmentSpan', spanJSON, { scopeData: finalScopeData });
5051
}
5152

5253
// Allow integrations to add additional data to the span JSON

0 commit comments

Comments
 (0)