Skip to content

Commit 37fbb91

Browse files
committed
Refactor TryCatchFinallyHooksBuilder class and callStack hook
1 parent d11d651 commit 37fbb91

File tree

4 files changed

+49
-26
lines changed

4 files changed

+49
-26
lines changed

src/TryCatchFinallyHooks.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,10 @@ export class TryCatchFinallyHooksBuilder<FullHookContext extends FunctionContext
4545
const _this = this
4646
const beforeHooksTry = this.beforeHooksTry.bind(this)
4747
const afterHooksTry = this.afterHooksTry?.bind(this)
48-
type TContext = FullHookContext & FunctionContext
4948
return (func:any)=>{
5049
return createTryCatchFinally(func, {
5150
onTry(funcCtx) {
52-
const ctx:TContext = funcCtx as any
51+
const ctx:FullHookContext = funcCtx as any
5352
if(args)
5453
(ctx as any).args = args
5554
const bht = beforeHooksTry(ctx)
@@ -122,6 +121,12 @@ export class TryCatchFinallyHooksBuilder<FullHookContext extends FunctionContext
122121
}
123122

124123
private _currentContext:(FullHookContext & FunctionContext)|undefined = undefined
124+
125+
126+
/**
127+
* Only safe to use in sync functions or in async functions before any awaited code.
128+
* Otherwise, the context may be changed by another async function - in that case use callStack hook instead
129+
*/
125130
get current(){
126131
return this._currentContext
127132
}

src/callStack.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ function createTrack(log:any){
77
.add(callStack)
88
.add({
99
onTry(ctx) {
10-
log("onTry", [...ctx.callstack.map(c=>c.args.name), ctx.args.name].join('/'))
10+
log("onTry", [...ctx.getCallStack().map(c=>c.name), ctx.name].join('/'))
1111
return {
1212
onFinally() {
13-
log("onFinally",[...ctx.callstack.map(c=>c.args.name), ctx.args.name].join('/'))
13+
log("onFinally",[...ctx.getCallStack().map(c=>c.name), ctx.name].join('/'))
1414
},
1515
onCatch() {
16-
log("onCatch",[...ctx.callstack.map(c=>c.args.name), ctx.args.name].join('/'))
16+
log("onCatch",[...ctx.getCallStack().map(c=>c.name), ctx.name].join('/'))
1717
}
1818
}
1919
}

src/callStack.ts

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,45 @@ import { FunctionContext } from "./tryCatchFinally";
1111
* During onTry Context.func (that is about to be invoked) is replaced with a special function wrapper that has assigned unique name `callstack_callId:<context.callId>`.
1212
* Now those function names in Error.captureCallstack() can be associated context.callstack.callId via list of context objects that are currently active.
1313
*
14-
* If you don't see actions in your callstack, increase Error.stackTraceLimit
14+
* If you don't see actions in your callstack, increase Error.stackTraceLimit, but keep in mind that it impacts performance
1515
*/
1616

1717
const callPrefix = 'callstack_callId:'
1818

19-
export type CallstackContext = { args: { name?: string;}, name:string, id: string, callstack: CallstackContext[] };
19+
export type CallStackContext = {
20+
args: {
21+
name?: string;
22+
isVisibleInCallStack?:boolean
23+
},
24+
name:string,
25+
id: string,
26+
27+
/**
28+
* Returns current tracked callstack. Filters Error.captureStackTrace by tracked functions.
29+
* If you don't see actions in your callstack, increase Error.stackTraceLimit
30+
*/
31+
getCallStack(): CallStackContext[]
32+
};
2033

2134

22-
export const callStack = TryCatchFinallyHooksBuilder.createHook<CallstackContext>(
35+
export const callStack = TryCatchFinallyHooksBuilder.createHook<CallStackContext>(
2336
(ctx)=>{
2437
if(!ctx.name)
2538
ctx.name = ctx.args.name || ctx.func.name || '<anonymous>'
2639

2740
if(!ctx.id)
2841
ctx.id = Math.random().toString(16).slice(2)
2942
getOrUpdateAllActiveContexts(actions=>[...actions, ctx])
30-
3143

32-
const origFunc = ctx.func
33-
const callstackCallWrapper = function (this:any,...args:any) { return origFunc.apply(this, args) }
34-
Object.defineProperty(callstackCallWrapper, 'name', {value:callPrefix+ctx.id})
35-
ctx.func = callstackCallWrapper
36-
37-
Object.defineProperty(ctx, 'callstack', {
38-
get() {
39-
const contextsMap = new Map(getOrUpdateAllActiveContexts().map(ctx=>[ctx.id, ctx]))
40-
const actionCallIdStack = getActionCallIdsStack()
41-
return actionCallIdStack.map(ctxId=>contextsMap.get(ctxId)!)
42-
}
43-
});
44+
const isVisible = 'isVisibleInCallStack' in ctx.args ? ctx.args.isVisibleInCallStack : true
45+
46+
if(isVisible){
47+
const origFunc = ctx.func
48+
const callstackCallWrapper = function (this:any,...args:any) { return origFunc.apply(this, args) }
49+
Object.defineProperty(callstackCallWrapper, 'name', {value:callPrefix+ctx.id})
50+
ctx.func = callstackCallWrapper
51+
}
52+
ctx.getCallStack = getCurrentCallstack
4453

4554
return {
4655
onFinally() {
@@ -51,6 +60,15 @@ export const callStack = TryCatchFinallyHooksBuilder.createHook<CallstackContext
5160
}
5261
)
5362

63+
/**
64+
* Returns current tracked callstack. Filters Error.captureStackTrace by tracked functions.
65+
* If you don't see actions in your callstack, increase Error.stackTraceLimit
66+
*/
67+
export function getCurrentCallstack():CallStackContext[]{
68+
const contextsMap = new Map(getOrUpdateAllActiveContexts().map(ctx=>[ctx.id, ctx]))
69+
return getActionCallIdsStack().map(ctxId=>contextsMap.get(ctxId)).filter(c=>c) as CallStackContext[]
70+
}
71+
5472
function prepareActionStackTrace(error: Error, stack: NodeJS.CallSite[]): string[] {
5573
return stack
5674
.map(s=>s.getFunctionName()!)
@@ -76,16 +94,16 @@ function getActionCallIdsStack():string[]
7694

7795
const allActiveContexts// mimicking AsyncLocalStorage
7896
= {
79-
_items:undefined as CallstackContext[]|undefined,
80-
getStore():CallstackContext[]|undefined{
97+
_items:undefined as CallStackContext[]|undefined,
98+
getStore():CallStackContext[]|undefined{
8199
return [...this._items||[]]
82100
},
83-
enterWith(items:CallstackContext[]){
101+
enterWith(items:CallStackContext[]){
84102
this._items = items||[]
85103
}
86104
}
87105

88-
function getOrUpdateAllActiveContexts(update?:(old:CallstackContext[])=>undefined|CallstackContext[]):CallstackContext[]{
106+
function getOrUpdateAllActiveContexts(update?:(old:CallStackContext[])=>undefined|CallStackContext[]):CallStackContext[]{
89107
const store = [...allActiveContexts.getStore()||[]]
90108
if(update)
91109
{

src/example/tracker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const track = new TryCatchFinallyHooksBuilder()
1111
return {
1212
onFinally() {
1313
const datadog: any = {};
14-
datadog.histogram(`action-${ctx.args.name}-duration`, ctx.duration, { tags: ['action:' + ctx.name, 'callstack:' + ctx.callstack.map(c => c.name).join('/')] });
14+
datadog.histogram(`action-${ctx.args.name}-duration`, ctx.duration, { tags: ['action:' + ctx.name, 'callstack:' + ctx.getCallStack().map(c => c.name).join('/')] });
1515
},
1616
};
1717
})

0 commit comments

Comments
 (0)