Skip to content
Merged
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
4 changes: 2 additions & 2 deletions packages/remix/src/server/integrations/tracing-channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ function subscribeCallRouteLoader(): void {
// With span streaming, a `function` span is named after the function it wraps. The route id
// stays on `match.route.id`.
name: client && hasSpanStreamingEnabled(client) ? 'loader' : description,
onlyIfParent: true,
attributes: {
[SENTRY_ORIGIN]: ORIGIN,
[SENTRY_OP]: FUNCTION,
Expand All @@ -203,7 +204,6 @@ function subscribeCallRouteLoader(): void {
});
},
{
requiresParentSpan: true,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should we just remove this config option? 🤔 this is in server-utils so semver is not required, we may drop this I guess to make it clearer to not use this in the future...?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

exactly the idea was to make this clearer going forward. there is not really a use case for requiresParentSpan that cannot be covered by onlyIfParent so we should just align

beforeSpanEnd: (span, data) => setResponseStatus(span, data.result),
},
);
Expand Down Expand Up @@ -231,6 +231,7 @@ function subscribeCallRouteAction(formDataCapture: FormDataCapture | undefined):
// With span streaming, a `function` span is named after the function it wraps. The route id
// stays on `match.route.id`.
name: client && hasSpanStreamingEnabled(client) ? 'action' : description,
onlyIfParent: true,
attributes: {
[SENTRY_ORIGIN]: ORIGIN,
[SENTRY_OP]: FUNCTION,
Expand All @@ -242,7 +243,6 @@ function subscribeCallRouteAction(formDataCapture: FormDataCapture | undefined):
});
},
{
requiresParentSpan: true,
beforeSpanEnd: (span, data) => setResponseStatus(span, data.result),
// Hold the span end until the (already in-flight) form-data read resolves, then apply the
// attributes and end (which sets the response status via `beforeSpanEnd`). On error, or when
Expand Down
5 changes: 2 additions & 3 deletions packages/remix/test/server/tracing-channel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ describe('remixIntegration (Orchestrion-based)', () => {
beforeEach(() => {
span = makeSpan();
startInactiveSpanSpy = vi.spyOn(SentryCore, 'startInactiveSpan').mockReturnValue(span);
// A truthy active span by default, so the `requiresParentSpan` gate passes.
getActiveSpanSpy = vi.spyOn(SentryCore, 'getActiveSpan').mockReturnValue({} as Span);
});

Expand Down Expand Up @@ -129,13 +128,13 @@ describe('remixIntegration (Orchestrion-based)', () => {
expect(span.end).toHaveBeenCalledTimes(1);
});

it('callRouteLoader: does not create a span without an active parent span', async () => {
it('callRouteLoader: does not create a recording span without an active parent span', async () => {
getActiveSpanSpy.mockReturnValue(undefined);
const ctx = { arguments: [{ routeId: 'x', request: makeRequest(), params: {} }] };

await tracingChannel(remixChannels.REMIX_CALL_ROUTE_LOADER).tracePromise(async () => ({ status: 200 }), ctx);

expect(startInactiveSpanSpy).not.toHaveBeenCalled();
expect(startInactiveSpanSpy).toHaveBeenCalledWith(expect.objectContaining({ onlyIfParent: true }));
});

it('callRouteAction: builds an ACTION span and captures the configured form-data keys', async () => {
Expand Down
10 changes: 3 additions & 7 deletions packages/server-utils/src/integrations/dataloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,7 @@ function subscribeConstruct(): void {
function subscribeLoad(): void {
const channel = diagnosticsChannel.tracingChannel<DataLoaderChannelContext>(CHANNELS.DATALOADER_LOAD);

bindTracingChannelToSpan(channel, data => startInactiveSpanFor(data.self, 'load', data.arguments[0]), {
requiresParentSpan: true,
});
bindTracingChannelToSpan(channel, data => startInactiveSpanFor(data.self, 'load', data.arguments[0]));

channel.end.subscribe(message => {
const data = message as TracingChannelPayloadWithSpan<DataLoaderChannelContext>;
Expand All @@ -194,10 +192,8 @@ function subscribeLoad(): void {
}

function subscribeSimpleOperation(channelName: ChannelName, operation: Operation): void {
bindTracingChannelToSpan(
diagnosticsChannel.tracingChannel<DataLoaderChannelContext>(channelName),
data => startInactiveSpanFor(data.self, operation, data.arguments[0]),
{ requiresParentSpan: true },
bindTracingChannelToSpan(diagnosticsChannel.tracingChannel<DataLoaderChannelContext>(channelName), data =>
startInactiveSpanFor(data.self, operation, data.arguments[0]),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ function instrumentInternalRequests(): void {
).toUpperCase();
return startInactiveSpan({
name: `${method} ${extractPathname(input)}`,
onlyIfParent: true,
attributes: {
[SENTRY_OP]: HTTP_SERVER,
[SENTRY_ORIGIN]: INTERNAL_REQUEST_ORIGIN,
},
});
},
{ requiresParentSpan: true },
);
}

Expand Down
54 changes: 22 additions & 32 deletions packages/server-utils/src/integrations/mongodb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,17 @@ function instrumentMongodb(): void {
* (both publish to this channel).
*/
function subscribeV4Command(): void {
bindTracingChannelToSpan(
diagnosticsChannel.tracingChannel<MongoChannelContext>(CHANNELS.MONGODB_COMMAND),
data => {
const args = data.arguments ?? [];
const ns = args[0] as MongodbNamespace | undefined;
const cmd = args[1] as Record<string, unknown> | undefined;
// Skip handshake/heartbeat commands (matches otel).
if (!ns || !cmd || typeof cmd !== 'object' || cmd.ismaster || cmd.hello) {
return undefined;
}
const operation = Object.keys(cmd)[0];
return startMongoSpan(getV4SpanAttributes(data.self, ns, cmd, operation, ORIGIN));
},
// Matches otel's `shouldSkipInstrumentation`: only trace when there is
// an active parent span, to avoid emitting orphaned mongodb spans.
{ requiresParentSpan: true },
);
bindTracingChannelToSpan(diagnosticsChannel.tracingChannel<MongoChannelContext>(CHANNELS.MONGODB_COMMAND), data => {
const args = data.arguments ?? [];
const ns = args[0] as MongodbNamespace | undefined;
const cmd = args[1] as Record<string, unknown> | undefined;
// Skip handshake/heartbeat commands (matches otel).
if (!ns || !cmd || typeof cmd !== 'object' || cmd.ismaster || cmd.hello) {
return undefined;
}
const operation = Object.keys(cmd)[0];
return startMongoSpan(getV4SpanAttributes(data.self, ns, cmd, operation, ORIGIN));
});
}

/**
Expand Down Expand Up @@ -150,21 +144,17 @@ function subscribeV3Wireprotocol(): void {
}

function bindV3(channelName: string, extract: (args: unknown[]) => V3CallInfo | undefined): void {
bindTracingChannelToSpan(
diagnosticsChannel.tracingChannel<MongoChannelContext>(channelName),
data => {
const args = data.arguments;
if (!args) {
return undefined;
}
const info = extract(args);
if (!info || typeof info.ns !== 'string') {
return undefined;
}
return startMongoSpan(getV3SpanAttributes(info.ns, info.topology, info.command, info.operation, ORIGIN));
},
{ requiresParentSpan: true },
);
bindTracingChannelToSpan(diagnosticsChannel.tracingChannel<MongoChannelContext>(channelName), data => {
const args = data.arguments;
if (!args) {
return undefined;
}
const info = extract(args);
if (!info || typeof info.ns !== 'string') {
return undefined;
}
return startMongoSpan(getV3SpanAttributes(info.ns, info.topology, info.command, info.operation, ORIGIN));
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ export function startMongoSpan(attributes: SpanAttributes): Span {

return startInactiveSpan({
name,
onlyIfParent: true,
attributes: {
[SENTRY_OP]: DB,
[SENTRY_KIND]: 'client',
Expand Down
49 changes: 23 additions & 26 deletions packages/server-utils/src/integrations/mysql2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,34 +74,31 @@ function instrumentMysql2Orchestrion(): void {
}

function subscribeQueryChannel(channelName: ChannelName): void {
bindTracingChannelToSpan(
diagnosticsChannel.tracingChannel<Mysql2QueryChannelContext>(channelName),
data => {
const statement = getQueryText(data.arguments);
const connectionAttributes = getConnectionAttributes(data.self?.config);
const { queryText, querySummary } = sanitizeSqlQueryWithSummary(statement, 'mysql');
bindTracingChannelToSpan(diagnosticsChannel.tracingChannel<Mysql2QueryChannelContext>(channelName), data => {
const statement = getQueryText(data.arguments);
const connectionAttributes = getConnectionAttributes(data.self?.config);
const { queryText, querySummary } = sanitizeSqlQueryWithSummary(statement, 'mysql');

const client = getClient();
const name =
client && hasSpanStreamingEnabled(client)
? querySummary || (connectionAttributes[DB_NAMESPACE] as string | undefined) || DB_SYSTEM_VALUE_MYSQL
: (queryText ?? 'mysql2.query');
const client = getClient();
const name =
client && hasSpanStreamingEnabled(client)
? querySummary || (connectionAttributes[DB_NAMESPACE] as string | undefined) || DB_SYSTEM_VALUE_MYSQL
: (queryText ?? 'mysql2.query');

return startInactiveSpan({
name,
attributes: {
[SENTRY_KIND]: 'client',
[SENTRY_ORIGIN]: ORIGIN,
[SENTRY_OP]: DB,
[DB_SYSTEM_NAME]: DB_SYSTEM_VALUE_MYSQL,
[DB_QUERY_TEXT]: queryText || undefined,
[DB_QUERY_SUMMARY]: querySummary,
...connectionAttributes,
},
});
},
{ requiresParentSpan: true },
);
return startInactiveSpan({
name,
onlyIfParent: true,
attributes: {
[SENTRY_KIND]: 'client',
[SENTRY_ORIGIN]: ORIGIN,
[SENTRY_OP]: DB,
[DB_SYSTEM_NAME]: DB_SYSTEM_VALUE_MYSQL,
[DB_QUERY_TEXT]: queryText || undefined,
[DB_QUERY_SUMMARY]: querySummary,
...connectionAttributes,
},
});
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,56 +92,50 @@ export function subscribeMysql2DiagnosticChannels(tracingChannel: MySQL2TracingC
}

function setupQueryChannel(tracingChannel: MySQL2TracingChannelFactory, channelName: string): void {
bindTracingChannelToSpan(
tracingChannel<MySQL2QueryData>(channelName),
data => {
// mysql2 does not sanitize its channel payload, so the statement may carry
// raw user values (on the `query` channel they are inlined). Strip every
// literal before it leaves the process; `values` is never attached.
const { queryText, querySummary } = sanitizeSqlQueryWithSummary(data.query, 'mysql');
const operation = queryText?.match(SQL_OPERATION_RE)?.[1]?.toUpperCase();
bindTracingChannelToSpan(tracingChannel<MySQL2QueryData>(channelName), data => {
// mysql2 does not sanitize its channel payload, so the statement may carry
// raw user values (on the `query` channel they are inlined). Strip every
// literal before it leaves the process; `values` is never attached.
const { queryText, querySummary } = sanitizeSqlQueryWithSummary(data.query, 'mysql');
const operation = queryText?.match(SQL_OPERATION_RE)?.[1]?.toUpperCase();

const client = getClient();
const name =
client && hasSpanStreamingEnabled(client)
? querySummary || data.database || DB_SYSTEM_NAME_VALUE_MYSQL
: queryText || 'mysql2.query';
const client = getClient();
const name =
client && hasSpanStreamingEnabled(client)
? querySummary || data.database || DB_SYSTEM_NAME_VALUE_MYSQL
: queryText || 'mysql2.query';

return startInactiveSpan({
name,
attributes: {
[SENTRY_ORIGIN]: ORIGIN,
[SENTRY_OP]: DB,
[DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_MYSQL,
[DB_QUERY_TEXT]: queryText,
[DB_QUERY_SUMMARY]: querySummary,
[DB_OPERATION_NAME]: operation,
[DB_NAMESPACE]: data.database || undefined,
[SERVER_ADDRESS]: data.serverAddress,
[SERVER_PORT]: data.serverPort,
},
});
},
{ requiresParentSpan: true },
);
return startInactiveSpan({
name,
onlyIfParent: true,
attributes: {
[SENTRY_ORIGIN]: ORIGIN,
[SENTRY_OP]: DB,
[DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_MYSQL,
[DB_QUERY_TEXT]: queryText,
[DB_QUERY_SUMMARY]: querySummary,
[DB_OPERATION_NAME]: operation,
[DB_NAMESPACE]: data.database || undefined,
[SERVER_ADDRESS]: data.serverAddress,
[SERVER_PORT]: data.serverPort,
},
});
});
}

function setupConnectChannel(tracingChannel: MySQL2TracingChannelFactory, channelName: string, spanName: string): void {
bindTracingChannelToSpan(
tracingChannel<MySQL2ConnectData>(channelName),
data => {
return startInactiveSpan({
name: spanName,
attributes: {
[SENTRY_ORIGIN]: ORIGIN,
[SENTRY_OP]: DB,
[DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_MYSQL,
[DB_NAMESPACE]: data.database || undefined,
[SERVER_ADDRESS]: data.serverAddress,
[SERVER_PORT]: data.serverPort,
},
});
},
{ requiresParentSpan: true },
);
bindTracingChannelToSpan(tracingChannel<MySQL2ConnectData>(channelName), data => {
return startInactiveSpan({
name: spanName,
onlyIfParent: true,
attributes: {
[SENTRY_ORIGIN]: ORIGIN,
[SENTRY_OP]: DB,
[DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_MYSQL,
[DB_NAMESPACE]: data.database || undefined,
[SERVER_ADDRESS]: data.serverAddress,
[SERVER_PORT]: data.serverPort,
},
});
});
}
15 changes: 6 additions & 9 deletions packages/server-utils/src/integrations/neon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,11 @@ function instrumentNeon(): void {
// the pg span shape.
bindPgQueryChannel(CHANNELS.NEON_QUERY, { origin: ORIGIN });

bindTracingChannelToSpan(
diagnosticsChannel.tracingChannel<NeonHttpQueryContext>(CHANNELS.NEON_HTTP_QUERY),
data => {
const span = startInactiveSpan(httpQuerySpanOptions(data.arguments));
httpQuerySpans.add(span);
return span;
},
{ requiresParentSpan: true },
);
bindTracingChannelToSpan(diagnosticsChannel.tracingChannel<NeonHttpQueryContext>(CHANNELS.NEON_HTTP_QUERY), data => {
const span = startInactiveSpan(httpQuerySpanOptions(data.arguments));
httpQuerySpans.add(span);
return span;
});

// The connection string is resolved inside the HTTP executor, so the query span is active
// when this settles. The executor's own context never sees the string.
Expand Down Expand Up @@ -109,6 +105,7 @@ function httpQuerySpanOptions(args: unknown[]): Parameters<typeof startInactiveS

return {
name,
onlyIfParent: true,
attributes: {
[SENTRY_OP]: DB,
[SENTRY_ORIGIN]: ORIGIN,
Expand Down
5 changes: 2 additions & 3 deletions packages/server-utils/src/integrations/postgres-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,7 @@ function instrumentPostgresJs(options: PostgresJsIntegrationOptions): void {

// Opt out of re-entrant `handle()` calls (then/catch/finally re-invoke
// it, guarded by `executed`) and queries already wrapped by the portable
// `instrumentPostgresJsSql`. The parent-span requirement is applied via
// `requiresParentSpan` below.
// `instrumentPostgresJsSql`.
if (query.executed === true || (query as Record<symbol, unknown>)[QUERY_FROM_INSTRUMENTED_SQL]) {
return undefined;
}
Expand All @@ -292,6 +291,7 @@ function instrumentPostgresJs(options: PostgresJsIntegrationOptions): void {
// `sentry.kind: client` matches the mysql/pg channel subscribers.
const span = startInactiveSpan({
name,
onlyIfParent: requireParentSpan !== false,
attributes: {
[SENTRY_OP]: DB,
[SENTRY_KIND]: 'client',
Expand Down Expand Up @@ -324,7 +324,6 @@ function instrumentPostgresJs(options: PostgresJsIntegrationOptions): void {
return span;
},
{
requiresParentSpan: requireParentSpan !== false,
deferSpanEnd({ data }) {
// `handle` is async: its promise settles on dispatch (asyncEnd), long
// before the query does. The resolve/reject wrappers own the ending.
Expand Down
Loading
Loading