Skip to content

Instantly share code, notes, and snippets.

@frandiox
Created December 16, 2021 06:04
Show Gist options
  • Save frandiox/385d215abc5a3e538aee9750abe08b57 to your computer and use it in GitHub Desktop.
Save frandiox/385d215abc5a3e538aee9750abe08b57 to your computer and use it in GitHub Desktop.
Global request IDs using error stacks
// ---- User code:
addEventListener("fetch", event => {
event.respondWith(handleEvent(event))
})
// ---- Lib code:
const globalStuff = new Map();
async function handleEvent({request}) {
// Generate a unique ID for this request
const requestId = crypto.randomUUID();
// Create a dynamic wrapper function with a custom name:
const wrapperName = 'REQUEST_ID:' + requestId;
const _dynamicHandleEvent = (...args) => actualHandleEvent(...args);
Object.defineProperty(_dynamicHandleEvent, 'name', {value: wrapperName});
// Save stuff globally, indexed by the current request ID
globalStuff.set('cache:' + requestId, new Map());
globalStuff.set('logger:' + requestId, {
log: (...args) => console.log(request, ...args),
warn: (...args) => console.warn(request, ...args),
});
// Continue normal event handling
let response;
try {
response = await _dynamicHandleEvent();
} catch(error) {
response = new Response(error.message, { status: 500 });
} finally {
// Cleanup
globalStuff.delete('cache:' + requestId);
globalStuff.delete('logger:' + requestId);
}
return response
}
function actualHandleEvent() {
return doDeepStuff();
}
function doDeepStuff() {
// ...
const requestId = getCurrentRequestId();
const myRequestCache = globalStuff.get('cache:' + requestId);
const promise = myRequestCache.get('myFetch') || fetch('https://example.com');
myRequestCache.set('myFetch', promise);
const logger = globalStuff.get('logger:' + requestId);
logger.log('This works!', requestId);
// ...
return new Response('Found Id: ' + requestId, {headers: {'content-type': 'text/plain'}})
}
function getCurrentRequestId() {
// Error stacks will contain the name of the wrapper function
const e = new Error('look ma no hands');
// Using the optional Error.prepareStackTrace below:
const requestId = e.stack;
// Parsing the raw stack:
// const [, requestId] = e.stack.match(/REQUEST_ID:([\w-]+)\s/);
return requestId;
}
// ---- Optional perf++, avoids creating error stacks.
// From https://v8.dev/docs/stack-trace-api#customizing-stack-traces :
// For efficiency stack traces are not formatted when they are captured but on demand,
// the first time the stack property is accessed.
Error.prepareStackTrace = function (error, stacks) {
if (error.message === 'look ma no hands') {
for (let i = stacks.length - 1; i > 0; i--) {
const fnName = stacks[i].getFunctionName() || '';
if (fnName.startsWith('REQUEST_ID:')) {
return fnName.split(':')[1];
}
}
}
// Fallback for normal errors
return error.stack;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment