-
-
Notifications
You must be signed in to change notification settings - Fork 45
/
Copy pathindex.js
122 lines (95 loc) · 3.02 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { createHook } from 'node:async_hooks'
import { readFileSync } from 'node:fs'
import { relative } from 'node:path'
import { fileURLToPath } from 'node:url'
const IGNORED_TYPES = [
'TIMERWRAP',
'PROMISE',
'PerformanceObserver',
'RANDOMBYTESREQUEST'
]
const asyncResources = new Map()
const hook = createHook({
init (asyncId, type, triggerAsyncId, resource) {
if (IGNORED_TYPES.includes(type)) {
return
}
const stacks = captureStackTraces().slice(1)
asyncResources.set(asyncId, {
type,
resourceRef: new WeakRef(resource),
stacks
})
},
destroy (asyncId) {
asyncResources.delete(asyncId)
}
})
hook.enable()
export default function whyIsNodeRunning (logger = console) {
hook.disable()
const activeAsyncResources = Array.from(asyncResources.values())
.filter(({ resourceRef }) => {
const resource = resourceRef.deref()
if (resource === undefined) {
return false
}
return resource.hasRef?.() ?? true
})
logger.error(`There are ${activeAsyncResources.length} handle(s) keeping the process running.`)
for (const asyncResource of activeAsyncResources) {
printStacks(asyncResource, logger)
}
}
function printStacks (asyncResource, logger) {
const stacks = asyncResource.stacks.filter((stack) => {
const fileName = stack.fileName
return fileName !== null && !fileName.startsWith('node:')
})
logger.error('')
logger.error(`# ${asyncResource.type}`)
if (!stacks[0]) {
logger.error('(unknown stack trace)')
return
}
const maxLength = stacks.reduce((length, stack) => Math.max(length, formatLocation(stack).length), 0)
for (const stack of stacks) {
const location = formatLocation(stack)
const padding = ' '.repeat(maxLength - location.length)
try {
const lines = readFileSync(normalizeFilePath(stack.fileName), 'utf-8').split(/\n|\r\n/)
const line = lines[stack.lineNumber - 1].trim()
logger.error(`${location}${padding} - ${line}`)
} catch (e) {
logger.error(`${location}${padding}`)
}
}
}
function formatLocation (stack) {
const filePath = formatFilePath(stack.fileName)
return `${filePath}:${stack.lineNumber}`
}
function formatFilePath (filePath) {
const absolutePath = normalizeFilePath(filePath)
const relativePath = relative(process.cwd(), absolutePath)
return relativePath.startsWith('..') ? absolutePath : relativePath
}
function normalizeFilePath (filePath) {
return filePath.startsWith('file://') ? fileURLToPath(filePath) : filePath
}
function prepareStackTrace(error, stackTraces) {
return stackTraces.map(stack => ({
lineNumber: stack.getLineNumber(),
fileName: stack.getFileName()
}))
}
// See: https://v8.dev/docs/stack-trace-api
function captureStackTraces () {
const target = {}
const original = Error.prepareStackTrace
Error.prepareStackTrace = prepareStackTrace
Error.captureStackTrace(target, captureStackTraces)
const capturedTraces = target.stack
Error.prepareStackTrace = original
return capturedTraces
}