UNPKG

stagnant

Version:

Measure your slow code, make it _fast_.

304 lines (253 loc) 10.1 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('node-fetch')) : typeof define === 'function' && define.amd ? define(['node-fetch'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.stagnant = factory(global.fetch)); }(this, (function (fetch) { 'use strict'; function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch); function defaultConfig(){ function onevent(){} function onerror(){} function onsuccess(){} function onflush(){} // eslint-disable-next-line no-undef const ourConsole = { log(){}, error(){}, warn(){} }; function generateId(){ return Math.random().toString(15).slice(2,8) } return { onevent, onerror, onsuccess, onflush, console: ourConsole, generateId } } function Main(config={}){ config = { ...defaultConfig(), ...config }; const { generateId } = config; async function dispatchEvent(event){ await config.onevent(event); if( event.error ) { await config.onerror(event); } else { await config.onsuccess(event); } } function Event({ parentId, id, startTime, endTime, name, data, error }){ return { parentId, id, startTime, endTime, name, data, error } } function RootEvent(){ const event = Event({ parentId: null ,id: generateId() ,startTime: Date.now() ,endTime: Date.now() ,error: null ,data: {} }); event.flush = async function flush(){ delete event.flush; event.endTime = Date.now(); await dispatchEvent(event); await config.onflush(event); return event }; return event } function setupEvent({ parentEvent, name, data, sync }){ const event = Event({ parentId: parentEvent.id ,id: generateId() ,name ,startTime: null ,endTime: null ,data: { ...parentEvent.data || {}, ...data } ,error: null ,sync: parentEvent.sync || sync }); const childP = Instance(event); return { childP, event } } function Instance(parentEvent){ function handler(...args){ const callbackIndex = args.findIndex( x => typeof x == 'function' ); let cb = args[callbackIndex]; let rest = callbackIndex > 0 ? args.slice(0,callbackIndex) : args; let [name, data] = rest; if (typeof name != 'string') { data = name; if( cb ) { name = data.name || cb.toString().replace( /\s/g, '' ).replace( /(.)*=>/, '' ); } } if ( data == null ) data = {}; return { name, data, callback: cb } } function handlerData({ data }){ parentEvent.data = { ...parentEvent.data, ...data }; return null } async function handlerAsync({ event, childP, callback }){ if ( parentEvent.sync ) { throw new Error('Cannot use an async trace within a synchronous trace') } try { event.startTime = Date.now(); const out = await callback(childP); event.endTime = Date.now(); return out } catch (e) { event.endTime = Date.now(); event.error = e; throw e } finally { dispatchEvent(event) .catch( e => config.console.error('Failed to dispatch event', e)); } } function handlerSync({ callback, name, event, childP }){ try { event.startTime = Date.now(); const out = callback(childP); event.endTime = Date.now(); if( out != null && 'then' in out ) { config.console.warn(name, 'A call to trace.sync was made but the response was async. This is likely a mistake and should be corrected.'); } return out } catch (e) { event.endTime = Date.now(); event.error = e; throw e } finally { dispatchEvent(event) .catch( e => config.console.error('Failed to dispatch event', e)); } } function routerOptions({ sync }, ...args){ const { data, callback, name } = handler(...args); const {event,childP} = callback ? setupEvent({ parentEvent, name, data, sync }) : {}; if( callback && sync ) { return handlerSync({ data, callback, childP, name, event }) } else if ( callback && !sync ) { return handlerAsync({ data, callback, childP, name, event }) } else { return handlerData({ data }) } } async function routerAsync(...args){ const out = await routerOptions({ sync: false }, ...args); return out } function routerSync(...args){ const out = routerOptions({ sync: true }, ...args); return out } routerAsync.sync = routerSync; return routerAsync } let rootEvent = RootEvent(); let handlerInstance = Instance(rootEvent); handlerInstance.flush = rootEvent.flush; handlerInstance.config = config; return handlerInstance } /** * Safely invoke a callback even if trace is null. * * useful when there are multiple entry points into a function and some are not * passing in a trace. * * @param {*} trace * @param {...any} args * @returns */ function call(trace, ...args){ const cb = args.find( x => typeof x == 'function' ); if ( trace ) { return trace( ...args ) } else if ( cb ) { return cb( (...args) => call(null, ...args) ) } return null } Main.call = call; /** * Create a no-op trace if provided trace is null * * Much like stagnant.call, useful when there are multiple entry points into a function and some are not * passing in a trace. * * @param {*} trace * @param {...any} args * @returns */ function ensure(trace){ if( trace ) { return trace } else { return (...args) => call(null, ...args) } } Main.ensure = ensure; /* globals process */ function Honey({ name: rootName='root' , dataset='default' , writeKey=process.env.HONEYCOMB_WRITE_KEY // pass in traceId/parentId from an existing trace // e.g. to continue a trace from another server, or // even the browser // otherwise leave blank , traceId= Math.random().toString(15).slice(2,8) , parentId= undefined , data={} , config={} }={}){ // Traces aren't nested, but a trace can contain nested spans. // We create a root trace for every invocation of Honey // every other event subsequently is a span within that root trace const root = Main({ async onevent(event){ const name = event.parentId ? event.name : rootName; const body = JSON.stringify({ ...event.data , ...data , name , error: event.error ? event.error.message : undefined ,'error.stack': event.error ? event.error.stack : undefined , 'trace.trace_id': 'trace-'+ traceId , 'trace.span_id': 'span-' + event.id , 'trace.parent_id': event.parentId ? 'span-' + event.parentId : parentId , service_name: 'stagnant' , duration_ms: event.endTime - event.startTime }); try { // console.log(name, event.error, event.endTime - event.startTime) const response = await fetch__default['default'](`https://api.honeycomb.io/1/events/${dataset}`, { method: 'post' ,headers: { 'X-Honeycomb-Team': writeKey ,'X-Honeycomb-Event-Time': event.startTime ,'Content-Type': 'application/json' } ,body }); root.config.console.log(name, response.status, body); if( !event.parentId ) { root.config.console.log('flushed', response.status ); } } catch (e) { root.config.console.error(e); } } , ...config }); return root } return Honey; }))); //# sourceMappingURL=stagnant-honeycomb.browser.js.map