Created
December 16, 2021 06:04
-
-
Save frandiox/385d215abc5a3e538aee9750abe08b57 to your computer and use it in GitHub Desktop.
Global request IDs using error stacks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ---- 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