@multiplayer-app/session-recorder-common 1.3.37 → 2.0.17-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/dist/esm/tsconfig.esm.tsbuildinfo +0 -1
- package/dist/esnext/tsconfig.esnext.tsbuildinfo +0 -1
- package/docs/img/header-js.png +0 -0
- package/eslint.config.js +0 -226
- package/src/SessionRecorderIdGenerator.ts +0 -70
- package/src/SessionRecorderTraceIdRatioBasedSampler.ts +0 -89
- package/src/constants/constants.base.ts +0 -111
- package/src/constants/constants.browser.ts +0 -1
- package/src/constants/constants.node.ts +0 -5
- package/src/exporters/SessionRecorderBrowserTraceExporter.ts +0 -196
- package/src/exporters/SessionRecorderGrpcLogsExporter.ts +0 -52
- package/src/exporters/SessionRecorderGrpcTraceExporter.ts +0 -50
- package/src/exporters/SessionRecorderHttpLogsExporter.ts +0 -58
- package/src/exporters/SessionRecorderHttpTraceExporter.ts +0 -68
- package/src/exporters/SessionRecorderLogsExporterWrapper.ts +0 -36
- package/src/exporters/SessionRecorderTraceExporterWrapper.ts +0 -36
- package/src/exporters/index-browser.ts +0 -1
- package/src/exporters/index-node.ts +0 -6
- package/src/exporters/index.ts +0 -7
- package/src/index-browser.ts +0 -6
- package/src/index-node.ts +0 -7
- package/src/index.ts +0 -7
- package/src/instrumentations/SessionRecorderHttpInstrumentationHooksNode.ts +0 -356
- package/src/instrumentations/index-node.ts +0 -1
- package/src/sdk/capture-exception.ts +0 -102
- package/src/sdk/id-generator.ts +0 -17
- package/src/sdk/index.ts +0 -8
- package/src/sdk/is-gzip.ts +0 -7
- package/src/sdk/mask.ts +0 -161
- package/src/sdk/save-continuous-deb-session.ts +0 -28
- package/src/sdk/schemify.ts +0 -57
- package/src/sdk/set-attribute.ts +0 -210
- package/src/sdk/set-resource-attributes.ts +0 -9
- package/src/type/crash-buffer.ts +0 -64
- package/src/type/index.ts +0 -4
- package/src/type/session-type.enum.ts +0 -20
- package/src/type/session.ts +0 -84
- package/src/type/user-type.enum.ts +0 -5
- package/tsconfig.base.es5.json +0 -8
- package/tsconfig.base.esm.json +0 -7
- package/tsconfig.base.esnext.json +0 -10
- package/tsconfig.base.json +0 -38
- package/tsconfig.esm.json +0 -12
- package/tsconfig.esnext.json +0 -12
- package/tsconfig.json +0 -25
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
|
2
|
-
import {
|
|
3
|
-
MULTIPLAYER_TRACE_DEBUG_PREFIX,
|
|
4
|
-
MULTIPLAYER_TRACE_CONTINUOUS_DEBUG_PREFIX,
|
|
5
|
-
} from '../constants/constants.base'
|
|
6
|
-
|
|
7
|
-
export interface SessionRecorderTraceExporterWrapperConfig {
|
|
8
|
-
exporter: SpanExporter
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class SessionRecorderTraceExporterWrapper implements SpanExporter {
|
|
12
|
-
private readonly exporter: SpanExporter
|
|
13
|
-
|
|
14
|
-
constructor(config: SessionRecorderTraceExporterWrapperConfig) {
|
|
15
|
-
this.exporter = config.exporter
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export(spans: ReadableSpan[], resultCallback: (result: { code: number }) => void): void {
|
|
19
|
-
const filteredSpans = spans.filter(span => {
|
|
20
|
-
const traceId = span.spanContext().traceId
|
|
21
|
-
return !traceId.startsWith(MULTIPLAYER_TRACE_DEBUG_PREFIX) &&
|
|
22
|
-
!traceId.startsWith(MULTIPLAYER_TRACE_CONTINUOUS_DEBUG_PREFIX)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
if (filteredSpans.length === 0) {
|
|
26
|
-
resultCallback({ code: 0 })
|
|
27
|
-
return
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
this.exporter.export(filteredSpans, resultCallback)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
shutdown(): Promise<void> {
|
|
34
|
-
return this.exporter.shutdown()
|
|
35
|
-
}
|
|
36
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './SessionRecorderBrowserTraceExporter'
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export * from './SessionRecorderHttpTraceExporter'
|
|
2
|
-
export * from './SessionRecorderHttpLogsExporter'
|
|
3
|
-
export * from './SessionRecorderGrpcTraceExporter'
|
|
4
|
-
export * from './SessionRecorderGrpcLogsExporter'
|
|
5
|
-
export * from './SessionRecorderTraceExporterWrapper'
|
|
6
|
-
export * from './SessionRecorderLogsExporterWrapper'
|
package/src/exporters/index.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export * from './SessionRecorderHttpTraceExporter'
|
|
2
|
-
export * from './SessionRecorderHttpLogsExporter'
|
|
3
|
-
export * from './SessionRecorderGrpcTraceExporter'
|
|
4
|
-
export * from './SessionRecorderGrpcLogsExporter'
|
|
5
|
-
export * from './SessionRecorderBrowserTraceExporter'
|
|
6
|
-
export * from './SessionRecorderTraceExporterWrapper'
|
|
7
|
-
export * from './SessionRecorderLogsExporterWrapper'
|
package/src/index-browser.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export * from './constants/constants.browser'
|
|
2
|
-
export { SessionRecorderIdGenerator } from './SessionRecorderIdGenerator'
|
|
3
|
-
export { SessionRecorderTraceIdRatioBasedSampler } from './SessionRecorderTraceIdRatioBasedSampler'
|
|
4
|
-
export * as SessionRecorderSdk from './sdk'
|
|
5
|
-
export * from './type'
|
|
6
|
-
export * from './exporters/index-browser'
|
package/src/index-node.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export * from './constants/constants.node'
|
|
2
|
-
export { SessionRecorderHttpInstrumentationHooksNode } from './instrumentations/SessionRecorderHttpInstrumentationHooksNode'
|
|
3
|
-
export { SessionRecorderIdGenerator } from './SessionRecorderIdGenerator'
|
|
4
|
-
export { SessionRecorderTraceIdRatioBasedSampler } from './SessionRecorderTraceIdRatioBasedSampler'
|
|
5
|
-
export * as SessionRecorderSdk from './sdk'
|
|
6
|
-
export * from './type'
|
|
7
|
-
export * from './exporters/index-node'
|
package/src/index.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export * from './constants/constants.node'
|
|
2
|
-
export { SessionRecorderIdGenerator } from './SessionRecorderIdGenerator'
|
|
3
|
-
export { SessionRecorderTraceIdRatioBasedSampler } from './SessionRecorderTraceIdRatioBasedSampler'
|
|
4
|
-
export * as SessionRecorderSdk from './sdk'
|
|
5
|
-
export * from './type'
|
|
6
|
-
export * from './exporters'
|
|
7
|
-
export * from './instrumentations/index-node'
|
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
IncomingMessage,
|
|
3
|
-
ServerResponse,
|
|
4
|
-
ClientRequest,
|
|
5
|
-
} from 'http'
|
|
6
|
-
import * as zlib from 'zlib'
|
|
7
|
-
import type { Span } from '@opentelemetry/api'
|
|
8
|
-
import {
|
|
9
|
-
ATTR_MULTIPLAYER_HTTP_REQUEST_BODY,
|
|
10
|
-
ATTR_MULTIPLAYER_HTTP_REQUEST_HEADERS,
|
|
11
|
-
ATTR_MULTIPLAYER_HTTP_RESPONSE_BODY,
|
|
12
|
-
ATTR_MULTIPLAYER_HTTP_RESPONSE_HEADERS,
|
|
13
|
-
MULTIPLAYER_MAX_HTTP_REQUEST_RESPONSE_SIZE,
|
|
14
|
-
ATTR_MULTIPLAYER_HTTP_RESPONSE_BODY_ENCODING,
|
|
15
|
-
MULTIPLAYER_TRACE_DEBUG_PREFIX,
|
|
16
|
-
} from '../constants/constants.node'
|
|
17
|
-
import {
|
|
18
|
-
mask,
|
|
19
|
-
isGzip,
|
|
20
|
-
} from '../sdk'
|
|
21
|
-
import {
|
|
22
|
-
sensitiveFields,
|
|
23
|
-
sensitiveHeaders,
|
|
24
|
-
} from '../sdk/mask'
|
|
25
|
-
|
|
26
|
-
interface HttpResponseHookOptions {
|
|
27
|
-
maxPayloadSizeBytes?: number
|
|
28
|
-
uncompressPayload?: boolean
|
|
29
|
-
|
|
30
|
-
captureHeaders?: boolean
|
|
31
|
-
captureBody?: boolean
|
|
32
|
-
|
|
33
|
-
isMaskBodyEnabled?: boolean
|
|
34
|
-
isMaskHeadersEnabled?: boolean
|
|
35
|
-
|
|
36
|
-
maskBody?: (arg: any, span: Span) => any
|
|
37
|
-
maskHeaders?: (arg: any, span: Span) => any
|
|
38
|
-
|
|
39
|
-
maskBodyFieldsList?: string[]
|
|
40
|
-
maskHeadersList?: string[]
|
|
41
|
-
|
|
42
|
-
headersToInclude?: string[]
|
|
43
|
-
headersToExclude?: string[]
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface HttpRequestHookOptions {
|
|
47
|
-
maxPayloadSizeBytes?: number
|
|
48
|
-
|
|
49
|
-
captureHeaders?: boolean
|
|
50
|
-
captureBody?: boolean
|
|
51
|
-
|
|
52
|
-
isMaskBodyEnabled?: boolean
|
|
53
|
-
isMaskHeadersEnabled?: boolean
|
|
54
|
-
|
|
55
|
-
maskBody?: (arg: any, span: Span) => any
|
|
56
|
-
maskHeaders?: (arg: any, span: Span) => any
|
|
57
|
-
|
|
58
|
-
maskBodyFieldsList?: string[]
|
|
59
|
-
maskHeadersList?: string[]
|
|
60
|
-
|
|
61
|
-
headersToInclude?: string[]
|
|
62
|
-
headersToExclude?: string[]
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const setDefaultOptions = (
|
|
66
|
-
options: HttpResponseHookOptions | HttpResponseHookOptions,
|
|
67
|
-
): Omit<HttpResponseHookOptions & HttpResponseHookOptions, 'maskBody' | 'maskHeaders'>
|
|
68
|
-
& {
|
|
69
|
-
maskBody: (arg: any, span: Span) => any
|
|
70
|
-
maskHeaders: (arg: any, span: Span) => any
|
|
71
|
-
captureHeaders: boolean,
|
|
72
|
-
captureBody: boolean,
|
|
73
|
-
isMaskBodyEnabled: boolean
|
|
74
|
-
isMaskHeadersEnabled: boolean
|
|
75
|
-
uncompressPayload: boolean,
|
|
76
|
-
maxPayloadSizeBytes: number
|
|
77
|
-
} => {
|
|
78
|
-
options.captureHeaders = 'captureHeaders' in options
|
|
79
|
-
? options.captureHeaders
|
|
80
|
-
: true
|
|
81
|
-
options.captureBody = 'captureBody' in options
|
|
82
|
-
? options.captureBody
|
|
83
|
-
: true
|
|
84
|
-
options.isMaskBodyEnabled = 'isMaskBodyEnabled' in options
|
|
85
|
-
? options.isMaskBodyEnabled
|
|
86
|
-
: true
|
|
87
|
-
options.isMaskHeadersEnabled = 'isMaskHeadersEnabled' in options
|
|
88
|
-
? options.isMaskHeadersEnabled
|
|
89
|
-
: true
|
|
90
|
-
options.uncompressPayload = 'uncompressPayload' in options
|
|
91
|
-
? options.uncompressPayload
|
|
92
|
-
: true
|
|
93
|
-
options.maskBody = options.maskBody || mask([
|
|
94
|
-
...(
|
|
95
|
-
Array.isArray(options.maskBodyFieldsList)
|
|
96
|
-
? options.maskBodyFieldsList
|
|
97
|
-
: sensitiveFields
|
|
98
|
-
),
|
|
99
|
-
...(
|
|
100
|
-
Array.isArray(options.maskHeadersList)
|
|
101
|
-
? options.maskHeadersList
|
|
102
|
-
: sensitiveHeaders
|
|
103
|
-
),
|
|
104
|
-
])
|
|
105
|
-
options.maskHeaders = options.maskHeaders || mask([
|
|
106
|
-
...(
|
|
107
|
-
Array.isArray(options.maskBodyFieldsList)
|
|
108
|
-
? options.maskBodyFieldsList
|
|
109
|
-
: sensitiveFields
|
|
110
|
-
),
|
|
111
|
-
...(
|
|
112
|
-
Array.isArray(options.maskHeadersList)
|
|
113
|
-
? options.maskHeadersList
|
|
114
|
-
: sensitiveHeaders
|
|
115
|
-
),
|
|
116
|
-
])
|
|
117
|
-
options.maxPayloadSizeBytes = options.maxPayloadSizeBytes || MULTIPLAYER_MAX_HTTP_REQUEST_RESPONSE_SIZE
|
|
118
|
-
|
|
119
|
-
return options as Omit<HttpResponseHookOptions & HttpResponseHookOptions, 'maskBody' | 'maskHeaders'>
|
|
120
|
-
& {
|
|
121
|
-
maskBody: (arg: any, span: Span) => any
|
|
122
|
-
maskHeaders: (arg: any, span: Span) => any
|
|
123
|
-
captureHeaders: boolean,
|
|
124
|
-
captureBody: boolean,
|
|
125
|
-
isMaskBodyEnabled: boolean,
|
|
126
|
-
isMaskHeadersEnabled: boolean,
|
|
127
|
-
uncompressPayload: boolean,
|
|
128
|
-
maxPayloadSizeBytes: number
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export const SessionRecorderHttpInstrumentationHooksNode = {
|
|
133
|
-
responseHook: (options: HttpResponseHookOptions = {}) =>
|
|
134
|
-
(span: Span, response: IncomingMessage | ServerResponse) => {
|
|
135
|
-
try {
|
|
136
|
-
const _options = setDefaultOptions(options)
|
|
137
|
-
|
|
138
|
-
if (!_options.captureBody && !_options.captureHeaders) {
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const _response = response as ServerResponse
|
|
143
|
-
const traceId = span.spanContext().traceId
|
|
144
|
-
|
|
145
|
-
if (_response.setHeader) {
|
|
146
|
-
_response.setHeader('X-Trace-Id', traceId)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const [oldWrite, oldEnd] = [_response.write, _response.end]
|
|
150
|
-
|
|
151
|
-
const chunks: Buffer[] = []
|
|
152
|
-
|
|
153
|
-
if (_options.captureBody) {
|
|
154
|
-
(_response.write as unknown) = function (...restArgs: any[]) {
|
|
155
|
-
chunks.push(Buffer.from(restArgs[0]))
|
|
156
|
-
|
|
157
|
-
// @ts-ignore
|
|
158
|
-
oldWrite.apply(_response, restArgs)
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
// @ts-ignore
|
|
164
|
-
_response.end = async function (...restArgs) {
|
|
165
|
-
if (_options.captureBody && restArgs[0]) {
|
|
166
|
-
chunks.push(Buffer.from(restArgs[0]))
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const responseBuffer = Buffer.concat(chunks)
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
_options.captureBody
|
|
173
|
-
&& responseBuffer.byteLength > 0
|
|
174
|
-
&& responseBuffer.byteLength < _options.maxPayloadSizeBytes
|
|
175
|
-
) {
|
|
176
|
-
let responseBody: string
|
|
177
|
-
let skipResponseBodyModification = false
|
|
178
|
-
|
|
179
|
-
if (isGzip(responseBuffer)) {
|
|
180
|
-
if (_options.uncompressPayload) {
|
|
181
|
-
const dezippedBuffer = await new Promise((resolve) => zlib
|
|
182
|
-
.gunzip(responseBuffer, function (err, dezipped) {
|
|
183
|
-
if (err) {
|
|
184
|
-
return resolve(Buffer.from(''))
|
|
185
|
-
} else {
|
|
186
|
-
return resolve(dezipped)
|
|
187
|
-
}
|
|
188
|
-
})) as Buffer
|
|
189
|
-
responseBody = dezippedBuffer.toString('utf-8')
|
|
190
|
-
} else {
|
|
191
|
-
span.setAttribute(
|
|
192
|
-
ATTR_MULTIPLAYER_HTTP_RESPONSE_BODY_ENCODING,
|
|
193
|
-
'gzip',
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
skipResponseBodyModification = true
|
|
197
|
-
responseBody = responseBuffer.toString('hex')
|
|
198
|
-
}
|
|
199
|
-
} else {
|
|
200
|
-
responseBody = responseBuffer.toString('utf-8')
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (responseBody.length) {
|
|
204
|
-
responseBody = JSON.parse(JSON.stringify(responseBody))
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (!skipResponseBodyModification) {
|
|
208
|
-
if (
|
|
209
|
-
traceId.startsWith(MULTIPLAYER_TRACE_DEBUG_PREFIX)
|
|
210
|
-
&& _options.isMaskBodyEnabled
|
|
211
|
-
) {
|
|
212
|
-
responseBody = _options.maskBody(responseBody, span)
|
|
213
|
-
} else if (typeof responseBody !== 'string') {
|
|
214
|
-
responseBody = JSON.stringify(responseBody)
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (responseBody.length) {
|
|
219
|
-
span.setAttribute(
|
|
220
|
-
ATTR_MULTIPLAYER_HTTP_RESPONSE_BODY,
|
|
221
|
-
responseBody,
|
|
222
|
-
)
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (_options.captureHeaders) {
|
|
227
|
-
let _headers = JSON.parse(JSON.stringify(_response.getHeaders() || {}))
|
|
228
|
-
if (_options.isMaskHeadersEnabled) {
|
|
229
|
-
_headers = _options.maskHeaders(_headers, span)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (_options.headersToInclude) {
|
|
233
|
-
const filteredHeaders: any = {}
|
|
234
|
-
for (const headerName of _options.headersToInclude) {
|
|
235
|
-
filteredHeaders[headerName] = _headers[headerName]
|
|
236
|
-
}
|
|
237
|
-
_headers = filteredHeaders
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (_options.headersToExclude?.length) {
|
|
241
|
-
for (const headerName of _options.headersToExclude) {
|
|
242
|
-
delete _headers[headerName]
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const stringifiedHeaders = JSON.stringify(_headers)
|
|
247
|
-
|
|
248
|
-
if (stringifiedHeaders?.length) {
|
|
249
|
-
span.setAttribute(
|
|
250
|
-
ATTR_MULTIPLAYER_HTTP_RESPONSE_HEADERS,
|
|
251
|
-
stringifiedHeaders,
|
|
252
|
-
)
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
// @ts-ignore
|
|
258
|
-
return oldEnd.apply(_response, restArgs)
|
|
259
|
-
}
|
|
260
|
-
} catch (error) {
|
|
261
|
-
// eslint-disable-next-line
|
|
262
|
-
console.error('An error occured in multiplayer otlp http responseHook', error)
|
|
263
|
-
}
|
|
264
|
-
},
|
|
265
|
-
requestHook: (options: HttpRequestHookOptions = {}) =>
|
|
266
|
-
(span: Span, request: ClientRequest | IncomingMessage) => {
|
|
267
|
-
try {
|
|
268
|
-
const _options = setDefaultOptions(options)
|
|
269
|
-
|
|
270
|
-
if (!_options.captureBody && !_options.captureHeaders) {
|
|
271
|
-
return
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const traceId = span.spanContext().traceId
|
|
275
|
-
const _request = request as IncomingMessage
|
|
276
|
-
|
|
277
|
-
if (_options.captureHeaders) {
|
|
278
|
-
let _headers = JSON.parse(JSON.stringify(_request.headers || {}))
|
|
279
|
-
|
|
280
|
-
if (_options.isMaskHeadersEnabled) {
|
|
281
|
-
_headers = _options.maskHeaders(_headers, span)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (_options.headersToInclude) {
|
|
285
|
-
const filteredHeaders: any = {}
|
|
286
|
-
for (const headerName of _options.headersToInclude) {
|
|
287
|
-
filteredHeaders[headerName] = _headers[headerName]
|
|
288
|
-
}
|
|
289
|
-
_headers = filteredHeaders
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (_options.headersToExclude?.length) {
|
|
293
|
-
for (const headerName of _options.headersToExclude) {
|
|
294
|
-
delete _headers[headerName]
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
span.setAttribute(
|
|
299
|
-
ATTR_MULTIPLAYER_HTTP_REQUEST_HEADERS,
|
|
300
|
-
JSON.stringify(_headers),
|
|
301
|
-
)
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const contentType = _request?.headers?.['content-type']
|
|
305
|
-
if (
|
|
306
|
-
_options.captureBody
|
|
307
|
-
&& contentType?.includes('application/json')
|
|
308
|
-
) {
|
|
309
|
-
let body = ''
|
|
310
|
-
_request.on('data', (chunk) => {
|
|
311
|
-
body += chunk
|
|
312
|
-
})
|
|
313
|
-
_request.on('end', () => {
|
|
314
|
-
try {
|
|
315
|
-
const requestBodySizeBytes = Buffer.byteLength(body, 'utf8')
|
|
316
|
-
|
|
317
|
-
if (
|
|
318
|
-
requestBodySizeBytes === 0
|
|
319
|
-
|| requestBodySizeBytes > _options.maxPayloadSizeBytes
|
|
320
|
-
) {
|
|
321
|
-
return
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
let requestBody = body
|
|
325
|
-
if (!requestBody) return
|
|
326
|
-
|
|
327
|
-
requestBody = JSON.parse(JSON.stringify(requestBody))
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
traceId.startsWith(MULTIPLAYER_TRACE_DEBUG_PREFIX)
|
|
331
|
-
&& _options.isMaskBodyEnabled
|
|
332
|
-
) {
|
|
333
|
-
requestBody = _options.maskBody(requestBody, span)
|
|
334
|
-
} else if (typeof requestBody !== 'string') {
|
|
335
|
-
requestBody = JSON.stringify(requestBody)
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (requestBody?.length) {
|
|
339
|
-
span.setAttribute(
|
|
340
|
-
ATTR_MULTIPLAYER_HTTP_REQUEST_BODY,
|
|
341
|
-
requestBody,
|
|
342
|
-
)
|
|
343
|
-
}
|
|
344
|
-
} catch (err) {
|
|
345
|
-
// eslint-disable-next-line
|
|
346
|
-
console.error('[MULTIPLAYER-HTTP-REQ-HOOK] An error occured in multiplayer otlp http requestHook', err)
|
|
347
|
-
}
|
|
348
|
-
})
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
} catch (error) {
|
|
352
|
-
// eslint-disable-next-line
|
|
353
|
-
console.error('An error occured in multiplayer otlp http requestHook', error)
|
|
354
|
-
}
|
|
355
|
-
},
|
|
356
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './SessionRecorderHttpInstrumentationHooksNode'
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { context, trace, SpanStatusCode } from '@opentelemetry/api'
|
|
2
|
-
import {
|
|
3
|
-
ATTR_EXCEPTION_MESSAGE,
|
|
4
|
-
ATTR_EXCEPTION_STACKTRACE,
|
|
5
|
-
ATTR_EXCEPTION_TYPE,
|
|
6
|
-
} from '@opentelemetry/semantic-conventions'
|
|
7
|
-
import { getResourceAttributes } from './set-resource-attributes'
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @description Add error to current span
|
|
11
|
-
* @param {Error} error
|
|
12
|
-
* @returns {void}
|
|
13
|
-
*/
|
|
14
|
-
export const captureException = (error: Error, errorInfo?: Record<string, any>) => {
|
|
15
|
-
if (!error || !shouldCaptureException(error)) {
|
|
16
|
-
return
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const activeContext = context.active()
|
|
20
|
-
|
|
21
|
-
let span = trace.getSpan(activeContext)
|
|
22
|
-
let isNewSpan = false
|
|
23
|
-
|
|
24
|
-
if (!span || !span.isRecording()) {
|
|
25
|
-
span = trace.getTracer('exception').startSpan(error.name || 'Error', {
|
|
26
|
-
attributes: {
|
|
27
|
-
[ATTR_EXCEPTION_MESSAGE]: error.message,
|
|
28
|
-
[ATTR_EXCEPTION_STACKTRACE]: error.stack,
|
|
29
|
-
[ATTR_EXCEPTION_TYPE]: error.name,
|
|
30
|
-
...getResourceAttributes(),
|
|
31
|
-
},
|
|
32
|
-
})
|
|
33
|
-
trace.setSpan(activeContext, span)
|
|
34
|
-
isNewSpan = true
|
|
35
|
-
} else {
|
|
36
|
-
span.setAttributes({
|
|
37
|
-
[ATTR_EXCEPTION_MESSAGE]: error.message,
|
|
38
|
-
[ATTR_EXCEPTION_STACKTRACE]: error.stack,
|
|
39
|
-
[ATTR_EXCEPTION_TYPE]: error.name,
|
|
40
|
-
})
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (errorInfo) {
|
|
44
|
-
Object.entries(errorInfo).forEach(([key, value]) => {
|
|
45
|
-
span.setAttribute(`error_info.${key}`, value)
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
span.recordException(error)
|
|
50
|
-
span.setStatus({
|
|
51
|
-
code: SpanStatusCode.ERROR,
|
|
52
|
-
message: error.message,
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
if (isNewSpan) {
|
|
56
|
-
span.end()
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Best-effort deduplication of exceptions that fire multiple times
|
|
62
|
-
* (e.g. framework handler + global handlers) within a short time window.
|
|
63
|
-
*/
|
|
64
|
-
|
|
65
|
-
const exceptionDedupeWindowMs = 2000
|
|
66
|
-
const recentExceptionFingerprints = new Map<string, number>()
|
|
67
|
-
|
|
68
|
-
export const shouldCaptureException = (error: Error, _errorInfo?: Record<string, any>): boolean => {
|
|
69
|
-
if (!error) return false
|
|
70
|
-
|
|
71
|
-
const now = Date.now()
|
|
72
|
-
|
|
73
|
-
// Build a fingerprint that is stable enough across repeated emissions
|
|
74
|
-
// but not so broad that different errors collapse into one.
|
|
75
|
-
const keyParts: string[] = []
|
|
76
|
-
keyParts.push(error.name || 'Error')
|
|
77
|
-
keyParts.push(error.message || '')
|
|
78
|
-
|
|
79
|
-
// First stack line tends to include file/line where it originated.
|
|
80
|
-
if (typeof error.stack === 'string') {
|
|
81
|
-
const firstFrame = error.stack.split('\n')[1] || ''
|
|
82
|
-
keyParts.push(firstFrame.trim())
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const fingerprint = keyParts.join('|').slice(0, 500)
|
|
86
|
-
|
|
87
|
-
const lastSeen = recentExceptionFingerprints.get(fingerprint)
|
|
88
|
-
if (lastSeen && now - lastSeen < exceptionDedupeWindowMs) {
|
|
89
|
-
return false
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
recentExceptionFingerprints.set(fingerprint, now)
|
|
93
|
-
|
|
94
|
-
// Cheap cleanup of old entries to avoid unbounded growth.
|
|
95
|
-
for (const [key, ts] of recentExceptionFingerprints) {
|
|
96
|
-
if (now - ts > exceptionDedupeWindowMs * 5) {
|
|
97
|
-
recentExceptionFingerprints.delete(key)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return true
|
|
102
|
-
}
|
package/src/sdk/id-generator.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
const SHARED_CHAR_CODES_ARRAY = Array(32)
|
|
2
|
-
|
|
3
|
-
export const getIdGenerator = (bytes: number) => {
|
|
4
|
-
return function generateId() {
|
|
5
|
-
for (let i = 0; i < bytes * 2; i++) {
|
|
6
|
-
SHARED_CHAR_CODES_ARRAY[i] = Math.floor(Math.random() * 16) + 48
|
|
7
|
-
// valid hex characters in the range 48-57 and 97-102
|
|
8
|
-
if (SHARED_CHAR_CODES_ARRAY[i] >= 58) {
|
|
9
|
-
SHARED_CHAR_CODES_ARRAY[i] += 39
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
return String.fromCharCode.apply(
|
|
13
|
-
null,
|
|
14
|
-
SHARED_CHAR_CODES_ARRAY.slice(0, bytes * 2),
|
|
15
|
-
)
|
|
16
|
-
}
|
|
17
|
-
}
|
package/src/sdk/index.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export { default as mask, sensitiveFields, sensitiveHeaders } from './mask'
|
|
2
|
-
export { default as schemify } from './schemify'
|
|
3
|
-
export * from './is-gzip'
|
|
4
|
-
export * from './id-generator'
|
|
5
|
-
export * from './capture-exception'
|
|
6
|
-
export * from './set-attribute'
|
|
7
|
-
export * from './save-continuous-deb-session'
|
|
8
|
-
export * from './set-resource-attributes'
|