Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import assert from 'node:assert/strict';
import * as Sentry from '@sentry/node';
import { createStorage } from 'unstorage';
import { withTracing } from 'unstorage/tracing';

const storage = withTracing(createStorage());

async function readAndWrite(key) {
await storage.setItem(key, 'value');
assert.equal(await storage.getItem(key), 'value');
}

async function run() {
await readAndWrite('before_parent');

const parent = Sentry.startInactiveSpan({ name: 'parent operation' });
await Sentry.withActiveSpan(parent, () => readAndWrite('inside_parent'));

await readAndWrite('after_parent');
await storage.dispose();

// Flush every operation before the parent signals that span collection is complete.
await Sentry.flush();
parent.end();
await Sentry.flush();
}

run();
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SerializedStreamedSpan } from '@sentry/core';
import {
CACHE_HIT,
CACHE_KEY,
Expand Down Expand Up @@ -114,6 +115,44 @@ describe.runIf(supports({ min: 20 }))('nitro integrations', () => {
});
});

createEsmTests(__dirname, 'scenario-storage-no-parent.mjs', 'instrument.mjs', (createRunner, test) => {
test('only instruments storage operations with an active parent span', async () => {
const runner = createRunner();
const cacheSpans = new Map<string, SerializedStreamedSpan>();
const spansPromise = runner.collectStreamedSpans(spans => {
for (const span of spans) {
if (span.attributes[SENTRY_ORIGIN]?.value === 'auto.cache.nitro') {
cacheSpans.set(span.span_id, span);
}
}
return spans.some(span => span.is_segment && span.name === 'parent operation');
});

await runner.unordered().start().completed();

const spans = await spansPromise;
const parent = spans.find(span => span.is_segment && span.name === 'parent operation');
expect(parent).toBeDefined();
expect(
Array.from(cacheSpans.values(), span => ({
operation: span.attributes[DB_OPERATION_NAME]?.value,
key: span.attributes[CACHE_KEY]?.value,
parentSpanId: span.parent_span_id,
traceId: span.trace_id,
isSegment: span.is_segment,
})),
).toEqual(
['setItem', 'getItem'].map(operation => ({
operation,
key: 'inside_parent',
parentSpanId: parent?.span_id,
traceId: parent?.trace_id,
isSegment: false,
})),
);
});
});

createEsmTests(__dirname, 'scenario.mjs', 'instrument-no-tracing.mjs', (createRunner, test) => {
test('sets Server-Timing headers without tracing enabled', async () => {
const runner = createRunner().start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ function setupStorageTracingChannel(operation: TracedOperation): void {
});
},
{
requiresParentSpan: true,
beforeSpanEnd(span, data) {
// Error status is set by the binding; the error itself is captured at the request boundary,
// not here (cache ops aren't an error boundary). Only enrich the success path.
Expand Down
Loading