Skip to content

Commit ae0c9db

Browse files
committed
maybe this
1 parent 29604aa commit ae0c9db

3 files changed

Lines changed: 88 additions & 0 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as Sentry from '@sentry/node';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
3+
4+
Sentry.init({
5+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
traceLifecycle: 'stream',
9+
transport: loggingTransport,
10+
});
11+
12+
async function run(): Promise<void> {
13+
await Sentry.startSpan({ name: 'test_transaction' }, async () => {
14+
await fetch(`${process.env.SERVER_URL}/api/v0`);
15+
});
16+
17+
await Sentry.flush();
18+
}
19+
20+
void run();
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { createTestServer } from '@sentry-internal/test-utils';
2+
import { expect, test } from 'vitest';
3+
import { createRunner } from '../../../../utils/runner';
4+
5+
test('captures streamed spans with sentry.op for outgoing fetch requests', async () => {
6+
expect.assertions(2);
7+
8+
const [SERVER_URL, closeTestServer] = await createTestServer()
9+
.get('/api/v0', () => {
10+
// Just ensure we're called
11+
expect(true).toBe(true);
12+
})
13+
.start();
14+
15+
await createRunner(__dirname, 'scenario.ts')
16+
.withEnv({ SERVER_URL })
17+
.expect({
18+
span: container => {
19+
const httpClientSpan = container.items.find(
20+
item =>
21+
item.attributes?.['sentry.op']?.type === 'string' && item.attributes['sentry.op'].value === 'http.client',
22+
);
23+
24+
expect(httpClientSpan).toBeDefined();
25+
},
26+
})
27+
.start()
28+
.completed();
29+
closeTestServer();
30+
});

packages/core/src/tracing/spans/captureSpan.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Client } from '../../client';
33
import type { ScopeData } from '../../scope';
44
import {
55
SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT,
6+
SEMANTIC_ATTRIBUTE_SENTRY_OP,
67
SEMANTIC_ATTRIBUTE_SENTRY_RELEASE,
78
SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME,
89
SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION,
@@ -79,6 +80,17 @@ export function captureSpan(span: Span, client: Client): SerializedStreamedSpanW
7980
});
8081
}
8182

83+
// Backfill sentry.op from span attributes when not explicitly set.
84+
// OTel-originated spans don't have sentry.op set — we infer it from semantic conventions.
85+
if (!processedSpan.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP]) {
86+
const inferredOp = inferOpFromAttributes(processedSpan.attributes);
87+
if (inferredOp) {
88+
safeSetSpanJSONAttributes(processedSpan, {
89+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: inferredOp,
90+
});
91+
}
92+
}
93+
8294
return {
8395
...streamedSpanJsonToSerializedSpan(processedSpan),
8496
_segmentSpan: segmentSpan,
@@ -150,3 +162,29 @@ export function safeSetSpanJSONAttributes(
150162
}
151163
});
152164
}
165+
166+
/**
167+
* Infer `sentry.op` from span attributes based on OTel semantic conventions.
168+
* This is needed because OTel-originated spans don't set `sentry.op` — the non-streamed
169+
* path infers it in the `SentrySpanExporter`, but streamed spans skip the exporter entirely.
170+
*/
171+
function inferOpFromAttributes(attributes?: RawAttributes<Record<string, unknown>>): string | undefined {
172+
if (!attributes) {
173+
return undefined;
174+
}
175+
176+
const httpMethod = attributes['http.request.method'] || attributes['http.method'];
177+
if (httpMethod) {
178+
// Determine client vs server from the span's parent:
179+
// - Spans with a server address are outgoing (client) requests
180+
// - The `sentry.origin` attribute can also indicate the direction
181+
return attributes['server.address'] || attributes['net.peer.name'] ? 'http.client' : 'http.server';
182+
}
183+
184+
const dbSystem = attributes['db.system.name'] || attributes['db.system'];
185+
if (dbSystem) {
186+
return 'db';
187+
}
188+
189+
return undefined;
190+
}

0 commit comments

Comments
 (0)