Skip to content

Commit 9c96aa6

Browse files
committed
fix: handle multiple cache keys and more complex calls
1 parent 4cf0de8 commit 9c96aa6

File tree

1 file changed

+74
-64
lines changed

1 file changed

+74
-64
lines changed

packages/nuxt/src/runtime/plugins/storage.server.ts

Lines changed: 74 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
type SpanAttributes,
3+
type StartSpanOptions,
34
captureException,
45
debug,
56
flushIfServerless,
@@ -26,20 +27,6 @@ type MaybeInstrumentedDriver = MaybeInstrumented<Driver>;
2627

2728
type DriverMethod = keyof Driver;
2829

29-
/**
30-
* Methods that should have a key argument.
31-
*/
32-
const KEYED_METHODS = new Set<DriverMethod>([
33-
'hasItem',
34-
'getItem',
35-
'getItemRaw',
36-
'getItems',
37-
'setItem',
38-
'setItemRaw',
39-
'setItems',
40-
'removeItem',
41-
]);
42-
4330
/**
4431
* Methods that should have a attribute to indicate a cache hit.
4532
*/
@@ -127,45 +114,35 @@ function createMethodWrapper(
127114
): (...args: unknown[]) => unknown {
128115
return new Proxy(original, {
129116
async apply(target, thisArg, args) {
130-
const attributes = getSpanAttributes(methodName, driver, mountBase, args);
117+
const options = createSpanStartOptions(methodName, driver, mountBase, args);
131118

132119
DEBUG_BUILD && debug.log(`[storage] Running method: "${methodName}" on driver: "${driver.name ?? 'unknown'}"`);
133120

134-
const spanName = KEYED_METHODS.has(methodName)
135-
? `${mountBase}${args?.[0]}`
136-
: `storage.${normalizeMethodName(methodName)}`;
137-
138-
return startSpan(
139-
{
140-
name: spanName,
141-
attributes,
142-
},
143-
async span => {
144-
try {
145-
const result = await target.apply(thisArg, args);
146-
span.setStatus({ code: SPAN_STATUS_OK });
147-
148-
if (CACHE_HIT_METHODS.has(methodName)) {
149-
span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_HIT, !isEmptyValue(result));
150-
}
151-
152-
return result;
153-
} catch (error) {
154-
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
155-
captureException(error, {
156-
mechanism: {
157-
handled: false,
158-
type: attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN],
159-
},
160-
});
161-
162-
// Re-throw the error to be handled by the caller
163-
throw error;
164-
} finally {
165-
await flushIfServerless();
121+
return startSpan(options, async span => {
122+
try {
123+
const result = await target.apply(thisArg, args);
124+
span.setStatus({ code: SPAN_STATUS_OK });
125+
126+
if (CACHE_HIT_METHODS.has(methodName)) {
127+
span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_HIT, !isEmptyValue(result));
166128
}
167-
},
168-
);
129+
130+
return result;
131+
} catch (error) {
132+
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
133+
captureException(error, {
134+
mechanism: {
135+
handled: false,
136+
type: options.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN],
137+
},
138+
});
139+
140+
// Re-throw the error to be handled by the caller
141+
throw error;
142+
} finally {
143+
await flushIfServerless();
144+
}
145+
});
169146
},
170147
});
171148
}
@@ -191,11 +168,30 @@ function wrapStorageMount(storage: Storage): Storage['mount'] {
191168

192169
return mountWithInstrumentation;
193170
}
171+
/**
172+
* Normalizes the method name to snake_case to be used in span names or op.
173+
*/
174+
function normalizeMethodName(methodName: string): string {
175+
return methodName.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
176+
}
194177

195178
/**
196-
* Gets the span attributes for the storage method.
179+
* Checks if the value is empty, used for cache hit detection.
197180
*/
198-
function getSpanAttributes(methodName: string, driver: Driver, mountBase: string, args: unknown[]): SpanAttributes {
181+
function isEmptyValue(value: unknown): boolean {
182+
return value === null || value === undefined;
183+
}
184+
185+
/**
186+
* Creates the span start options for the storage method.
187+
*/
188+
function createSpanStartOptions(
189+
methodName: keyof Driver,
190+
driver: Driver,
191+
mountBase: string,
192+
args: unknown[],
193+
): StartSpanOptions {
194+
const key = normalizeKey(args?.[0]);
199195
const attributes: SpanAttributes = {
200196
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: `cache.${normalizeMethodName(methodName)}`,
201197
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nuxt',
@@ -204,24 +200,38 @@ function getSpanAttributes(methodName: string, driver: Driver, mountBase: string
204200
'db.system.name': driver.name ?? 'unknown',
205201
};
206202

207-
// Add the key if it's a get/set/del call
208-
if (args?.[0] && typeof args[0] === 'string') {
209-
attributes[SEMANTIC_ATTRIBUTE_CACHE_KEY] = `${mountBase}${args[0]}`;
203+
if (key) {
204+
attributes[SEMANTIC_ATTRIBUTE_CACHE_KEY] = `${mountBase}${key}`;
210205
}
211206

212-
return attributes;
207+
return {
208+
name: `${normalizeMethodName(methodName)} ${mountBase}${key}`,
209+
attributes,
210+
};
213211
}
214212

215213
/**
216-
* Normalizes the method name to snake_case to be used in span names or op.
214+
* Normalizes the key to a string for display purposes.
215+
* @param key The key to normalize.
217216
*/
218-
function normalizeMethodName(methodName: string): string {
219-
return methodName.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
220-
}
217+
function normalizeKey(key: unknown): string {
218+
if (isEmptyValue(key)) {
219+
return '';
220+
}
221221

222-
/**
223-
* Checks if the value is empty, used for cache hit detection.
224-
*/
225-
function isEmptyValue(value: unknown): boolean {
226-
return value === null || value === undefined;
222+
if (typeof key === 'string') {
223+
return key;
224+
}
225+
226+
// Handles an object with a key property
227+
if (typeof key === 'object' && key !== null && 'key' in key) {
228+
return `${key.key}`;
229+
}
230+
231+
// Handles an array of keys
232+
if (Array.isArray(key)) {
233+
return key.map(k => normalizeKey(k)).join(', ');
234+
}
235+
236+
return String(key);
227237
}

0 commit comments

Comments
 (0)