-
Notifications
You must be signed in to change notification settings - Fork 323
/
Copy pathdependencies.js
171 lines (151 loc) · 5.51 KB
/
dependencies.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
'use strict'
const path = require('path')
const parse = require('module-details-from-path')
const requirePackageJson = require('../require-package-json')
const { sendData } = require('./send-data')
const dc = require('dc-polyfill')
const { fileURLToPath } = require('url')
const { isTrue } = require('../../src/util')
const savedDependenciesToSend = new Set()
const detectedDependencyKeys = new Set()
const detectedDependencyVersions = new Set()
const FILE_URI_START = `file://`
const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
let immediate, config, application, host, initialLoad
let isFirstModule = true
let getRetryData
let updateRetryData
function createBatchPayload (payload) {
const batchPayload = []
// eslint-disable-next-line array-callback-return
payload.map(item => {
batchPayload.push({
request_type: item.reqType,
payload: item.payload
})
})
return batchPayload
}
function waitAndSend (config, application, host) {
if (!immediate) {
immediate = setImmediate(() => {
immediate = null
if (savedDependenciesToSend.size > 0) {
const dependencies = Array.from(savedDependenciesToSend.values())
// if a depencdency is from the initial load, *always* send the event
// Otherwise, only send if dependencyCollection is enabled
.filter(dep => {
const initialLoadModule = isTrue(dep.split(' ')[2])
const sendModule = initialLoadModule || (config.telemetry?.dependencyCollection)
if (!sendModule) savedDependenciesToSend.delete(dep) // we'll never send it
return sendModule
})
.splice(0, 2000) // v2 documentation specifies up to 2000 dependencies can be sent at once
.map(pair => {
savedDependenciesToSend.delete(pair)
const [name, version] = pair.split(' ')
return { name, version }
})
let currPayload
const retryData = getRetryData()
if (retryData) {
currPayload = { reqType: 'app-dependencies-loaded', payload: { dependencies } }
} else {
if (!dependencies.length) return // no retry data and no dependencies, nothing to send
currPayload = { dependencies }
}
const payload = retryData ? createBatchPayload([currPayload, retryData]) : currPayload
const reqType = retryData ? 'message-batch' : 'app-dependencies-loaded'
sendData(config, application, host, reqType, payload, updateRetryData)
if (savedDependenciesToSend.size > 0) {
waitAndSend(config, application, host)
}
}
})
immediate.unref()
}
}
function loadAllTheLoadedModules () {
if (require.cache) {
const filenames = Object.keys(require.cache)
filenames.forEach(filename => {
onModuleLoad({ filename })
})
}
}
function onModuleLoad (data) {
if (isFirstModule) {
isFirstModule = false
loadAllTheLoadedModules()
}
if (data) {
let filename = data.filename
if (filename && filename.startsWith(FILE_URI_START)) {
try {
filename = fileURLToPath(filename)
} catch (e) {
// cannot transform url to path
}
}
const parseResult = filename && parse(filename)
const request = data.request || (parseResult && parseResult.name)
const dependencyKey = parseResult && parseResult.basedir ? parseResult.basedir : request
if (filename && request && isDependency(filename, request) && !detectedDependencyKeys.has(dependencyKey)) {
detectedDependencyKeys.add(dependencyKey)
if (parseResult) {
const { name, basedir } = parseResult
if (basedir) {
try {
const { version } = requirePackageJson(basedir, module)
const dependencyAndVersion = `${name} ${version}`
if (!detectedDependencyVersions.has(dependencyAndVersion)) {
savedDependenciesToSend.add(`${dependencyAndVersion} ${initialLoad}`)
detectedDependencyVersions.add(dependencyAndVersion)
waitAndSend(config, application, host)
}
} catch (e) {
// can not read the package.json, do nothing
}
}
}
}
}
}
function start (_config = {}, _application, _host, getRetryDataFunction, updateRetryDatafunction) {
config = _config
application = _application
host = _host
initialLoad = true
getRetryData = getRetryDataFunction
updateRetryData = updateRetryDatafunction
moduleLoadStartChannel.subscribe(onModuleLoad)
// try and capture intially loaded modules in the first tick
// since, ideally, the tracer (and this module) should be loaded first,
// this should capture any first-tick dependencies
queueMicrotask(() => { initialLoad = false })
}
function isDependency (filename, request) {
const isDependencyWithSlash = isDependencyWithSeparator(filename, request, '/')
if (isDependencyWithSlash && process.platform === 'win32') {
return isDependencyWithSeparator(filename, request, path.sep)
}
return isDependencyWithSlash
}
function isDependencyWithSeparator (filename, request, sep) {
return request.indexOf(`..${sep}`) !== 0 &&
request.indexOf(`.${sep}`) !== 0 &&
request.indexOf(sep) !== 0 &&
request.indexOf(`:${sep}`) !== 1
}
function stop () {
config = null
application = null
host = null
detectedDependencyKeys.clear()
savedDependenciesToSend.clear()
detectedDependencyVersions.clear()
if (moduleLoadStartChannel.hasSubscribers) {
moduleLoadStartChannel.unsubscribe(onModuleLoad)
}
}
module.exports = { start, stop }