@sentry/node 8.0.0-rc.3 → 8.1.0
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/cjs/cron/common.js +56 -0
- package/cjs/cron/common.js.map +1 -0
- package/cjs/cron/cron.js +112 -0
- package/cjs/cron/cron.js.map +1 -0
- package/cjs/cron/index.js +15 -0
- package/cjs/cron/index.js.map +1 -0
- package/cjs/cron/node-cron.js +72 -0
- package/cjs/cron/node-cron.js.map +1 -0
- package/cjs/cron/node-schedule.js +69 -0
- package/cjs/cron/node-schedule.js.map +1 -0
- package/cjs/debug-build.js +11 -0
- package/cjs/debug-build.js.map +1 -0
- package/cjs/index.js +85 -4005
- package/cjs/index.js.map +1 -1
- package/cjs/integrations/anr/index.js +211 -0
- package/cjs/integrations/anr/index.js.map +1 -0
- package/cjs/integrations/console.js +40 -0
- package/cjs/integrations/console.js.map +1 -0
- package/cjs/integrations/context.js +435 -0
- package/cjs/integrations/context.js.map +1 -0
- package/cjs/integrations/contextlines.js +137 -0
- package/cjs/integrations/contextlines.js.map +1 -0
- package/cjs/integrations/http.js +161 -0
- package/cjs/integrations/http.js.map +1 -0
- package/cjs/integrations/local-variables/common.js +87 -0
- package/cjs/integrations/local-variables/common.js.map +1 -0
- package/cjs/integrations/local-variables/index.js +12 -0
- package/cjs/integrations/local-variables/index.js.map +1 -0
- package/cjs/integrations/local-variables/local-variables-async.js +149 -0
- package/cjs/integrations/local-variables/local-variables-async.js.map +1 -0
- package/cjs/integrations/local-variables/local-variables-sync.js +376 -0
- package/cjs/integrations/local-variables/local-variables-sync.js.map +1 -0
- package/cjs/integrations/modules.js +99 -0
- package/cjs/integrations/modules.js.map +1 -0
- package/cjs/integrations/node-fetch.js +81 -0
- package/cjs/integrations/node-fetch.js.map +1 -0
- package/cjs/integrations/onuncaughtexception.js +135 -0
- package/cjs/integrations/onuncaughtexception.js.map +1 -0
- package/cjs/integrations/onunhandledrejection.js +90 -0
- package/cjs/integrations/onunhandledrejection.js.map +1 -0
- package/cjs/integrations/spotlight.js +109 -0
- package/cjs/integrations/spotlight.js.map +1 -0
- package/cjs/integrations/tracing/connect.js +41 -0
- package/cjs/integrations/tracing/connect.js.map +1 -0
- package/cjs/integrations/tracing/express.js +127 -0
- package/cjs/integrations/tracing/express.js.map +1 -0
- package/cjs/integrations/tracing/fastify.js +84 -0
- package/cjs/integrations/tracing/fastify.js.map +1 -0
- package/cjs/integrations/tracing/graphql.js +32 -0
- package/cjs/integrations/tracing/graphql.js.map +1 -0
- package/cjs/integrations/tracing/hapi/index.js +106 -0
- package/cjs/integrations/tracing/hapi/index.js.map +1 -0
- package/cjs/integrations/tracing/index.js +43 -0
- package/cjs/integrations/tracing/index.js.map +1 -0
- package/cjs/integrations/tracing/koa.js +60 -0
- package/cjs/integrations/tracing/koa.js.map +1 -0
- package/cjs/integrations/tracing/mongo.js +31 -0
- package/cjs/integrations/tracing/mongo.js.map +1 -0
- package/cjs/integrations/tracing/mongoose.js +31 -0
- package/cjs/integrations/tracing/mongoose.js.map +1 -0
- package/cjs/integrations/tracing/mysql.js +24 -0
- package/cjs/integrations/tracing/mysql.js.map +1 -0
- package/cjs/integrations/tracing/mysql2.js +31 -0
- package/cjs/integrations/tracing/mysql2.js.map +1 -0
- package/cjs/integrations/tracing/nest.js +69 -0
- package/cjs/integrations/tracing/nest.js.map +1 -0
- package/cjs/integrations/tracing/postgres.js +32 -0
- package/cjs/integrations/tracing/postgres.js.map +1 -0
- package/cjs/integrations/tracing/prisma.js +48 -0
- package/cjs/integrations/tracing/prisma.js.map +1 -0
- package/cjs/integrations/tracing/redis.js +29 -0
- package/cjs/integrations/tracing/redis.js.map +1 -0
- package/cjs/nodeVersion.js +10 -0
- package/cjs/nodeVersion.js.map +1 -0
- package/cjs/otel/contextManager.js +16 -0
- package/cjs/otel/contextManager.js.map +1 -0
- package/cjs/proxy/base.js +127 -0
- package/cjs/proxy/base.js.map +1 -0
- package/cjs/proxy/index.js +178 -0
- package/cjs/proxy/index.js.map +1 -0
- package/cjs/proxy/parse-proxy-response.js +104 -0
- package/cjs/proxy/parse-proxy-response.js.map +1 -0
- package/cjs/sdk/api.js +45 -0
- package/cjs/sdk/api.js.map +1 -0
- package/cjs/sdk/client.js +63 -0
- package/cjs/sdk/client.js.map +1 -0
- package/cjs/sdk/init.js +318 -0
- package/cjs/sdk/init.js.map +1 -0
- package/cjs/sdk/initOtel.js +58 -0
- package/cjs/sdk/initOtel.js.map +1 -0
- package/cjs/sdk/scope.js +18 -0
- package/cjs/sdk/scope.js.map +1 -0
- package/cjs/transports/http.js +163 -0
- package/cjs/transports/http.js.map +1 -0
- package/cjs/utils/addOriginToSpan.js +11 -0
- package/cjs/utils/addOriginToSpan.js.map +1 -0
- package/cjs/utils/errorhandling.js +44 -0
- package/cjs/utils/errorhandling.js.map +1 -0
- package/cjs/utils/getRequestUrl.js +18 -0
- package/cjs/utils/getRequestUrl.js.map +1 -0
- package/cjs/utils/module.js +62 -0
- package/cjs/utils/module.js.map +1 -0
- package/esm/cron/common.js +54 -0
- package/esm/cron/common.js.map +1 -0
- package/esm/cron/cron.js +110 -0
- package/esm/cron/cron.js.map +1 -0
- package/esm/cron/index.js +13 -0
- package/esm/cron/index.js.map +1 -0
- package/esm/cron/node-cron.js +67 -0
- package/esm/cron/node-cron.js.map +1 -0
- package/esm/cron/node-schedule.js +64 -0
- package/esm/cron/node-schedule.js.map +1 -0
- package/esm/debug-build.js +9 -0
- package/esm/debug-build.js.map +1 -0
- package/esm/index.js +35 -3961
- package/esm/index.js.map +1 -1
- package/esm/integrations/anr/index.js +204 -0
- package/esm/integrations/anr/index.js.map +1 -0
- package/esm/integrations/anr/worker-script.js +2 -2
- package/esm/integrations/console.js +38 -0
- package/esm/integrations/console.js.map +1 -0
- package/esm/integrations/context.js +427 -0
- package/esm/integrations/context.js.map +1 -0
- package/esm/integrations/contextlines.js +131 -0
- package/esm/integrations/contextlines.js.map +1 -0
- package/esm/integrations/http.js +156 -0
- package/esm/integrations/http.js.map +1 -0
- package/esm/integrations/local-variables/common.js +81 -0
- package/esm/integrations/local-variables/common.js.map +1 -0
- package/esm/integrations/local-variables/index.js +10 -0
- package/esm/integrations/local-variables/index.js.map +1 -0
- package/esm/integrations/local-variables/local-variables-async.js +143 -0
- package/esm/integrations/local-variables/local-variables-async.js.map +1 -0
- package/esm/integrations/local-variables/local-variables-sync.js +370 -0
- package/esm/integrations/local-variables/local-variables-sync.js.map +1 -0
- package/esm/integrations/local-variables/worker-script.js +1 -1
- package/esm/integrations/modules.js +97 -0
- package/esm/integrations/modules.js.map +1 -0
- package/esm/integrations/node-fetch.js +79 -0
- package/esm/integrations/node-fetch.js.map +1 -0
- package/esm/integrations/onuncaughtexception.js +132 -0
- package/esm/integrations/onuncaughtexception.js.map +1 -0
- package/esm/integrations/onunhandledrejection.js +87 -0
- package/esm/integrations/onunhandledrejection.js.map +1 -0
- package/esm/integrations/spotlight.js +106 -0
- package/esm/integrations/spotlight.js.map +1 -0
- package/esm/integrations/tracing/connect.js +38 -0
- package/esm/integrations/tracing/connect.js.map +1 -0
- package/esm/integrations/tracing/express.js +120 -0
- package/esm/integrations/tracing/express.js.map +1 -0
- package/esm/integrations/tracing/fastify.js +78 -0
- package/esm/integrations/tracing/fastify.js.map +1 -0
- package/esm/integrations/tracing/graphql.js +30 -0
- package/esm/integrations/tracing/graphql.js.map +1 -0
- package/esm/integrations/tracing/hapi/index.js +99 -0
- package/esm/integrations/tracing/hapi/index.js.map +1 -0
- package/esm/integrations/tracing/index.js +41 -0
- package/esm/integrations/tracing/index.js.map +1 -0
- package/esm/integrations/tracing/koa.js +57 -0
- package/esm/integrations/tracing/koa.js.map +1 -0
- package/esm/integrations/tracing/mongo.js +29 -0
- package/esm/integrations/tracing/mongo.js.map +1 -0
- package/esm/integrations/tracing/mongoose.js +29 -0
- package/esm/integrations/tracing/mongoose.js.map +1 -0
- package/esm/integrations/tracing/mysql.js +22 -0
- package/esm/integrations/tracing/mysql.js.map +1 -0
- package/esm/integrations/tracing/mysql2.js +29 -0
- package/esm/integrations/tracing/mysql2.js.map +1 -0
- package/esm/integrations/tracing/nest.js +63 -0
- package/esm/integrations/tracing/nest.js.map +1 -0
- package/esm/integrations/tracing/postgres.js +30 -0
- package/esm/integrations/tracing/postgres.js.map +1 -0
- package/esm/integrations/tracing/prisma.js +43 -0
- package/esm/integrations/tracing/prisma.js.map +1 -0
- package/esm/integrations/tracing/redis.js +27 -0
- package/esm/integrations/tracing/redis.js.map +1 -0
- package/esm/nodeVersion.js +7 -0
- package/esm/nodeVersion.js.map +1 -0
- package/esm/otel/contextManager.js +14 -0
- package/esm/otel/contextManager.js.map +1 -0
- package/esm/proxy/base.js +122 -0
- package/esm/proxy/base.js.map +1 -0
- package/esm/proxy/index.js +172 -0
- package/esm/proxy/index.js.map +1 -0
- package/esm/proxy/parse-proxy-response.js +102 -0
- package/esm/proxy/parse-proxy-response.js.map +1 -0
- package/esm/sdk/api.js +42 -0
- package/esm/sdk/api.js.map +1 -0
- package/esm/sdk/client.js +58 -0
- package/esm/sdk/client.js.map +1 -0
- package/esm/sdk/init.js +312 -0
- package/esm/sdk/init.js.map +1 -0
- package/esm/sdk/initOtel.js +55 -0
- package/esm/sdk/initOtel.js.map +1 -0
- package/esm/sdk/scope.js +16 -0
- package/esm/sdk/scope.js.map +1 -0
- package/esm/transports/http.js +158 -0
- package/esm/transports/http.js.map +1 -0
- package/esm/utils/addOriginToSpan.js +9 -0
- package/esm/utils/addOriginToSpan.js.map +1 -0
- package/esm/utils/errorhandling.js +42 -0
- package/esm/utils/errorhandling.js.map +1 -0
- package/esm/utils/getRequestUrl.js +16 -0
- package/esm/utils/getRequestUrl.js.map +1 -0
- package/esm/utils/module.js +60 -0
- package/esm/utils/module.js.map +1 -0
- package/package.json +16 -16
- package/types/index.d.ts +2 -2
- package/types/index.d.ts.map +1 -1
- package/types/integrations/tracing/hapi/index.d.ts.map +1 -1
- package/types/integrations/tracing/koa.d.ts.map +1 -1
- package/types/sdk/init.d.ts +9 -1
- package/types/sdk/init.d.ts.map +1 -1
- package/types-ts3.8/index.d.ts +2 -2
- package/types-ts3.8/sdk/init.d.ts +9 -1
package/esm/index.js
CHANGED
|
@@ -1,3963 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export {
|
|
6
|
-
|
|
7
|
-
export {
|
|
8
|
-
|
|
1
|
+
export { httpIntegration } from './integrations/http.js';
|
|
2
|
+
export { nativeNodeFetchIntegration } from './integrations/node-fetch.js';
|
|
3
|
+
export { consoleIntegration } from './integrations/console.js';
|
|
4
|
+
export { nodeContextIntegration } from './integrations/context.js';
|
|
5
|
+
export { contextLinesIntegration } from './integrations/contextlines.js';
|
|
6
|
+
export { localVariablesIntegration } from './integrations/local-variables/index.js';
|
|
7
|
+
export { modulesIntegration } from './integrations/modules.js';
|
|
8
|
+
export { onUncaughtExceptionIntegration } from './integrations/onuncaughtexception.js';
|
|
9
|
+
export { onUnhandledRejectionIntegration } from './integrations/onunhandledrejection.js';
|
|
10
|
+
export { anrIntegration } from './integrations/anr/index.js';
|
|
11
|
+
export { expressErrorHandler, expressIntegration, setupExpressErrorHandler } from './integrations/tracing/express.js';
|
|
12
|
+
export { fastifyIntegration, setupFastifyErrorHandler } from './integrations/tracing/fastify.js';
|
|
13
|
+
export { graphqlIntegration } from './integrations/tracing/graphql.js';
|
|
14
|
+
export { mongoIntegration } from './integrations/tracing/mongo.js';
|
|
15
|
+
export { mongooseIntegration } from './integrations/tracing/mongoose.js';
|
|
16
|
+
export { mysqlIntegration } from './integrations/tracing/mysql.js';
|
|
17
|
+
export { mysql2Integration } from './integrations/tracing/mysql2.js';
|
|
18
|
+
export { redisIntegration } from './integrations/tracing/redis.js';
|
|
19
|
+
export { nestIntegration, setupNestErrorHandler } from './integrations/tracing/nest.js';
|
|
20
|
+
export { postgresIntegration } from './integrations/tracing/postgres.js';
|
|
21
|
+
export { prismaIntegration } from './integrations/tracing/prisma.js';
|
|
22
|
+
export { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/hapi/index.js';
|
|
23
|
+
export { koaIntegration, setupKoaErrorHandler } from './integrations/tracing/koa.js';
|
|
24
|
+
export { connectIntegration, setupConnectErrorHandler } from './integrations/tracing/connect.js';
|
|
25
|
+
export { spotlightIntegration } from './integrations/spotlight.js';
|
|
26
|
+
export { getDefaultIntegrations, getDefaultIntegrationsWithoutPerformance, init, initWithoutDefaultIntegrations } from './sdk/init.js';
|
|
27
|
+
export { initOpenTelemetry } from './sdk/initOtel.js';
|
|
28
|
+
export { getAutoPerformanceIntegrations } from './integrations/tracing/index.js';
|
|
29
|
+
export { defaultStackParser, getSentryRelease } from './sdk/api.js';
|
|
30
|
+
export { createGetModuleFromFilename } from './utils/module.js';
|
|
31
|
+
export { makeNodeTransport } from './transports/http.js';
|
|
32
|
+
export { NodeClient } from './sdk/client.js';
|
|
33
|
+
export { cron } from './cron/index.js';
|
|
9
34
|
export { DEFAULT_USER_INCLUDES, addRequestDataToEvent, extractRequestData } from '@sentry/utils';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import { execFile } from 'child_process';
|
|
13
|
-
import { readFile, readdir, promises, existsSync, readFileSync } from 'fs';
|
|
14
|
-
import * as os from 'os';
|
|
15
|
-
import { join, dirname, posix, sep } from 'path';
|
|
16
|
-
import { Worker, isMainThread, threadId } from 'worker_threads';
|
|
17
|
-
import * as inspector from 'inspector';
|
|
18
|
-
import { Session } from 'inspector';
|
|
19
|
-
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
|
|
20
|
-
import { isWrapped as isWrapped$1 } from '@opentelemetry/core';
|
|
21
|
-
import { FastifyInstrumentation } from '@opentelemetry/instrumentation-fastify';
|
|
22
|
-
import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql';
|
|
23
|
-
import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb';
|
|
24
|
-
import { MongooseInstrumentation } from '@opentelemetry/instrumentation-mongoose';
|
|
25
|
-
import { MySQLInstrumentation } from '@opentelemetry/instrumentation-mysql';
|
|
26
|
-
import { MySQL2Instrumentation } from '@opentelemetry/instrumentation-mysql2';
|
|
27
|
-
import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis';
|
|
28
|
-
import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core';
|
|
29
|
-
import { PgInstrumentation } from '@opentelemetry/instrumentation-pg';
|
|
30
|
-
import * as prismaInstrumentation from '@prisma/instrumentation';
|
|
31
|
-
import { HapiInstrumentation } from '@opentelemetry/instrumentation-hapi';
|
|
32
|
-
import { KoaInstrumentation } from '@opentelemetry/instrumentation-koa';
|
|
33
|
-
import { SEMATTRS_HTTP_ROUTE, SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_NAMESPACE, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
|
|
34
|
-
import { ConnectInstrumentation } from '@opentelemetry/instrumentation-connect';
|
|
35
|
-
import * as http from 'http';
|
|
36
|
-
import moduleModule from 'module';
|
|
37
|
-
import * as http$1 from 'node:http';
|
|
38
|
-
import * as https from 'node:https';
|
|
39
|
-
import { Readable } from 'stream';
|
|
40
|
-
import { createGzip } from 'zlib';
|
|
41
|
-
import * as net from 'net';
|
|
42
|
-
import * as tls from 'tls';
|
|
43
|
-
import { Resource } from '@opentelemetry/resources';
|
|
44
|
-
import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
|
|
45
|
-
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Update the active isolation scope.
|
|
49
|
-
* Should be used with caution!
|
|
50
|
-
*/
|
|
51
|
-
function setIsolationScope(isolationScope) {
|
|
52
|
-
const scopes = getScopesFromContext(context.active());
|
|
53
|
-
if (scopes) {
|
|
54
|
-
scopes.isolationScope = isolationScope;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Adds an origin to an OTEL Span. */
|
|
59
|
-
function addOriginToSpan(span, origin) {
|
|
60
|
-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, origin);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Build a full URL from request options. */
|
|
64
|
-
function getRequestUrl(requestOptions) {
|
|
65
|
-
const protocol = requestOptions.protocol || '';
|
|
66
|
-
const hostname = requestOptions.hostname || requestOptions.host || '';
|
|
67
|
-
// Don't log standard :80 (http) and :443 (https) ports to reduce the noise
|
|
68
|
-
// Also don't add port if the hostname already includes a port
|
|
69
|
-
const port =
|
|
70
|
-
!requestOptions.port || requestOptions.port === 80 || requestOptions.port === 443 || /^(.*):(\d+)$/.test(hostname)
|
|
71
|
-
? ''
|
|
72
|
-
: `:${requestOptions.port}`;
|
|
73
|
-
const path = requestOptions.path ? requestOptions.path : '/';
|
|
74
|
-
return `${protocol}//${hostname}${port}${path}`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function _optionalChain$e(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
78
|
-
|
|
79
|
-
const _httpIntegration = ((options = {}) => {
|
|
80
|
-
const _breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs;
|
|
81
|
-
const _ignoreOutgoingRequests = options.ignoreOutgoingRequests;
|
|
82
|
-
const _ignoreIncomingRequests = options.ignoreIncomingRequests;
|
|
83
|
-
const _InstrumentationClass = options._instrumentation || HttpInstrumentation;
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
name: 'Http',
|
|
87
|
-
setupOnce() {
|
|
88
|
-
addOpenTelemetryInstrumentation(
|
|
89
|
-
new _InstrumentationClass({
|
|
90
|
-
ignoreOutgoingRequestHook: request => {
|
|
91
|
-
const url = getRequestUrl(request);
|
|
92
|
-
|
|
93
|
-
if (!url) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (isSentryRequestUrl(url, getClient())) {
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (_ignoreOutgoingRequests && _ignoreOutgoingRequests(url)) {
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return false;
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
ignoreIncomingRequestHook: request => {
|
|
109
|
-
const url = getRequestUrl(request);
|
|
110
|
-
|
|
111
|
-
const method = _optionalChain$e([request, 'access', _ => _.method, 'optionalAccess', _2 => _2.toUpperCase, 'call', _3 => _3()]);
|
|
112
|
-
// We do not capture OPTIONS/HEAD requests as transactions
|
|
113
|
-
if (method === 'OPTIONS' || method === 'HEAD') {
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (_ignoreIncomingRequests && _ignoreIncomingRequests(url)) {
|
|
118
|
-
return true;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return false;
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
requireParentforOutgoingSpans: false,
|
|
125
|
-
requireParentforIncomingSpans: false,
|
|
126
|
-
requestHook: (span, req) => {
|
|
127
|
-
addOriginToSpan(span, 'auto.http.otel.http');
|
|
128
|
-
|
|
129
|
-
// both, incoming requests and "client" requests made within the app trigger the requestHook
|
|
130
|
-
// we only want to isolate and further annotate incoming requests (IncomingMessage)
|
|
131
|
-
if (_isClientRequest(req)) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const scopes = getCapturedScopesOnSpan(span);
|
|
136
|
-
|
|
137
|
-
const isolationScope = (scopes.isolationScope || getIsolationScope()).clone();
|
|
138
|
-
const scope = scopes.scope || getCurrentScope();
|
|
139
|
-
|
|
140
|
-
// Update the isolation scope, isolate this request
|
|
141
|
-
isolationScope.setSDKProcessingMetadata({ request: req });
|
|
142
|
-
|
|
143
|
-
const client = getClient();
|
|
144
|
-
if (client && client.getOptions().autoSessionTracking) {
|
|
145
|
-
isolationScope.setRequestSession({ status: 'ok' });
|
|
146
|
-
}
|
|
147
|
-
setIsolationScope(isolationScope);
|
|
148
|
-
setCapturedScopesOnSpan(span, scope, isolationScope);
|
|
149
|
-
|
|
150
|
-
// attempt to update the scope's `transactionName` based on the request URL
|
|
151
|
-
// Ideally, framework instrumentations coming after the HttpInstrumentation
|
|
152
|
-
// update the transactionName once we get a parameterized route.
|
|
153
|
-
const attributes = spanToJSON(span).data;
|
|
154
|
-
if (!attributes) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const httpMethod = String(attributes['http.method']).toUpperCase() || 'GET';
|
|
159
|
-
const httpTarget = stripUrlQueryAndFragment(String(attributes['http.target'])) || '/';
|
|
160
|
-
const bestEffortTransactionName = `${httpMethod} ${httpTarget}`;
|
|
161
|
-
|
|
162
|
-
isolationScope.setTransactionName(bestEffortTransactionName);
|
|
163
|
-
},
|
|
164
|
-
responseHook: (span, res) => {
|
|
165
|
-
if (_breadcrumbs) {
|
|
166
|
-
_addRequestBreadcrumb$1(span, res);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const client = getClient();
|
|
170
|
-
if (client && client.getOptions().autoSessionTracking) {
|
|
171
|
-
setImmediate(() => {
|
|
172
|
-
client['_captureRequestSession']();
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
}),
|
|
177
|
-
);
|
|
178
|
-
},
|
|
179
|
-
};
|
|
180
|
-
}) ;
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* The http integration instruments Node's internal http and https modules.
|
|
184
|
-
* It creates breadcrumbs and spans for outgoing HTTP requests which will be attached to the currently active span.
|
|
185
|
-
*/
|
|
186
|
-
const httpIntegration = defineIntegration(_httpIntegration);
|
|
187
|
-
|
|
188
|
-
/** Add a breadcrumb for outgoing requests. */
|
|
189
|
-
function _addRequestBreadcrumb$1(span, response) {
|
|
190
|
-
if (getSpanKind(span) !== SpanKind.CLIENT) {
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const data = getRequestSpanData(span);
|
|
195
|
-
addBreadcrumb(
|
|
196
|
-
{
|
|
197
|
-
category: 'http',
|
|
198
|
-
data: {
|
|
199
|
-
status_code: response.statusCode,
|
|
200
|
-
...data,
|
|
201
|
-
},
|
|
202
|
-
type: 'http',
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
event: 'response',
|
|
206
|
-
// TODO FN: Do we need access to `request` here?
|
|
207
|
-
// If we do, we'll have to use the `applyCustomAttributesOnSpan` hook instead,
|
|
208
|
-
// but this has worse context semantics than request/responseHook.
|
|
209
|
-
response,
|
|
210
|
-
},
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Determines if @param req is a ClientRequest, meaning the request was created within the express app
|
|
216
|
-
* and it's an outgoing request.
|
|
217
|
-
* Checking for properties instead of using `instanceOf` to avoid importing the request classes.
|
|
218
|
-
*/
|
|
219
|
-
function _isClientRequest(req) {
|
|
220
|
-
return 'outputData' in req && 'outputSize' in req && !('client' in req) && !('statusCode' in req);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code.
|
|
225
|
-
*
|
|
226
|
-
* ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking.
|
|
227
|
-
*/
|
|
228
|
-
const DEBUG_BUILD = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
|
|
229
|
-
|
|
230
|
-
const NODE_VERSION = parseSemver(process.versions.node) ;
|
|
231
|
-
const NODE_MAJOR = NODE_VERSION.major;
|
|
232
|
-
|
|
233
|
-
const _nativeNodeFetchIntegration = ((options = {}) => {
|
|
234
|
-
const _breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs;
|
|
235
|
-
const _ignoreOutgoingRequests = options.ignoreOutgoingRequests;
|
|
236
|
-
|
|
237
|
-
async function getInstrumentation() {
|
|
238
|
-
// Only add NodeFetch if Node >= 18, as previous versions do not support it
|
|
239
|
-
if (NODE_MAJOR < 18) {
|
|
240
|
-
DEBUG_BUILD && logger.log('NodeFetch is not supported on Node < 18, skipping instrumentation...');
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
try {
|
|
245
|
-
const pkg = await import('opentelemetry-instrumentation-fetch-node');
|
|
246
|
-
return new pkg.FetchInstrumentation({
|
|
247
|
-
ignoreRequestHook: (request) => {
|
|
248
|
-
const url = request.origin;
|
|
249
|
-
return _ignoreOutgoingRequests && url && _ignoreOutgoingRequests(url);
|
|
250
|
-
},
|
|
251
|
-
onRequest: ({ span }) => {
|
|
252
|
-
_updateSpan(span);
|
|
253
|
-
|
|
254
|
-
if (_breadcrumbs) {
|
|
255
|
-
_addRequestBreadcrumb(span);
|
|
256
|
-
}
|
|
257
|
-
},
|
|
258
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
259
|
-
} );
|
|
260
|
-
} catch (error) {
|
|
261
|
-
// Could not load instrumentation
|
|
262
|
-
DEBUG_BUILD && logger.log('Could not load NodeFetch instrumentation.');
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return {
|
|
267
|
-
name: 'NodeFetch',
|
|
268
|
-
setupOnce() {
|
|
269
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
270
|
-
getInstrumentation().then(instrumentation => {
|
|
271
|
-
if (instrumentation) {
|
|
272
|
-
addOpenTelemetryInstrumentation(instrumentation);
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
},
|
|
276
|
-
};
|
|
277
|
-
}) ;
|
|
278
|
-
|
|
279
|
-
const nativeNodeFetchIntegration = defineIntegration(_nativeNodeFetchIntegration);
|
|
280
|
-
|
|
281
|
-
/** Update the span with data we need. */
|
|
282
|
-
function _updateSpan(span) {
|
|
283
|
-
addOriginToSpan(span, 'auto.http.otel.node_fetch');
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/** Add a breadcrumb for outgoing requests. */
|
|
287
|
-
function _addRequestBreadcrumb(span) {
|
|
288
|
-
if (getSpanKind(span) !== SpanKind.CLIENT) {
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const data = getRequestSpanData(span);
|
|
293
|
-
addBreadcrumb({
|
|
294
|
-
category: 'http',
|
|
295
|
-
data: {
|
|
296
|
-
...data,
|
|
297
|
-
},
|
|
298
|
-
type: 'http',
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const INTEGRATION_NAME$8 = 'Console';
|
|
303
|
-
|
|
304
|
-
const _consoleIntegration = (() => {
|
|
305
|
-
return {
|
|
306
|
-
name: INTEGRATION_NAME$8,
|
|
307
|
-
setup(client) {
|
|
308
|
-
addConsoleInstrumentationHandler(({ args, level }) => {
|
|
309
|
-
if (getClient$1() !== client) {
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
addBreadcrumb(
|
|
314
|
-
{
|
|
315
|
-
category: 'console',
|
|
316
|
-
level: severityLevelFromString(level),
|
|
317
|
-
message: util.format.apply(undefined, args),
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
input: [...args],
|
|
321
|
-
level,
|
|
322
|
-
},
|
|
323
|
-
);
|
|
324
|
-
});
|
|
325
|
-
},
|
|
326
|
-
};
|
|
327
|
-
}) ;
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Capture console logs as breadcrumbs.
|
|
331
|
-
*/
|
|
332
|
-
const consoleIntegration = defineIntegration(_consoleIntegration);
|
|
333
|
-
|
|
334
|
-
function _optionalChain$d(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
335
|
-
const readFileAsync$1 = promisify(readFile);
|
|
336
|
-
const readDirAsync = promisify(readdir);
|
|
337
|
-
|
|
338
|
-
const INTEGRATION_NAME$7 = 'Context';
|
|
339
|
-
|
|
340
|
-
const _nodeContextIntegration = ((options = {}) => {
|
|
341
|
-
let cachedContext;
|
|
342
|
-
|
|
343
|
-
const _options = {
|
|
344
|
-
app: true,
|
|
345
|
-
os: true,
|
|
346
|
-
device: true,
|
|
347
|
-
culture: true,
|
|
348
|
-
cloudResource: true,
|
|
349
|
-
...options,
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
/** Add contexts to the event. Caches the context so we only look it up once. */
|
|
353
|
-
async function addContext(event) {
|
|
354
|
-
if (cachedContext === undefined) {
|
|
355
|
-
cachedContext = _getContexts();
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const updatedContext = _updateContext(await cachedContext);
|
|
359
|
-
|
|
360
|
-
event.contexts = {
|
|
361
|
-
...event.contexts,
|
|
362
|
-
app: { ...updatedContext.app, ..._optionalChain$d([event, 'access', _ => _.contexts, 'optionalAccess', _2 => _2.app]) },
|
|
363
|
-
os: { ...updatedContext.os, ..._optionalChain$d([event, 'access', _3 => _3.contexts, 'optionalAccess', _4 => _4.os]) },
|
|
364
|
-
device: { ...updatedContext.device, ..._optionalChain$d([event, 'access', _5 => _5.contexts, 'optionalAccess', _6 => _6.device]) },
|
|
365
|
-
culture: { ...updatedContext.culture, ..._optionalChain$d([event, 'access', _7 => _7.contexts, 'optionalAccess', _8 => _8.culture]) },
|
|
366
|
-
cloud_resource: { ...updatedContext.cloud_resource, ..._optionalChain$d([event, 'access', _9 => _9.contexts, 'optionalAccess', _10 => _10.cloud_resource]) },
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
return event;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/** Get the contexts from node. */
|
|
373
|
-
async function _getContexts() {
|
|
374
|
-
const contexts = {};
|
|
375
|
-
|
|
376
|
-
if (_options.os) {
|
|
377
|
-
contexts.os = await getOsContext();
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (_options.app) {
|
|
381
|
-
contexts.app = getAppContext();
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (_options.device) {
|
|
385
|
-
contexts.device = getDeviceContext(_options.device);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if (_options.culture) {
|
|
389
|
-
const culture = getCultureContext();
|
|
390
|
-
|
|
391
|
-
if (culture) {
|
|
392
|
-
contexts.culture = culture;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (_options.cloudResource) {
|
|
397
|
-
contexts.cloud_resource = getCloudResourceContext();
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return contexts;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
return {
|
|
404
|
-
name: INTEGRATION_NAME$7,
|
|
405
|
-
processEvent(event) {
|
|
406
|
-
return addContext(event);
|
|
407
|
-
},
|
|
408
|
-
};
|
|
409
|
-
}) ;
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Capture context about the environment and the device that the client is running on, to events.
|
|
413
|
-
*/
|
|
414
|
-
const nodeContextIntegration = defineIntegration(_nodeContextIntegration);
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Updates the context with dynamic values that can change
|
|
418
|
-
*/
|
|
419
|
-
function _updateContext(contexts) {
|
|
420
|
-
// Only update properties if they exist
|
|
421
|
-
if (_optionalChain$d([contexts, 'optionalAccess', _11 => _11.app, 'optionalAccess', _12 => _12.app_memory])) {
|
|
422
|
-
contexts.app.app_memory = process.memoryUsage().rss;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
if (_optionalChain$d([contexts, 'optionalAccess', _13 => _13.device, 'optionalAccess', _14 => _14.free_memory])) {
|
|
426
|
-
contexts.device.free_memory = os.freemem();
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return contexts;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Returns the operating system context.
|
|
434
|
-
*
|
|
435
|
-
* Based on the current platform, this uses a different strategy to provide the
|
|
436
|
-
* most accurate OS information. Since this might involve spawning subprocesses
|
|
437
|
-
* or accessing the file system, this should only be executed lazily and cached.
|
|
438
|
-
*
|
|
439
|
-
* - On macOS (Darwin), this will execute the `sw_vers` utility. The context
|
|
440
|
-
* has a `name`, `version`, `build` and `kernel_version` set.
|
|
441
|
-
* - On Linux, this will try to load a distribution release from `/etc` and set
|
|
442
|
-
* the `name`, `version` and `kernel_version` fields.
|
|
443
|
-
* - On all other platforms, only a `name` and `version` will be returned. Note
|
|
444
|
-
* that `version` might actually be the kernel version.
|
|
445
|
-
*/
|
|
446
|
-
async function getOsContext() {
|
|
447
|
-
const platformId = os.platform();
|
|
448
|
-
switch (platformId) {
|
|
449
|
-
case 'darwin':
|
|
450
|
-
return getDarwinInfo();
|
|
451
|
-
case 'linux':
|
|
452
|
-
return getLinuxInfo();
|
|
453
|
-
default:
|
|
454
|
-
return {
|
|
455
|
-
name: PLATFORM_NAMES[platformId] || platformId,
|
|
456
|
-
version: os.release(),
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
function getCultureContext() {
|
|
462
|
-
try {
|
|
463
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
|
|
464
|
-
if (typeof (process.versions ).icu !== 'string') {
|
|
465
|
-
// Node was built without ICU support
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// Check that node was built with full Intl support. Its possible it was built without support for non-English
|
|
470
|
-
// locales which will make resolvedOptions inaccurate
|
|
471
|
-
//
|
|
472
|
-
// https://nodejs.org/api/intl.html#detecting-internationalization-support
|
|
473
|
-
const january = new Date(9e8);
|
|
474
|
-
const spanish = new Intl.DateTimeFormat('es', { month: 'long' });
|
|
475
|
-
if (spanish.format(january) === 'enero') {
|
|
476
|
-
const options = Intl.DateTimeFormat().resolvedOptions();
|
|
477
|
-
|
|
478
|
-
return {
|
|
479
|
-
locale: options.locale,
|
|
480
|
-
timezone: options.timeZone,
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
} catch (err) {
|
|
484
|
-
//
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
function getAppContext() {
|
|
491
|
-
const app_memory = process.memoryUsage().rss;
|
|
492
|
-
const app_start_time = new Date(Date.now() - process.uptime() * 1000).toISOString();
|
|
493
|
-
|
|
494
|
-
return { app_start_time, app_memory };
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Gets device information from os
|
|
499
|
-
*/
|
|
500
|
-
function getDeviceContext(deviceOpt) {
|
|
501
|
-
const device = {};
|
|
502
|
-
|
|
503
|
-
// Sometimes os.uptime() throws due to lacking permissions: https://github.com/getsentry/sentry-javascript/issues/8202
|
|
504
|
-
let uptime;
|
|
505
|
-
try {
|
|
506
|
-
uptime = os.uptime && os.uptime();
|
|
507
|
-
} catch (e) {
|
|
508
|
-
// noop
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// os.uptime or its return value seem to be undefined in certain environments (e.g. Azure functions).
|
|
512
|
-
// Hence, we only set boot time, if we get a valid uptime value.
|
|
513
|
-
// @see https://github.com/getsentry/sentry-javascript/issues/5856
|
|
514
|
-
if (typeof uptime === 'number') {
|
|
515
|
-
device.boot_time = new Date(Date.now() - uptime * 1000).toISOString();
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
device.arch = os.arch();
|
|
519
|
-
|
|
520
|
-
if (deviceOpt === true || deviceOpt.memory) {
|
|
521
|
-
device.memory_size = os.totalmem();
|
|
522
|
-
device.free_memory = os.freemem();
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
if (deviceOpt === true || deviceOpt.cpu) {
|
|
526
|
-
const cpuInfo = os.cpus();
|
|
527
|
-
if (cpuInfo && cpuInfo.length) {
|
|
528
|
-
const firstCpu = cpuInfo[0];
|
|
529
|
-
|
|
530
|
-
device.processor_count = cpuInfo.length;
|
|
531
|
-
device.cpu_description = firstCpu.model;
|
|
532
|
-
device.processor_frequency = firstCpu.speed;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
return device;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/** Mapping of Node's platform names to actual OS names. */
|
|
540
|
-
const PLATFORM_NAMES = {
|
|
541
|
-
aix: 'IBM AIX',
|
|
542
|
-
freebsd: 'FreeBSD',
|
|
543
|
-
openbsd: 'OpenBSD',
|
|
544
|
-
sunos: 'SunOS',
|
|
545
|
-
win32: 'Windows',
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
/** Linux version file to check for a distribution. */
|
|
549
|
-
|
|
550
|
-
/** Mapping of linux release files located in /etc to distributions. */
|
|
551
|
-
const LINUX_DISTROS = [
|
|
552
|
-
{ name: 'fedora-release', distros: ['Fedora'] },
|
|
553
|
-
{ name: 'redhat-release', distros: ['Red Hat Linux', 'Centos'] },
|
|
554
|
-
{ name: 'redhat_version', distros: ['Red Hat Linux'] },
|
|
555
|
-
{ name: 'SuSE-release', distros: ['SUSE Linux'] },
|
|
556
|
-
{ name: 'lsb-release', distros: ['Ubuntu Linux', 'Arch Linux'] },
|
|
557
|
-
{ name: 'debian_version', distros: ['Debian'] },
|
|
558
|
-
{ name: 'debian_release', distros: ['Debian'] },
|
|
559
|
-
{ name: 'arch-release', distros: ['Arch Linux'] },
|
|
560
|
-
{ name: 'gentoo-release', distros: ['Gentoo Linux'] },
|
|
561
|
-
{ name: 'novell-release', distros: ['SUSE Linux'] },
|
|
562
|
-
{ name: 'alpine-release', distros: ['Alpine Linux'] },
|
|
563
|
-
];
|
|
564
|
-
|
|
565
|
-
/** Functions to extract the OS version from Linux release files. */
|
|
566
|
-
const LINUX_VERSIONS
|
|
567
|
-
|
|
568
|
-
= {
|
|
569
|
-
alpine: content => content,
|
|
570
|
-
arch: content => matchFirst(/distrib_release=(.*)/, content),
|
|
571
|
-
centos: content => matchFirst(/release ([^ ]+)/, content),
|
|
572
|
-
debian: content => content,
|
|
573
|
-
fedora: content => matchFirst(/release (..)/, content),
|
|
574
|
-
mint: content => matchFirst(/distrib_release=(.*)/, content),
|
|
575
|
-
red: content => matchFirst(/release ([^ ]+)/, content),
|
|
576
|
-
suse: content => matchFirst(/VERSION = (.*)\n/, content),
|
|
577
|
-
ubuntu: content => matchFirst(/distrib_release=(.*)/, content),
|
|
578
|
-
};
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* Executes a regular expression with one capture group.
|
|
582
|
-
*
|
|
583
|
-
* @param regex A regular expression to execute.
|
|
584
|
-
* @param text Content to execute the RegEx on.
|
|
585
|
-
* @returns The captured string if matched; otherwise undefined.
|
|
586
|
-
*/
|
|
587
|
-
function matchFirst(regex, text) {
|
|
588
|
-
const match = regex.exec(text);
|
|
589
|
-
return match ? match[1] : undefined;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/** Loads the macOS operating system context. */
|
|
593
|
-
async function getDarwinInfo() {
|
|
594
|
-
// Default values that will be used in case no operating system information
|
|
595
|
-
// can be loaded. The default version is computed via heuristics from the
|
|
596
|
-
// kernel version, but the build ID is missing.
|
|
597
|
-
const darwinInfo = {
|
|
598
|
-
kernel_version: os.release(),
|
|
599
|
-
name: 'Mac OS X',
|
|
600
|
-
version: `10.${Number(os.release().split('.')[0]) - 4}`,
|
|
601
|
-
};
|
|
602
|
-
|
|
603
|
-
try {
|
|
604
|
-
// We try to load the actual macOS version by executing the `sw_vers` tool.
|
|
605
|
-
// This tool should be available on every standard macOS installation. In
|
|
606
|
-
// case this fails, we stick with the values computed above.
|
|
607
|
-
|
|
608
|
-
const output = await new Promise((resolve, reject) => {
|
|
609
|
-
execFile('/usr/bin/sw_vers', (error, stdout) => {
|
|
610
|
-
if (error) {
|
|
611
|
-
reject(error);
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
resolve(stdout);
|
|
615
|
-
});
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
darwinInfo.name = matchFirst(/^ProductName:\s+(.*)$/m, output);
|
|
619
|
-
darwinInfo.version = matchFirst(/^ProductVersion:\s+(.*)$/m, output);
|
|
620
|
-
darwinInfo.build = matchFirst(/^BuildVersion:\s+(.*)$/m, output);
|
|
621
|
-
} catch (e) {
|
|
622
|
-
// ignore
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
return darwinInfo;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
/** Returns a distribution identifier to look up version callbacks. */
|
|
629
|
-
function getLinuxDistroId(name) {
|
|
630
|
-
return name.split(' ')[0].toLowerCase();
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/** Loads the Linux operating system context. */
|
|
634
|
-
async function getLinuxInfo() {
|
|
635
|
-
// By default, we cannot assume anything about the distribution or Linux
|
|
636
|
-
// version. `os.release()` returns the kernel version and we assume a generic
|
|
637
|
-
// "Linux" name, which will be replaced down below.
|
|
638
|
-
const linuxInfo = {
|
|
639
|
-
kernel_version: os.release(),
|
|
640
|
-
name: 'Linux',
|
|
641
|
-
};
|
|
642
|
-
|
|
643
|
-
try {
|
|
644
|
-
// We start guessing the distribution by listing files in the /etc
|
|
645
|
-
// directory. This is were most Linux distributions (except Knoppix) store
|
|
646
|
-
// release files with certain distribution-dependent meta data. We search
|
|
647
|
-
// for exactly one known file defined in `LINUX_DISTROS` and exit if none
|
|
648
|
-
// are found. In case there are more than one file, we just stick with the
|
|
649
|
-
// first one.
|
|
650
|
-
const etcFiles = await readDirAsync('/etc');
|
|
651
|
-
const distroFile = LINUX_DISTROS.find(file => etcFiles.includes(file.name));
|
|
652
|
-
if (!distroFile) {
|
|
653
|
-
return linuxInfo;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// Once that file is known, load its contents. To make searching in those
|
|
657
|
-
// files easier, we lowercase the file contents. Since these files are
|
|
658
|
-
// usually quite small, this should not allocate too much memory and we only
|
|
659
|
-
// hold on to it for a very short amount of time.
|
|
660
|
-
const distroPath = join('/etc', distroFile.name);
|
|
661
|
-
const contents = ((await readFileAsync$1(distroPath, { encoding: 'utf-8' })) ).toLowerCase();
|
|
662
|
-
|
|
663
|
-
// Some Linux distributions store their release information in the same file
|
|
664
|
-
// (e.g. RHEL and Centos). In those cases, we scan the file for an
|
|
665
|
-
// identifier, that basically consists of the first word of the linux
|
|
666
|
-
// distribution name (e.g. "red" for Red Hat). In case there is no match, we
|
|
667
|
-
// just assume the first distribution in our list.
|
|
668
|
-
const { distros } = distroFile;
|
|
669
|
-
linuxInfo.name = distros.find(d => contents.indexOf(getLinuxDistroId(d)) >= 0) || distros[0];
|
|
670
|
-
|
|
671
|
-
// Based on the found distribution, we can now compute the actual version
|
|
672
|
-
// number. This is different for every distribution, so several strategies
|
|
673
|
-
// are computed in `LINUX_VERSIONS`.
|
|
674
|
-
const id = getLinuxDistroId(linuxInfo.name);
|
|
675
|
-
linuxInfo.version = LINUX_VERSIONS[id](contents);
|
|
676
|
-
} catch (e) {
|
|
677
|
-
// ignore
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
return linuxInfo;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
/**
|
|
684
|
-
* Grabs some information about hosting provider based on best effort.
|
|
685
|
-
*/
|
|
686
|
-
function getCloudResourceContext() {
|
|
687
|
-
if (process.env.VERCEL) {
|
|
688
|
-
// https://vercel.com/docs/concepts/projects/environment-variables/system-environment-variables#system-environment-variables
|
|
689
|
-
return {
|
|
690
|
-
'cloud.provider': 'vercel',
|
|
691
|
-
'cloud.region': process.env.VERCEL_REGION,
|
|
692
|
-
};
|
|
693
|
-
} else if (process.env.AWS_REGION) {
|
|
694
|
-
// https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html
|
|
695
|
-
return {
|
|
696
|
-
'cloud.provider': 'aws',
|
|
697
|
-
'cloud.region': process.env.AWS_REGION,
|
|
698
|
-
'cloud.platform': process.env.AWS_EXECUTION_ENV,
|
|
699
|
-
};
|
|
700
|
-
} else if (process.env.GCP_PROJECT) {
|
|
701
|
-
// https://cloud.google.com/composer/docs/how-to/managing/environment-variables#reserved_variables
|
|
702
|
-
return {
|
|
703
|
-
'cloud.provider': 'gcp',
|
|
704
|
-
};
|
|
705
|
-
} else if (process.env.ALIYUN_REGION_ID) {
|
|
706
|
-
// TODO: find where I found these environment variables - at least gc.github.com returns something
|
|
707
|
-
return {
|
|
708
|
-
'cloud.provider': 'alibaba_cloud',
|
|
709
|
-
'cloud.region': process.env.ALIYUN_REGION_ID,
|
|
710
|
-
};
|
|
711
|
-
} else if (process.env.WEBSITE_SITE_NAME && process.env.REGION_NAME) {
|
|
712
|
-
// https://learn.microsoft.com/en-us/azure/app-service/reference-app-settings?tabs=kudu%2Cdotnet#app-environment
|
|
713
|
-
return {
|
|
714
|
-
'cloud.provider': 'azure',
|
|
715
|
-
'cloud.region': process.env.REGION_NAME,
|
|
716
|
-
};
|
|
717
|
-
} else if (process.env.IBM_CLOUD_REGION) {
|
|
718
|
-
// TODO: find where I found these environment variables - at least gc.github.com returns something
|
|
719
|
-
return {
|
|
720
|
-
'cloud.provider': 'ibm_cloud',
|
|
721
|
-
'cloud.region': process.env.IBM_CLOUD_REGION,
|
|
722
|
-
};
|
|
723
|
-
} else if (process.env.TENCENTCLOUD_REGION) {
|
|
724
|
-
// https://www.tencentcloud.com/document/product/583/32748
|
|
725
|
-
return {
|
|
726
|
-
'cloud.provider': 'tencent_cloud',
|
|
727
|
-
'cloud.region': process.env.TENCENTCLOUD_REGION,
|
|
728
|
-
'cloud.account.id': process.env.TENCENTCLOUD_APPID,
|
|
729
|
-
'cloud.availability_zone': process.env.TENCENTCLOUD_ZONE,
|
|
730
|
-
};
|
|
731
|
-
} else if (process.env.NETLIFY) {
|
|
732
|
-
// https://docs.netlify.com/configure-builds/environment-variables/#read-only-variables
|
|
733
|
-
return {
|
|
734
|
-
'cloud.provider': 'netlify',
|
|
735
|
-
};
|
|
736
|
-
} else if (process.env.FLY_REGION) {
|
|
737
|
-
// https://fly.io/docs/reference/runtime-environment/
|
|
738
|
-
return {
|
|
739
|
-
'cloud.provider': 'fly.io',
|
|
740
|
-
'cloud.region': process.env.FLY_REGION,
|
|
741
|
-
};
|
|
742
|
-
} else if (process.env.DYNO) {
|
|
743
|
-
// https://devcenter.heroku.com/articles/dynos#local-environment-variables
|
|
744
|
-
return {
|
|
745
|
-
'cloud.provider': 'heroku',
|
|
746
|
-
};
|
|
747
|
-
} else {
|
|
748
|
-
return undefined;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
function _optionalChain$c(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
753
|
-
const FILE_CONTENT_CACHE = new LRUMap(100);
|
|
754
|
-
const DEFAULT_LINES_OF_CONTEXT = 7;
|
|
755
|
-
const INTEGRATION_NAME$6 = 'ContextLines';
|
|
756
|
-
|
|
757
|
-
const readFileAsync = promises.readFile;
|
|
758
|
-
|
|
759
|
-
/** Exported only for tests, as a type-safe variant. */
|
|
760
|
-
const _contextLinesIntegration = ((options = {}) => {
|
|
761
|
-
const contextLines = options.frameContextLines !== undefined ? options.frameContextLines : DEFAULT_LINES_OF_CONTEXT;
|
|
762
|
-
|
|
763
|
-
return {
|
|
764
|
-
name: INTEGRATION_NAME$6,
|
|
765
|
-
processEvent(event) {
|
|
766
|
-
return addSourceContext(event, contextLines);
|
|
767
|
-
},
|
|
768
|
-
};
|
|
769
|
-
}) ;
|
|
770
|
-
|
|
771
|
-
/**
|
|
772
|
-
* Capture the lines before and after the frame's context.
|
|
773
|
-
*/
|
|
774
|
-
const contextLinesIntegration = defineIntegration(_contextLinesIntegration);
|
|
775
|
-
|
|
776
|
-
async function addSourceContext(event, contextLines) {
|
|
777
|
-
// keep a lookup map of which files we've already enqueued to read,
|
|
778
|
-
// so we don't enqueue the same file multiple times which would cause multiple i/o reads
|
|
779
|
-
const enqueuedReadSourceFileTasks = {};
|
|
780
|
-
const readSourceFileTasks = [];
|
|
781
|
-
|
|
782
|
-
if (contextLines > 0 && _optionalChain$c([event, 'access', _2 => _2.exception, 'optionalAccess', _3 => _3.values])) {
|
|
783
|
-
for (const exception of event.exception.values) {
|
|
784
|
-
if (!_optionalChain$c([exception, 'access', _4 => _4.stacktrace, 'optionalAccess', _5 => _5.frames])) {
|
|
785
|
-
continue;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
// We want to iterate in reverse order as calling cache.get will bump the file in our LRU cache.
|
|
789
|
-
// This ends up prioritizes source context for frames at the top of the stack instead of the bottom.
|
|
790
|
-
for (let i = exception.stacktrace.frames.length - 1; i >= 0; i--) {
|
|
791
|
-
const frame = exception.stacktrace.frames[i];
|
|
792
|
-
// Call cache.get to bump the file to the top of the cache and ensure we have not already
|
|
793
|
-
// enqueued a read operation for this filename
|
|
794
|
-
if (frame.filename && !enqueuedReadSourceFileTasks[frame.filename] && !FILE_CONTENT_CACHE.get(frame.filename)) {
|
|
795
|
-
readSourceFileTasks.push(_readSourceFile(frame.filename));
|
|
796
|
-
enqueuedReadSourceFileTasks[frame.filename] = 1;
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
// check if files to read > 0, if so, await all of them to be read before adding source contexts.
|
|
803
|
-
// Normally, Promise.all here could be short circuited if one of the promises rejects, but we
|
|
804
|
-
// are guarding from that by wrapping the i/o read operation in a try/catch.
|
|
805
|
-
if (readSourceFileTasks.length > 0) {
|
|
806
|
-
await Promise.all(readSourceFileTasks);
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// Perform the same loop as above, but this time we can assume all files are in the cache
|
|
810
|
-
// and attempt to add source context to frames.
|
|
811
|
-
if (contextLines > 0 && _optionalChain$c([event, 'access', _6 => _6.exception, 'optionalAccess', _7 => _7.values])) {
|
|
812
|
-
for (const exception of event.exception.values) {
|
|
813
|
-
if (exception.stacktrace && exception.stacktrace.frames) {
|
|
814
|
-
await addSourceContextToFrames(exception.stacktrace.frames, contextLines);
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
return event;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
/** Adds context lines to frames */
|
|
823
|
-
function addSourceContextToFrames(frames, contextLines) {
|
|
824
|
-
for (const frame of frames) {
|
|
825
|
-
// Only add context if we have a filename and it hasn't already been added
|
|
826
|
-
if (frame.filename && frame.context_line === undefined) {
|
|
827
|
-
const sourceFileLines = FILE_CONTENT_CACHE.get(frame.filename);
|
|
828
|
-
|
|
829
|
-
if (sourceFileLines) {
|
|
830
|
-
try {
|
|
831
|
-
addContextToFrame(sourceFileLines, frame, contextLines);
|
|
832
|
-
} catch (e) {
|
|
833
|
-
// anomaly, being defensive in case
|
|
834
|
-
// unlikely to ever happen in practice but can definitely happen in theory
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
/**
|
|
842
|
-
* Reads file contents and caches them in a global LRU cache.
|
|
843
|
-
* If reading fails, mark the file as null in the cache so we don't try again.
|
|
844
|
-
*
|
|
845
|
-
* @param filename filepath to read content from.
|
|
846
|
-
*/
|
|
847
|
-
async function _readSourceFile(filename) {
|
|
848
|
-
const cachedFile = FILE_CONTENT_CACHE.get(filename);
|
|
849
|
-
|
|
850
|
-
// We have already attempted to read this file and failed, do not try again
|
|
851
|
-
if (cachedFile === null) {
|
|
852
|
-
return null;
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
// We have a cache hit, return it
|
|
856
|
-
if (cachedFile !== undefined) {
|
|
857
|
-
return cachedFile;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// Guard from throwing if readFile fails, this enables us to use Promise.all and
|
|
861
|
-
// not have it short circuiting if one of the promises rejects + since context lines are added
|
|
862
|
-
// on a best effort basis, we want to throw here anyways.
|
|
863
|
-
|
|
864
|
-
// If we made it to here, it means that our file is not cache nor marked as failed, so attempt to read it
|
|
865
|
-
let content = null;
|
|
866
|
-
try {
|
|
867
|
-
const rawFileContents = await readFileAsync(filename, 'utf-8');
|
|
868
|
-
content = rawFileContents.split('\n');
|
|
869
|
-
} catch (_) {
|
|
870
|
-
// if we fail, we will mark the file as null in the cache and short circuit next time we try to read it
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
FILE_CONTENT_CACHE.set(filename, content);
|
|
874
|
-
return content;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
/**
|
|
878
|
-
* Creates a rate limiter that will call the disable callback when the rate limit is reached and the enable callback
|
|
879
|
-
* when a timeout has occurred.
|
|
880
|
-
* @param maxPerSecond Maximum number of calls per second
|
|
881
|
-
* @param enable Callback to enable capture
|
|
882
|
-
* @param disable Callback to disable capture
|
|
883
|
-
* @returns A function to call to increment the rate limiter count
|
|
884
|
-
*/
|
|
885
|
-
function createRateLimiter(
|
|
886
|
-
maxPerSecond,
|
|
887
|
-
enable,
|
|
888
|
-
disable,
|
|
889
|
-
) {
|
|
890
|
-
let count = 0;
|
|
891
|
-
let retrySeconds = 5;
|
|
892
|
-
let disabledTimeout = 0;
|
|
893
|
-
|
|
894
|
-
setInterval(() => {
|
|
895
|
-
if (disabledTimeout === 0) {
|
|
896
|
-
if (count > maxPerSecond) {
|
|
897
|
-
retrySeconds *= 2;
|
|
898
|
-
disable(retrySeconds);
|
|
899
|
-
|
|
900
|
-
// Cap at one day
|
|
901
|
-
if (retrySeconds > 86400) {
|
|
902
|
-
retrySeconds = 86400;
|
|
903
|
-
}
|
|
904
|
-
disabledTimeout = retrySeconds;
|
|
905
|
-
}
|
|
906
|
-
} else {
|
|
907
|
-
disabledTimeout -= 1;
|
|
908
|
-
|
|
909
|
-
if (disabledTimeout === 0) {
|
|
910
|
-
enable();
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
count = 0;
|
|
915
|
-
}, 1000).unref();
|
|
916
|
-
|
|
917
|
-
return () => {
|
|
918
|
-
count += 1;
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
// Add types for the exception event data
|
|
923
|
-
|
|
924
|
-
/** Could this be an anonymous function? */
|
|
925
|
-
function isAnonymous(name) {
|
|
926
|
-
return name !== undefined && (name.length === 0 || name === '?' || name === '<anonymous>');
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
/** Do the function names appear to match? */
|
|
930
|
-
function functionNamesMatch(a, b) {
|
|
931
|
-
return a === b || (isAnonymous(a) && isAnonymous(b));
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
/** Creates a unique hash from stack frames */
|
|
935
|
-
function hashFrames(frames) {
|
|
936
|
-
if (frames === undefined) {
|
|
937
|
-
return;
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
// Only hash the 10 most recent frames (ie. the last 10)
|
|
941
|
-
return frames.slice(-10).reduce((acc, frame) => `${acc},${frame.function},${frame.lineno},${frame.colno}`, '');
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
/**
|
|
945
|
-
* We use the stack parser to create a unique hash from the exception stack trace
|
|
946
|
-
* This is used to lookup vars when the exception passes through the event processor
|
|
947
|
-
*/
|
|
948
|
-
function hashFromStack(stackParser, stack) {
|
|
949
|
-
if (stack === undefined) {
|
|
950
|
-
return undefined;
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
return hashFrames(stackParser(stack, 1));
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
function _optionalChain$b(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
957
|
-
// This string is a placeholder that gets overwritten with the worker code.
|
|
958
|
-
const base64WorkerScript$1 = 'LyohIEBzZW50cnkvbm9kZSA4LjAuMC1yYy4zICg3NTY0MDYxKSB8IGh0dHBzOi8vZ2l0aHViLmNvbS9nZXRzZW50cnkvc2VudHJ5LWphdmFzY3JpcHQgKi8KaW1wb3J0e1Nlc3Npb24gYXMgZX1mcm9tIm5vZGU6aW5zcGVjdG9yL3Byb21pc2VzIjtpbXBvcnR7d29ya2VyRGF0YSBhcyBuLHBhcmVudFBvcnQgYXMgdH1mcm9tIndvcmtlcl90aHJlYWRzIjtpbXBvcnR7cG9zaXggYXMgbyxzZXAgYXMgcn1mcm9tInBhdGgiO2NvbnN0IGk9NTAscz0iPyIsYz0vXChlcnJvcjogKC4qKVwpLyxhPS9jYXB0dXJlTWVzc2FnZXxjYXB0dXJlRXhjZXB0aW9uLztjb25zdCB1PS9eKFxTKzpcXHxcLz8pKFtcc1xTXSo/KSgoPzpcLnsxLDJ9fFteL1xcXSs/fCkoXC5bXi4vXFxdKnwpKSg/OlsvXFxdKikkLztmdW5jdGlvbiBmKGUpe2NvbnN0IG49ZnVuY3Rpb24oZSl7Y29uc3Qgbj1lLmxlbmd0aD4xMDI0P2A8dHJ1bmNhdGVkPiR7ZS5zbGljZSgtMTAyNCl9YDplLHQ9dS5leGVjKG4pO3JldHVybiB0P3Quc2xpY2UoMSk6W119KGUpLHQ9blswXTtsZXQgbz1uWzFdO3JldHVybiB0fHxvPyhvJiYobz1vLnNsaWNlKDAsby5sZW5ndGgtMSkpLHQrbyk6Ii4ifWZ1bmN0aW9uIGwoZSxuPSExKXtyZXR1cm4hKG58fGUmJiFlLnN0YXJ0c1dpdGgoIi8iKSYmIWUubWF0Y2goL15bQS1aXTovKSYmIWUuc3RhcnRzV2l0aCgiLiIpJiYhZS5tYXRjaCgvXlthLXpBLVpdKFthLXpBLVowLTkuXC0rXSkqOlwvXC8vKSkmJnZvaWQgMCE9PWUmJiFlLmluY2x1ZGVzKCJub2RlX21vZHVsZXMvIil9ZnVuY3Rpb24gZChlKXtjb25zdCBuPS9eXHMqWy1dezQsfSQvLHQ9L2F0ICg/OmFzeW5jICk/KD86KC4rPylccytcKCk/KD86KC4rKTooXGQrKTooXGQrKT98KFteKV0rKSlcKT8vO3JldHVybiBvPT57Y29uc3Qgcj1vLm1hdGNoKHQpO2lmKHIpe2xldCBuLHQsbyxpLGM7aWYoclsxXSl7bz1yWzFdO2xldCBlPW8ubGFzdEluZGV4T2YoIi4iKTtpZigiLiI9PT1vW2UtMV0mJmUtLSxlPjApe249by5zbGljZSgwLGUpLHQ9by5zbGljZShlKzEpO2NvbnN0IHI9bi5pbmRleE9mKCIuTW9kdWxlIik7cj4wJiYobz1vLnNsaWNlKHIrMSksbj1uLnNsaWNlKDAscikpfWk9dm9pZCAwfXQmJihpPW4sYz10KSwiPGFub255bW91cz4iPT09dCYmKGM9dm9pZCAwLG89dm9pZCAwKSx2b2lkIDA9PT1vJiYoYz1jfHxzLG89aT9gJHtpfS4ke2N9YDpjKTtsZXQgYT1yWzJdJiZyWzJdLnN0YXJ0c1dpdGgoImZpbGU6Ly8iKT9yWzJdLnNsaWNlKDcpOnJbMl07Y29uc3QgdT0ibmF0aXZlIj09PXJbNV07cmV0dXJuIGEmJmEubWF0Y2goL1wvW0EtWl06LykmJihhPWEuc2xpY2UoMSkpLGF8fCFyWzVdfHx1fHwoYT1yWzVdKSx7ZmlsZW5hbWU6YSxtb2R1bGU6ZT9lKGEpOnZvaWQgMCxmdW5jdGlvbjpvLGxpbmVubzpwYXJzZUludChyWzNdLDEwKXx8dm9pZCAwLGNvbG5vOnBhcnNlSW50KHJbNF0sMTApfHx2b2lkIDAsaW5fYXBwOmwoYSx1KX19aWYoby5tYXRjaChuKSlyZXR1cm57ZmlsZW5hbWU6b319fWZ1bmN0aW9uIHAoZSl7cmV0dXJuIGUucmVwbGFjZSgvXltBLVpdOi8sIiIpLnJlcGxhY2UoL1xcL2csIi8iKX1jb25zdCBnPW4sbT1mdW5jdGlvbiguLi5lKXtjb25zdCBuPWUuc29ydCgoKGUsbik9PmVbMF0tblswXSkpLm1hcCgoZT0+ZVsxXSkpO3JldHVybihlLHQ9MCxvPTApPT57Y29uc3Qgcj1bXSx1PWUuc3BsaXQoIlxuIik7Zm9yKGxldCBlPXQ7ZTx1Lmxlbmd0aDtlKyspe2NvbnN0IHQ9dVtlXTtpZih0Lmxlbmd0aD4xMDI0KWNvbnRpbnVlO2NvbnN0IHM9Yy50ZXN0KHQpP3QucmVwbGFjZShjLCIkMSIpOnQ7aWYoIXMubWF0Y2goL1xTKkVycm9yOiAvKSl7Zm9yKGNvbnN0IGUgb2Ygbil7Y29uc3Qgbj1lKHMpO2lmKG4pe3IucHVzaChuKTticmVha319aWYoci5sZW5ndGg+PWkrbylicmVha319cmV0dXJuIGZ1bmN0aW9uKGUpe2lmKCFlLmxlbmd0aClyZXR1cm5bXTtjb25zdCBuPUFycmF5LmZyb20oZSk7L3NlbnRyeVdyYXBwZWQvLnRlc3QobltuLmxlbmd0aC0xXS5mdW5jdGlvbnx8IiIpJiZuLnBvcCgpO24ucmV2ZXJzZSgpLGEudGVzdChuW24ubGVuZ3RoLTFdLmZ1bmN0aW9ufHwiIikmJihuLnBvcCgpLGEudGVzdChuW24ubGVuZ3RoLTFdLmZ1bmN0aW9ufHwiIikmJm4ucG9wKCkpO3JldHVybiBuLnNsaWNlKDAsaSkubWFwKChlPT4oey4uLmUsZmlsZW5hbWU6ZS5maWxlbmFtZXx8bltuLmxlbmd0aC0xXS5maWxlbmFtZSxmdW5jdGlvbjplLmZ1bmN0aW9ufHxzfSkpKX0oci5zbGljZShvKSl9fShbOTAsZChmdW5jdGlvbihlPShwcm9jZXNzLmFyZ3ZbMV0/Zihwcm9jZXNzLmFyZ3ZbMV0pOnByb2Nlc3MuY3dkKCkpLG49IlxcIj09PXIpe2NvbnN0IHQ9bj9wKGUpOmU7cmV0dXJuIGU9PntpZighZSlyZXR1cm47Y29uc3Qgcj1uP3AoZSk6ZTtsZXR7ZGlyOmksYmFzZTpzLGV4dDpjfT1vLnBhcnNlKHIpOyIuanMiIT09YyYmIi5tanMiIT09YyYmIi5janMiIT09Y3x8KHM9cy5zbGljZSgwLC0xKmMubGVuZ3RoKSksaXx8KGk9Ii4iKTtjb25zdCBhPWkubGFzdEluZGV4T2YoIi9ub2RlX21vZHVsZXMiKTtpZihhPi0xKXJldHVybmAke2kuc2xpY2UoYSsxNCkucmVwbGFjZSgvXC8vZywiLiIpfToke3N9YDtpZihpLnN0YXJ0c1dpdGgodCkpe2xldCBlPWkuc2xpY2UodC5sZW5ndGgrMSkucmVwbGFjZSgvXC8vZywiLiIpO3JldHVybiBlJiYoZSs9IjoiKSxlKz1zLGV9cmV0dXJuIHN9fShnLmJhc2VQYXRoKSldKTtmdW5jdGlvbiBiKC4uLmUpe2cuZGVidWcmJmNvbnNvbGUubG9nKCJbTG9jYWxWYXJpYWJsZXMgV29ya2VyXSIsLi4uZSl9YXN5bmMgZnVuY3Rpb24gdihlLG4sdCxvKXtjb25zdCByPWF3YWl0IGUucG9zdCgiUnVudGltZS5nZXRQcm9wZXJ0aWVzIix7b2JqZWN0SWQ6bixvd25Qcm9wZXJ0aWVzOiEwfSk7b1t0XT1yLnJlc3VsdC5maWx0ZXIoKGU9PiJsZW5ndGgiIT09ZS5uYW1lJiYhaXNOYU4ocGFyc2VJbnQoZS5uYW1lLDEwKSkpKS5zb3J0KCgoZSxuKT0+cGFyc2VJbnQoZS5uYW1lLDEwKS1wYXJzZUludChuLm5hbWUsMTApKSkubWFwKChlPT5lLnZhbHVlPy52YWx1ZSkpfWFzeW5jIGZ1bmN0aW9uICQoZSxuLHQsbyl7Y29uc3Qgcj1hd2FpdCBlLnBvc3QoIlJ1bnRpbWUuZ2V0UHJvcGVydGllcyIse29iamVjdElkOm4sb3duUHJvcGVydGllczohMH0pO29bdF09ci5yZXN1bHQubWFwKChlPT5bZS5uYW1lLGUudmFsdWU/LnZhbHVlXSkpLnJlZHVjZSgoKGUsW24sdF0pPT4oZVtuXT10LGUpKSx7fSl9ZnVuY3Rpb24gdyhlLG4pe2UudmFsdWUmJigidmFsdWUiaW4gZS52YWx1ZT92b2lkIDA9PT1lLnZhbHVlLnZhbHVlfHxudWxsPT09ZS52YWx1ZS52YWx1ZT9uW2UubmFtZV09YDwke2UudmFsdWUudmFsdWV9PmA6bltlLm5hbWVdPWUudmFsdWUudmFsdWU6ImRlc2NyaXB0aW9uImluIGUudmFsdWUmJiJmdW5jdGlvbiIhPT1lLnZhbHVlLnR5cGU/bltlLm5hbWVdPWA8JHtlLnZhbHVlLmRlc2NyaXB0aW9ufT5gOiJ1bmRlZmluZWQiPT09ZS52YWx1ZS50eXBlJiYobltlLm5hbWVdPSI8dW5kZWZpbmVkPiIpKX1hc3luYyBmdW5jdGlvbiB5KGUsbil7Y29uc3QgdD1hd2FpdCBlLnBvc3QoIlJ1bnRpbWUuZ2V0UHJvcGVydGllcyIse29iamVjdElkOm4sb3duUHJvcGVydGllczohMH0pLG89e307Zm9yKGNvbnN0IG4gb2YgdC5yZXN1bHQpaWYobj8udmFsdWU/Lm9iamVjdElkJiYiQXJyYXkiPT09bj8udmFsdWUuY2xhc3NOYW1lKXtjb25zdCB0PW4udmFsdWUub2JqZWN0SWQ7YXdhaXQgdihlLHQsbi5uYW1lLG8pfWVsc2UgaWYobj8udmFsdWU/Lm9iamVjdElkJiYiT2JqZWN0Ij09PW4/LnZhbHVlPy5jbGFzc05hbWUpe2NvbnN0IHQ9bi52YWx1ZS5vYmplY3RJZDthd2FpdCAkKGUsdCxuLm5hbWUsbyl9ZWxzZSBuPy52YWx1ZSYmdyhuLG8pO3JldHVybiBvfWxldCBoOyhhc3luYyBmdW5jdGlvbigpe2NvbnN0IG49bmV3IGU7bi5jb25uZWN0VG9NYWluVGhyZWFkKCksYigiQ29ubmVjdGVkIHRvIG1haW4gdGhyZWFkIik7bGV0IG89ITE7bi5vbigiRGVidWdnZXIucmVzdW1lZCIsKCgpPT57bz0hMX0pKSxuLm9uKCJEZWJ1Z2dlci5wYXVzZWQiLChlPT57bz0hMCxhc3luYyBmdW5jdGlvbihlLG4se3JlYXNvbjpvLGRhdGE6cixjYWxsRnJhbWVzOml9KXtpZigiZXhjZXB0aW9uIiE9PW8mJiJwcm9taXNlUmVqZWN0aW9uIiE9PW8pcmV0dXJuO2g/LigpO2NvbnN0IHM9ZnVuY3Rpb24oZSxuKXtpZih2b2lkIDAhPT1uKXJldHVybiBmdW5jdGlvbihlKXtpZih2b2lkIDAhPT1lKXJldHVybiBlLnNsaWNlKC0xMCkucmVkdWNlKCgoZSxuKT0+YCR7ZX0sJHtuLmZ1bmN0aW9ufSwke24ubGluZW5vfSwke24uY29sbm99YCksIiIpfShlKG4sMSkpfShuLHI/LmRlc2NyaXB0aW9uKTtpZihudWxsPT1zKXJldHVybjtjb25zdCBjPVtdO2ZvcihsZXQgbj0wO248aS5sZW5ndGg7bisrKXtjb25zdHtzY29wZUNoYWluOnQsZnVuY3Rpb25OYW1lOm8sdGhpczpyfT1pW25dLHM9dC5maW5kKChlPT4ibG9jYWwiPT09ZS50eXBlKSksYT0iZ2xvYmFsIiE9PXIuY2xhc3NOYW1lJiZyLmNsYXNzTmFtZT9gJHtyLmNsYXNzTmFtZX0uJHtvfWA6bztpZih2b2lkIDA9PT1zPy5vYmplY3Qub2JqZWN0SWQpY1tuXT17ZnVuY3Rpb246YX07ZWxzZXtjb25zdCB0PWF3YWl0IHkoZSxzLm9iamVjdC5vYmplY3RJZCk7Y1tuXT17ZnVuY3Rpb246YSx2YXJzOnR9fX10Py5wb3N0TWVzc2FnZSh7ZXhjZXB0aW9uSGFzaDpzLGZyYW1lczpjfSl9KG4sbSxlLnBhcmFtcykudGhlbigoKCk9Pm8/bi5wb3N0KCJEZWJ1Z2dlci5yZXN1bWUiKTpQcm9taXNlLnJlc29sdmUoKSksKGU9Pnt9KSl9KSksYXdhaXQgbi5wb3N0KCJEZWJ1Z2dlci5lbmFibGUiKTtjb25zdCByPSExIT09Zy5jYXB0dXJlQWxsRXhjZXB0aW9ucztpZihhd2FpdCBuLnBvc3QoIkRlYnVnZ2VyLnNldFBhdXNlT25FeGNlcHRpb25zIix7c3RhdGU6cj8iYWxsIjoidW5jYXVnaHQifSkscil7Y29uc3QgZT1nLm1heEV4Y2VwdGlvbnNQZXJTZWNvbmR8fDUwO2g9ZnVuY3Rpb24oZSxuLHQpe2xldCBvPTAscj01LGk9MDtyZXR1cm4gc2V0SW50ZXJ2YWwoKCgpPT57MD09PWk/bz5lJiYocio9Mix0KHIpLHI+ODY0MDAmJihyPTg2NDAwKSxpPXIpOihpLT0xLDA9PT1pJiZuKCkpLG89MH0pLDFlMykudW5yZWYoKSwoKT0+e28rPTF9fShlLChhc3luYygpPT57YigiUmF0ZS1saW1pdCBsaWZ0ZWQuIiksYXdhaXQgbi5wb3N0KCJEZWJ1Z2dlci5zZXRQYXVzZU9uRXhjZXB0aW9ucyIse3N0YXRlOiJhbGwifSl9KSwoYXN5bmMgZT0+e2IoYFJhdGUtbGltaXQgZXhjZWVkZWQuIERpc2FibGluZyBjYXB0dXJpbmcgb2YgY2F1Z2h0IGV4Y2VwdGlvbnMgZm9yICR7ZX0gc2Vjb25kcy5gKSxhd2FpdCBuLnBvc3QoIkRlYnVnZ2VyLnNldFBhdXNlT25FeGNlcHRpb25zIix7c3RhdGU6InVuY2F1Z2h0In0pfSkpfX0pKCkuY2F0Y2goKGU9PntiKCJGYWlsZWQgdG8gc3RhcnQgZGVidWdnZXIiLGUpfSkpLHNldEludGVydmFsKCgoKT0+e30pLDFlNCk7';
|
|
959
|
-
|
|
960
|
-
function log$1(...args) {
|
|
961
|
-
logger.log('[LocalVariables]', ...args);
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
/**
|
|
965
|
-
* Adds local variables to exception frames
|
|
966
|
-
*/
|
|
967
|
-
const localVariablesAsyncIntegration = defineIntegration(((
|
|
968
|
-
integrationOptions = {},
|
|
969
|
-
) => {
|
|
970
|
-
const cachedFrames = new LRUMap(20);
|
|
971
|
-
|
|
972
|
-
function addLocalVariablesToException(exception) {
|
|
973
|
-
const hash = hashFrames(_optionalChain$b([exception, 'optionalAccess', _ => _.stacktrace, 'optionalAccess', _2 => _2.frames]));
|
|
974
|
-
|
|
975
|
-
if (hash === undefined) {
|
|
976
|
-
return;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
// Check if we have local variables for an exception that matches the hash
|
|
980
|
-
// remove is identical to get but also removes the entry from the cache
|
|
981
|
-
const cachedFrame = cachedFrames.remove(hash);
|
|
982
|
-
|
|
983
|
-
if (cachedFrame === undefined) {
|
|
984
|
-
return;
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
// Filter out frames where the function name is `new Promise` since these are in the error.stack frames
|
|
988
|
-
// but do not appear in the debugger call frames
|
|
989
|
-
const frames = (_optionalChain$b([exception, 'access', _3 => _3.stacktrace, 'optionalAccess', _4 => _4.frames]) || []).filter(frame => frame.function !== 'new Promise');
|
|
990
|
-
|
|
991
|
-
for (let i = 0; i < frames.length; i++) {
|
|
992
|
-
// Sentry frames are in reverse order
|
|
993
|
-
const frameIndex = frames.length - i - 1;
|
|
994
|
-
|
|
995
|
-
// Drop out if we run out of frames to match up
|
|
996
|
-
if (!frames[frameIndex] || !cachedFrame[i]) {
|
|
997
|
-
break;
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
if (
|
|
1001
|
-
// We need to have vars to add
|
|
1002
|
-
cachedFrame[i].vars === undefined ||
|
|
1003
|
-
// We're not interested in frames that are not in_app because the vars are not relevant
|
|
1004
|
-
frames[frameIndex].in_app === false ||
|
|
1005
|
-
// The function names need to match
|
|
1006
|
-
!functionNamesMatch(frames[frameIndex].function, cachedFrame[i].function)
|
|
1007
|
-
) {
|
|
1008
|
-
continue;
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
frames[frameIndex].vars = cachedFrame[i].vars;
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
function addLocalVariablesToEvent(event) {
|
|
1016
|
-
for (const exception of _optionalChain$b([event, 'access', _5 => _5.exception, 'optionalAccess', _6 => _6.values]) || []) {
|
|
1017
|
-
addLocalVariablesToException(exception);
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
return event;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
async function startInspector() {
|
|
1024
|
-
// We load inspector dynamically because on some platforms Node is built without inspector support
|
|
1025
|
-
const inspector = await import('inspector');
|
|
1026
|
-
if (!inspector.url()) {
|
|
1027
|
-
inspector.open(0);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
function startWorker(options) {
|
|
1032
|
-
const worker = new Worker(new URL(`data:application/javascript;base64,${base64WorkerScript$1}`), {
|
|
1033
|
-
workerData: options,
|
|
1034
|
-
// We don't want any Node args to be passed to the worker
|
|
1035
|
-
execArgv: [],
|
|
1036
|
-
});
|
|
1037
|
-
|
|
1038
|
-
process.on('exit', () => {
|
|
1039
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1040
|
-
worker.terminate();
|
|
1041
|
-
});
|
|
1042
|
-
|
|
1043
|
-
worker.on('message', ({ exceptionHash, frames }) => {
|
|
1044
|
-
cachedFrames.set(exceptionHash, frames);
|
|
1045
|
-
});
|
|
1046
|
-
|
|
1047
|
-
worker.once('error', (err) => {
|
|
1048
|
-
log$1('Worker error', err);
|
|
1049
|
-
});
|
|
1050
|
-
|
|
1051
|
-
worker.once('exit', (code) => {
|
|
1052
|
-
log$1('Worker exit', code);
|
|
1053
|
-
});
|
|
1054
|
-
|
|
1055
|
-
// Ensure this thread can't block app exit
|
|
1056
|
-
worker.unref();
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
return {
|
|
1060
|
-
name: 'LocalVariablesAsync',
|
|
1061
|
-
setup(client) {
|
|
1062
|
-
const clientOptions = client.getOptions();
|
|
1063
|
-
|
|
1064
|
-
if (!clientOptions.includeLocalVariables) {
|
|
1065
|
-
return;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
const options = {
|
|
1069
|
-
...integrationOptions,
|
|
1070
|
-
debug: logger.isEnabled(),
|
|
1071
|
-
};
|
|
1072
|
-
|
|
1073
|
-
startInspector().then(
|
|
1074
|
-
() => {
|
|
1075
|
-
try {
|
|
1076
|
-
startWorker(options);
|
|
1077
|
-
} catch (e) {
|
|
1078
|
-
logger.error('Failed to start worker', e);
|
|
1079
|
-
}
|
|
1080
|
-
},
|
|
1081
|
-
e => {
|
|
1082
|
-
logger.error('Failed to start inspector', e);
|
|
1083
|
-
},
|
|
1084
|
-
);
|
|
1085
|
-
},
|
|
1086
|
-
processEvent(event) {
|
|
1087
|
-
return addLocalVariablesToEvent(event);
|
|
1088
|
-
},
|
|
1089
|
-
};
|
|
1090
|
-
}) );
|
|
1091
|
-
|
|
1092
|
-
function _optionalChain$a(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
1093
|
-
/** Creates a container for callbacks to be called sequentially */
|
|
1094
|
-
function createCallbackList(complete) {
|
|
1095
|
-
// A collection of callbacks to be executed last to first
|
|
1096
|
-
let callbacks = [];
|
|
1097
|
-
|
|
1098
|
-
let completedCalled = false;
|
|
1099
|
-
function checkedComplete(result) {
|
|
1100
|
-
callbacks = [];
|
|
1101
|
-
if (completedCalled) {
|
|
1102
|
-
return;
|
|
1103
|
-
}
|
|
1104
|
-
completedCalled = true;
|
|
1105
|
-
complete(result);
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
// complete should be called last
|
|
1109
|
-
callbacks.push(checkedComplete);
|
|
1110
|
-
|
|
1111
|
-
function add(fn) {
|
|
1112
|
-
callbacks.push(fn);
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
function next(result) {
|
|
1116
|
-
const popped = callbacks.pop() || checkedComplete;
|
|
1117
|
-
|
|
1118
|
-
try {
|
|
1119
|
-
popped(result);
|
|
1120
|
-
} catch (_) {
|
|
1121
|
-
// If there is an error, we still want to call the complete callback
|
|
1122
|
-
checkedComplete(result);
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
return { add, next };
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
/**
|
|
1130
|
-
* Promise API is available as `Experimental` and in Node 19 only.
|
|
1131
|
-
*
|
|
1132
|
-
* Callback-based API is `Stable` since v14 and `Experimental` since v8.
|
|
1133
|
-
* Because of that, we are creating our own `AsyncSession` class.
|
|
1134
|
-
*
|
|
1135
|
-
* https://nodejs.org/docs/latest-v19.x/api/inspector.html#promises-api
|
|
1136
|
-
* https://nodejs.org/docs/latest-v14.x/api/inspector.html
|
|
1137
|
-
*/
|
|
1138
|
-
class AsyncSession {
|
|
1139
|
-
|
|
1140
|
-
/** Throws if inspector API is not available */
|
|
1141
|
-
constructor() {
|
|
1142
|
-
this._session = new Session();
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
/** @inheritdoc */
|
|
1146
|
-
configureAndConnect(onPause, captureAll) {
|
|
1147
|
-
this._session.connect();
|
|
1148
|
-
|
|
1149
|
-
this._session.on('Debugger.paused', event => {
|
|
1150
|
-
onPause(event, () => {
|
|
1151
|
-
// After the pause work is complete, resume execution or the exception context memory is leaked
|
|
1152
|
-
this._session.post('Debugger.resume');
|
|
1153
|
-
});
|
|
1154
|
-
});
|
|
1155
|
-
|
|
1156
|
-
this._session.post('Debugger.enable');
|
|
1157
|
-
this._session.post('Debugger.setPauseOnExceptions', { state: captureAll ? 'all' : 'uncaught' });
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
setPauseOnExceptions(captureAll) {
|
|
1161
|
-
this._session.post('Debugger.setPauseOnExceptions', { state: captureAll ? 'all' : 'uncaught' });
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
/** @inheritdoc */
|
|
1165
|
-
getLocalVariables(objectId, complete) {
|
|
1166
|
-
this._getProperties(objectId, props => {
|
|
1167
|
-
const { add, next } = createCallbackList(complete);
|
|
1168
|
-
|
|
1169
|
-
for (const prop of props) {
|
|
1170
|
-
if (_optionalChain$a([prop, 'optionalAccess', _2 => _2.value, 'optionalAccess', _3 => _3.objectId]) && _optionalChain$a([prop, 'optionalAccess', _4 => _4.value, 'access', _5 => _5.className]) === 'Array') {
|
|
1171
|
-
const id = prop.value.objectId;
|
|
1172
|
-
add(vars => this._unrollArray(id, prop.name, vars, next));
|
|
1173
|
-
} else if (_optionalChain$a([prop, 'optionalAccess', _6 => _6.value, 'optionalAccess', _7 => _7.objectId]) && _optionalChain$a([prop, 'optionalAccess', _8 => _8.value, 'optionalAccess', _9 => _9.className]) === 'Object') {
|
|
1174
|
-
const id = prop.value.objectId;
|
|
1175
|
-
add(vars => this._unrollObject(id, prop.name, vars, next));
|
|
1176
|
-
} else if (_optionalChain$a([prop, 'optionalAccess', _10 => _10.value])) {
|
|
1177
|
-
add(vars => this._unrollOther(prop, vars, next));
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
next({});
|
|
1182
|
-
});
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
/**
|
|
1186
|
-
* Gets all the PropertyDescriptors of an object
|
|
1187
|
-
*/
|
|
1188
|
-
_getProperties(objectId, next) {
|
|
1189
|
-
this._session.post(
|
|
1190
|
-
'Runtime.getProperties',
|
|
1191
|
-
{
|
|
1192
|
-
objectId,
|
|
1193
|
-
ownProperties: true,
|
|
1194
|
-
},
|
|
1195
|
-
(err, params) => {
|
|
1196
|
-
if (err) {
|
|
1197
|
-
next([]);
|
|
1198
|
-
} else {
|
|
1199
|
-
next(params.result);
|
|
1200
|
-
}
|
|
1201
|
-
},
|
|
1202
|
-
);
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
/**
|
|
1206
|
-
* Unrolls an array property
|
|
1207
|
-
*/
|
|
1208
|
-
_unrollArray(objectId, name, vars, next) {
|
|
1209
|
-
this._getProperties(objectId, props => {
|
|
1210
|
-
vars[name] = props
|
|
1211
|
-
.filter(v => v.name !== 'length' && !isNaN(parseInt(v.name, 10)))
|
|
1212
|
-
.sort((a, b) => parseInt(a.name, 10) - parseInt(b.name, 10))
|
|
1213
|
-
.map(v => _optionalChain$a([v, 'optionalAccess', _11 => _11.value, 'optionalAccess', _12 => _12.value]));
|
|
1214
|
-
|
|
1215
|
-
next(vars);
|
|
1216
|
-
});
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
/**
|
|
1220
|
-
* Unrolls an object property
|
|
1221
|
-
*/
|
|
1222
|
-
_unrollObject(objectId, name, vars, next) {
|
|
1223
|
-
this._getProperties(objectId, props => {
|
|
1224
|
-
vars[name] = props
|
|
1225
|
-
.map(v => [v.name, _optionalChain$a([v, 'optionalAccess', _13 => _13.value, 'optionalAccess', _14 => _14.value])])
|
|
1226
|
-
.reduce((obj, [key, val]) => {
|
|
1227
|
-
obj[key] = val;
|
|
1228
|
-
return obj;
|
|
1229
|
-
}, {} );
|
|
1230
|
-
|
|
1231
|
-
next(vars);
|
|
1232
|
-
});
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
/**
|
|
1236
|
-
* Unrolls other properties
|
|
1237
|
-
*/
|
|
1238
|
-
_unrollOther(prop, vars, next) {
|
|
1239
|
-
if (prop.value) {
|
|
1240
|
-
if ('value' in prop.value) {
|
|
1241
|
-
if (prop.value.value === undefined || prop.value.value === null) {
|
|
1242
|
-
vars[prop.name] = `<${prop.value.value}>`;
|
|
1243
|
-
} else {
|
|
1244
|
-
vars[prop.name] = prop.value.value;
|
|
1245
|
-
}
|
|
1246
|
-
} else if ('description' in prop.value && prop.value.type !== 'function') {
|
|
1247
|
-
vars[prop.name] = `<${prop.value.description}>`;
|
|
1248
|
-
} else if (prop.value.type === 'undefined') {
|
|
1249
|
-
vars[prop.name] = '<undefined>';
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
next(vars);
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
/**
|
|
1258
|
-
* When using Vercel pkg, the inspector module is not available.
|
|
1259
|
-
* https://github.com/getsentry/sentry-javascript/issues/6769
|
|
1260
|
-
*/
|
|
1261
|
-
function tryNewAsyncSession() {
|
|
1262
|
-
try {
|
|
1263
|
-
return new AsyncSession();
|
|
1264
|
-
} catch (e) {
|
|
1265
|
-
return undefined;
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
const INTEGRATION_NAME$5 = 'LocalVariables';
|
|
1270
|
-
|
|
1271
|
-
/**
|
|
1272
|
-
* Adds local variables to exception frames
|
|
1273
|
-
*/
|
|
1274
|
-
const _localVariablesSyncIntegration = ((
|
|
1275
|
-
options = {},
|
|
1276
|
-
session = tryNewAsyncSession(),
|
|
1277
|
-
) => {
|
|
1278
|
-
const cachedFrames = new LRUMap(20);
|
|
1279
|
-
let rateLimiter;
|
|
1280
|
-
let shouldProcessEvent = false;
|
|
1281
|
-
|
|
1282
|
-
function handlePaused(
|
|
1283
|
-
stackParser,
|
|
1284
|
-
{ params: { reason, data, callFrames } },
|
|
1285
|
-
complete,
|
|
1286
|
-
) {
|
|
1287
|
-
if (reason !== 'exception' && reason !== 'promiseRejection') {
|
|
1288
|
-
complete();
|
|
1289
|
-
return;
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
_optionalChain$a([rateLimiter, 'optionalCall', _15 => _15()]);
|
|
1293
|
-
|
|
1294
|
-
// data.description contains the original error.stack
|
|
1295
|
-
const exceptionHash = hashFromStack(stackParser, _optionalChain$a([data, 'optionalAccess', _16 => _16.description]));
|
|
1296
|
-
|
|
1297
|
-
if (exceptionHash == undefined) {
|
|
1298
|
-
complete();
|
|
1299
|
-
return;
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
const { add, next } = createCallbackList(frames => {
|
|
1303
|
-
cachedFrames.set(exceptionHash, frames);
|
|
1304
|
-
complete();
|
|
1305
|
-
});
|
|
1306
|
-
|
|
1307
|
-
// Because we're queuing up and making all these calls synchronously, we can potentially overflow the stack
|
|
1308
|
-
// For this reason we only attempt to get local variables for the first 5 frames
|
|
1309
|
-
for (let i = 0; i < Math.min(callFrames.length, 5); i++) {
|
|
1310
|
-
const { scopeChain, functionName, this: obj } = callFrames[i];
|
|
1311
|
-
|
|
1312
|
-
const localScope = scopeChain.find(scope => scope.type === 'local');
|
|
1313
|
-
|
|
1314
|
-
// obj.className is undefined in ESM modules
|
|
1315
|
-
const fn = obj.className === 'global' || !obj.className ? functionName : `${obj.className}.${functionName}`;
|
|
1316
|
-
|
|
1317
|
-
if (_optionalChain$a([localScope, 'optionalAccess', _17 => _17.object, 'access', _18 => _18.objectId]) === undefined) {
|
|
1318
|
-
add(frames => {
|
|
1319
|
-
frames[i] = { function: fn };
|
|
1320
|
-
next(frames);
|
|
1321
|
-
});
|
|
1322
|
-
} else {
|
|
1323
|
-
const id = localScope.object.objectId;
|
|
1324
|
-
add(frames =>
|
|
1325
|
-
_optionalChain$a([session, 'optionalAccess', _19 => _19.getLocalVariables, 'call', _20 => _20(id, vars => {
|
|
1326
|
-
frames[i] = { function: fn, vars };
|
|
1327
|
-
next(frames);
|
|
1328
|
-
})]),
|
|
1329
|
-
);
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
next([]);
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
function addLocalVariablesToException(exception) {
|
|
1337
|
-
const hash = hashFrames(_optionalChain$a([exception, 'optionalAccess', _21 => _21.stacktrace, 'optionalAccess', _22 => _22.frames]));
|
|
1338
|
-
|
|
1339
|
-
if (hash === undefined) {
|
|
1340
|
-
return;
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
// Check if we have local variables for an exception that matches the hash
|
|
1344
|
-
// remove is identical to get but also removes the entry from the cache
|
|
1345
|
-
const cachedFrame = cachedFrames.remove(hash);
|
|
1346
|
-
|
|
1347
|
-
if (cachedFrame === undefined) {
|
|
1348
|
-
return;
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
// Filter out frames where the function name is `new Promise` since these are in the error.stack frames
|
|
1352
|
-
// but do not appear in the debugger call frames
|
|
1353
|
-
const frames = (_optionalChain$a([exception, 'access', _23 => _23.stacktrace, 'optionalAccess', _24 => _24.frames]) || []).filter(frame => frame.function !== 'new Promise');
|
|
1354
|
-
|
|
1355
|
-
for (let i = 0; i < frames.length; i++) {
|
|
1356
|
-
// Sentry frames are in reverse order
|
|
1357
|
-
const frameIndex = frames.length - i - 1;
|
|
1358
|
-
|
|
1359
|
-
// Drop out if we run out of frames to match up
|
|
1360
|
-
if (!frames[frameIndex] || !cachedFrame[i]) {
|
|
1361
|
-
break;
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
if (
|
|
1365
|
-
// We need to have vars to add
|
|
1366
|
-
cachedFrame[i].vars === undefined ||
|
|
1367
|
-
// We're not interested in frames that are not in_app because the vars are not relevant
|
|
1368
|
-
frames[frameIndex].in_app === false ||
|
|
1369
|
-
// The function names need to match
|
|
1370
|
-
!functionNamesMatch(frames[frameIndex].function, cachedFrame[i].function)
|
|
1371
|
-
) {
|
|
1372
|
-
continue;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
frames[frameIndex].vars = cachedFrame[i].vars;
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
function addLocalVariablesToEvent(event) {
|
|
1380
|
-
for (const exception of _optionalChain$a([event, 'optionalAccess', _25 => _25.exception, 'optionalAccess', _26 => _26.values]) || []) {
|
|
1381
|
-
addLocalVariablesToException(exception);
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
return event;
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
return {
|
|
1388
|
-
name: INTEGRATION_NAME$5,
|
|
1389
|
-
setupOnce() {
|
|
1390
|
-
const client = getClient$1();
|
|
1391
|
-
const clientOptions = _optionalChain$a([client, 'optionalAccess', _27 => _27.getOptions, 'call', _28 => _28()]);
|
|
1392
|
-
|
|
1393
|
-
if (session && _optionalChain$a([clientOptions, 'optionalAccess', _29 => _29.includeLocalVariables])) {
|
|
1394
|
-
// Only setup this integration if the Node version is >= v18
|
|
1395
|
-
// https://github.com/getsentry/sentry-javascript/issues/7697
|
|
1396
|
-
const unsupportedNodeVersion = NODE_MAJOR < 18;
|
|
1397
|
-
|
|
1398
|
-
if (unsupportedNodeVersion) {
|
|
1399
|
-
logger.log('The `LocalVariables` integration is only supported on Node >= v18.');
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
const captureAll = options.captureAllExceptions !== false;
|
|
1404
|
-
|
|
1405
|
-
session.configureAndConnect(
|
|
1406
|
-
(ev, complete) =>
|
|
1407
|
-
handlePaused(clientOptions.stackParser, ev , complete),
|
|
1408
|
-
captureAll,
|
|
1409
|
-
);
|
|
1410
|
-
|
|
1411
|
-
if (captureAll) {
|
|
1412
|
-
const max = options.maxExceptionsPerSecond || 50;
|
|
1413
|
-
|
|
1414
|
-
rateLimiter = createRateLimiter(
|
|
1415
|
-
max,
|
|
1416
|
-
() => {
|
|
1417
|
-
logger.log('Local variables rate-limit lifted.');
|
|
1418
|
-
_optionalChain$a([session, 'optionalAccess', _30 => _30.setPauseOnExceptions, 'call', _31 => _31(true)]);
|
|
1419
|
-
},
|
|
1420
|
-
seconds => {
|
|
1421
|
-
logger.log(
|
|
1422
|
-
`Local variables rate-limit exceeded. Disabling capturing of caught exceptions for ${seconds} seconds.`,
|
|
1423
|
-
);
|
|
1424
|
-
_optionalChain$a([session, 'optionalAccess', _32 => _32.setPauseOnExceptions, 'call', _33 => _33(false)]);
|
|
1425
|
-
},
|
|
1426
|
-
);
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
shouldProcessEvent = true;
|
|
1430
|
-
}
|
|
1431
|
-
},
|
|
1432
|
-
processEvent(event) {
|
|
1433
|
-
if (shouldProcessEvent) {
|
|
1434
|
-
return addLocalVariablesToEvent(event);
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
return event;
|
|
1438
|
-
},
|
|
1439
|
-
// These are entirely for testing
|
|
1440
|
-
_getCachedFramesCount() {
|
|
1441
|
-
return cachedFrames.size;
|
|
1442
|
-
},
|
|
1443
|
-
_getFirstCachedFrame() {
|
|
1444
|
-
return cachedFrames.values()[0];
|
|
1445
|
-
},
|
|
1446
|
-
};
|
|
1447
|
-
}) ;
|
|
1448
|
-
|
|
1449
|
-
/**
|
|
1450
|
-
* Adds local variables to exception frames.
|
|
1451
|
-
*/
|
|
1452
|
-
const localVariablesSyncIntegration = defineIntegration(_localVariablesSyncIntegration);
|
|
1453
|
-
|
|
1454
|
-
const localVariablesIntegration = (options = {}) => {
|
|
1455
|
-
return NODE_VERSION.major < 19 ? localVariablesSyncIntegration(options) : localVariablesAsyncIntegration(options);
|
|
1456
|
-
};
|
|
1457
|
-
|
|
1458
|
-
let moduleCache;
|
|
1459
|
-
|
|
1460
|
-
const INTEGRATION_NAME$4 = 'Modules';
|
|
1461
|
-
|
|
1462
|
-
const _modulesIntegration = (() => {
|
|
1463
|
-
return {
|
|
1464
|
-
name: INTEGRATION_NAME$4,
|
|
1465
|
-
processEvent(event) {
|
|
1466
|
-
event.modules = {
|
|
1467
|
-
...event.modules,
|
|
1468
|
-
..._getModules(),
|
|
1469
|
-
};
|
|
1470
|
-
|
|
1471
|
-
return event;
|
|
1472
|
-
},
|
|
1473
|
-
};
|
|
1474
|
-
}) ;
|
|
1475
|
-
|
|
1476
|
-
/**
|
|
1477
|
-
* Add node modules / packages to the event.
|
|
1478
|
-
*/
|
|
1479
|
-
const modulesIntegration = defineIntegration(_modulesIntegration);
|
|
1480
|
-
|
|
1481
|
-
/** Extract information about paths */
|
|
1482
|
-
function getPaths() {
|
|
1483
|
-
try {
|
|
1484
|
-
return require.cache ? Object.keys(require.cache ) : [];
|
|
1485
|
-
} catch (e) {
|
|
1486
|
-
return [];
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
/** Extract information about package.json modules */
|
|
1491
|
-
function collectModules()
|
|
1492
|
-
|
|
1493
|
-
{
|
|
1494
|
-
const mainPaths = (require.main && require.main.paths) || [];
|
|
1495
|
-
const paths = getPaths();
|
|
1496
|
-
const infos
|
|
1497
|
-
|
|
1498
|
-
= {};
|
|
1499
|
-
const seen
|
|
1500
|
-
|
|
1501
|
-
= {};
|
|
1502
|
-
|
|
1503
|
-
paths.forEach(path => {
|
|
1504
|
-
let dir = path;
|
|
1505
|
-
|
|
1506
|
-
/** Traverse directories upward in the search of package.json file */
|
|
1507
|
-
const updir = () => {
|
|
1508
|
-
const orig = dir;
|
|
1509
|
-
dir = dirname(orig);
|
|
1510
|
-
|
|
1511
|
-
if (!dir || orig === dir || seen[orig]) {
|
|
1512
|
-
return undefined;
|
|
1513
|
-
}
|
|
1514
|
-
if (mainPaths.indexOf(dir) < 0) {
|
|
1515
|
-
return updir();
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
const pkgfile = join(orig, 'package.json');
|
|
1519
|
-
seen[orig] = true;
|
|
1520
|
-
|
|
1521
|
-
if (!existsSync(pkgfile)) {
|
|
1522
|
-
return updir();
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
try {
|
|
1526
|
-
const info = JSON.parse(readFileSync(pkgfile, 'utf8'))
|
|
1527
|
-
|
|
1528
|
-
;
|
|
1529
|
-
infos[info.name] = info.version;
|
|
1530
|
-
} catch (_oO) {
|
|
1531
|
-
// no-empty
|
|
1532
|
-
}
|
|
1533
|
-
};
|
|
1534
|
-
|
|
1535
|
-
updir();
|
|
1536
|
-
});
|
|
1537
|
-
|
|
1538
|
-
return infos;
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
/** Fetches the list of modules and the versions loaded by the entry file for your node.js app. */
|
|
1542
|
-
function _getModules() {
|
|
1543
|
-
if (!moduleCache) {
|
|
1544
|
-
moduleCache = collectModules();
|
|
1545
|
-
}
|
|
1546
|
-
return moduleCache;
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
const DEFAULT_SHUTDOWN_TIMEOUT = 2000;
|
|
1550
|
-
|
|
1551
|
-
/**
|
|
1552
|
-
* @hidden
|
|
1553
|
-
*/
|
|
1554
|
-
function logAndExitProcess(error) {
|
|
1555
|
-
consoleSandbox(() => {
|
|
1556
|
-
// eslint-disable-next-line no-console
|
|
1557
|
-
console.error(error);
|
|
1558
|
-
});
|
|
1559
|
-
|
|
1560
|
-
const client = getClient$1();
|
|
1561
|
-
|
|
1562
|
-
if (client === undefined) {
|
|
1563
|
-
DEBUG_BUILD && logger.warn('No NodeClient was defined, we are exiting the process now.');
|
|
1564
|
-
global.process.exit(1);
|
|
1565
|
-
return;
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
const options = client.getOptions();
|
|
1569
|
-
const timeout =
|
|
1570
|
-
(options && options.shutdownTimeout && options.shutdownTimeout > 0 && options.shutdownTimeout) ||
|
|
1571
|
-
DEFAULT_SHUTDOWN_TIMEOUT;
|
|
1572
|
-
client.close(timeout).then(
|
|
1573
|
-
(result) => {
|
|
1574
|
-
if (!result) {
|
|
1575
|
-
DEBUG_BUILD && logger.warn('We reached the timeout for emptying the request buffer, still exiting now!');
|
|
1576
|
-
}
|
|
1577
|
-
global.process.exit(1);
|
|
1578
|
-
},
|
|
1579
|
-
error => {
|
|
1580
|
-
DEBUG_BUILD && logger.error(error);
|
|
1581
|
-
},
|
|
1582
|
-
);
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
const INTEGRATION_NAME$3 = 'OnUncaughtException';
|
|
1586
|
-
|
|
1587
|
-
/**
|
|
1588
|
-
* Add a global exception handler.
|
|
1589
|
-
*/
|
|
1590
|
-
const onUncaughtExceptionIntegration = defineIntegration((options = {}) => {
|
|
1591
|
-
const optionsWithDefaults = {
|
|
1592
|
-
exitEvenIfOtherHandlersAreRegistered: false,
|
|
1593
|
-
...options,
|
|
1594
|
-
};
|
|
1595
|
-
|
|
1596
|
-
return {
|
|
1597
|
-
name: INTEGRATION_NAME$3,
|
|
1598
|
-
setup(client) {
|
|
1599
|
-
global.process.on('uncaughtException', makeErrorHandler(client, optionsWithDefaults));
|
|
1600
|
-
},
|
|
1601
|
-
};
|
|
1602
|
-
});
|
|
1603
|
-
|
|
1604
|
-
/** Exported only for tests */
|
|
1605
|
-
function makeErrorHandler(client, options) {
|
|
1606
|
-
const timeout = 2000;
|
|
1607
|
-
let caughtFirstError = false;
|
|
1608
|
-
let caughtSecondError = false;
|
|
1609
|
-
let calledFatalError = false;
|
|
1610
|
-
let firstError;
|
|
1611
|
-
|
|
1612
|
-
const clientOptions = client.getOptions();
|
|
1613
|
-
|
|
1614
|
-
return Object.assign(
|
|
1615
|
-
(error) => {
|
|
1616
|
-
let onFatalError = logAndExitProcess;
|
|
1617
|
-
|
|
1618
|
-
if (options.onFatalError) {
|
|
1619
|
-
onFatalError = options.onFatalError;
|
|
1620
|
-
} else if (clientOptions.onFatalError) {
|
|
1621
|
-
onFatalError = clientOptions.onFatalError ;
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
// Attaching a listener to `uncaughtException` will prevent the node process from exiting. We generally do not
|
|
1625
|
-
// want to alter this behaviour so we check for other listeners that users may have attached themselves and adjust
|
|
1626
|
-
// exit behaviour of the SDK accordingly:
|
|
1627
|
-
// - If other listeners are attached, do not exit.
|
|
1628
|
-
// - If the only listener attached is ours, exit.
|
|
1629
|
-
const userProvidedListenersCount = (global.process.listeners('uncaughtException') ).filter(
|
|
1630
|
-
listener => {
|
|
1631
|
-
// There are 3 listeners we ignore:
|
|
1632
|
-
return (
|
|
1633
|
-
// as soon as we're using domains this listener is attached by node itself
|
|
1634
|
-
listener.name !== 'domainUncaughtExceptionClear' &&
|
|
1635
|
-
// the handler we register for tracing
|
|
1636
|
-
listener.tag !== 'sentry_tracingErrorCallback' &&
|
|
1637
|
-
// the handler we register in this integration
|
|
1638
|
-
(listener )._errorHandler !== true
|
|
1639
|
-
);
|
|
1640
|
-
},
|
|
1641
|
-
).length;
|
|
1642
|
-
|
|
1643
|
-
const processWouldExit = userProvidedListenersCount === 0;
|
|
1644
|
-
const shouldApplyFatalHandlingLogic = options.exitEvenIfOtherHandlersAreRegistered || processWouldExit;
|
|
1645
|
-
|
|
1646
|
-
if (!caughtFirstError) {
|
|
1647
|
-
// this is the first uncaught error and the ultimate reason for shutting down
|
|
1648
|
-
// we want to do absolutely everything possible to ensure it gets captured
|
|
1649
|
-
// also we want to make sure we don't go recursion crazy if more errors happen after this one
|
|
1650
|
-
firstError = error;
|
|
1651
|
-
caughtFirstError = true;
|
|
1652
|
-
|
|
1653
|
-
if (getClient$1() === client) {
|
|
1654
|
-
captureException(error, {
|
|
1655
|
-
originalException: error,
|
|
1656
|
-
captureContext: {
|
|
1657
|
-
level: 'fatal',
|
|
1658
|
-
},
|
|
1659
|
-
mechanism: {
|
|
1660
|
-
handled: false,
|
|
1661
|
-
type: 'onuncaughtexception',
|
|
1662
|
-
},
|
|
1663
|
-
});
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
if (!calledFatalError && shouldApplyFatalHandlingLogic) {
|
|
1667
|
-
calledFatalError = true;
|
|
1668
|
-
onFatalError(error);
|
|
1669
|
-
}
|
|
1670
|
-
} else {
|
|
1671
|
-
if (shouldApplyFatalHandlingLogic) {
|
|
1672
|
-
if (calledFatalError) {
|
|
1673
|
-
// we hit an error *after* calling onFatalError - pretty boned at this point, just shut it down
|
|
1674
|
-
DEBUG_BUILD &&
|
|
1675
|
-
logger.warn(
|
|
1676
|
-
'uncaught exception after calling fatal error shutdown callback - this is bad! forcing shutdown',
|
|
1677
|
-
);
|
|
1678
|
-
logAndExitProcess(error);
|
|
1679
|
-
} else if (!caughtSecondError) {
|
|
1680
|
-
// two cases for how we can hit this branch:
|
|
1681
|
-
// - capturing of first error blew up and we just caught the exception from that
|
|
1682
|
-
// - quit trying to capture, proceed with shutdown
|
|
1683
|
-
// - a second independent error happened while waiting for first error to capture
|
|
1684
|
-
// - want to avoid causing premature shutdown before first error capture finishes
|
|
1685
|
-
// it's hard to immediately tell case 1 from case 2 without doing some fancy/questionable domain stuff
|
|
1686
|
-
// so let's instead just delay a bit before we proceed with our action here
|
|
1687
|
-
// in case 1, we just wait a bit unnecessarily but ultimately do the same thing
|
|
1688
|
-
// in case 2, the delay hopefully made us wait long enough for the capture to finish
|
|
1689
|
-
// two potential nonideal outcomes:
|
|
1690
|
-
// nonideal case 1: capturing fails fast, we sit around for a few seconds unnecessarily before proceeding correctly by calling onFatalError
|
|
1691
|
-
// nonideal case 2: case 2 happens, 1st error is captured but slowly, timeout completes before capture and we treat second error as the sendErr of (nonexistent) failure from trying to capture first error
|
|
1692
|
-
// note that after hitting this branch, we might catch more errors where (caughtSecondError && !calledFatalError)
|
|
1693
|
-
// we ignore them - they don't matter to us, we're just waiting for the second error timeout to finish
|
|
1694
|
-
caughtSecondError = true;
|
|
1695
|
-
setTimeout(() => {
|
|
1696
|
-
if (!calledFatalError) {
|
|
1697
|
-
// it was probably case 1, let's treat err as the sendErr and call onFatalError
|
|
1698
|
-
calledFatalError = true;
|
|
1699
|
-
onFatalError(firstError, error);
|
|
1700
|
-
}
|
|
1701
|
-
}, timeout); // capturing could take at least sendTimeout to fail, plus an arbitrary second for how long it takes to collect surrounding source etc
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
},
|
|
1706
|
-
{ _errorHandler: true },
|
|
1707
|
-
);
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
const INTEGRATION_NAME$2 = 'OnUnhandledRejection';
|
|
1711
|
-
|
|
1712
|
-
const _onUnhandledRejectionIntegration = ((options = {}) => {
|
|
1713
|
-
const mode = options.mode || 'warn';
|
|
1714
|
-
|
|
1715
|
-
return {
|
|
1716
|
-
name: INTEGRATION_NAME$2,
|
|
1717
|
-
setup(client) {
|
|
1718
|
-
global.process.on('unhandledRejection', makeUnhandledPromiseHandler(client, { mode }));
|
|
1719
|
-
},
|
|
1720
|
-
};
|
|
1721
|
-
}) ;
|
|
1722
|
-
|
|
1723
|
-
/**
|
|
1724
|
-
* Add a global promise rejection handler.
|
|
1725
|
-
*/
|
|
1726
|
-
const onUnhandledRejectionIntegration = defineIntegration(_onUnhandledRejectionIntegration);
|
|
1727
|
-
|
|
1728
|
-
/**
|
|
1729
|
-
* Send an exception with reason
|
|
1730
|
-
* @param reason string
|
|
1731
|
-
* @param promise promise
|
|
1732
|
-
*
|
|
1733
|
-
* Exported only for tests.
|
|
1734
|
-
*/
|
|
1735
|
-
function makeUnhandledPromiseHandler(
|
|
1736
|
-
client,
|
|
1737
|
-
options,
|
|
1738
|
-
) {
|
|
1739
|
-
return function sendUnhandledPromise(reason, promise) {
|
|
1740
|
-
if (getClient$1() !== client) {
|
|
1741
|
-
return;
|
|
1742
|
-
}
|
|
1743
|
-
|
|
1744
|
-
captureException(reason, {
|
|
1745
|
-
originalException: promise,
|
|
1746
|
-
captureContext: {
|
|
1747
|
-
extra: { unhandledPromiseRejection: true },
|
|
1748
|
-
},
|
|
1749
|
-
mechanism: {
|
|
1750
|
-
handled: false,
|
|
1751
|
-
type: 'onunhandledrejection',
|
|
1752
|
-
},
|
|
1753
|
-
});
|
|
1754
|
-
|
|
1755
|
-
handleRejection(reason, options);
|
|
1756
|
-
};
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
/**
|
|
1760
|
-
* Handler for `mode` option
|
|
1761
|
-
|
|
1762
|
-
*/
|
|
1763
|
-
function handleRejection(
|
|
1764
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1765
|
-
reason,
|
|
1766
|
-
options,
|
|
1767
|
-
) {
|
|
1768
|
-
// https://github.com/nodejs/node/blob/7cf6f9e964aa00772965391c23acda6d71972a9a/lib/internal/process/promises.js#L234-L240
|
|
1769
|
-
const rejectionWarning =
|
|
1770
|
-
'This error originated either by ' +
|
|
1771
|
-
'throwing inside of an async function without a catch block, ' +
|
|
1772
|
-
'or by rejecting a promise which was not handled with .catch().' +
|
|
1773
|
-
' The promise rejected with the reason:';
|
|
1774
|
-
|
|
1775
|
-
/* eslint-disable no-console */
|
|
1776
|
-
if (options.mode === 'warn') {
|
|
1777
|
-
consoleSandbox(() => {
|
|
1778
|
-
console.warn(rejectionWarning);
|
|
1779
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
1780
|
-
console.error(reason && reason.stack ? reason.stack : reason);
|
|
1781
|
-
});
|
|
1782
|
-
} else if (options.mode === 'strict') {
|
|
1783
|
-
consoleSandbox(() => {
|
|
1784
|
-
console.warn(rejectionWarning);
|
|
1785
|
-
});
|
|
1786
|
-
logAndExitProcess(reason);
|
|
1787
|
-
}
|
|
1788
|
-
/* eslint-enable no-console */
|
|
1789
|
-
}
|
|
1790
|
-
|
|
1791
|
-
function _optionalChain$9(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
1792
|
-
// This string is a placeholder that gets overwritten with the worker code.
|
|
1793
|
-
const base64WorkerScript = 'LyohIEBzZW50cnkvbm9kZSA4LjAuMC1yYy4zICg3NTY0MDYxKSB8IGh0dHBzOi8vZ2l0aHViLmNvbS9nZXRzZW50cnkvc2VudHJ5LWphdmFzY3JpcHQgKi8KaW1wb3J0e1Nlc3Npb24gYXMgdH1mcm9tImluc3BlY3RvciI7aW1wb3J0e3BhcmVudFBvcnQgYXMgZSx3b3JrZXJEYXRhIGFzIG59ZnJvbSJ3b3JrZXJfdGhyZWFkcyI7aW1wb3J0e3Bvc2l4IGFzIHIsc2VwIGFzIG99ZnJvbSJwYXRoIjtpbXBvcnQqYXMgcyBmcm9tIm5vZGU6aHR0cCI7aW1wb3J0KmFzIGkgZnJvbSJub2RlOmh0dHBzIjtpbXBvcnR7UmVhZGFibGUgYXMgY31mcm9tInN0cmVhbSI7aW1wb3J0e2NyZWF0ZUd6aXAgYXMgdX1mcm9tInpsaWIiO2ltcG9ydCphcyBhIGZyb20ibmV0IjtpbXBvcnQqYXMgZiBmcm9tInRscyI7aW1wb3J0KmFzIGggZnJvbSJodHRwIjtjb25zdCBwPU9iamVjdC5wcm90b3R5cGUudG9TdHJpbmc7ZnVuY3Rpb24gbCh0LGUpe3JldHVybiBwLmNhbGwodCk9PT1gW29iamVjdCAke2V9XWB9ZnVuY3Rpb24gZCh0KXtyZXR1cm4gbCh0LCJPYmplY3QiKX1mdW5jdGlvbiBtKHQpe3JldHVybiBCb29sZWFuKHQmJnQudGhlbiYmImZ1bmN0aW9uIj09dHlwZW9mIHQudGhlbil9ZnVuY3Rpb24gZyh0LGUpe3RyeXtyZXR1cm4gdCBpbnN0YW5jZW9mIGV9Y2F0Y2godCl7cmV0dXJuITF9fWNvbnN0IHk9Z2xvYmFsVGhpcztmdW5jdGlvbiBiKHQsZSxuKXtjb25zdCByPW58fHksbz1yLl9fU0VOVFJZX189ci5fX1NFTlRSWV9ffHx7fTtyZXR1cm4gb1t0XXx8KG9bdF09ZSgpKX1jb25zdCB2PXksXz04MDtmdW5jdGlvbiB3KHQsZSl7Y29uc3Qgbj10LHI9W107bGV0IG8scyxpLGMsdTtpZighbnx8IW4udGFnTmFtZSlyZXR1cm4iIjtpZih2LkhUTUxFbGVtZW50JiZuIGluc3RhbmNlb2YgSFRNTEVsZW1lbnQmJm4uZGF0YXNldCl7aWYobi5kYXRhc2V0LnNlbnRyeUNvbXBvbmVudClyZXR1cm4gbi5kYXRhc2V0LnNlbnRyeUNvbXBvbmVudDtpZihuLmRhdGFzZXQuc2VudHJ5RWxlbWVudClyZXR1cm4gbi5kYXRhc2V0LnNlbnRyeUVsZW1lbnR9ci5wdXNoKG4udGFnTmFtZS50b0xvd2VyQ2FzZSgpKTtjb25zdCBhPWUmJmUubGVuZ3RoP2UuZmlsdGVyKCh0PT5uLmdldEF0dHJpYnV0ZSh0KSkpLm1hcCgodD0+W3Qsbi5nZXRBdHRyaWJ1dGUodCldKSk6bnVsbDtpZihhJiZhLmxlbmd0aClhLmZvckVhY2goKHQ9PntyLnB1c2goYFske3RbMF19PSIke3RbMV19Il1gKX0pKTtlbHNlIGlmKG4uaWQmJnIucHVzaChgIyR7bi5pZH1gKSxvPW4uY2xhc3NOYW1lLG8mJmwobywiU3RyaW5nIikpZm9yKHM9by5zcGxpdCgvXHMrLyksdT0wO3U8cy5sZW5ndGg7dSsrKXIucHVzaChgLiR7c1t1XX1gKTtjb25zdCBmPVsiYXJpYS1sYWJlbCIsInR5cGUiLCJuYW1lIiwidGl0bGUiLCJhbHQiXTtmb3IodT0wO3U8Zi5sZW5ndGg7dSsrKWk9Zlt1XSxjPW4uZ2V0QXR0cmlidXRlKGkpLGMmJnIucHVzaChgWyR7aX09IiR7Y30iXWApO3JldHVybiByLmpvaW4oIiIpfWNvbnN0IFM9InVuZGVmaW5lZCI9PXR5cGVvZiBfX1NFTlRSWV9ERUJVR19ffHxfX1NFTlRSWV9ERUJVR19fLCQ9WyJkZWJ1ZyIsImluZm8iLCJ3YXJuIiwiZXJyb3IiLCJsb2ciLCJhc3NlcnQiLCJ0cmFjZSJdLEU9e307ZnVuY3Rpb24geCh0KXtpZighKCJjb25zb2xlImluIHkpKXJldHVybiB0KCk7Y29uc3QgZT15LmNvbnNvbGUsbj17fSxyPU9iamVjdC5rZXlzKEUpO3IuZm9yRWFjaCgodD0+e2NvbnN0IHI9RVt0XTtuW3RdPWVbdF0sZVt0XT1yfSkpO3RyeXtyZXR1cm4gdCgpfWZpbmFsbHl7ci5mb3JFYWNoKCh0PT57ZVt0XT1uW3RdfSkpfX1jb25zdCBOPWZ1bmN0aW9uKCl7bGV0IHQ9ITE7Y29uc3QgZT17ZW5hYmxlOigpPT57dD0hMH0sZGlzYWJsZTooKT0+e3Q9ITF9LGlzRW5hYmxlZDooKT0+dH07cmV0dXJuIFM/JC5mb3JFYWNoKChuPT57ZVtuXT0oLi4uZSk9Pnt0JiZ4KCgoKT0+e3kuY29uc29sZVtuXShgU2VudHJ5IExvZ2dlciBbJHtufV06YCwuLi5lKX0pKX19KSk6JC5mb3JFYWNoKCh0PT57ZVt0XT0oKT0+e319KSksZX0oKTtmdW5jdGlvbiBrKHQsZT0hMSl7Y29uc3R7aG9zdDpuLHBhdGg6cixwYXNzOm8scG9ydDpzLHByb2plY3RJZDppLHByb3RvY29sOmMscHVibGljS2V5OnV9PXQ7cmV0dXJuYCR7Y306Ly8ke3V9JHtlJiZvP2A6JHtvfWA6IiJ9QCR7bn0ke3M/YDoke3N9YDoiIn0vJHtyP2Ake3J9L2A6cn0ke2l9YH1jbGFzcyBDIGV4dGVuZHMgRXJyb3J7Y29uc3RydWN0b3IodCxlPSJ3YXJuIil7c3VwZXIodCksdGhpcy5tZXNzYWdlPXQsdGhpcy5uYW1lPW5ldy50YXJnZXQucHJvdG90eXBlLmNvbnN0cnVjdG9yLm5hbWUsT2JqZWN0LnNldFByb3RvdHlwZU9mKHRoaXMsbmV3LnRhcmdldC5wcm90b3R5cGUpLHRoaXMubG9nTGV2ZWw9ZX19ZnVuY3Rpb24gRCh0KXtpZihmdW5jdGlvbih0KXtzd2l0Y2gocC5jYWxsKHQpKXtjYXNlIltvYmplY3QgRXJyb3JdIjpjYXNlIltvYmplY3QgRXhjZXB0aW9uXSI6Y2FzZSJbb2JqZWN0IERPTUV4Y2VwdGlvbl0iOnJldHVybiEwO2RlZmF1bHQ6cmV0dXJuIGcodCxFcnJvcil9fSh0KSlyZXR1cm57bWVzc2FnZTp0Lm1lc3NhZ2UsbmFtZTp0Lm5hbWUsc3RhY2s6dC5zdGFjaywuLi5PKHQpfTtpZihlPXQsInVuZGVmaW5lZCIhPXR5cGVvZiBFdmVudCYmZyhlLEV2ZW50KSl7Y29uc3QgZT17dHlwZTp0LnR5cGUsdGFyZ2V0OlQodC50YXJnZXQpLGN1cnJlbnRUYXJnZXQ6VCh0LmN1cnJlbnRUYXJnZXQpLC4uLk8odCl9O3JldHVybiJ1bmRlZmluZWQiIT10eXBlb2YgQ3VzdG9tRXZlbnQmJmcodCxDdXN0b21FdmVudCkmJihlLmRldGFpbD10LmRldGFpbCksZX1yZXR1cm4gdDt2YXIgZX1mdW5jdGlvbiBUKHQpe3RyeXtyZXR1cm4gZT10LCJ1bmRlZmluZWQiIT10eXBlb2YgRWxlbWVudCYmZyhlLEVsZW1lbnQpP2Z1bmN0aW9uKHQsZT17fSl7aWYoIXQpcmV0dXJuIjx1bmtub3duPiI7dHJ5e2xldCBuPXQ7Y29uc3Qgcj01LG89W107bGV0IHM9MCxpPTA7Y29uc3QgYz0iID4gIix1PWMubGVuZ3RoO2xldCBhO2NvbnN0IGY9QXJyYXkuaXNBcnJheShlKT9lOmUua2V5QXR0cnMsaD0hQXJyYXkuaXNBcnJheShlKSYmZS5tYXhTdHJpbmdMZW5ndGh8fF87Zm9yKDtuJiZzKys8ciYmKGE9dyhuLGYpLCEoImh0bWwiPT09YXx8cz4xJiZpK28ubGVuZ3RoKnUrYS5sZW5ndGg+PWgpKTspby5wdXNoKGEpLGkrPWEubGVuZ3RoLG49bi5wYXJlbnROb2RlO3JldHVybiBvLnJldmVyc2UoKS5qb2luKGMpfWNhdGNoKHQpe3JldHVybiI8dW5rbm93bj4ifX0odCk6T2JqZWN0LnByb3RvdHlwZS50b1N0cmluZy5jYWxsKHQpfWNhdGNoKHQpe3JldHVybiI8dW5rbm93bj4ifXZhciBlfWZ1bmN0aW9uIE8odCl7aWYoIm9iamVjdCI9PXR5cGVvZiB0JiZudWxsIT09dCl7Y29uc3QgZT17fTtmb3IoY29uc3QgbiBpbiB0KU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbCh0LG4pJiYoZVtuXT10W25dKTtyZXR1cm4gZX1yZXR1cm57fX1mdW5jdGlvbiBqKHQpe3JldHVybiBSKHQsbmV3IE1hcCl9ZnVuY3Rpb24gUih0LGUpe2lmKGZ1bmN0aW9uKHQpe2lmKCFkKHQpKXJldHVybiExO3RyeXtjb25zdCBlPU9iamVjdC5nZXRQcm90b3R5cGVPZih0KS5jb25zdHJ1Y3Rvci5uYW1lO3JldHVybiFlfHwiT2JqZWN0Ij09PWV9Y2F0Y2godCl7cmV0dXJuITB9fSh0KSl7Y29uc3Qgbj1lLmdldCh0KTtpZih2b2lkIDAhPT1uKXJldHVybiBuO2NvbnN0IHI9e307ZS5zZXQodCxyKTtmb3IoY29uc3QgbiBvZiBPYmplY3Qua2V5cyh0KSl2b2lkIDAhPT10W25dJiYocltuXT1SKHRbbl0sZSkpO3JldHVybiByfWlmKEFycmF5LmlzQXJyYXkodCkpe2NvbnN0IG49ZS5nZXQodCk7aWYodm9pZCAwIT09bilyZXR1cm4gbjtjb25zdCByPVtdO3JldHVybiBlLnNldCh0LHIpLHQuZm9yRWFjaCgodD0+e3IucHVzaChSKHQsZSkpfSkpLHJ9cmV0dXJuIHR9Y29uc3QgQT01MCxJPSI/IixQPS9jYXB0dXJlTWVzc2FnZXxjYXB0dXJlRXhjZXB0aW9uLztjb25zdCBVPSI8YW5vbnltb3VzPiI7ZnVuY3Rpb24gTSgpe2NvbnN0IHQ9eSxlPXQuY3J5cHRvfHx0Lm1zQ3J5cHRvO2xldCBuPSgpPT4xNipNYXRoLnJhbmRvbSgpO3RyeXtpZihlJiZlLnJhbmRvbVVVSUQpcmV0dXJuIGUucmFuZG9tVVVJRCgpLnJlcGxhY2UoLy0vZywiIik7ZSYmZS5nZXRSYW5kb21WYWx1ZXMmJihuPSgpPT57Y29uc3QgdD1uZXcgVWludDhBcnJheSgxKTtyZXR1cm4gZS5nZXRSYW5kb21WYWx1ZXModCksdFswXX0pfWNhdGNoKHQpe31yZXR1cm4oWzFlN10rMWUzKzRlMys4ZTMrMWUxMSkucmVwbGFjZSgvWzAxOF0vZywodD0+KHReKDE1Jm4oKSk+PnQvNCkudG9TdHJpbmcoMTYpKSl9ZnVuY3Rpb24gTCh0LGU9MTAwLG49MS8wKXt0cnl7cmV0dXJuIEIoIiIsdCxlLG4pfWNhdGNoKHQpe3JldHVybntFUlJPUjpgKipub24tc2VyaWFsaXphYmxlKiogKCR7dH0pYH19fWZ1bmN0aW9uIEIodCxlLG49MS8wLHI9MS8wLG89ZnVuY3Rpb24oKXtjb25zdCB0PSJmdW5jdGlvbiI9PXR5cGVvZiBXZWFrU2V0LGU9dD9uZXcgV2Vha1NldDpbXTtyZXR1cm5bZnVuY3Rpb24obil7aWYodClyZXR1cm4hIWUuaGFzKG4pfHwoZS5hZGQobiksITEpO2ZvcihsZXQgdD0wO3Q8ZS5sZW5ndGg7dCsrKWlmKGVbdF09PT1uKXJldHVybiEwO3JldHVybiBlLnB1c2gobiksITF9LGZ1bmN0aW9uKG4pe2lmKHQpZS5kZWxldGUobik7ZWxzZSBmb3IobGV0IHQ9MDt0PGUubGVuZ3RoO3QrKylpZihlW3RdPT09bil7ZS5zcGxpY2UodCwxKTticmVha319XX0oKSl7Y29uc3RbcyxpXT1vO2lmKG51bGw9PWV8fFsibnVtYmVyIiwiYm9vbGVhbiIsInN0cmluZyJdLmluY2x1ZGVzKHR5cGVvZiBlKSYmIU51bWJlci5pc05hTihlKSlyZXR1cm4gZTtjb25zdCBjPWZ1bmN0aW9uKHQsZSl7dHJ5e2lmKCJkb21haW4iPT09dCYmZSYmIm9iamVjdCI9PXR5cGVvZiBlJiZlLnQpcmV0dXJuIltEb21haW5dIjtpZigiZG9tYWluRW1pdHRlciI9PT10KXJldHVybiJbRG9tYWluRW1pdHRlcl0iO2lmKCJ1bmRlZmluZWQiIT10eXBlb2YgZ2xvYmFsJiZlPT09Z2xvYmFsKXJldHVybiJbR2xvYmFsXSI7aWYoInVuZGVmaW5lZCIhPXR5cGVvZiB3aW5kb3cmJmU9PT13aW5kb3cpcmV0dXJuIltXaW5kb3ddIjtpZigidW5kZWZpbmVkIiE9dHlwZW9mIGRvY3VtZW50JiZlPT09ZG9jdW1lbnQpcmV0dXJuIltEb2N1bWVudF0iO2lmKCJvYmplY3QiPT10eXBlb2Yobj1lKSYmbnVsbCE9PW4mJihuLl9faXNWdWV8fG4ubykpcmV0dXJuIltWdWVWaWV3TW9kZWxdIjtpZihmdW5jdGlvbih0KXtyZXR1cm4gZCh0KSYmIm5hdGl2ZUV2ZW50ImluIHQmJiJwcmV2ZW50RGVmYXVsdCJpbiB0JiYic3RvcFByb3BhZ2F0aW9uImluIHR9KGUpKXJldHVybiJbU3ludGhldGljRXZlbnRdIjtpZigibnVtYmVyIj09dHlwZW9mIGUmJmUhPWUpcmV0dXJuIltOYU5dIjtpZigiZnVuY3Rpb24iPT10eXBlb2YgZSlyZXR1cm5gW0Z1bmN0aW9uOiAke2Z1bmN0aW9uKHQpe3RyeXtyZXR1cm4gdCYmImZ1bmN0aW9uIj09dHlwZW9mIHQmJnQubmFtZXx8VX1jYXRjaCh0KXtyZXR1cm4gVX19KGUpfV1gO2lmKCJzeW1ib2wiPT10eXBlb2YgZSlyZXR1cm5gWyR7U3RyaW5nKGUpfV1gO2lmKCJiaWdpbnQiPT10eXBlb2YgZSlyZXR1cm5gW0JpZ0ludDogJHtTdHJpbmcoZSl9XWA7Y29uc3Qgcj1mdW5jdGlvbih0KXtjb25zdCBlPU9iamVjdC5nZXRQcm90b3R5cGVPZih0KTtyZXR1cm4gZT9lLmNvbnN0cnVjdG9yLm5hbWU6Im51bGwgcHJvdG90eXBlIn0oZSk7cmV0dXJuL15IVE1MKFx3KilFbGVtZW50JC8udGVzdChyKT9gW0hUTUxFbGVtZW50OiAke3J9XWA6YFtvYmplY3QgJHtyfV1gfWNhdGNoKHQpe3JldHVybmAqKm5vbi1zZXJpYWxpemFibGUqKiAoJHt0fSlgfXZhciBufSh0LGUpO2lmKCFjLnN0YXJ0c1dpdGgoIltvYmplY3QgIikpcmV0dXJuIGM7aWYoZS5fX3NlbnRyeV9za2lwX25vcm1hbGl6YXRpb25fXylyZXR1cm4gZTtjb25zdCB1PSJudW1iZXIiPT10eXBlb2YgZS5fX3NlbnRyeV9vdmVycmlkZV9ub3JtYWxpemF0aW9uX2RlcHRoX18/ZS5fX3NlbnRyeV9vdmVycmlkZV9ub3JtYWxpemF0aW9uX2RlcHRoX186bjtpZigwPT09dSlyZXR1cm4gYy5yZXBsYWNlKCJvYmplY3QgIiwiIik7aWYocyhlKSlyZXR1cm4iW0NpcmN1bGFyIH5dIjtjb25zdCBhPWU7aWYoYSYmImZ1bmN0aW9uIj09dHlwZW9mIGEudG9KU09OKXRyeXtyZXR1cm4gQigiIixhLnRvSlNPTigpLHUtMSxyLG8pfWNhdGNoKHQpe31jb25zdCBmPUFycmF5LmlzQXJyYXkoZSk/W106e307bGV0IGg9MDtjb25zdCBwPUQoZSk7Zm9yKGNvbnN0IHQgaW4gcCl7aWYoIU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChwLHQpKWNvbnRpbnVlO2lmKGg+PXIpe2ZbdF09IltNYXhQcm9wZXJ0aWVzIH5dIjticmVha31jb25zdCBlPXBbdF07Zlt0XT1CKHQsZSx1LTEscixvKSxoKyt9cmV0dXJuIGkoZSksZn1mdW5jdGlvbiBHKHQsZSl7Y29uc3Qgbj1lLnJlcGxhY2UoL1xcL2csIi8iKS5yZXBsYWNlKC9bfFxce30oKVtcXV4kKyo/Ll0vZywiXFwkJiIpO2xldCByPXQ7dHJ5e3I9ZGVjb2RlVVJJKHQpfWNhdGNoKHQpe31yZXR1cm4gci5yZXBsYWNlKC9cXC9nLCIvIikucmVwbGFjZSgvd2VicGFjazpcLz8vZywiIikucmVwbGFjZShuZXcgUmVnRXhwKGAoZmlsZTovLyk/Lyoke259LypgLCJpZyIpLCJhcHA6Ly8vIil9Y29uc3QgSj0vXihcUys6XFx8XC8/KShbXHNcU10qPykoKD86XC57MSwyfXxbXi9cXF0rP3wpKFwuW14uL1xcXSp8KSkoPzpbL1xcXSopJC87ZnVuY3Rpb24geih0KXtjb25zdCBlPWZ1bmN0aW9uKHQpe2NvbnN0IGU9dC5sZW5ndGg+MTAyND9gPHRydW5jYXRlZD4ke3Quc2xpY2UoLTEwMjQpfWA6dCxuPUouZXhlYyhlKTtyZXR1cm4gbj9uLnNsaWNlKDEpOltdfSh0KSxuPWVbMF07bGV0IHI9ZVsxXTtyZXR1cm4gbnx8cj8ociYmKHI9ci5zbGljZSgwLHIubGVuZ3RoLTEpKSxuK3IpOiIuIn12YXIgSDtmdW5jdGlvbiBXKHQpe3JldHVybiBuZXcgWSgoZT0+e2UodCl9KSl9IWZ1bmN0aW9uKHQpe3RbdC5QRU5ESU5HPTBdPSJQRU5ESU5HIjt0W3QuUkVTT0xWRUQ9MV09IlJFU09MVkVEIjt0W3QuUkVKRUNURUQ9Ml09IlJFSkVDVEVEIn0oSHx8KEg9e30pKTtjbGFzcyBZe2NvbnN0cnVjdG9yKHQpe1kucHJvdG90eXBlLl9faW5pdC5jYWxsKHRoaXMpLFkucHJvdG90eXBlLl9faW5pdDIuY2FsbCh0aGlzKSxZLnByb3RvdHlwZS5fX2luaXQzLmNhbGwodGhpcyksWS5wcm90b3R5cGUuX19pbml0NC5jYWxsKHRoaXMpLHRoaXMuaT1ILlBFTkRJTkcsdGhpcy51PVtdO3RyeXt0KHRoaXMuaCx0aGlzLnApfWNhdGNoKHQpe3RoaXMucCh0KX19dGhlbih0LGUpe3JldHVybiBuZXcgWSgoKG4scik9Pnt0aGlzLnUucHVzaChbITEsZT0+e2lmKHQpdHJ5e24odChlKSl9Y2F0Y2godCl7cih0KX1lbHNlIG4oZSl9LHQ9PntpZihlKXRyeXtuKGUodCkpfWNhdGNoKHQpe3IodCl9ZWxzZSByKHQpfV0pLHRoaXMubCgpfSkpfWNhdGNoKHQpe3JldHVybiB0aGlzLnRoZW4oKHQ9PnQpLHQpfWZpbmFsbHkodCl7cmV0dXJuIG5ldyBZKCgoZSxuKT0+e2xldCByLG87cmV0dXJuIHRoaXMudGhlbigoZT0+e289ITEscj1lLHQmJnQoKX0pLChlPT57bz0hMCxyPWUsdCYmdCgpfSkpLnRoZW4oKCgpPT57bz9uKHIpOmUocil9KSl9KSl9X19pbml0KCl7dGhpcy5oPXQ9Pnt0aGlzLm0oSC5SRVNPTFZFRCx0KX19X19pbml0Migpe3RoaXMucD10PT57dGhpcy5tKEguUkVKRUNURUQsdCl9fV9faW5pdDMoKXt0aGlzLm09KHQsZSk9Pnt0aGlzLmk9PT1ILlBFTkRJTkcmJihtKGUpP2UudGhlbih0aGlzLmgsdGhpcy5wKToodGhpcy5pPXQsdGhpcy52PWUsdGhpcy5sKCkpKX19X19pbml0NCgpe3RoaXMubD0oKT0+e2lmKHRoaXMuaT09PUguUEVORElORylyZXR1cm47Y29uc3QgdD10aGlzLnUuc2xpY2UoKTt0aGlzLnU9W10sdC5mb3JFYWNoKCh0PT57dFswXXx8KHRoaXMuaT09PUguUkVTT0xWRUQmJnRbMV0odGhpcy52KSx0aGlzLmk9PT1ILlJFSkVDVEVEJiZ0WzJdKHRoaXMudiksdFswXT0hMCl9KSl9fX1mdW5jdGlvbiBxKHQpe2NvbnN0IGU9W107ZnVuY3Rpb24gbih0KXtyZXR1cm4gZS5zcGxpY2UoZS5pbmRleE9mKHQpLDEpWzBdfXJldHVybnskOmUsYWRkOmZ1bmN0aW9uKHIpe2lmKCEodm9pZCAwPT09dHx8ZS5sZW5ndGg8dCkpcmV0dXJuIG89bmV3IEMoIk5vdCBhZGRpbmcgUHJvbWlzZSBiZWNhdXNlIGJ1ZmZlciBsaW1pdCB3YXMgcmVhY2hlZC4iKSxuZXcgWSgoKHQsZSk9PntlKG8pfSkpO3ZhciBvO2NvbnN0IHM9cigpO3JldHVybi0xPT09ZS5pbmRleE9mKHMpJiZlLnB1c2gocykscy50aGVuKCgoKT0+bihzKSkpLnRoZW4obnVsbCwoKCk9Pm4ocykudGhlbihudWxsLCgoKT0+e30pKSkpLHN9LGRyYWluOmZ1bmN0aW9uKHQpe3JldHVybiBuZXcgWSgoKG4scik9PntsZXQgbz1lLmxlbmd0aDtpZighbylyZXR1cm4gbighMCk7Y29uc3Qgcz1zZXRUaW1lb3V0KCgoKT0+e3QmJnQ+MCYmbighMSl9KSx0KTtlLmZvckVhY2goKHQ9PntXKHQpLnRoZW4oKCgpPT57LS1vfHwoY2xlYXJUaW1lb3V0KHMpLG4oITApKX0pLHIpfSkpfSkpfX19ZnVuY3Rpb24gRih0LGU9ITEpe3JldHVybiEoZXx8dCYmIXQuc3RhcnRzV2l0aCgiLyIpJiYhdC5tYXRjaCgvXltBLVpdOi8pJiYhdC5zdGFydHNXaXRoKCIuIikmJiF0Lm1hdGNoKC9eW2EtekEtWl0oW2EtekEtWjAtOS5cLStdKSo6XC9cLy8pKSYmdm9pZCAwIT09dCYmIXQuaW5jbHVkZXMoIm5vZGVfbW9kdWxlcy8iKX1jb25zdCBLPTFlMztmdW5jdGlvbiBWKCl7cmV0dXJuIERhdGUubm93KCkvS31jb25zdCBaPWZ1bmN0aW9uKCl7Y29uc3R7cGVyZm9ybWFuY2U6dH09eTtpZighdHx8IXQubm93KXJldHVybiBWO2NvbnN0IGU9RGF0ZS5ub3coKS10Lm5vdygpLG49bnVsbD09dC50aW1lT3JpZ2luP2U6dC50aW1lT3JpZ2luO3JldHVybigpPT4obit0Lm5vdygpKS9LfSgpO2Z1bmN0aW9uIFEodCxlPVtdKXtyZXR1cm5bdCxlXX1mdW5jdGlvbiBYKHQsZSl7Y29uc3Qgbj10WzFdO2Zvcihjb25zdCB0IG9mIG4pe2lmKGUodCx0WzBdLnR5cGUpKXJldHVybiEwfXJldHVybiExfWZ1bmN0aW9uIHR0KHQpe3JldHVybiB5Ll9fU0VOVFJZX18mJnkuX19TRU5UUllfXy5lbmNvZGVQb2x5ZmlsbD95Ll9fU0VOVFJZX18uZW5jb2RlUG9seWZpbGwodCk6KG5ldyBUZXh0RW5jb2RlcikuZW5jb2RlKHQpfWZ1bmN0aW9uIGV0KHQpe2NvbnN0W2Usbl09dDtsZXQgcj1KU09OLnN0cmluZ2lmeShlKTtmdW5jdGlvbiBvKHQpeyJzdHJpbmciPT10eXBlb2Ygcj9yPSJzdHJpbmciPT10eXBlb2YgdD9yK3Q6W3R0KHIpLHRdOnIucHVzaCgic3RyaW5nIj09dHlwZW9mIHQ/dHQodCk6dCl9Zm9yKGNvbnN0IHQgb2Ygbil7Y29uc3RbZSxuXT10O2lmKG8oYFxuJHtKU09OLnN0cmluZ2lmeShlKX1cbmApLCJzdHJpbmciPT10eXBlb2Ygbnx8biBpbnN0YW5jZW9mIFVpbnQ4QXJyYXkpbyhuKTtlbHNle2xldCB0O3RyeXt0PUpTT04uc3RyaW5naWZ5KG4pfWNhdGNoKGUpe3Q9SlNPTi5zdHJpbmdpZnkoTChuKSl9byh0KX19cmV0dXJuInN0cmluZyI9PXR5cGVvZiByP3I6ZnVuY3Rpb24odCl7Y29uc3QgZT10LnJlZHVjZSgoKHQsZSk9PnQrZS5sZW5ndGgpLDApLG49bmV3IFVpbnQ4QXJyYXkoZSk7bGV0IHI9MDtmb3IoY29uc3QgZSBvZiB0KW4uc2V0KGUscikscis9ZS5sZW5ndGg7cmV0dXJuIG59KHIpfSgoKT0+e2NvbnN0e3BlcmZvcm1hbmNlOnR9PXk7aWYoIXR8fCF0Lm5vdylyZXR1cm47Y29uc3QgZT0zNmU1LG49dC5ub3coKSxyPURhdGUubm93KCksbz10LnRpbWVPcmlnaW4/TWF0aC5hYnModC50aW1lT3JpZ2luK24tcik6ZSxzPW88ZSxpPXQudGltaW5nJiZ0LnRpbWluZy5uYXZpZ2F0aW9uU3RhcnQsYz0ibnVtYmVyIj09dHlwZW9mIGk/TWF0aC5hYnMoaStuLXIpOmU7KHN8fGM8ZSkmJihvPD1jJiZ0LnRpbWVPcmlnaW4pfSkoKTtjb25zdCBudD17c2Vzc2lvbjoic2Vzc2lvbiIsc2Vzc2lvbnM6InNlc3Npb24iLGF0dGFjaG1lbnQ6ImF0dGFjaG1lbnQiLHRyYW5zYWN0aW9uOiJ0cmFuc2FjdGlvbiIsZXZlbnQ6ImVycm9yIixjbGllbnRfcmVwb3J0OiJpbnRlcm5hbCIsdXNlcl9yZXBvcnQ6ImRlZmF1bHQiLHByb2ZpbGU6InByb2ZpbGUiLHJlcGxheV9ldmVudDoicmVwbGF5IixyZXBsYXlfcmVjb3JkaW5nOiJyZXBsYXkiLGNoZWNrX2luOiJtb25pdG9yIixmZWVkYmFjazoiZmVlZGJhY2siLHNwYW46InNwYW4iLHN0YXRzZDoibWV0cmljX2J1Y2tldCJ9O2Z1bmN0aW9uIHJ0KHQpe3JldHVybiBudFt0XX1mdW5jdGlvbiBvdCh0KXtpZighdHx8IXQuc2RrKXJldHVybjtjb25zdHtuYW1lOmUsdmVyc2lvbjpufT10LnNkaztyZXR1cm57bmFtZTplLHZlcnNpb246bn19Y29uc3Qgc3Q9NmU0O2Z1bmN0aW9uIGl0KHQse3N0YXR1c0NvZGU6ZSxoZWFkZXJzOm59LHI9RGF0ZS5ub3coKSl7Y29uc3Qgbz17Li4udH0scz1uJiZuWyJ4LXNlbnRyeS1yYXRlLWxpbWl0cyJdLGk9biYmblsicmV0cnktYWZ0ZXIiXTtpZihzKWZvcihjb25zdCB0IG9mIHMudHJpbSgpLnNwbGl0KCIsIikpe2NvbnN0W2UsbiwsLHNdPXQuc3BsaXQoIjoiLDUpLGk9cGFyc2VJbnQoZSwxMCksYz0xZTMqKGlzTmFOKGkpPzYwOmkpO2lmKG4pZm9yKGNvbnN0IHQgb2Ygbi5zcGxpdCgiOyIpKSJtZXRyaWNfYnVja2V0Ij09PXQmJnMmJiFzLnNwbGl0KCI7IikuaW5jbHVkZXMoImN1c3RvbSIpfHwob1t0XT1yK2MpO2Vsc2Ugby5hbGw9citjfWVsc2UgaT9vLmFsbD1yK2Z1bmN0aW9uKHQsZT1EYXRlLm5vdygpKXtjb25zdCBuPXBhcnNlSW50KGAke3R9YCwxMCk7aWYoIWlzTmFOKG4pKXJldHVybiAxZTMqbjtjb25zdCByPURhdGUucGFyc2UoYCR7dH1gKTtyZXR1cm4gaXNOYU4ocik/c3Q6ci1lfShpLHIpOjQyOT09PWUmJihvLmFsbD1yKzZlNCk7cmV0dXJuIG99Y29uc3QgY3Q9InVuZGVmaW5lZCI9PXR5cGVvZiBfX1NFTlRSWV9ERUJVR19ffHxfX1NFTlRSWV9ERUJVR19fO2Z1bmN0aW9uIHV0KCl7cmV0dXJuIGF0KHkpLHl9ZnVuY3Rpb24gYXQodCl7cmV0dXJuIHQuX19TRU5UUllfX3x8KHQuX19TRU5UUllfXz17ZXh0ZW5zaW9uczp7fX0pLHQuX19TRU5UUllfX31mdW5jdGlvbiBmdCh0KXtjb25zdCBlPVooKSxuPXtzaWQ6TSgpLGluaXQ6ITAsdGltZXN0YW1wOmUsc3RhcnRlZDplLGR1cmF0aW9uOjAsc3RhdHVzOiJvayIsZXJyb3JzOjAsaWdub3JlRHVyYXRpb246ITEsdG9KU09OOigpPT5mdW5jdGlvbih0KXtyZXR1cm4gaih7c2lkOmAke3Quc2lkfWAsaW5pdDp0LmluaXQsc3RhcnRlZDpuZXcgRGF0ZSgxZTMqdC5zdGFydGVkKS50b0lTT1N0cmluZygpLHRpbWVzdGFtcDpuZXcgRGF0ZSgxZTMqdC50aW1lc3RhbXApLnRvSVNPU3RyaW5nKCksc3RhdHVzOnQuc3RhdHVzLGVycm9yczp0LmVycm9ycyxkaWQ6Im51bWJlciI9PXR5cGVvZiB0LmRpZHx8InN0cmluZyI9PXR5cGVvZiB0LmRpZD9gJHt0LmRpZH1gOnZvaWQgMCxkdXJhdGlvbjp0LmR1cmF0aW9uLGFibm9ybWFsX21lY2hhbmlzbTp0LmFibm9ybWFsX21lY2hhbmlzbSxhdHRyczp7cmVsZWFzZTp0LnJlbGVhc2UsZW52aXJvbm1lbnQ6dC5lbnZpcm9ubWVudCxpcF9hZGRyZXNzOnQuaXBBZGRyZXNzLHVzZXJfYWdlbnQ6dC51c2VyQWdlbnR9fSl9KG4pfTtyZXR1cm4gdCYmaHQobix0KSxufWZ1bmN0aW9uIGh0KHQsZT17fSl7aWYoZS51c2VyJiYoIXQuaXBBZGRyZXNzJiZlLnVzZXIuaXBfYWRkcmVzcyYmKHQuaXBBZGRyZXNzPWUudXNlci5pcF9hZGRyZXNzKSx0LmRpZHx8ZS5kaWR8fCh0LmRpZD1lLnVzZXIuaWR8fGUudXNlci5lbWFpbHx8ZS51c2VyLnVzZXJuYW1lKSksdC50aW1lc3RhbXA9ZS50aW1lc3RhbXB8fFooKSxlLmFibm9ybWFsX21lY2hhbmlzbSYmKHQuYWJub3JtYWxfbWVjaGFuaXNtPWUuYWJub3JtYWxfbWVjaGFuaXNtKSxlLmlnbm9yZUR1cmF0aW9uJiYodC5pZ25vcmVEdXJhdGlvbj1lLmlnbm9yZUR1cmF0aW9uKSxlLnNpZCYmKHQuc2lkPTMyPT09ZS5zaWQubGVuZ3RoP2Uuc2lkOk0oKSksdm9pZCAwIT09ZS5pbml0JiYodC5pbml0PWUuaW5pdCksIXQuZGlkJiZlLmRpZCYmKHQuZGlkPWAke2UuZGlkfWApLCJudW1iZXIiPT10eXBlb2YgZS5zdGFydGVkJiYodC5zdGFydGVkPWUuc3RhcnRlZCksdC5pZ25vcmVEdXJhdGlvbil0LmR1cmF0aW9uPXZvaWQgMDtlbHNlIGlmKCJudW1iZXIiPT10eXBlb2YgZS5kdXJhdGlvbil0LmR1cmF0aW9uPWUuZHVyYXRpb247ZWxzZXtjb25zdCBlPXQudGltZXN0YW1wLXQuc3RhcnRlZDt0LmR1cmF0aW9uPWU+PTA/ZTowfWUucmVsZWFzZSYmKHQucmVsZWFzZT1lLnJlbGVhc2UpLGUuZW52aXJvbm1lbnQmJih0LmVudmlyb25tZW50PWUuZW52aXJvbm1lbnQpLCF0LmlwQWRkcmVzcyYmZS5pcEFkZHJlc3MmJih0LmlwQWRkcmVzcz1lLmlwQWRkcmVzcyksIXQudXNlckFnZW50JiZlLnVzZXJBZ2VudCYmKHQudXNlckFnZW50PWUudXNlckFnZW50KSwibnVtYmVyIj09dHlwZW9mIGUuZXJyb3JzJiYodC5lcnJvcnM9ZS5lcnJvcnMpLGUuc3RhdHVzJiYodC5zdGF0dXM9ZS5zdGF0dXMpfWNvbnN0IHB0PSJfc2VudHJ5U3BhbiI7ZnVuY3Rpb24gbHQodCxlKXtlP2Z1bmN0aW9uKHQsZSxuKXt0cnl7T2JqZWN0LmRlZmluZVByb3BlcnR5KHQsZSx7dmFsdWU6bix3cml0YWJsZTohMCxjb25maWd1cmFibGU6ITB9KX1jYXRjaChuKXtTJiZOLmxvZyhgRmFpbGVkIHRvIGFkZCBub24tZW51bWVyYWJsZSBwcm9wZXJ0eSAiJHtlfSIgdG8gb2JqZWN0YCx0KX19KHQscHQsZSk6ZGVsZXRlIHRbcHRdfWZ1bmN0aW9uIGR0KHQpe3JldHVybiB0W3B0XX1jbGFzcyBtdHtjb25zdHJ1Y3Rvcigpe3RoaXMuXz0hMSx0aGlzLlM9W10sdGhpcy5OPVtdLHRoaXMuaz1bXSx0aGlzLkM9W10sdGhpcy5EPXt9LHRoaXMuVD17fSx0aGlzLk89e30sdGhpcy5qPXt9LHRoaXMuUj17fSx0aGlzLkE9Z3QoKX1jbG9uZSgpe2NvbnN0IHQ9bmV3IG10O3JldHVybiB0Lms9Wy4uLnRoaXMua10sdC5UPXsuLi50aGlzLlR9LHQuTz17Li4udGhpcy5PfSx0Lmo9ey4uLnRoaXMuan0sdC5EPXRoaXMuRCx0Lkk9dGhpcy5JLHQuUD10aGlzLlAsdC5VPXRoaXMuVSx0Lk09dGhpcy5NLHQuTj1bLi4udGhpcy5OXSx0Lkw9dGhpcy5MLHQuQz1bLi4udGhpcy5DXSx0LlI9ey4uLnRoaXMuUn0sdC5BPXsuLi50aGlzLkF9LHQuQj10aGlzLkIsbHQodCxkdCh0aGlzKSksdH1zZXRDbGllbnQodCl7dGhpcy5CPXR9Z2V0Q2xpZW50KCl7cmV0dXJuIHRoaXMuQn1hZGRTY29wZUxpc3RlbmVyKHQpe3RoaXMuUy5wdXNoKHQpfWFkZEV2ZW50UHJvY2Vzc29yKHQpe3JldHVybiB0aGlzLk4ucHVzaCh0KSx0aGlzfXNldFVzZXIodCl7cmV0dXJuIHRoaXMuRD10fHx7ZW1haWw6dm9pZCAwLGlkOnZvaWQgMCxpcF9hZGRyZXNzOnZvaWQgMCx1c2VybmFtZTp2b2lkIDB9LHRoaXMuUCYmaHQodGhpcy5QLHt1c2VyOnR9KSx0aGlzLkcoKSx0aGlzfWdldFVzZXIoKXtyZXR1cm4gdGhpcy5EfWdldFJlcXVlc3RTZXNzaW9uKCl7cmV0dXJuIHRoaXMuTH1zZXRSZXF1ZXN0U2Vzc2lvbih0KXtyZXR1cm4gdGhpcy5MPXQsdGhpc31zZXRUYWdzKHQpe3JldHVybiB0aGlzLlQ9ey4uLnRoaXMuVCwuLi50fSx0aGlzLkcoKSx0aGlzfXNldFRhZyh0LGUpe3JldHVybiB0aGlzLlQ9ey4uLnRoaXMuVCxbdF06ZX0sdGhpcy5HKCksdGhpc31zZXRFeHRyYXModCl7cmV0dXJuIHRoaXMuTz17Li4udGhpcy5PLC4uLnR9LHRoaXMuRygpLHRoaXN9c2V0RXh0cmEodCxlKXtyZXR1cm4gdGhpcy5PPXsuLi50aGlzLk8sW3RdOmV9LHRoaXMuRygpLHRoaXN9c2V0RmluZ2VycHJpbnQodCl7cmV0dXJuIHRoaXMuTT10LHRoaXMuRygpLHRoaXN9c2V0TGV2ZWwodCl7cmV0dXJuIHRoaXMuST10LHRoaXMuRygpLHRoaXN9c2V0VHJhbnNhY3Rpb25OYW1lKHQpe3JldHVybiB0aGlzLlU9dCx0aGlzLkcoKSx0aGlzfXNldENvbnRleHQodCxlKXtyZXR1cm4gbnVsbD09PWU/ZGVsZXRlIHRoaXMualt0XTp0aGlzLmpbdF09ZSx0aGlzLkcoKSx0aGlzfXNldFNlc3Npb24odCl7cmV0dXJuIHQ/dGhpcy5QPXQ6ZGVsZXRlIHRoaXMuUCx0aGlzLkcoKSx0aGlzfWdldFNlc3Npb24oKXtyZXR1cm4gdGhpcy5QfXVwZGF0ZSh0KXtpZighdClyZXR1cm4gdGhpcztjb25zdCBlPSJmdW5jdGlvbiI9PXR5cGVvZiB0P3QodGhpcyk6dCxbbixyXT1lIGluc3RhbmNlb2YgbXQ/W2UuZ2V0U2NvcGVEYXRhKCksZS5nZXRSZXF1ZXN0U2Vzc2lvbigpXTpkKGUpP1t0LHQucmVxdWVzdFNlc3Npb25dOltdLHt0YWdzOm8sZXh0cmE6cyx1c2VyOmksY29udGV4dHM6YyxsZXZlbDp1LGZpbmdlcnByaW50OmE9W10scHJvcGFnYXRpb25Db250ZXh0OmZ9PW58fHt9O3JldHVybiB0aGlzLlQ9ey4uLnRoaXMuVCwuLi5vfSx0aGlzLk89ey4uLnRoaXMuTywuLi5zfSx0aGlzLmo9ey4uLnRoaXMuaiwuLi5jfSxpJiZPYmplY3Qua2V5cyhpKS5sZW5ndGgmJih0aGlzLkQ9aSksdSYmKHRoaXMuST11KSxhLmxlbmd0aCYmKHRoaXMuTT1hKSxmJiYodGhpcy5BPWYpLHImJih0aGlzLkw9ciksdGhpc31jbGVhcigpe3JldHVybiB0aGlzLms9W10sdGhpcy5UPXt9LHRoaXMuTz17fSx0aGlzLkQ9e30sdGhpcy5qPXt9LHRoaXMuST12b2lkIDAsdGhpcy5VPXZvaWQgMCx0aGlzLk09dm9pZCAwLHRoaXMuTD12b2lkIDAsdGhpcy5QPXZvaWQgMCxsdCh0aGlzLHZvaWQgMCksdGhpcy5DPVtdLHRoaXMuQT1ndCgpLHRoaXMuRygpLHRoaXN9YWRkQnJlYWRjcnVtYih0LGUpe2NvbnN0IG49Im51bWJlciI9PXR5cGVvZiBlP2U6MTAwO2lmKG48PTApcmV0dXJuIHRoaXM7Y29uc3Qgcj17dGltZXN0YW1wOlYoKSwuLi50fSxvPXRoaXMuaztyZXR1cm4gby5wdXNoKHIpLHRoaXMuaz1vLmxlbmd0aD5uP28uc2xpY2UoLW4pOm8sdGhpcy5HKCksdGhpc31nZXRMYXN0QnJlYWRjcnVtYigpe3JldHVybiB0aGlzLmtbdGhpcy5rLmxlbmd0aC0xXX1jbGVhckJyZWFkY3J1bWJzKCl7cmV0dXJuIHRoaXMuaz1bXSx0aGlzLkcoKSx0aGlzfWFkZEF0dGFjaG1lbnQodCl7cmV0dXJuIHRoaXMuQy5wdXNoKHQpLHRoaXN9Y2xlYXJBdHRhY2htZW50cygpe3JldHVybiB0aGlzLkM9W10sdGhpc31nZXRTY29wZURhdGEoKXtyZXR1cm57YnJlYWRjcnVtYnM6dGhpcy5rLGF0dGFjaG1lbnRzOnRoaXMuQyxjb250ZXh0czp0aGlzLmosdGFnczp0aGlzLlQsZXh0cmE6dGhpcy5PLHVzZXI6dGhpcy5ELGxldmVsOnRoaXMuSSxmaW5nZXJwcmludDp0aGlzLk18fFtdLGV2ZW50UHJvY2Vzc29yczp0aGlzLk4scHJvcGFnYXRpb25Db250ZXh0OnRoaXMuQSxzZGtQcm9jZXNzaW5nTWV0YWRhdGE6dGhpcy5SLHRyYW5zYWN0aW9uTmFtZTp0aGlzLlUsc3BhbjpkdCh0aGlzKX19c2V0U0RLUHJvY2Vzc2luZ01ldGFkYXRhKHQpe3JldHVybiB0aGlzLlI9ey4uLnRoaXMuUiwuLi50fSx0aGlzfXNldFByb3BhZ2F0aW9uQ29udGV4dCh0KXtyZXR1cm4gdGhpcy5BPXQsdGhpc31nZXRQcm9wYWdhdGlvbkNvbnRleHQoKXtyZXR1cm4gdGhpcy5BfWNhcHR1cmVFeGNlcHRpb24odCxlKXtjb25zdCBuPWUmJmUuZXZlbnRfaWQ/ZS5ldmVudF9pZDpNKCk7aWYoIXRoaXMuQilyZXR1cm4gTi53YXJuKCJObyBjbGllbnQgY29uZmlndXJlZCBvbiBzY29wZSAtIHdpbGwgbm90IGNhcHR1cmUgZXhjZXB0aW9uISIpLG47Y29uc3Qgcj1uZXcgRXJyb3IoIlNlbnRyeSBzeW50aGV0aWNFeGNlcHRpb24iKTtyZXR1cm4gdGhpcy5CLmNhcHR1cmVFeGNlcHRpb24odCx7b3JpZ2luYWxFeGNlcHRpb246dCxzeW50aGV0aWNFeGNlcHRpb246ciwuLi5lLGV2ZW50X2lkOm59LHRoaXMpLG59Y2FwdHVyZU1lc3NhZ2UodCxlLG4pe2NvbnN0IHI9biYmbi5ldmVudF9pZD9uLmV2ZW50X2lkOk0oKTtpZighdGhpcy5CKXJldHVybiBOLndhcm4oIk5vIGNsaWVudCBjb25maWd1cmVkIG9uIHNjb3BlIC0gd2lsbCBub3QgY2FwdHVyZSBtZXNzYWdlISIpLHI7Y29uc3Qgbz1uZXcgRXJyb3IodCk7cmV0dXJuIHRoaXMuQi5jYXB0dXJlTWVzc2FnZSh0LGUse29yaWdpbmFsRXhjZXB0aW9uOnQsc3ludGhldGljRXhjZXB0aW9uOm8sLi4ubixldmVudF9pZDpyfSx0aGlzKSxyfWNhcHR1cmVFdmVudCh0LGUpe2NvbnN0IG49ZSYmZS5ldmVudF9pZD9lLmV2ZW50X2lkOk0oKTtyZXR1cm4gdGhpcy5CPyh0aGlzLkIuY2FwdHVyZUV2ZW50KHQsey4uLmUsZXZlbnRfaWQ6bn0sdGhpcyksbik6KE4ud2FybigiTm8gY2xpZW50IGNvbmZpZ3VyZWQgb24gc2NvcGUgLSB3aWxsIG5vdCBjYXB0dXJlIGV2ZW50ISIpLG4pfUcoKXt0aGlzLl98fCh0aGlzLl89ITAsdGhpcy5TLmZvckVhY2goKHQ9Pnt0KHRoaXMpfSkpLHRoaXMuXz0hMSl9fWZ1bmN0aW9uIGd0KCl7cmV0dXJue3RyYWNlSWQ6TSgpLHNwYW5JZDpNKCkuc3Vic3RyaW5nKDE2KX19ZnVuY3Rpb24geXQoKXtyZXR1cm4gJHQodXQoKSkuZ2V0Q3VycmVudFNjb3BlKCkuZ2V0Q2xpZW50KCl9Y2xhc3MgYnR7Y29uc3RydWN0b3IodCxlKXtsZXQgbixyO249dHx8bmV3IG10LHI9ZXx8bmV3IG10LHRoaXMuSj1be3Njb3BlOm59XSx0aGlzLkg9cn13aXRoU2NvcGUodCl7Y29uc3QgZT10aGlzLlcoKTtsZXQgbjt0cnl7bj10KGUpfWNhdGNoKHQpe3Rocm93IHRoaXMuWSgpLHR9cmV0dXJuIG0obik/bi50aGVuKCh0PT4odGhpcy5ZKCksdCkpLCh0PT57dGhyb3cgdGhpcy5ZKCksdH0pKToodGhpcy5ZKCksbil9Z2V0Q2xpZW50KCl7cmV0dXJuIHRoaXMuZ2V0U3RhY2tUb3AoKS5jbGllbnR9Z2V0U2NvcGUoKXtyZXR1cm4gdGhpcy5nZXRTdGFja1RvcCgpLnNjb3BlfWdldElzb2xhdGlvblNjb3BlKCl7cmV0dXJuIHRoaXMuSH1nZXRTdGFjaygpe3JldHVybiB0aGlzLkp9Z2V0U3RhY2tUb3AoKXtyZXR1cm4gdGhpcy5KW3RoaXMuSi5sZW5ndGgtMV19Vygpe2NvbnN0IHQ9dGhpcy5nZXRTY29wZSgpLmNsb25lKCk7cmV0dXJuIHRoaXMuZ2V0U3RhY2soKS5wdXNoKHtjbGllbnQ6dGhpcy5nZXRDbGllbnQoKSxzY29wZTp0fSksdH1ZKCl7cmV0dXJuISh0aGlzLmdldFN0YWNrKCkubGVuZ3RoPD0xKSYmISF0aGlzLmdldFN0YWNrKCkucG9wKCl9fWZ1bmN0aW9uIHZ0KCl7Y29uc3QgdD1hdCh1dCgpKTtyZXR1cm4gdC5odWJ8fCh0Lmh1Yj1uZXcgYnQoYigiZGVmYXVsdEN1cnJlbnRTY29wZSIsKCgpPT5uZXcgbXQpKSxiKCJkZWZhdWx0SXNvbGF0aW9uU2NvcGUiLCgoKT0+bmV3IG10KSkpKSx0Lmh1Yn1mdW5jdGlvbiBfdCh0KXtyZXR1cm4gdnQoKS53aXRoU2NvcGUodCl9ZnVuY3Rpb24gd3QodCxlKXtjb25zdCBuPXZ0KCk7cmV0dXJuIG4ud2l0aFNjb3BlKCgoKT0+KG4uZ2V0U3RhY2tUb3AoKS5zY29wZT10LGUodCkpKSl9ZnVuY3Rpb24gU3QodCl7cmV0dXJuIHZ0KCkud2l0aFNjb3BlKCgoKT0+dCh2dCgpLmdldElzb2xhdGlvblNjb3BlKCkpKSl9ZnVuY3Rpb24gJHQodCl7Y29uc3QgZT1hdCh0KTtyZXR1cm4gZS5hY3M/ZS5hY3M6e3dpdGhJc29sYXRpb25TY29wZTpTdCx3aXRoU2NvcGU6X3Qsd2l0aFNldFNjb3BlOnd0LHdpdGhTZXRJc29sYXRpb25TY29wZToodCxlKT0+U3QoZSksZ2V0Q3VycmVudFNjb3BlOigpPT52dCgpLmdldFNjb3BlKCksZ2V0SXNvbGF0aW9uU2NvcGU6KCk9PnZ0KCkuZ2V0SXNvbGF0aW9uU2NvcGUoKX19Y29uc3QgRXQ9InNlbnRyeS5zb3VyY2UiLHh0PSJzZW50cnkuc2FtcGxlX3JhdGUiLE50PSJzZW50cnkub3AiLGt0PSJzZW50cnkub3JpZ2luIixDdD0wLER0PTEsVHQ9InByb2R1Y3Rpb24iLE90PSJfZnJvemVuRHNjIjtmdW5jdGlvbiBqdCh0KXtjb25zdCBlPXl0KCk7aWYoIWUpcmV0dXJue307Y29uc3Qgbj1mdW5jdGlvbih0LGUpe2NvbnN0IG49ZS5nZXRPcHRpb25zKCkse3B1YmxpY0tleTpyfT1lLmdldERzbigpfHx7fSxvPWooe2Vudmlyb25tZW50Om4uZW52aXJvbm1lbnR8fFR0LHJlbGVhc2U6bi5yZWxlYXNlLHB1YmxpY19rZXk6cix0cmFjZV9pZDp0fSk7cmV0dXJuIGUuZW1pdCgiY3JlYXRlRHNjIixvKSxvfShVdCh0KS50cmFjZV9pZHx8IiIsZSkscj1CdCh0KTtpZighcilyZXR1cm4gbjtjb25zdCBvPXJbT3RdO2lmKG8pcmV0dXJuIG87Y29uc3Qgcz1VdChyKSxpPXMuZGF0YXx8e30sYz1pW3h0XTtudWxsIT1jJiYobi5zYW1wbGVfcmF0ZT1gJHtjfWApO2NvbnN0IHU9aVtFdF07cmV0dXJuIHUmJiJ1cmwiIT09dSYmKG4udHJhbnNhY3Rpb249cy5kZXNjcmlwdGlvbiksbi5zYW1wbGVkPVN0cmluZyhmdW5jdGlvbih0KXtjb25zdHt0cmFjZUZsYWdzOmV9PXQuc3BhbkNvbnRleHQoKTtyZXR1cm4gZT09PVJ0fShyKSksZS5lbWl0KCJjcmVhdGVEc2MiLG4pLG59Y29uc3QgUnQ9MTtmdW5jdGlvbiBBdCh0KXtjb25zdHtzcGFuSWQ6ZSx0cmFjZUlkOm59PXQuc3BhbkNvbnRleHQoKSx7cGFyZW50X3NwYW5faWQ6cn09VXQodCk7cmV0dXJuIGooe3BhcmVudF9zcGFuX2lkOnIsc3Bhbl9pZDplLHRyYWNlX2lkOm59KX1mdW5jdGlvbiBJdCh0KXtyZXR1cm4ibnVtYmVyIj09dHlwZW9mIHQ/UHQodCk6QXJyYXkuaXNBcnJheSh0KT90WzBdK3RbMV0vMWU5OnQgaW5zdGFuY2VvZiBEYXRlP1B0KHQuZ2V0VGltZSgpKTpaKCl9ZnVuY3Rpb24gUHQodCl7cmV0dXJuIHQ+OTk5OTk5OTk5OT90LzFlMzp0fWZ1bmN0aW9uIFV0KHQpe2lmKGZ1bmN0aW9uKHQpe3JldHVybiJmdW5jdGlvbiI9PXR5cGVvZiB0LmdldFNwYW5KU09OfSh0KSlyZXR1cm4gdC5nZXRTcGFuSlNPTigpO3RyeXtjb25zdHtzcGFuSWQ6ZSx0cmFjZUlkOm59PXQuc3BhbkNvbnRleHQoKTtpZihmdW5jdGlvbih0KXtjb25zdCBlPXQ7cmV0dXJuISEoZS5hdHRyaWJ1dGVzJiZlLnN0YXJ0VGltZSYmZS5uYW1lJiZlLmVuZFRpbWUmJmUuc3RhdHVzKX0odCkpe2NvbnN0e2F0dHJpYnV0ZXM6cixzdGFydFRpbWU6byxuYW1lOnMsZW5kVGltZTppLHBhcmVudFNwYW5JZDpjLHN0YXR1czp1fT10O3JldHVybiBqKHtzcGFuX2lkOmUsdHJhY2VfaWQ6bixkYXRhOnIsZGVzY3JpcHRpb246cyxwYXJlbnRfc3Bhbl9pZDpjLHN0YXJ0X3RpbWVzdGFtcDpJdChvKSx0aW1lc3RhbXA6SXQoaSl8fHZvaWQgMCxzdGF0dXM6TXQodSksb3A6cltOdF0sb3JpZ2luOnJba3RdLHE6dm9pZCAwfSl9cmV0dXJue3NwYW5faWQ6ZSx0cmFjZV9pZDpufX1jYXRjaCh0KXtyZXR1cm57fX19ZnVuY3Rpb24gTXQodCl7aWYodCYmdC5jb2RlIT09Q3QpcmV0dXJuIHQuY29kZT09PUR0PyJvayI6dC5tZXNzYWdlfHwidW5rbm93bl9lcnJvciJ9Y29uc3QgTHQ9Il9zZW50cnlSb290U3BhbiI7ZnVuY3Rpb24gQnQodCl7cmV0dXJuIHRbTHRdfHx0fWZ1bmN0aW9uIEd0KHQsZSxuLHIpe2NvbnN0IG89b3Qobikscz10LnR5cGUmJiJyZXBsYXlfZXZlbnQiIT09dC50eXBlP3QudHlwZToiZXZlbnQiOyFmdW5jdGlvbih0LGUpe2UmJih0LnNkaz10LnNka3x8e30sdC5zZGsubmFtZT10LnNkay5uYW1lfHxlLm5hbWUsdC5zZGsudmVyc2lvbj10LnNkay52ZXJzaW9ufHxlLnZlcnNpb24sdC5zZGsuaW50ZWdyYXRpb25zPVsuLi50LnNkay5pbnRlZ3JhdGlvbnN8fFtdLC4uLmUuaW50ZWdyYXRpb25zfHxbXV0sdC5zZGsucGFja2FnZXM9Wy4uLnQuc2RrLnBhY2thZ2VzfHxbXSwuLi5lLnBhY2thZ2VzfHxbXV0pfSh0LG4mJm4uc2RrKTtjb25zdCBpPWZ1bmN0aW9uKHQsZSxuLHIpe2NvbnN0IG89dC5zZGtQcm9jZXNzaW5nTWV0YWRhdGEmJnQuc2RrUHJvY2Vzc2luZ01ldGFkYXRhLmR5bmFtaWNTYW1wbGluZ0NvbnRleHQ7cmV0dXJue2V2ZW50X2lkOnQuZXZlbnRfaWQsc2VudF9hdDoobmV3IERhdGUpLnRvSVNPU3RyaW5nKCksLi4uZSYme3NkazplfSwuLi4hIW4mJnImJntkc246ayhyKX0sLi4ubyYme3RyYWNlOmooey4uLm99KX19fSh0LG8scixlKTtkZWxldGUgdC5zZGtQcm9jZXNzaW5nTWV0YWRhdGE7cmV0dXJuIFEoaSxbW3t0eXBlOnN9LHRdXSl9Y29uc3QgSnQ9Il9fU0VOVFJZX1NVUFBSRVNTX1RSQUNJTkdfXyI7ZnVuY3Rpb24genQodCl7Y29uc3QgZT0kdCh1dCgpKTtyZXR1cm4gZS5zdXBwcmVzc1RyYWNpbmc/ZS5zdXBwcmVzc1RyYWNpbmcodCk6ZnVuY3Rpb24oLi4udCl7Y29uc3QgZT0kdCh1dCgpKTtpZigyPT09dC5sZW5ndGgpe2NvbnN0W24scl09dDtyZXR1cm4gbj9lLndpdGhTZXRTY29wZShuLHIpOmUud2l0aFNjb3BlKHIpfXJldHVybiBlLndpdGhTY29wZSh0WzBdKX0oKGU9PihlLnNldFNES1Byb2Nlc3NpbmdNZXRhZGF0YSh7W0p0XTohMH0pLHQoKSkpKX1mdW5jdGlvbiBIdCh0LGUpe2NvbnN0e2ZpbmdlcnByaW50Om4sc3BhbjpyLGJyZWFkY3J1bWJzOm8sc2RrUHJvY2Vzc2luZ01ldGFkYXRhOnN9PWU7IWZ1bmN0aW9uKHQsZSl7Y29uc3R7ZXh0cmE6bix0YWdzOnIsdXNlcjpvLGNvbnRleHRzOnMsbGV2ZWw6aSx0cmFuc2FjdGlvbk5hbWU6Y309ZSx1PWoobik7dSYmT2JqZWN0LmtleXModSkubGVuZ3RoJiYodC5leHRyYT17Li4udSwuLi50LmV4dHJhfSk7Y29uc3QgYT1qKHIpO2EmJk9iamVjdC5rZXlzKGEpLmxlbmd0aCYmKHQudGFncz17Li4uYSwuLi50LnRhZ3N9KTtjb25zdCBmPWoobyk7ZiYmT2JqZWN0LmtleXMoZikubGVuZ3RoJiYodC51c2VyPXsuLi5mLC4uLnQudXNlcn0pO2NvbnN0IGg9aihzKTtoJiZPYmplY3Qua2V5cyhoKS5sZW5ndGgmJih0LmNvbnRleHRzPXsuLi5oLC4uLnQuY29udGV4dHN9KTtpJiYodC5sZXZlbD1pKTtjJiYidHJhbnNhY3Rpb24iIT09dC50eXBlJiYodC50cmFuc2FjdGlvbj1jKX0odCxlKSxyJiZmdW5jdGlvbih0LGUpe3QuY29udGV4dHM9e3RyYWNlOkF0KGUpLC4uLnQuY29udGV4dHN9LHQuc2RrUHJvY2Vzc2luZ01ldGFkYXRhPXtkeW5hbWljU2FtcGxpbmdDb250ZXh0Omp0KGUpLC4uLnQuc2RrUHJvY2Vzc2luZ01ldGFkYXRhfTtjb25zdCBuPUJ0KGUpLHI9VXQobikuZGVzY3JpcHRpb247ciYmIXQudHJhbnNhY3Rpb24mJiJ0cmFuc2FjdGlvbiI9PT10LnR5cGUmJih0LnRyYW5zYWN0aW9uPXIpfSh0LHIpLGZ1bmN0aW9uKHQsZSl7dC5maW5nZXJwcmludD10LmZpbmdlcnByaW50P2Z1bmN0aW9uKHQpe3JldHVybiBBcnJheS5pc0FycmF5KHQpP3Q6W3RdfSh0LmZpbmdlcnByaW50KTpbXSxlJiYodC5maW5nZXJwcmludD10LmZpbmdlcnByaW50LmNvbmNhdChlKSk7dC5maW5nZXJwcmludCYmIXQuZmluZ2VycHJpbnQubGVuZ3RoJiZkZWxldGUgdC5maW5nZXJwcmludH0odCxuKSxmdW5jdGlvbih0LGUpe2NvbnN0IG49Wy4uLnQuYnJlYWRjcnVtYnN8fFtdLC4uLmVdO3QuYnJlYWRjcnVtYnM9bi5sZW5ndGg/bjp2b2lkIDB9KHQsbyksZnVuY3Rpb24odCxlKXt0LnNka1Byb2Nlc3NpbmdNZXRhZGF0YT17Li4udC5zZGtQcm9jZXNzaW5nTWV0YWRhdGEsLi4uZX19KHQscyl9Y29uc3QgV3Q9IjciO2Z1bmN0aW9uIFl0KHQsZSl7cmV0dXJuIG49e3NlbnRyeV9rZXk6dC5wdWJsaWNLZXksc2VudHJ5X3ZlcnNpb246V3QsLi4uZSYme3NlbnRyeV9jbGllbnQ6YCR7ZS5uYW1lfS8ke2UudmVyc2lvbn1gfX0sT2JqZWN0LmtleXMobikubWFwKCh0PT5gJHtlbmNvZGVVUklDb21wb25lbnQodCl9PSR7ZW5jb2RlVVJJQ29tcG9uZW50KG5bdF0pfWApKS5qb2luKCImIik7dmFyIG59Y29uc3QgcXQ9NjQ7ZnVuY3Rpb24gRnQodCxlLG49cSh0LmJ1ZmZlclNpemV8fHF0KSl7bGV0IHI9e307cmV0dXJue3NlbmQ6ZnVuY3Rpb24obyl7Y29uc3Qgcz1bXTtpZihYKG8sKChlLG4pPT57Y29uc3Qgbz1ydChuKTtpZihmdW5jdGlvbih0LGUsbj1EYXRlLm5vdygpKXtyZXR1cm4gZnVuY3Rpb24odCxlKXtyZXR1cm4gdFtlXXx8dC5hbGx8fDB9KHQsZSk+bn0ocixvKSl7Y29uc3Qgcj1LdChlLG4pO3QucmVjb3JkRHJvcHBlZEV2ZW50KCJyYXRlbGltaXRfYmFja29mZiIsbyxyKX1lbHNlIHMucHVzaChlKX0pKSwwPT09cy5sZW5ndGgpcmV0dXJuIFcoe30pO2NvbnN0IGk9UShvWzBdLHMpLGM9ZT0+e1goaSwoKG4scik9Pntjb25zdCBvPUt0KG4scik7dC5yZWNvcmREcm9wcGVkRXZlbnQoZSxydChyKSxvKX0pKX07cmV0dXJuIG4uYWRkKCgoKT0+ZSh7Ym9keTpldChpKX0pLnRoZW4oKHQ9Pih2b2lkIDAhPT10LnN0YXR1c0NvZGUmJih0LnN0YXR1c0NvZGU8MjAwfHx0LnN0YXR1c0NvZGU+PTMwMCkmJmN0JiZOLndhcm4oYFNlbnRyeSByZXNwb25kZWQgd2l0aCBzdGF0dXMgY29kZSAke3Quc3RhdHVzQ29kZX0gdG8gc2VudCBldmVudC5gKSxyPWl0KHIsdCksdCkpLCh0PT57dGhyb3cgYygibmV0d29ya19lcnJvciIpLHR9KSkpKS50aGVuKCh0PT50KSwodD0+e2lmKHQgaW5zdGFuY2VvZiBDKXJldHVybiBjdCYmTi5lcnJvcigiU2tpcHBlZCBzZW5kaW5nIGV2ZW50IGJlY2F1c2UgYnVmZmVyIGlzIGZ1bGwuIiksYygicXVldWVfb3ZlcmZsb3ciKSxXKHt9KTt0aHJvdyB0fSkpfSxmbHVzaDp0PT5uLmRyYWluKHQpfX1mdW5jdGlvbiBLdCh0LGUpe2lmKCJldmVudCI9PT1lfHwidHJhbnNhY3Rpb24iPT09ZSlyZXR1cm4gQXJyYXkuaXNBcnJheSh0KT90WzFdOnZvaWQgMH1jb25zdCBWdD1TeW1ib2woIkFnZW50QmFzZUludGVybmFsU3RhdGUiKTtjbGFzcyBadCBleHRlbmRzIGguQWdlbnR7W1Z0XTtvcHRpb25zO2tlZXBBbGl2ZTtjb25zdHJ1Y3Rvcih0KXtzdXBlcih0KSx0aGlzW1Z0XT17fX1pc1NlY3VyZUVuZHBvaW50KHQpe2lmKHQpe2lmKCJib29sZWFuIj09dHlwZW9mIHQuc2VjdXJlRW5kcG9pbnQpcmV0dXJuIHQuc2VjdXJlRW5kcG9pbnQ7aWYoInN0cmluZyI9PXR5cGVvZiB0LnByb3RvY29sKXJldHVybiJodHRwczoiPT09dC5wcm90b2NvbH1jb25zdHtzdGFjazplfT1uZXcgRXJyb3I7cmV0dXJuInN0cmluZyI9PXR5cGVvZiBlJiZlLnNwbGl0KCJcbiIpLnNvbWUoKHQ9Pi0xIT09dC5pbmRleE9mKCIoaHR0cHMuanM6Iil8fC0xIT09dC5pbmRleE9mKCJub2RlOmh0dHBzOiIpKSl9Y3JlYXRlU29ja2V0KHQsZSxuKXtjb25zdCByPXsuLi5lLHNlY3VyZUVuZHBvaW50OnRoaXMuaXNTZWN1cmVFbmRwb2ludChlKX07UHJvbWlzZS5yZXNvbHZlKCkudGhlbigoKCk9PnRoaXMuY29ubmVjdCh0LHIpKSkudGhlbigobz0+e2lmKG8gaW5zdGFuY2VvZiBoLkFnZW50KXJldHVybiBvLmFkZFJlcXVlc3QodCxyKTt0aGlzW1Z0XS5jdXJyZW50U29ja2V0PW8sc3VwZXIuY3JlYXRlU29ja2V0KHQsZSxuKX0pLG4pfWNyZWF0ZUNvbm5lY3Rpb24oKXtjb25zdCB0PXRoaXNbVnRdLmN1cnJlbnRTb2NrZXQ7aWYodGhpc1tWdF0uY3VycmVudFNvY2tldD12b2lkIDAsIXQpdGhyb3cgbmV3IEVycm9yKCJObyBzb2NrZXQgd2FzIHJldHVybmVkIGluIHRoZSBgY29ubmVjdCgpYCBmdW5jdGlvbiIpO3JldHVybiB0fWdldCBkZWZhdWx0UG9ydCgpe3JldHVybiB0aGlzW1Z0XS5kZWZhdWx0UG9ydD8/KCJodHRwczoiPT09dGhpcy5wcm90b2NvbD80NDM6ODApfXNldCBkZWZhdWx0UG9ydCh0KXt0aGlzW1Z0XSYmKHRoaXNbVnRdLmRlZmF1bHRQb3J0PXQpfWdldCBwcm90b2NvbCgpe3JldHVybiB0aGlzW1Z0XS5wcm90b2NvbD8/KHRoaXMuaXNTZWN1cmVFbmRwb2ludCgpPyJodHRwczoiOiJodHRwOiIpfXNldCBwcm90b2NvbCh0KXt0aGlzW1Z0XSYmKHRoaXNbVnRdLnByb3RvY29sPXQpfX1mdW5jdGlvbiBRdCguLi50KXtOLmxvZygiW2h0dHBzLXByb3h5LWFnZW50OnBhcnNlLXByb3h5LXJlc3BvbnNlXSIsLi4udCl9ZnVuY3Rpb24gWHQodCl7cmV0dXJuIG5ldyBQcm9taXNlKCgoZSxuKT0+e2xldCByPTA7Y29uc3Qgbz1bXTtmdW5jdGlvbiBzKCl7Y29uc3QgYz10LnJlYWQoKTtjP2Z1bmN0aW9uKGMpe28ucHVzaChjKSxyKz1jLmxlbmd0aDtjb25zdCB1PUJ1ZmZlci5jb25jYXQobyxyKSxhPXUuaW5kZXhPZigiXHJcblxyXG4iKTtpZigtMT09PWEpcmV0dXJuIFF0KCJoYXZlIG5vdCByZWNlaXZlZCBlbmQgb2YgSFRUUCBoZWFkZXJzIHlldC4uLiIpLHZvaWQgcygpO2NvbnN0IGY9dS5zbGljZSgwLGEpLnRvU3RyaW5nKCJhc2NpaSIpLnNwbGl0KCJcclxuIiksaD1mLnNoaWZ0KCk7aWYoIWgpcmV0dXJuIHQuZGVzdHJveSgpLG4obmV3IEVycm9yKCJObyBoZWFkZXIgcmVjZWl2ZWQgZnJvbSBwcm94eSBDT05ORUNUIHJlc3BvbnNlIikpO2NvbnN0IHA9aC5zcGxpdCgiICIpLGw9K3BbMV0sZD1wLnNsaWNlKDIpLmpvaW4oIiAiKSxtPXt9O2Zvcihjb25zdCBlIG9mIGYpe2lmKCFlKWNvbnRpbnVlO2NvbnN0IHI9ZS5pbmRleE9mKCI6Iik7aWYoLTE9PT1yKXJldHVybiB0LmRlc3Ryb3koKSxuKG5ldyBFcnJvcihgSW52YWxpZCBoZWFkZXIgZnJvbSBwcm94eSBDT05ORUNUIHJlc3BvbnNlOiAiJHtlfSJgKSk7Y29uc3Qgbz1lLnNsaWNlKDAscikudG9Mb3dlckNhc2UoKSxzPWUuc2xpY2UocisxKS50cmltU3RhcnQoKSxpPW1bb107InN0cmluZyI9PXR5cGVvZiBpP21bb109W2ksc106QXJyYXkuaXNBcnJheShpKT9pLnB1c2gocyk6bVtvXT1zfVF0KCJnb3QgcHJveHkgc2VydmVyIHJlc3BvbnNlOiAlbyAlbyIsaCxtKSxpKCksZSh7Y29ubmVjdDp7c3RhdHVzQ29kZTpsLHN0YXR1c1RleHQ6ZCxoZWFkZXJzOm19LGJ1ZmZlcmVkOnV9KX0oYyk6dC5vbmNlKCJyZWFkYWJsZSIscyl9ZnVuY3Rpb24gaSgpe3QucmVtb3ZlTGlzdGVuZXIoImVuZCIsYyksdC5yZW1vdmVMaXN0ZW5lcigiZXJyb3IiLHUpLHQucmVtb3ZlTGlzdGVuZXIoInJlYWRhYmxlIixzKX1mdW5jdGlvbiBjKCl7aSgpLFF0KCJvbmVuZCIpLG4obmV3IEVycm9yKCJQcm94eSBjb25uZWN0aW9uIGVuZGVkIGJlZm9yZSByZWNlaXZpbmcgQ09OTkVDVCByZXNwb25zZSIpKX1mdW5jdGlvbiB1KHQpe2koKSxRdCgib25lcnJvciAlbyIsdCksbih0KX10Lm9uKCJlcnJvciIsdSksdC5vbigiZW5kIixjKSxzKCl9KSl9ZnVuY3Rpb24gdGUoLi4udCl7Ti5sb2coIltodHRwcy1wcm94eS1hZ2VudF0iLC4uLnQpfWNsYXNzIGVlIGV4dGVuZHMgWnR7c3RhdGljIHByb3RvY29scz1bImh0dHAiLCJodHRwcyJdO3Byb3h5O3Byb3h5SGVhZGVycztjb25uZWN0T3B0cztjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKGUpLHRoaXMub3B0aW9ucz17fSx0aGlzLnByb3h5PSJzdHJpbmciPT10eXBlb2YgdD9uZXcgVVJMKHQpOnQsdGhpcy5wcm94eUhlYWRlcnM9ZT8uaGVhZGVycz8/e30sdGUoIkNyZWF0aW5nIG5ldyBIdHRwc1Byb3h5QWdlbnQgaW5zdGFuY2U6ICVvIix0aGlzLnByb3h5LmhyZWYpO2NvbnN0IG49KHRoaXMucHJveHkuaG9zdG5hbWV8fHRoaXMucHJveHkuaG9zdCkucmVwbGFjZSgvXlxbfFxdJC9nLCIiKSxyPXRoaXMucHJveHkucG9ydD9wYXJzZUludCh0aGlzLnByb3h5LnBvcnQsMTApOiJodHRwczoiPT09dGhpcy5wcm94eS5wcm90b2NvbD80NDM6ODA7dGhpcy5jb25uZWN0T3B0cz17QUxQTlByb3RvY29sczpbImh0dHAvMS4xIl0sLi4uZT9yZShlLCJoZWFkZXJzIik6bnVsbCxob3N0Om4scG9ydDpyfX1hc3luYyBjb25uZWN0KHQsZSl7Y29uc3R7cHJveHk6bn09dGhpcztpZighZS5ob3N0KXRocm93IG5ldyBUeXBlRXJyb3IoJ05vICJob3N0IiBwcm92aWRlZCcpO2xldCByO2lmKCJodHRwczoiPT09bi5wcm90b2NvbCl7dGUoIkNyZWF0aW5nIGB0bHMuU29ja2V0YDogJW8iLHRoaXMuY29ubmVjdE9wdHMpO2NvbnN0IHQ9dGhpcy5jb25uZWN0T3B0cy5zZXJ2ZXJuYW1lfHx0aGlzLmNvbm5lY3RPcHRzLmhvc3Q7cj1mLmNvbm5lY3Qoey4uLnRoaXMuY29ubmVjdE9wdHMsc2VydmVybmFtZTp0JiZhLmlzSVAodCk/dm9pZCAwOnR9KX1lbHNlIHRlKCJDcmVhdGluZyBgbmV0LlNvY2tldGA6ICVvIix0aGlzLmNvbm5lY3RPcHRzKSxyPWEuY29ubmVjdCh0aGlzLmNvbm5lY3RPcHRzKTtjb25zdCBvPSJmdW5jdGlvbiI9PXR5cGVvZiB0aGlzLnByb3h5SGVhZGVycz90aGlzLnByb3h5SGVhZGVycygpOnsuLi50aGlzLnByb3h5SGVhZGVyc30scz1hLmlzSVB2NihlLmhvc3QpP2BbJHtlLmhvc3R9XWA6ZS5ob3N0O2xldCBpPWBDT05ORUNUICR7c306JHtlLnBvcnR9IEhUVFAvMS4xXHJcbmA7aWYobi51c2VybmFtZXx8bi5wYXNzd29yZCl7Y29uc3QgdD1gJHtkZWNvZGVVUklDb21wb25lbnQobi51c2VybmFtZSl9OiR7ZGVjb2RlVVJJQ29tcG9uZW50KG4ucGFzc3dvcmQpfWA7b1siUHJveHktQXV0aG9yaXphdGlvbiJdPWBCYXNpYyAke0J1ZmZlci5mcm9tKHQpLnRvU3RyaW5nKCJiYXNlNjQiKX1gfW8uSG9zdD1gJHtzfToke2UucG9ydH1gLG9bIlByb3h5LUNvbm5lY3Rpb24iXXx8KG9bIlByb3h5LUNvbm5lY3Rpb24iXT10aGlzLmtlZXBBbGl2ZT8iS2VlcC1BbGl2ZSI6ImNsb3NlIik7Zm9yKGNvbnN0IHQgb2YgT2JqZWN0LmtleXMobykpaSs9YCR7dH06ICR7b1t0XX1cclxuYDtjb25zdCBjPVh0KHIpO3Iud3JpdGUoYCR7aX1cclxuYCk7Y29uc3R7Y29ubmVjdDp1LGJ1ZmZlcmVkOmh9PWF3YWl0IGM7aWYodC5lbWl0KCJwcm94eUNvbm5lY3QiLHUpLHRoaXMuZW1pdCgicHJveHlDb25uZWN0Iix1LHQpLDIwMD09PXUuc3RhdHVzQ29kZSl7aWYodC5vbmNlKCJzb2NrZXQiLG5lKSxlLnNlY3VyZUVuZHBvaW50KXt0ZSgiVXBncmFkaW5nIHNvY2tldCBjb25uZWN0aW9uIHRvIFRMUyIpO2NvbnN0IHQ9ZS5zZXJ2ZXJuYW1lfHxlLmhvc3Q7cmV0dXJuIGYuY29ubmVjdCh7Li4ucmUoZSwiaG9zdCIsInBhdGgiLCJwb3J0Iiksc29ja2V0OnIsc2VydmVybmFtZTphLmlzSVAodCk/dm9pZCAwOnR9KX1yZXR1cm4gcn1yLmRlc3Ryb3koKTtjb25zdCBwPW5ldyBhLlNvY2tldCh7d3JpdGFibGU6ITF9KTtyZXR1cm4gcC5yZWFkYWJsZT0hMCx0Lm9uY2UoInNvY2tldCIsKHQ9Pnt0ZSgiUmVwbGF5aW5nIHByb3h5IGJ1ZmZlciBmb3IgZmFpbGVkIHJlcXVlc3QiKSx0LnB1c2goaCksdC5wdXNoKG51bGwpfSkpLHB9fWZ1bmN0aW9uIG5lKHQpe3QucmVzdW1lKCl9ZnVuY3Rpb24gcmUodCwuLi5lKXtjb25zdCBuPXt9O2xldCByO2ZvcihyIGluIHQpZS5pbmNsdWRlcyhyKXx8KG5bcl09dFtyXSk7cmV0dXJuIG59Y29uc3Qgb2U9MzI3Njg7ZnVuY3Rpb24gc2UodCl7cmV0dXJuIHQucmVwbGFjZSgvXltBLVpdOi8sIiIpLnJlcGxhY2UoL1xcL2csIi8iKX1jb25zdCBpZT1uO2xldCBjZSx1ZT0hMTtmdW5jdGlvbiBhZSh0KXtpZS5kZWJ1ZyYmY29uc29sZS5sb2coYFtBTlIgV29ya2VyXSAke3R9YCl9dmFyIGZlLGhlLHBlO2NvbnN0IGxlPWZ1bmN0aW9uKHQpe2xldCBlO3RyeXtlPW5ldyBVUkwodC51cmwpfWNhdGNoKGUpe3JldHVybiB4KCgoKT0+e2NvbnNvbGUud2FybigiW0BzZW50cnkvbm9kZV06IEludmFsaWQgZHNuIG9yIHR1bm5lbCBvcHRpb24sIHdpbGwgbm90IHNlbmQgYW55IGV2ZW50cy4gVGhlIHR1bm5lbCBvcHRpb24gbXVzdCBiZSBhIGZ1bGwgVVJMIHdoZW4gdXNlZC4iKX0pKSxGdCh0LCgoKT0+UHJvbWlzZS5yZXNvbHZlKHt9KSkpfWNvbnN0IG49Imh0dHBzOiI9PT1lLnByb3RvY29sLHI9ZnVuY3Rpb24odCxlKXtjb25zdHtub19wcm94eTpufT1wcm9jZXNzLmVudjtyZXR1cm4gbiYmbi5zcGxpdCgiLCIpLnNvbWUoKGU9PnQuaG9zdC5lbmRzV2l0aChlKXx8dC5ob3N0bmFtZS5lbmRzV2l0aChlKSkpP3ZvaWQgMDplfShlLHQucHJveHl8fChuP3Byb2Nlc3MuZW52Lmh0dHBzX3Byb3h5OnZvaWQgMCl8fHByb2Nlc3MuZW52Lmh0dHBfcHJveHkpLG89bj9pOnMsYT12b2lkIDAhPT10LmtlZXBBbGl2ZSYmdC5rZWVwQWxpdmUsZj1yP25ldyBlZShyKTpuZXcgby5BZ2VudCh7a2VlcEFsaXZlOmEsbWF4U29ja2V0czozMCx0aW1lb3V0OjJlM30pO3JldHVybiB6dCgoKCk9Pntjb25zdCBlPWZ1bmN0aW9uKHQsZSxuKXtjb25zdHtob3N0bmFtZTpyLHBhdGhuYW1lOm8scG9ydDpzLHByb3RvY29sOmksc2VhcmNoOmF9PW5ldyBVUkwodC51cmwpO3JldHVybiBmdW5jdGlvbihmKXtyZXR1cm4gbmV3IFByb21pc2UoKChoLHApPT57bGV0IGw9ZnVuY3Rpb24odCl7cmV0dXJuIG5ldyBjKHtyZWFkKCl7dGhpcy5wdXNoKHQpLHRoaXMucHVzaChudWxsKX19KX0oZi5ib2R5KTtjb25zdCBkPXsuLi50LmhlYWRlcnN9O2YuYm9keS5sZW5ndGg+b2UmJihkWyJjb250ZW50LWVuY29kaW5nIl09Imd6aXAiLGw9bC5waXBlKHUoKSkpO2NvbnN0IG09ZS5yZXF1ZXN0KHttZXRob2Q6IlBPU1QiLGFnZW50Om4saGVhZGVyczpkLGhvc3RuYW1lOnIscGF0aDpgJHtvfSR7YX1gLHBvcnQ6cyxwcm90b2NvbDppLGNhOnQuY2FDZXJ0c30sKHQ9Pnt0Lm9uKCJkYXRhIiwoKCk9Pnt9KSksdC5vbigiZW5kIiwoKCk9Pnt9KSksdC5zZXRFbmNvZGluZygidXRmOCIpO2NvbnN0IGU9dC5oZWFkZXJzWyJyZXRyeS1hZnRlciJdPz9udWxsLG49dC5oZWFkZXJzWyJ4LXNlbnRyeS1yYXRlLWxpbWl0cyJdPz9udWxsO2goe3N0YXR1c0NvZGU6dC5zdGF0dXNDb2RlLGhlYWRlcnM6eyJyZXRyeS1hZnRlciI6ZSwieC1zZW50cnktcmF0ZS1saW1pdHMiOkFycmF5LmlzQXJyYXkobik/blswXTpufX0pfSkpO20ub24oImVycm9yIixwKSxsLnBpcGUobSl9KSl9fSh0LHQuaHR0cE1vZHVsZT8/byxmKTtyZXR1cm4gRnQodCxlKX0pKX0oe3VybDooZmU9aWUuZHNuLGhlPWllLnR1bm5lbCxwZT1pZS5zZGtNZXRhZGF0YS5zZGssaGV8fGAke2Z1bmN0aW9uKHQpe3JldHVybmAke2Z1bmN0aW9uKHQpe2NvbnN0IGU9dC5wcm90b2NvbD9gJHt0LnByb3RvY29sfTpgOiIiLG49dC5wb3J0P2A6JHt0LnBvcnR9YDoiIjtyZXR1cm5gJHtlfS8vJHt0Lmhvc3R9JHtufSR7dC5wYXRoP2AvJHt0LnBhdGh9YDoiIn0vYXBpL2B9KHQpfSR7dC5wcm9qZWN0SWR9L2VudmVsb3BlL2B9KGZlKX0/JHtZdChmZSxwZSl9YCkscmVjb3JkRHJvcHBlZEV2ZW50OigpPT57fX0pO2FzeW5jIGZ1bmN0aW9uIGRlKCl7aWYoY2Upe2FlKCJTZW5kaW5nIGFibm9ybWFsIHNlc3Npb24iKSxodChjZSx7c3RhdHVzOiJhYm5vcm1hbCIsYWJub3JtYWxfbWVjaGFuaXNtOiJhbnJfZm9yZWdyb3VuZCJ9KTtjb25zdCB0PWZ1bmN0aW9uKHQsZSxuLHIpe2NvbnN0IG89b3Qobik7cmV0dXJuIFEoe3NlbnRfYXQ6KG5ldyBEYXRlKS50b0lTT1N0cmluZygpLC4uLm8mJntzZGs6b30sLi4uISFyJiZlJiZ7ZHNuOmsoZSl9fSxbImFnZ3JlZ2F0ZXMiaW4gdD9be3R5cGU6InNlc3Npb25zIn0sdF06W3t0eXBlOiJzZXNzaW9uIn0sdC50b0pTT04oKV1dKX0oY2UsaWUuZHNuLGllLnNka01ldGFkYXRhLGllLnR1bm5lbCk7YWUoSlNPTi5zdHJpbmdpZnkodCkpLGF3YWl0IGxlLnNlbmQodCk7dHJ5e2U/LnBvc3RNZXNzYWdlKCJzZXNzaW9uLWVuZGVkIil9Y2F0Y2godCl7fX19ZnVuY3Rpb24gbWUodCl7aWYoIXQpcmV0dXJuO2NvbnN0IGU9ZnVuY3Rpb24odCl7aWYoIXQubGVuZ3RoKXJldHVybltdO2NvbnN0IGU9QXJyYXkuZnJvbSh0KTtyZXR1cm4vc2VudHJ5V3JhcHBlZC8udGVzdChlW2UubGVuZ3RoLTFdLmZ1bmN0aW9ufHwiIikmJmUucG9wKCksZS5yZXZlcnNlKCksUC50ZXN0KGVbZS5sZW5ndGgtMV0uZnVuY3Rpb258fCIiKSYmKGUucG9wKCksUC50ZXN0KGVbZS5sZW5ndGgtMV0uZnVuY3Rpb258fCIiKSYmZS5wb3AoKSksZS5zbGljZSgwLEEpLm1hcCgodD0+KHsuLi50LGZpbGVuYW1lOnQuZmlsZW5hbWV8fGVbZS5sZW5ndGgtMV0uZmlsZW5hbWUsZnVuY3Rpb246dC5mdW5jdGlvbnx8SX0pKSl9KHQpO2lmKGllLmFwcFJvb3RQYXRoKWZvcihjb25zdCB0IG9mIGUpdC5maWxlbmFtZSYmKHQuZmlsZW5hbWU9Ryh0LmZpbGVuYW1lLGllLmFwcFJvb3RQYXRoKSk7cmV0dXJuIGV9YXN5bmMgZnVuY3Rpb24gZ2UodCxlKXtpZih1ZSlyZXR1cm47dWU9ITAsYXdhaXQgZGUoKSxhZSgiU2VuZGluZyBldmVudCIpO2NvbnN0IG49e2V2ZW50X2lkOk0oKSxjb250ZXh0czppZS5jb250ZXh0cyxyZWxlYXNlOmllLnJlbGVhc2UsZW52aXJvbm1lbnQ6aWUuZW52aXJvbm1lbnQsZGlzdDppZS5kaXN0LHBsYXRmb3JtOiJub2RlIixsZXZlbDoiZXJyb3IiLGV4Y2VwdGlvbjp7dmFsdWVzOlt7dHlwZToiQXBwbGljYXRpb25Ob3RSZXNwb25kaW5nIix2YWx1ZTpgQXBwbGljYXRpb24gTm90IFJlc3BvbmRpbmcgZm9yIGF0IGxlYXN0ICR7aWUuYW5yVGhyZXNob2xkfSBtc2Asc3RhY2t0cmFjZTp7ZnJhbWVzOm1lKHQpfSxtZWNoYW5pc206e3R5cGU6IkFOUiJ9fV19LHRhZ3M6aWUuc3RhdGljVGFnc307ZSYmZnVuY3Rpb24odCxlKXtpZihIdCh0LGUpLCF0LmNvbnRleHRzPy50cmFjZSl7Y29uc3R7dHJhY2VJZDpuLHNwYW5JZDpyLHBhcmVudFNwYW5JZDpvfT1lLnByb3BhZ2F0aW9uQ29udGV4dDt0LmNvbnRleHRzPXt0cmFjZTp7dHJhY2VfaWQ6bixzcGFuX2lkOnIscGFyZW50X3NwYW5faWQ6b30sLi4udC5jb250ZXh0c319fShuLGUpO2NvbnN0IHI9R3QobixpZS5kc24saWUuc2RrTWV0YWRhdGEsaWUudHVubmVsKTthZShKU09OLnN0cmluZ2lmeShyKSksYXdhaXQgbGUuc2VuZChyKSxhd2FpdCBsZS5mbHVzaCgyZTMpLHNldFRpbWVvdXQoKCgpPT57cHJvY2Vzcy5leGl0KDApfSksNWUzKX1sZXQgeWU7aWYoYWUoIlN0YXJ0ZWQiKSxpZS5jYXB0dXJlU3RhY2tUcmFjZSl7YWUoIkNvbm5lY3RpbmcgdG8gZGVidWdnZXIiKTtjb25zdCBlPW5ldyB0O2UuY29ubmVjdFRvTWFpblRocmVhZCgpLGFlKCJDb25uZWN0ZWQgdG8gZGVidWdnZXIiKTtjb25zdCBuPW5ldyBNYXA7ZS5vbigiRGVidWdnZXIuc2NyaXB0UGFyc2VkIiwodD0+e24uc2V0KHQucGFyYW1zLnNjcmlwdElkLHQucGFyYW1zLnVybCl9KSksZS5vbigiRGVidWdnZXIucGF1c2VkIiwodD0+e2lmKCJvdGhlciI9PT10LnBhcmFtcy5yZWFzb24pdHJ5e2FlKCJEZWJ1Z2dlciBwYXVzZWQiKTtjb25zdCBzPVsuLi50LnBhcmFtcy5jYWxsRnJhbWVzXSxpPWllLmFwcFJvb3RQYXRoP2Z1bmN0aW9uKHQ9KHByb2Nlc3MuYXJndlsxXT96KHByb2Nlc3MuYXJndlsxXSk6cHJvY2Vzcy5jd2QoKSksZT0iXFwiPT09byl7Y29uc3Qgbj1lP3NlKHQpOnQ7cmV0dXJuIHQ9PntpZighdClyZXR1cm47Y29uc3Qgbz1lP3NlKHQpOnQ7bGV0e2RpcjpzLGJhc2U6aSxleHQ6Y309ci5wYXJzZShvKTsiLmpzIiE9PWMmJiIubWpzIiE9PWMmJiIuY2pzIiE9PWN8fChpPWkuc2xpY2UoMCwtMSpjLmxlbmd0aCkpLHN8fChzPSIuIik7Y29uc3QgdT1zLmxhc3RJbmRleE9mKCIvbm9kZV9tb2R1bGVzIik7aWYodT4tMSlyZXR1cm5gJHtzLnNsaWNlKHUrMTQpLnJlcGxhY2UoL1wvL2csIi4iKX06JHtpfWA7aWYocy5zdGFydHNXaXRoKG4pKXtsZXQgdD1zLnNsaWNlKG4ubGVuZ3RoKzEpLnJlcGxhY2UoL1wvL2csIi4iKTtyZXR1cm4gdCYmKHQrPSI6IiksdCs9aSx0fXJldHVybiBpfX0oaWUuYXBwUm9vdFBhdGgpOigpPT57fSxjPXMubWFwKCh0PT5mdW5jdGlvbih0LGUsbil7Y29uc3Qgcj1lP2UucmVwbGFjZSgvXmZpbGU6XC9cLy8sIiIpOnZvaWQgMCxvPXQubG9jYXRpb24uY29sdW1uTnVtYmVyP3QubG9jYXRpb24uY29sdW1uTnVtYmVyKzE6dm9pZCAwLHM9dC5sb2NhdGlvbi5saW5lTnVtYmVyP3QubG9jYXRpb24ubGluZU51bWJlcisxOnZvaWQgMDtyZXR1cm4gaih7ZmlsZW5hbWU6cixtb2R1bGU6bihyKSxmdW5jdGlvbjp0LmZ1bmN0aW9uTmFtZXx8SSxjb2xubzpvLGxpbmVubzpzLGluX2FwcDpyP0Yocik6dm9pZCAwfSl9KHQsbi5nZXQodC5sb2NhdGlvbi5zY3JpcHRJZCksaSkpKSx1PXNldFRpbWVvdXQoKCgpPT57Z2UoYykudGhlbihudWxsLCgoKT0+e2FlKCJTZW5kaW5nIEFOUiBldmVudCBmYWlsZWQuIil9KSl9KSw1ZTMpO2UucG9zdCgiUnVudGltZS5ldmFsdWF0ZSIse2V4cHJlc3Npb246Imdsb2JhbC5fX1NFTlRSWV9HRVRfU0NPUEVTX18oKTsiLHNpbGVudDohMCxyZXR1cm5CeVZhbHVlOiEwfSwoKHQsbik9Pnt0JiZhZShgRXJyb3IgZXhlY3V0aW5nIHNjcmlwdDogJyR7dC5tZXNzYWdlfSdgKSxjbGVhclRpbWVvdXQodSk7Y29uc3Qgcj1uJiZuLnJlc3VsdD9uLnJlc3VsdC52YWx1ZTp2b2lkIDA7ZS5wb3N0KCJEZWJ1Z2dlci5yZXN1bWUiKSxlLnBvc3QoIkRlYnVnZ2VyLmRpc2FibGUiKSxnZShjLHIpLnRoZW4obnVsbCwoKCk9PnthZSgiU2VuZGluZyBBTlIgZXZlbnQgZmFpbGVkLiIpfSkpfSkpfWNhdGNoKHQpe3Rocm93IGUucG9zdCgiRGVidWdnZXIucmVzdW1lIiksZS5wb3N0KCJEZWJ1Z2dlci5kaXNhYmxlIiksdH19KSkseWU9KCk9Pnt0cnl7ZS5wb3N0KCJEZWJ1Z2dlci5lbmFibGUiLCgoKT0+e2UucG9zdCgiRGVidWdnZXIucGF1c2UiKX0pKX1jYXRjaCh0KXt9fX1jb25zdHtwb2xsOmJlfT1mdW5jdGlvbih0LGUsbixyKXtjb25zdCBvPXQoKTtsZXQgcz0hMSxpPSEwO3JldHVybiBzZXRJbnRlcnZhbCgoKCk9Pntjb25zdCB0PW8uZ2V0VGltZU1zKCk7ITE9PT1zJiZ0PmUrbiYmKHM9ITAsaSYmcigpKSx0PGUrbiYmKHM9ITEpfSksMjApLHtwb2xsOigpPT57by5yZXNldCgpfSxlbmFibGVkOnQ9PntpPXR9fX0oKGZ1bmN0aW9uKCl7bGV0IHQ9cHJvY2Vzcy5ocnRpbWUoKTtyZXR1cm57Z2V0VGltZU1zOigpPT57Y29uc3RbZSxuXT1wcm9jZXNzLmhydGltZSh0KTtyZXR1cm4gTWF0aC5mbG9vcigxZTMqZStuLzFlNil9LHJlc2V0OigpPT57dD1wcm9jZXNzLmhydGltZSgpfX19KSxpZS5wb2xsSW50ZXJ2YWwsaWUuYW5yVGhyZXNob2xkLChmdW5jdGlvbigpe2FlKCJXYXRjaGRvZyB0aW1lb3V0IikseWU/KGFlKCJQYXVzaW5nIGRlYnVnZ2VyIHRvIGNhcHR1cmUgc3RhY2sgdHJhY2UiKSx5ZSgpKTooYWUoIkNhcHR1cmluZyBldmVudCB3aXRob3V0IGEgc3RhY2sgdHJhY2UiKSxnZSgpLnRoZW4obnVsbCwoKCk9PnthZSgiU2VuZGluZyBBTlIgZXZlbnQgZmFpbGVkIG9uIHdhdGNoZG9nIHRpbWVvdXQuIil9KSkpfSkpO2U/Lm9uKCJtZXNzYWdlIiwodD0+e3Quc2Vzc2lvbiYmKGNlPWZ0KHQuc2Vzc2lvbikpLGJlKCl9KSk7';
|
|
1794
|
-
|
|
1795
|
-
const DEFAULT_INTERVAL = 50;
|
|
1796
|
-
const DEFAULT_HANG_THRESHOLD = 5000;
|
|
1797
|
-
|
|
1798
|
-
function log(message, ...args) {
|
|
1799
|
-
logger.log(`[ANR] ${message}`, ...args);
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
function globalWithScopeFetchFn() {
|
|
1803
|
-
return GLOBAL_OBJ;
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
|
-
/** Fetches merged scope data */
|
|
1807
|
-
function getScopeData() {
|
|
1808
|
-
const scope = getGlobalScope().getScopeData();
|
|
1809
|
-
mergeScopeData(scope, getIsolationScope().getScopeData());
|
|
1810
|
-
mergeScopeData(scope, getCurrentScope().getScopeData());
|
|
1811
|
-
|
|
1812
|
-
// We remove attachments because they likely won't serialize well as json
|
|
1813
|
-
scope.attachments = [];
|
|
1814
|
-
// We can't serialize event processor functions
|
|
1815
|
-
scope.eventProcessors = [];
|
|
1816
|
-
|
|
1817
|
-
return scope;
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
/**
|
|
1821
|
-
* Gets contexts by calling all event processors. This shouldn't be called until all integrations are setup
|
|
1822
|
-
*/
|
|
1823
|
-
async function getContexts(client) {
|
|
1824
|
-
let event = { message: 'ANR' };
|
|
1825
|
-
const eventHint = {};
|
|
1826
|
-
|
|
1827
|
-
for (const processor of client.getEventProcessors()) {
|
|
1828
|
-
if (event === null) break;
|
|
1829
|
-
event = await processor(event, eventHint);
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
return _optionalChain$9([event, 'optionalAccess', _2 => _2.contexts]) || {};
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1835
|
-
const INTEGRATION_NAME$1 = 'Anr';
|
|
1836
|
-
|
|
1837
|
-
const _anrIntegration = ((options = {}) => {
|
|
1838
|
-
if (NODE_VERSION.major < 16 || (NODE_VERSION.major === 16 && NODE_VERSION.minor < 17)) {
|
|
1839
|
-
throw new Error('ANR detection requires Node 16.17.0 or later');
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
let worker;
|
|
1843
|
-
let client;
|
|
1844
|
-
|
|
1845
|
-
// Hookup the scope fetch function to the global object so that it can be called from the worker thread via the
|
|
1846
|
-
// debugger when it pauses
|
|
1847
|
-
const gbl = globalWithScopeFetchFn();
|
|
1848
|
-
gbl.__SENTRY_GET_SCOPES__ = getScopeData;
|
|
1849
|
-
|
|
1850
|
-
return {
|
|
1851
|
-
name: INTEGRATION_NAME$1,
|
|
1852
|
-
startWorker: () => {
|
|
1853
|
-
if (worker) {
|
|
1854
|
-
return;
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
if (client) {
|
|
1858
|
-
worker = _startWorker(client, options);
|
|
1859
|
-
}
|
|
1860
|
-
},
|
|
1861
|
-
stopWorker: () => {
|
|
1862
|
-
if (worker) {
|
|
1863
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1864
|
-
worker.then(stop => {
|
|
1865
|
-
stop();
|
|
1866
|
-
worker = undefined;
|
|
1867
|
-
});
|
|
1868
|
-
}
|
|
1869
|
-
},
|
|
1870
|
-
setup(initClient) {
|
|
1871
|
-
client = initClient;
|
|
1872
|
-
|
|
1873
|
-
// setImmediate is used to ensure that all other integrations have had their setup called first.
|
|
1874
|
-
// This allows us to call into all integrations to fetch the full context
|
|
1875
|
-
setImmediate(() => this.startWorker());
|
|
1876
|
-
},
|
|
1877
|
-
} ;
|
|
1878
|
-
}) ;
|
|
1879
|
-
|
|
1880
|
-
const anrIntegration = defineIntegration(_anrIntegration) ;
|
|
1881
|
-
|
|
1882
|
-
/**
|
|
1883
|
-
* Starts the ANR worker thread
|
|
1884
|
-
*
|
|
1885
|
-
* @returns A function to stop the worker
|
|
1886
|
-
*/
|
|
1887
|
-
async function _startWorker(
|
|
1888
|
-
client,
|
|
1889
|
-
integrationOptions,
|
|
1890
|
-
) {
|
|
1891
|
-
const dsn = client.getDsn();
|
|
1892
|
-
|
|
1893
|
-
if (!dsn) {
|
|
1894
|
-
return () => {
|
|
1895
|
-
//
|
|
1896
|
-
};
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
const contexts = await getContexts(client);
|
|
1900
|
-
|
|
1901
|
-
// These will not be accurate if sent later from the worker thread
|
|
1902
|
-
_optionalChainDelete([contexts, 'access', _3 => _3.app, 'optionalAccess', _4 => delete _4.app_memory]);
|
|
1903
|
-
_optionalChainDelete([contexts, 'access', _5 => _5.device, 'optionalAccess', _6 => delete _6.free_memory]);
|
|
1904
|
-
|
|
1905
|
-
const initOptions = client.getOptions();
|
|
1906
|
-
|
|
1907
|
-
const sdkMetadata = client.getSdkMetadata() || {};
|
|
1908
|
-
if (sdkMetadata.sdk) {
|
|
1909
|
-
sdkMetadata.sdk.integrations = initOptions.integrations.map(i => i.name);
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
const options = {
|
|
1913
|
-
debug: logger.isEnabled(),
|
|
1914
|
-
dsn,
|
|
1915
|
-
tunnel: initOptions.tunnel,
|
|
1916
|
-
environment: initOptions.environment || 'production',
|
|
1917
|
-
release: initOptions.release,
|
|
1918
|
-
dist: initOptions.dist,
|
|
1919
|
-
sdkMetadata,
|
|
1920
|
-
appRootPath: integrationOptions.appRootPath,
|
|
1921
|
-
pollInterval: integrationOptions.pollInterval || DEFAULT_INTERVAL,
|
|
1922
|
-
anrThreshold: integrationOptions.anrThreshold || DEFAULT_HANG_THRESHOLD,
|
|
1923
|
-
captureStackTrace: !!integrationOptions.captureStackTrace,
|
|
1924
|
-
staticTags: integrationOptions.staticTags || {},
|
|
1925
|
-
contexts,
|
|
1926
|
-
};
|
|
1927
|
-
|
|
1928
|
-
if (options.captureStackTrace) {
|
|
1929
|
-
if (!inspector.url()) {
|
|
1930
|
-
inspector.open(0);
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
const worker = new Worker(new URL(`data:application/javascript;base64,${base64WorkerScript}`), {
|
|
1935
|
-
workerData: options,
|
|
1936
|
-
// We don't want any Node args to be passed to the worker
|
|
1937
|
-
execArgv: [],
|
|
1938
|
-
});
|
|
1939
|
-
|
|
1940
|
-
process.on('exit', () => {
|
|
1941
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1942
|
-
worker.terminate();
|
|
1943
|
-
});
|
|
1944
|
-
|
|
1945
|
-
const timer = setInterval(() => {
|
|
1946
|
-
try {
|
|
1947
|
-
const currentSession = getCurrentScope().getSession();
|
|
1948
|
-
// We need to copy the session object and remove the toJSON method so it can be sent to the worker
|
|
1949
|
-
// serialized without making it a SerializedSession
|
|
1950
|
-
const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined;
|
|
1951
|
-
// message the worker to tell it the main event loop is still running
|
|
1952
|
-
worker.postMessage({ session });
|
|
1953
|
-
} catch (_) {
|
|
1954
|
-
//
|
|
1955
|
-
}
|
|
1956
|
-
}, options.pollInterval);
|
|
1957
|
-
// Timer should not block exit
|
|
1958
|
-
timer.unref();
|
|
1959
|
-
|
|
1960
|
-
worker.on('message', (msg) => {
|
|
1961
|
-
if (msg === 'session-ended') {
|
|
1962
|
-
log('ANR event sent from ANR worker. Clearing session in this thread.');
|
|
1963
|
-
getCurrentScope().setSession(undefined);
|
|
1964
|
-
}
|
|
1965
|
-
});
|
|
1966
|
-
|
|
1967
|
-
worker.once('error', (err) => {
|
|
1968
|
-
clearInterval(timer);
|
|
1969
|
-
log('ANR worker error', err);
|
|
1970
|
-
});
|
|
1971
|
-
|
|
1972
|
-
worker.once('exit', (code) => {
|
|
1973
|
-
clearInterval(timer);
|
|
1974
|
-
log('ANR worker exit', code);
|
|
1975
|
-
});
|
|
1976
|
-
|
|
1977
|
-
// Ensure this thread can't block app exit
|
|
1978
|
-
worker.unref();
|
|
1979
|
-
|
|
1980
|
-
return () => {
|
|
1981
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1982
|
-
worker.terminate();
|
|
1983
|
-
clearInterval(timer);
|
|
1984
|
-
};
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
function _optionalChain$8(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
1988
|
-
|
|
1989
|
-
const _expressIntegration = (() => {
|
|
1990
|
-
return {
|
|
1991
|
-
name: 'Express',
|
|
1992
|
-
setupOnce() {
|
|
1993
|
-
addOpenTelemetryInstrumentation(
|
|
1994
|
-
new ExpressInstrumentation({
|
|
1995
|
-
requestHook(span) {
|
|
1996
|
-
addOriginToSpan(span, 'auto.http.otel.express');
|
|
1997
|
-
},
|
|
1998
|
-
spanNameHook(info, defaultName) {
|
|
1999
|
-
if (getIsolationScope() === getDefaultIsolationScope()) {
|
|
2000
|
-
DEBUG_BUILD &&
|
|
2001
|
-
logger.warn('Isolation scope is still default isolation scope - skipping setting transactionName');
|
|
2002
|
-
return defaultName;
|
|
2003
|
-
}
|
|
2004
|
-
if (info.layerType === 'request_handler') {
|
|
2005
|
-
// type cast b/c Otel unfortunately types info.request as any :(
|
|
2006
|
-
const req = info.request ;
|
|
2007
|
-
const method = req.method ? req.method.toUpperCase() : 'GET';
|
|
2008
|
-
getIsolationScope().setTransactionName(`${method} ${info.route}`);
|
|
2009
|
-
}
|
|
2010
|
-
return defaultName;
|
|
2011
|
-
},
|
|
2012
|
-
}),
|
|
2013
|
-
);
|
|
2014
|
-
},
|
|
2015
|
-
};
|
|
2016
|
-
}) ;
|
|
2017
|
-
|
|
2018
|
-
/**
|
|
2019
|
-
* Express integration
|
|
2020
|
-
*
|
|
2021
|
-
* Capture tracing data for express.
|
|
2022
|
-
* In order to capture exceptions, you have to call `setupExpressErrorHandler(app)` before any other middleware and after all controllers.
|
|
2023
|
-
*/
|
|
2024
|
-
const expressIntegration = defineIntegration(_expressIntegration);
|
|
2025
|
-
|
|
2026
|
-
/**
|
|
2027
|
-
* An Express-compatible error handler.
|
|
2028
|
-
*/
|
|
2029
|
-
function expressErrorHandler(options
|
|
2030
|
-
|
|
2031
|
-
) {
|
|
2032
|
-
return function sentryErrorMiddleware(
|
|
2033
|
-
error,
|
|
2034
|
-
_req,
|
|
2035
|
-
res,
|
|
2036
|
-
next,
|
|
2037
|
-
) {
|
|
2038
|
-
const shouldHandleError = _optionalChain$8([options, 'optionalAccess', _ => _.shouldHandleError]) || defaultShouldHandleError;
|
|
2039
|
-
|
|
2040
|
-
if (shouldHandleError(error)) {
|
|
2041
|
-
const client = getClient$1();
|
|
2042
|
-
if (client && client.getOptions().autoSessionTracking) {
|
|
2043
|
-
// Check if the `SessionFlusher` is instantiated on the client to go into this branch that marks the
|
|
2044
|
-
// `requestSession.status` as `Crashed`, and this check is necessary because the `SessionFlusher` is only
|
|
2045
|
-
// instantiated when the the`requestHandler` middleware is initialised, which indicates that we should be
|
|
2046
|
-
// running in SessionAggregates mode
|
|
2047
|
-
const isSessionAggregatesMode = client['_sessionFlusher'] !== undefined;
|
|
2048
|
-
if (isSessionAggregatesMode) {
|
|
2049
|
-
const requestSession = getIsolationScope().getRequestSession();
|
|
2050
|
-
// If an error bubbles to the `errorHandler`, then this is an unhandled error, and should be reported as a
|
|
2051
|
-
// Crashed session. The `_requestSession.status` is checked to ensure that this error is happening within
|
|
2052
|
-
// the bounds of a request, and if so the status is updated
|
|
2053
|
-
if (requestSession && requestSession.status !== undefined) {
|
|
2054
|
-
requestSession.status = 'crashed';
|
|
2055
|
-
}
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
|
|
2059
|
-
const eventId = captureException(error, { mechanism: { type: 'middleware', handled: false } });
|
|
2060
|
-
(res ).sentry = eventId;
|
|
2061
|
-
next(error);
|
|
2062
|
-
|
|
2063
|
-
return;
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
|
-
next(error);
|
|
2067
|
-
};
|
|
2068
|
-
}
|
|
2069
|
-
|
|
2070
|
-
/**
|
|
2071
|
-
* Setup an error handler for Express.
|
|
2072
|
-
* The error handler must be before any other middleware and after all controllers.
|
|
2073
|
-
*/
|
|
2074
|
-
function setupExpressErrorHandler(app) {
|
|
2075
|
-
app.use(expressErrorHandler());
|
|
2076
|
-
|
|
2077
|
-
if (!isWrapped$1(app.use)) {
|
|
2078
|
-
consoleSandbox(() => {
|
|
2079
|
-
// eslint-disable-next-line no-console
|
|
2080
|
-
console.warn(
|
|
2081
|
-
'[Sentry] Express is not instrumented. This is likely because you required/imported express before calling `Sentry.init()`.',
|
|
2082
|
-
);
|
|
2083
|
-
});
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
|
-
function getStatusCodeFromResponse(error) {
|
|
2088
|
-
const statusCode = error.status || error.statusCode || error.status_code || (error.output && error.output.statusCode);
|
|
2089
|
-
return statusCode ? parseInt(statusCode , 10) : 500;
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
|
-
/** Returns true if response code is internal server error */
|
|
2093
|
-
function defaultShouldHandleError(error) {
|
|
2094
|
-
const status = getStatusCodeFromResponse(error);
|
|
2095
|
-
return status >= 500;
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
function _optionalChain$7(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2099
|
-
const _fastifyIntegration = (() => {
|
|
2100
|
-
return {
|
|
2101
|
-
name: 'Fastify',
|
|
2102
|
-
setupOnce() {
|
|
2103
|
-
addOpenTelemetryInstrumentation(
|
|
2104
|
-
new FastifyInstrumentation({
|
|
2105
|
-
requestHook(span) {
|
|
2106
|
-
addOriginToSpan(span, 'auto.http.otel.fastify');
|
|
2107
|
-
},
|
|
2108
|
-
}),
|
|
2109
|
-
);
|
|
2110
|
-
},
|
|
2111
|
-
};
|
|
2112
|
-
}) ;
|
|
2113
|
-
|
|
2114
|
-
/**
|
|
2115
|
-
* Express integration
|
|
2116
|
-
*
|
|
2117
|
-
* Capture tracing data for fastify.
|
|
2118
|
-
*/
|
|
2119
|
-
const fastifyIntegration = defineIntegration(_fastifyIntegration);
|
|
2120
|
-
|
|
2121
|
-
// We inline the types we care about here
|
|
2122
|
-
|
|
2123
|
-
/**
|
|
2124
|
-
* Setup an error handler for Fastify.
|
|
2125
|
-
*/
|
|
2126
|
-
function setupFastifyErrorHandler(fastify) {
|
|
2127
|
-
const plugin = Object.assign(
|
|
2128
|
-
function (fastify, _options, done) {
|
|
2129
|
-
fastify.addHook('onError', async (_request, _reply, error) => {
|
|
2130
|
-
captureException(error);
|
|
2131
|
-
});
|
|
2132
|
-
|
|
2133
|
-
// registering `onRequest` hook here instead of using Otel `onRequest` callback b/c `onRequest` hook
|
|
2134
|
-
// is ironically called in the fastify `preHandler` hook which is called later in the lifecycle:
|
|
2135
|
-
// https://fastify.dev/docs/latest/Reference/Lifecycle/
|
|
2136
|
-
fastify.addHook('onRequest', async (request, _reply) => {
|
|
2137
|
-
const reqWithRouteInfo = request ;
|
|
2138
|
-
|
|
2139
|
-
// Taken from Otel Fastify instrumentation:
|
|
2140
|
-
// https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/opentelemetry-instrumentation-fastify/src/instrumentation.ts#L94-L96
|
|
2141
|
-
const routeName = _optionalChain$7([reqWithRouteInfo, 'access', _ => _.routeOptions, 'optionalAccess', _2 => _2.url]) || reqWithRouteInfo.routerPath;
|
|
2142
|
-
const method = _optionalChain$7([reqWithRouteInfo, 'access', _3 => _3.routeOptions, 'optionalAccess', _4 => _4.method]) || 'GET';
|
|
2143
|
-
|
|
2144
|
-
getIsolationScope().setTransactionName(`${method} ${routeName}`);
|
|
2145
|
-
});
|
|
2146
|
-
|
|
2147
|
-
done();
|
|
2148
|
-
},
|
|
2149
|
-
{
|
|
2150
|
-
[Symbol.for('skip-override')]: true,
|
|
2151
|
-
[Symbol.for('fastify.display-name')]: 'sentry-fastify-error-handler',
|
|
2152
|
-
},
|
|
2153
|
-
);
|
|
2154
|
-
|
|
2155
|
-
fastify.register(plugin);
|
|
2156
|
-
|
|
2157
|
-
if (!isWrapped$1(fastify.addHook)) {
|
|
2158
|
-
consoleSandbox(() => {
|
|
2159
|
-
// eslint-disable-next-line no-console
|
|
2160
|
-
console.warn(
|
|
2161
|
-
'[Sentry] Fastify is not instrumented. This is likely because you required/imported fastify before calling `Sentry.init()`.',
|
|
2162
|
-
);
|
|
2163
|
-
});
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
|
|
2167
|
-
const _graphqlIntegration = (() => {
|
|
2168
|
-
return {
|
|
2169
|
-
name: 'Graphql',
|
|
2170
|
-
setupOnce() {
|
|
2171
|
-
addOpenTelemetryInstrumentation(
|
|
2172
|
-
new GraphQLInstrumentation({
|
|
2173
|
-
ignoreTrivialResolveSpans: true,
|
|
2174
|
-
responseHook(span) {
|
|
2175
|
-
addOriginToSpan(span, 'auto.graphql.otel.graphql');
|
|
2176
|
-
},
|
|
2177
|
-
}),
|
|
2178
|
-
);
|
|
2179
|
-
},
|
|
2180
|
-
};
|
|
2181
|
-
}) ;
|
|
2182
|
-
|
|
2183
|
-
/**
|
|
2184
|
-
* GraphQL integration
|
|
2185
|
-
*
|
|
2186
|
-
* Capture tracing data for GraphQL.
|
|
2187
|
-
*/
|
|
2188
|
-
const graphqlIntegration = defineIntegration(_graphqlIntegration);
|
|
2189
|
-
|
|
2190
|
-
const _mongoIntegration = (() => {
|
|
2191
|
-
return {
|
|
2192
|
-
name: 'Mongo',
|
|
2193
|
-
setupOnce() {
|
|
2194
|
-
addOpenTelemetryInstrumentation(
|
|
2195
|
-
new MongoDBInstrumentation({
|
|
2196
|
-
responseHook(span) {
|
|
2197
|
-
addOriginToSpan(span, 'auto.db.otel.mongo');
|
|
2198
|
-
},
|
|
2199
|
-
}),
|
|
2200
|
-
);
|
|
2201
|
-
},
|
|
2202
|
-
};
|
|
2203
|
-
}) ;
|
|
2204
|
-
|
|
2205
|
-
/**
|
|
2206
|
-
* MongoDB integration
|
|
2207
|
-
*
|
|
2208
|
-
* Capture tracing data for MongoDB.
|
|
2209
|
-
*/
|
|
2210
|
-
const mongoIntegration = defineIntegration(_mongoIntegration);
|
|
2211
|
-
|
|
2212
|
-
const _mongooseIntegration = (() => {
|
|
2213
|
-
return {
|
|
2214
|
-
name: 'Mongoose',
|
|
2215
|
-
setupOnce() {
|
|
2216
|
-
addOpenTelemetryInstrumentation(
|
|
2217
|
-
new MongooseInstrumentation({
|
|
2218
|
-
responseHook(span) {
|
|
2219
|
-
addOriginToSpan(span, 'auto.db.otel.mongoose');
|
|
2220
|
-
},
|
|
2221
|
-
}),
|
|
2222
|
-
);
|
|
2223
|
-
},
|
|
2224
|
-
};
|
|
2225
|
-
}) ;
|
|
2226
|
-
|
|
2227
|
-
/**
|
|
2228
|
-
* Mongoose integration
|
|
2229
|
-
*
|
|
2230
|
-
* Capture tracing data for Mongoose.
|
|
2231
|
-
*/
|
|
2232
|
-
const mongooseIntegration = defineIntegration(_mongooseIntegration);
|
|
2233
|
-
|
|
2234
|
-
const _mysqlIntegration = (() => {
|
|
2235
|
-
return {
|
|
2236
|
-
name: 'Mysql',
|
|
2237
|
-
setupOnce() {
|
|
2238
|
-
addOpenTelemetryInstrumentation(new MySQLInstrumentation({}));
|
|
2239
|
-
},
|
|
2240
|
-
};
|
|
2241
|
-
}) ;
|
|
2242
|
-
|
|
2243
|
-
/**
|
|
2244
|
-
* MySQL integration
|
|
2245
|
-
*
|
|
2246
|
-
* Capture tracing data for mysql.
|
|
2247
|
-
*/
|
|
2248
|
-
const mysqlIntegration = defineIntegration(_mysqlIntegration);
|
|
2249
|
-
|
|
2250
|
-
const _mysql2Integration = (() => {
|
|
2251
|
-
return {
|
|
2252
|
-
name: 'Mysql2',
|
|
2253
|
-
setupOnce() {
|
|
2254
|
-
addOpenTelemetryInstrumentation(
|
|
2255
|
-
new MySQL2Instrumentation({
|
|
2256
|
-
responseHook(span) {
|
|
2257
|
-
addOriginToSpan(span, 'auto.db.otel.mysql2');
|
|
2258
|
-
},
|
|
2259
|
-
}),
|
|
2260
|
-
);
|
|
2261
|
-
},
|
|
2262
|
-
};
|
|
2263
|
-
}) ;
|
|
2264
|
-
|
|
2265
|
-
/**
|
|
2266
|
-
* MySQL2 integration
|
|
2267
|
-
*
|
|
2268
|
-
* Capture tracing data for mysql2
|
|
2269
|
-
*/
|
|
2270
|
-
const mysql2Integration = defineIntegration(_mysql2Integration);
|
|
2271
|
-
|
|
2272
|
-
const _redisIntegration = (() => {
|
|
2273
|
-
return {
|
|
2274
|
-
name: 'Redis',
|
|
2275
|
-
setupOnce() {
|
|
2276
|
-
addOpenTelemetryInstrumentation([
|
|
2277
|
-
new IORedisInstrumentation({}),
|
|
2278
|
-
// todo: implement them gradually
|
|
2279
|
-
// new LegacyRedisInstrumentation({}),
|
|
2280
|
-
// new RedisInstrumentation({}),
|
|
2281
|
-
]);
|
|
2282
|
-
},
|
|
2283
|
-
};
|
|
2284
|
-
}) ;
|
|
2285
|
-
|
|
2286
|
-
/**
|
|
2287
|
-
* Redis integration for "ioredis"
|
|
2288
|
-
*
|
|
2289
|
-
* Capture tracing data for redis and ioredis.
|
|
2290
|
-
*/
|
|
2291
|
-
const redisIntegration = defineIntegration(_redisIntegration);
|
|
2292
|
-
|
|
2293
|
-
function _optionalChain$6(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2294
|
-
const _nestIntegration = (() => {
|
|
2295
|
-
return {
|
|
2296
|
-
name: 'Nest',
|
|
2297
|
-
setupOnce() {
|
|
2298
|
-
addOpenTelemetryInstrumentation(new NestInstrumentation({}));
|
|
2299
|
-
},
|
|
2300
|
-
};
|
|
2301
|
-
}) ;
|
|
2302
|
-
|
|
2303
|
-
/**
|
|
2304
|
-
* Nest framework integration
|
|
2305
|
-
*
|
|
2306
|
-
* Capture tracing data for nest.
|
|
2307
|
-
*/
|
|
2308
|
-
const nestIntegration = defineIntegration(_nestIntegration);
|
|
2309
|
-
|
|
2310
|
-
/**
|
|
2311
|
-
* Setup an error handler for Nest.
|
|
2312
|
-
*/
|
|
2313
|
-
function setupNestErrorHandler(app, baseFilter) {
|
|
2314
|
-
app.useGlobalInterceptors({
|
|
2315
|
-
intercept(context, next) {
|
|
2316
|
-
if (getIsolationScope() === getDefaultIsolationScope()) {
|
|
2317
|
-
logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.');
|
|
2318
|
-
return next.handle();
|
|
2319
|
-
}
|
|
2320
|
-
|
|
2321
|
-
if (context.getType() === 'http') {
|
|
2322
|
-
const req = context.switchToHttp().getRequest();
|
|
2323
|
-
if (req.route) {
|
|
2324
|
-
getIsolationScope().setTransactionName(`${_optionalChain$6([req, 'access', _ => _.method, 'optionalAccess', _2 => _2.toUpperCase, 'call', _3 => _3()]) || 'GET'} ${req.route.path}`);
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
|
|
2328
|
-
return next.handle();
|
|
2329
|
-
},
|
|
2330
|
-
});
|
|
2331
|
-
|
|
2332
|
-
const wrappedFilter = new Proxy(baseFilter, {
|
|
2333
|
-
get(target, prop, receiver) {
|
|
2334
|
-
if (prop === 'catch') {
|
|
2335
|
-
const originalCatch = Reflect.get(target, prop, receiver);
|
|
2336
|
-
|
|
2337
|
-
return (exception, host) => {
|
|
2338
|
-
captureException(exception);
|
|
2339
|
-
return originalCatch.apply(target, [exception, host]);
|
|
2340
|
-
};
|
|
2341
|
-
}
|
|
2342
|
-
return Reflect.get(target, prop, receiver);
|
|
2343
|
-
},
|
|
2344
|
-
});
|
|
2345
|
-
|
|
2346
|
-
app.useGlobalFilters(wrappedFilter);
|
|
2347
|
-
}
|
|
2348
|
-
|
|
2349
|
-
const _postgresIntegration = (() => {
|
|
2350
|
-
return {
|
|
2351
|
-
name: 'Postgres',
|
|
2352
|
-
setupOnce() {
|
|
2353
|
-
addOpenTelemetryInstrumentation(
|
|
2354
|
-
new PgInstrumentation({
|
|
2355
|
-
requireParentSpan: true,
|
|
2356
|
-
requestHook(span) {
|
|
2357
|
-
addOriginToSpan(span, 'auto.db.otel.postgres');
|
|
2358
|
-
},
|
|
2359
|
-
}),
|
|
2360
|
-
);
|
|
2361
|
-
},
|
|
2362
|
-
};
|
|
2363
|
-
}) ;
|
|
2364
|
-
|
|
2365
|
-
/**
|
|
2366
|
-
* Postgres integration
|
|
2367
|
-
*
|
|
2368
|
-
* Capture tracing data for pg.
|
|
2369
|
-
*/
|
|
2370
|
-
const postgresIntegration = defineIntegration(_postgresIntegration);
|
|
2371
|
-
|
|
2372
|
-
function _optionalChain$5(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// When importing CJS modules into an ESM module, we cannot import the named exports directly.
|
|
2373
|
-
|
|
2374
|
-
const _prismaIntegration = (() => {
|
|
2375
|
-
return {
|
|
2376
|
-
name: 'Prisma',
|
|
2377
|
-
setupOnce() {
|
|
2378
|
-
addOpenTelemetryInstrumentation(
|
|
2379
|
-
// does not have a hook to adjust spans & add origin
|
|
2380
|
-
new prismaInstrumentation.PrismaInstrumentation({}),
|
|
2381
|
-
);
|
|
2382
|
-
},
|
|
2383
|
-
|
|
2384
|
-
setup(client) {
|
|
2385
|
-
client.on('spanStart', span => {
|
|
2386
|
-
const spanJSON = spanToJSON(span);
|
|
2387
|
-
if (_optionalChain$5([spanJSON, 'access', _ => _.description, 'optionalAccess', _2 => _2.startsWith, 'call', _3 => _3('prisma:')])) {
|
|
2388
|
-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.prisma');
|
|
2389
|
-
}
|
|
2390
|
-
|
|
2391
|
-
if (spanJSON.description === 'prisma:engine:db_query') {
|
|
2392
|
-
span.setAttribute('db.system', 'prisma');
|
|
2393
|
-
}
|
|
2394
|
-
});
|
|
2395
|
-
},
|
|
2396
|
-
};
|
|
2397
|
-
}) ;
|
|
2398
|
-
|
|
2399
|
-
/**
|
|
2400
|
-
* Prisma integration
|
|
2401
|
-
*
|
|
2402
|
-
* Capture tracing data for prisma.
|
|
2403
|
-
* Note: This requieres to set:
|
|
2404
|
-
* previewFeatures = ["tracing"]
|
|
2405
|
-
* For the prisma client.
|
|
2406
|
-
* See https://www.prisma.io/docs/concepts/components/prisma-client/opentelemetry-tracing for more details.
|
|
2407
|
-
*/
|
|
2408
|
-
const prismaIntegration = defineIntegration(_prismaIntegration);
|
|
2409
|
-
|
|
2410
|
-
function _optionalChain$4(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2411
|
-
const _hapiIntegration = (() => {
|
|
2412
|
-
return {
|
|
2413
|
-
name: 'Hapi',
|
|
2414
|
-
setupOnce() {
|
|
2415
|
-
addOpenTelemetryInstrumentation(new HapiInstrumentation());
|
|
2416
|
-
},
|
|
2417
|
-
};
|
|
2418
|
-
}) ;
|
|
2419
|
-
|
|
2420
|
-
/**
|
|
2421
|
-
* Hapi integration
|
|
2422
|
-
*
|
|
2423
|
-
* Capture tracing data for Hapi.
|
|
2424
|
-
* If you also want to capture errors, you need to call `setupHapiErrorHandler(server)` after you set up your server.
|
|
2425
|
-
*/
|
|
2426
|
-
const hapiIntegration = defineIntegration(_hapiIntegration);
|
|
2427
|
-
|
|
2428
|
-
function isBoomObject(response) {
|
|
2429
|
-
return response && (response ).isBoom !== undefined;
|
|
2430
|
-
}
|
|
2431
|
-
|
|
2432
|
-
function isErrorEvent(event) {
|
|
2433
|
-
return event && (event ).error !== undefined;
|
|
2434
|
-
}
|
|
2435
|
-
|
|
2436
|
-
function sendErrorToSentry(errorData) {
|
|
2437
|
-
captureException(errorData, {
|
|
2438
|
-
mechanism: {
|
|
2439
|
-
type: 'hapi',
|
|
2440
|
-
handled: false,
|
|
2441
|
-
data: {
|
|
2442
|
-
function: 'hapiErrorPlugin',
|
|
2443
|
-
},
|
|
2444
|
-
},
|
|
2445
|
-
});
|
|
2446
|
-
}
|
|
2447
|
-
|
|
2448
|
-
const hapiErrorPlugin = {
|
|
2449
|
-
name: 'SentryHapiErrorPlugin',
|
|
2450
|
-
version: SDK_VERSION,
|
|
2451
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2452
|
-
register: async function (serverArg) {
|
|
2453
|
-
const server = serverArg ;
|
|
2454
|
-
|
|
2455
|
-
server.events.on('request', (request, event) => {
|
|
2456
|
-
if (getIsolationScope() !== getDefaultIsolationScope()) {
|
|
2457
|
-
const route = request.route;
|
|
2458
|
-
if (route && route.path) {
|
|
2459
|
-
getIsolationScope().setTransactionName(`${_optionalChain$4([route, 'access', _ => _.method, 'optionalAccess', _2 => _2.toUpperCase, 'call', _3 => _3()]) || 'GET'} ${route.path}`);
|
|
2460
|
-
}
|
|
2461
|
-
} else {
|
|
2462
|
-
DEBUG_BUILD &&
|
|
2463
|
-
logger.warn('Isolation scope is still the default isolation scope - skipping setting transactionName');
|
|
2464
|
-
}
|
|
2465
|
-
|
|
2466
|
-
const activeSpan = getActiveSpan();
|
|
2467
|
-
const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;
|
|
2468
|
-
|
|
2469
|
-
if (request.response && isBoomObject(request.response)) {
|
|
2470
|
-
sendErrorToSentry(request.response);
|
|
2471
|
-
} else if (isErrorEvent(event)) {
|
|
2472
|
-
sendErrorToSentry(event.error);
|
|
2473
|
-
}
|
|
2474
|
-
|
|
2475
|
-
if (rootSpan) {
|
|
2476
|
-
rootSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
2477
|
-
rootSpan.end();
|
|
2478
|
-
}
|
|
2479
|
-
});
|
|
2480
|
-
},
|
|
2481
|
-
};
|
|
2482
|
-
|
|
2483
|
-
/**
|
|
2484
|
-
* Add a Hapi plugin to capture errors to Sentry.
|
|
2485
|
-
*/
|
|
2486
|
-
async function setupHapiErrorHandler(server) {
|
|
2487
|
-
await server.register(hapiErrorPlugin);
|
|
2488
|
-
|
|
2489
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
2490
|
-
if (!isWrapped$1(server.register)) {
|
|
2491
|
-
consoleSandbox(() => {
|
|
2492
|
-
// eslint-disable-next-line no-console
|
|
2493
|
-
console.warn(
|
|
2494
|
-
'[Sentry] Hapi is not instrumented. This is likely because you required/imported hapi before calling `Sentry.init()`.',
|
|
2495
|
-
);
|
|
2496
|
-
});
|
|
2497
|
-
}
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2500
|
-
const _koaIntegration = (() => {
|
|
2501
|
-
return {
|
|
2502
|
-
name: 'Koa',
|
|
2503
|
-
setupOnce() {
|
|
2504
|
-
addOpenTelemetryInstrumentation(
|
|
2505
|
-
new KoaInstrumentation({
|
|
2506
|
-
requestHook(span, info) {
|
|
2507
|
-
if (getIsolationScope() === getDefaultIsolationScope()) {
|
|
2508
|
-
DEBUG_BUILD &&
|
|
2509
|
-
logger.warn('Isolation scope is default isolation scope - skipping setting transactionName');
|
|
2510
|
-
return;
|
|
2511
|
-
}
|
|
2512
|
-
const attributes = spanToJSON(span).data;
|
|
2513
|
-
const route = attributes && attributes[SEMATTRS_HTTP_ROUTE];
|
|
2514
|
-
const method = info.context.request.method.toUpperCase() || 'GET';
|
|
2515
|
-
if (route) {
|
|
2516
|
-
getIsolationScope().setTransactionName(`${method} ${route}`);
|
|
2517
|
-
}
|
|
2518
|
-
},
|
|
2519
|
-
}),
|
|
2520
|
-
);
|
|
2521
|
-
},
|
|
2522
|
-
};
|
|
2523
|
-
}) ;
|
|
2524
|
-
|
|
2525
|
-
const koaIntegration = defineIntegration(_koaIntegration);
|
|
2526
|
-
|
|
2527
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2528
|
-
const setupKoaErrorHandler = (app) => {
|
|
2529
|
-
app.use(async (ctx, next) => {
|
|
2530
|
-
try {
|
|
2531
|
-
await next();
|
|
2532
|
-
} catch (error) {
|
|
2533
|
-
captureException(error);
|
|
2534
|
-
}
|
|
2535
|
-
});
|
|
2536
|
-
|
|
2537
|
-
if (!isWrapped$1(app.use)) {
|
|
2538
|
-
consoleSandbox(() => {
|
|
2539
|
-
// eslint-disable-next-line no-console
|
|
2540
|
-
console.warn(
|
|
2541
|
-
'[Sentry] Koa is not instrumented. This is likely because you required/imported koa before calling `Sentry.init()`.',
|
|
2542
|
-
);
|
|
2543
|
-
});
|
|
2544
|
-
}
|
|
2545
|
-
};
|
|
2546
|
-
|
|
2547
|
-
const _connectIntegration = (() => {
|
|
2548
|
-
return {
|
|
2549
|
-
name: 'Connect',
|
|
2550
|
-
setupOnce() {
|
|
2551
|
-
addOpenTelemetryInstrumentation(new ConnectInstrumentation({}));
|
|
2552
|
-
},
|
|
2553
|
-
};
|
|
2554
|
-
}) ;
|
|
2555
|
-
|
|
2556
|
-
const connectIntegration = defineIntegration(_connectIntegration);
|
|
2557
|
-
|
|
2558
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2559
|
-
function connectErrorMiddleware(err, req, res, next) {
|
|
2560
|
-
captureException(err);
|
|
2561
|
-
next(err);
|
|
2562
|
-
}
|
|
2563
|
-
|
|
2564
|
-
const setupConnectErrorHandler = (app) => {
|
|
2565
|
-
app.use(connectErrorMiddleware);
|
|
2566
|
-
|
|
2567
|
-
if (!isWrapped$1(app.use)) {
|
|
2568
|
-
consoleSandbox(() => {
|
|
2569
|
-
// eslint-disable-next-line no-console
|
|
2570
|
-
console.warn(
|
|
2571
|
-
'[Sentry] Connect is not instrumented. This is likely because you required/imported connect before calling `Sentry.init()`.',
|
|
2572
|
-
);
|
|
2573
|
-
});
|
|
2574
|
-
}
|
|
2575
|
-
};
|
|
2576
|
-
|
|
2577
|
-
const INTEGRATION_NAME = 'Spotlight';
|
|
2578
|
-
|
|
2579
|
-
const _spotlightIntegration = ((options = {}) => {
|
|
2580
|
-
const _options = {
|
|
2581
|
-
sidecarUrl: options.sidecarUrl || 'http://localhost:8969/stream',
|
|
2582
|
-
};
|
|
2583
|
-
|
|
2584
|
-
return {
|
|
2585
|
-
name: INTEGRATION_NAME,
|
|
2586
|
-
setup(client) {
|
|
2587
|
-
if (typeof process === 'object' && process.env && process.env.NODE_ENV !== 'development') {
|
|
2588
|
-
logger.warn("[Spotlight] It seems you're not in dev mode. Do you really want to have Spotlight enabled?");
|
|
2589
|
-
}
|
|
2590
|
-
connectToSpotlight(client, _options);
|
|
2591
|
-
},
|
|
2592
|
-
};
|
|
2593
|
-
}) ;
|
|
2594
|
-
|
|
2595
|
-
/**
|
|
2596
|
-
* Use this integration to send errors and transactions to Spotlight.
|
|
2597
|
-
*
|
|
2598
|
-
* Learn more about spotlight at https://spotlightjs.com
|
|
2599
|
-
*
|
|
2600
|
-
* Important: This integration only works with Node 18 or newer.
|
|
2601
|
-
*/
|
|
2602
|
-
const spotlightIntegration = defineIntegration(_spotlightIntegration);
|
|
2603
|
-
|
|
2604
|
-
function connectToSpotlight(client, options) {
|
|
2605
|
-
const spotlightUrl = parseSidecarUrl(options.sidecarUrl);
|
|
2606
|
-
if (!spotlightUrl) {
|
|
2607
|
-
return;
|
|
2608
|
-
}
|
|
2609
|
-
|
|
2610
|
-
let failedRequests = 0;
|
|
2611
|
-
|
|
2612
|
-
client.on('beforeEnvelope', (envelope) => {
|
|
2613
|
-
if (failedRequests > 3) {
|
|
2614
|
-
logger.warn('[Spotlight] Disabled Sentry -> Spotlight integration due to too many failed requests');
|
|
2615
|
-
return;
|
|
2616
|
-
}
|
|
2617
|
-
|
|
2618
|
-
const serializedEnvelope = serializeEnvelope(envelope);
|
|
2619
|
-
|
|
2620
|
-
const request = getNativeHttpRequest();
|
|
2621
|
-
const req = request(
|
|
2622
|
-
{
|
|
2623
|
-
method: 'POST',
|
|
2624
|
-
path: spotlightUrl.pathname,
|
|
2625
|
-
hostname: spotlightUrl.hostname,
|
|
2626
|
-
port: spotlightUrl.port,
|
|
2627
|
-
headers: {
|
|
2628
|
-
'Content-Type': 'application/x-sentry-envelope',
|
|
2629
|
-
},
|
|
2630
|
-
},
|
|
2631
|
-
res => {
|
|
2632
|
-
res.on('data', () => {
|
|
2633
|
-
// Drain socket
|
|
2634
|
-
});
|
|
2635
|
-
|
|
2636
|
-
res.on('end', () => {
|
|
2637
|
-
// Drain socket
|
|
2638
|
-
});
|
|
2639
|
-
res.setEncoding('utf8');
|
|
2640
|
-
},
|
|
2641
|
-
);
|
|
2642
|
-
|
|
2643
|
-
req.on('error', () => {
|
|
2644
|
-
failedRequests++;
|
|
2645
|
-
logger.warn('[Spotlight] Failed to send envelope to Spotlight Sidecar');
|
|
2646
|
-
});
|
|
2647
|
-
req.write(serializedEnvelope);
|
|
2648
|
-
req.end();
|
|
2649
|
-
});
|
|
2650
|
-
}
|
|
2651
|
-
|
|
2652
|
-
function parseSidecarUrl(url) {
|
|
2653
|
-
try {
|
|
2654
|
-
return new URL(`${url}`);
|
|
2655
|
-
} catch (e) {
|
|
2656
|
-
logger.warn(`[Spotlight] Invalid sidecar URL: ${url}`);
|
|
2657
|
-
return undefined;
|
|
2658
|
-
}
|
|
2659
|
-
}
|
|
2660
|
-
|
|
2661
|
-
/**
|
|
2662
|
-
* We want to get an unpatched http request implementation to avoid capturing our own calls.
|
|
2663
|
-
*/
|
|
2664
|
-
function getNativeHttpRequest() {
|
|
2665
|
-
const { request } = http;
|
|
2666
|
-
if (isWrapped(request)) {
|
|
2667
|
-
return request.__sentry_original__;
|
|
2668
|
-
}
|
|
2669
|
-
|
|
2670
|
-
return request;
|
|
2671
|
-
}
|
|
2672
|
-
|
|
2673
|
-
function isWrapped(impl) {
|
|
2674
|
-
return '__sentry_original__' in impl;
|
|
2675
|
-
}
|
|
2676
|
-
|
|
2677
|
-
/**
|
|
2678
|
-
* With OTEL, all performance integrations will be added, as OTEL only initializes them when the patched package is actually required.
|
|
2679
|
-
*/
|
|
2680
|
-
function getAutoPerformanceIntegrations() {
|
|
2681
|
-
return [
|
|
2682
|
-
expressIntegration(),
|
|
2683
|
-
fastifyIntegration(),
|
|
2684
|
-
graphqlIntegration(),
|
|
2685
|
-
mongoIntegration(),
|
|
2686
|
-
mongooseIntegration(),
|
|
2687
|
-
mysqlIntegration(),
|
|
2688
|
-
mysql2Integration(),
|
|
2689
|
-
redisIntegration(),
|
|
2690
|
-
postgresIntegration(),
|
|
2691
|
-
// For now, we do not include prisma by default because it has ESM issues
|
|
2692
|
-
// See https://github.com/prisma/prisma/issues/23410
|
|
2693
|
-
// TODO v8: Figure out a better solution for this, maybe only disable in ESM mode?
|
|
2694
|
-
// prismaIntegration(),
|
|
2695
|
-
nestIntegration(),
|
|
2696
|
-
hapiIntegration(),
|
|
2697
|
-
koaIntegration(),
|
|
2698
|
-
connectIntegration(),
|
|
2699
|
-
];
|
|
2700
|
-
}
|
|
2701
|
-
|
|
2702
|
-
function _nullishCoalesce$2(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }/**
|
|
2703
|
-
* This code was originally forked from https://github.com/TooTallNate/proxy-agents/tree/b133295fd16f6475578b6b15bd9b4e33ecb0d0b7
|
|
2704
|
-
* With the following licence:
|
|
2705
|
-
*
|
|
2706
|
-
* (The MIT License)
|
|
2707
|
-
*
|
|
2708
|
-
* Copyright (c) 2013 Nathan Rajlich <nathan@tootallnate.net>*
|
|
2709
|
-
*
|
|
2710
|
-
* Permission is hereby granted, free of charge, to any person obtaining
|
|
2711
|
-
* a copy of this software and associated documentation files (the
|
|
2712
|
-
* 'Software'), to deal in the Software without restriction, including
|
|
2713
|
-
* without limitation the rights to use, copy, modify, merge, publish,
|
|
2714
|
-
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
2715
|
-
* permit persons to whom the Software is furnished to do so, subject to
|
|
2716
|
-
* the following conditions:*
|
|
2717
|
-
*
|
|
2718
|
-
* The above copyright notice and this permission notice shall be
|
|
2719
|
-
* included in all copies or substantial portions of the Software.*
|
|
2720
|
-
*
|
|
2721
|
-
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
2722
|
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
2723
|
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
2724
|
-
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
2725
|
-
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
2726
|
-
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
2727
|
-
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
2728
|
-
*/
|
|
2729
|
-
|
|
2730
|
-
const INTERNAL = Symbol('AgentBaseInternalState');
|
|
2731
|
-
|
|
2732
|
-
class Agent extends http.Agent {
|
|
2733
|
-
|
|
2734
|
-
// Set by `http.Agent` - missing from `@types/node`
|
|
2735
|
-
|
|
2736
|
-
constructor(opts) {
|
|
2737
|
-
super(opts);
|
|
2738
|
-
this[INTERNAL] = {};
|
|
2739
|
-
}
|
|
2740
|
-
|
|
2741
|
-
/**
|
|
2742
|
-
* Determine whether this is an `http` or `https` request.
|
|
2743
|
-
*/
|
|
2744
|
-
isSecureEndpoint(options) {
|
|
2745
|
-
if (options) {
|
|
2746
|
-
// First check the `secureEndpoint` property explicitly, since this
|
|
2747
|
-
// means that a parent `Agent` is "passing through" to this instance.
|
|
2748
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
2749
|
-
if (typeof (options ).secureEndpoint === 'boolean') {
|
|
2750
|
-
return options.secureEndpoint;
|
|
2751
|
-
}
|
|
2752
|
-
|
|
2753
|
-
// If no explicit `secure` endpoint, check if `protocol` property is
|
|
2754
|
-
// set. This will usually be the case since using a full string URL
|
|
2755
|
-
// or `URL` instance should be the most common usage.
|
|
2756
|
-
if (typeof options.protocol === 'string') {
|
|
2757
|
-
return options.protocol === 'https:';
|
|
2758
|
-
}
|
|
2759
|
-
}
|
|
2760
|
-
|
|
2761
|
-
// Finally, if no `protocol` property was set, then fall back to
|
|
2762
|
-
// checking the stack trace of the current call stack, and try to
|
|
2763
|
-
// detect the "https" module.
|
|
2764
|
-
const { stack } = new Error();
|
|
2765
|
-
if (typeof stack !== 'string') return false;
|
|
2766
|
-
return stack.split('\n').some(l => l.indexOf('(https.js:') !== -1 || l.indexOf('node:https:') !== -1);
|
|
2767
|
-
}
|
|
2768
|
-
|
|
2769
|
-
createSocket(req, options, cb) {
|
|
2770
|
-
const connectOpts = {
|
|
2771
|
-
...options,
|
|
2772
|
-
secureEndpoint: this.isSecureEndpoint(options),
|
|
2773
|
-
};
|
|
2774
|
-
Promise.resolve()
|
|
2775
|
-
.then(() => this.connect(req, connectOpts))
|
|
2776
|
-
.then(socket => {
|
|
2777
|
-
if (socket instanceof http.Agent) {
|
|
2778
|
-
// @ts-expect-error `addRequest()` isn't defined in `@types/node`
|
|
2779
|
-
return socket.addRequest(req, connectOpts);
|
|
2780
|
-
}
|
|
2781
|
-
this[INTERNAL].currentSocket = socket;
|
|
2782
|
-
// @ts-expect-error `createSocket()` isn't defined in `@types/node`
|
|
2783
|
-
super.createSocket(req, options, cb);
|
|
2784
|
-
}, cb);
|
|
2785
|
-
}
|
|
2786
|
-
|
|
2787
|
-
createConnection() {
|
|
2788
|
-
const socket = this[INTERNAL].currentSocket;
|
|
2789
|
-
this[INTERNAL].currentSocket = undefined;
|
|
2790
|
-
if (!socket) {
|
|
2791
|
-
throw new Error('No socket was returned in the `connect()` function');
|
|
2792
|
-
}
|
|
2793
|
-
return socket;
|
|
2794
|
-
}
|
|
2795
|
-
|
|
2796
|
-
get defaultPort() {
|
|
2797
|
-
return _nullishCoalesce$2(this[INTERNAL].defaultPort, () => ( (this.protocol === 'https:' ? 443 : 80)));
|
|
2798
|
-
}
|
|
2799
|
-
|
|
2800
|
-
set defaultPort(v) {
|
|
2801
|
-
if (this[INTERNAL]) {
|
|
2802
|
-
this[INTERNAL].defaultPort = v;
|
|
2803
|
-
}
|
|
2804
|
-
}
|
|
2805
|
-
|
|
2806
|
-
get protocol() {
|
|
2807
|
-
return _nullishCoalesce$2(this[INTERNAL].protocol, () => ( (this.isSecureEndpoint() ? 'https:' : 'http:')));
|
|
2808
|
-
}
|
|
2809
|
-
|
|
2810
|
-
set protocol(v) {
|
|
2811
|
-
if (this[INTERNAL]) {
|
|
2812
|
-
this[INTERNAL].protocol = v;
|
|
2813
|
-
}
|
|
2814
|
-
}
|
|
2815
|
-
}
|
|
2816
|
-
|
|
2817
|
-
function debug$1(...args) {
|
|
2818
|
-
logger.log('[https-proxy-agent:parse-proxy-response]', ...args);
|
|
2819
|
-
}
|
|
2820
|
-
|
|
2821
|
-
function parseProxyResponse(socket) {
|
|
2822
|
-
return new Promise((resolve, reject) => {
|
|
2823
|
-
// we need to buffer any HTTP traffic that happens with the proxy before we get
|
|
2824
|
-
// the CONNECT response, so that if the response is anything other than an "200"
|
|
2825
|
-
// response code, then we can re-play the "data" events on the socket once the
|
|
2826
|
-
// HTTP parser is hooked up...
|
|
2827
|
-
let buffersLength = 0;
|
|
2828
|
-
const buffers = [];
|
|
2829
|
-
|
|
2830
|
-
function read() {
|
|
2831
|
-
const b = socket.read();
|
|
2832
|
-
if (b) ondata(b);
|
|
2833
|
-
else socket.once('readable', read);
|
|
2834
|
-
}
|
|
2835
|
-
|
|
2836
|
-
function cleanup() {
|
|
2837
|
-
socket.removeListener('end', onend);
|
|
2838
|
-
socket.removeListener('error', onerror);
|
|
2839
|
-
socket.removeListener('readable', read);
|
|
2840
|
-
}
|
|
2841
|
-
|
|
2842
|
-
function onend() {
|
|
2843
|
-
cleanup();
|
|
2844
|
-
debug$1('onend');
|
|
2845
|
-
reject(new Error('Proxy connection ended before receiving CONNECT response'));
|
|
2846
|
-
}
|
|
2847
|
-
|
|
2848
|
-
function onerror(err) {
|
|
2849
|
-
cleanup();
|
|
2850
|
-
debug$1('onerror %o', err);
|
|
2851
|
-
reject(err);
|
|
2852
|
-
}
|
|
2853
|
-
|
|
2854
|
-
function ondata(b) {
|
|
2855
|
-
buffers.push(b);
|
|
2856
|
-
buffersLength += b.length;
|
|
2857
|
-
|
|
2858
|
-
const buffered = Buffer.concat(buffers, buffersLength);
|
|
2859
|
-
const endOfHeaders = buffered.indexOf('\r\n\r\n');
|
|
2860
|
-
|
|
2861
|
-
if (endOfHeaders === -1) {
|
|
2862
|
-
// keep buffering
|
|
2863
|
-
debug$1('have not received end of HTTP headers yet...');
|
|
2864
|
-
read();
|
|
2865
|
-
return;
|
|
2866
|
-
}
|
|
2867
|
-
|
|
2868
|
-
const headerParts = buffered.slice(0, endOfHeaders).toString('ascii').split('\r\n');
|
|
2869
|
-
const firstLine = headerParts.shift();
|
|
2870
|
-
if (!firstLine) {
|
|
2871
|
-
socket.destroy();
|
|
2872
|
-
return reject(new Error('No header received from proxy CONNECT response'));
|
|
2873
|
-
}
|
|
2874
|
-
const firstLineParts = firstLine.split(' ');
|
|
2875
|
-
const statusCode = +firstLineParts[1];
|
|
2876
|
-
const statusText = firstLineParts.slice(2).join(' ');
|
|
2877
|
-
const headers = {};
|
|
2878
|
-
for (const header of headerParts) {
|
|
2879
|
-
if (!header) continue;
|
|
2880
|
-
const firstColon = header.indexOf(':');
|
|
2881
|
-
if (firstColon === -1) {
|
|
2882
|
-
socket.destroy();
|
|
2883
|
-
return reject(new Error(`Invalid header from proxy CONNECT response: "${header}"`));
|
|
2884
|
-
}
|
|
2885
|
-
const key = header.slice(0, firstColon).toLowerCase();
|
|
2886
|
-
const value = header.slice(firstColon + 1).trimStart();
|
|
2887
|
-
const current = headers[key];
|
|
2888
|
-
if (typeof current === 'string') {
|
|
2889
|
-
headers[key] = [current, value];
|
|
2890
|
-
} else if (Array.isArray(current)) {
|
|
2891
|
-
current.push(value);
|
|
2892
|
-
} else {
|
|
2893
|
-
headers[key] = value;
|
|
2894
|
-
}
|
|
2895
|
-
}
|
|
2896
|
-
debug$1('got proxy server response: %o %o', firstLine, headers);
|
|
2897
|
-
cleanup();
|
|
2898
|
-
resolve({
|
|
2899
|
-
connect: {
|
|
2900
|
-
statusCode,
|
|
2901
|
-
statusText,
|
|
2902
|
-
headers,
|
|
2903
|
-
},
|
|
2904
|
-
buffered,
|
|
2905
|
-
});
|
|
2906
|
-
}
|
|
2907
|
-
|
|
2908
|
-
socket.on('error', onerror);
|
|
2909
|
-
socket.on('end', onend);
|
|
2910
|
-
|
|
2911
|
-
read();
|
|
2912
|
-
});
|
|
2913
|
-
}
|
|
2914
|
-
|
|
2915
|
-
function _nullishCoalesce$1(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }function _optionalChain$3(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2916
|
-
|
|
2917
|
-
function debug(...args) {
|
|
2918
|
-
logger.log('[https-proxy-agent]', ...args);
|
|
2919
|
-
}
|
|
2920
|
-
|
|
2921
|
-
/**
|
|
2922
|
-
* The `HttpsProxyAgent` implements an HTTP Agent subclass that connects to
|
|
2923
|
-
* the specified "HTTP(s) proxy server" in order to proxy HTTPS requests.
|
|
2924
|
-
*
|
|
2925
|
-
* Outgoing HTTP requests are first tunneled through the proxy server using the
|
|
2926
|
-
* `CONNECT` HTTP request method to establish a connection to the proxy server,
|
|
2927
|
-
* and then the proxy server connects to the destination target and issues the
|
|
2928
|
-
* HTTP request from the proxy server.
|
|
2929
|
-
*
|
|
2930
|
-
* `https:` requests have their socket connection upgraded to TLS once
|
|
2931
|
-
* the connection to the proxy server has been established.
|
|
2932
|
-
*/
|
|
2933
|
-
class HttpsProxyAgent extends Agent {
|
|
2934
|
-
static __initStatic() {this.protocols = ['http', 'https']; }
|
|
2935
|
-
|
|
2936
|
-
constructor(proxy, opts) {
|
|
2937
|
-
super(opts);
|
|
2938
|
-
this.options = {};
|
|
2939
|
-
this.proxy = typeof proxy === 'string' ? new URL(proxy) : proxy;
|
|
2940
|
-
this.proxyHeaders = _nullishCoalesce$1(_optionalChain$3([opts, 'optionalAccess', _2 => _2.headers]), () => ( {}));
|
|
2941
|
-
debug('Creating new HttpsProxyAgent instance: %o', this.proxy.href);
|
|
2942
|
-
|
|
2943
|
-
// Trim off the brackets from IPv6 addresses
|
|
2944
|
-
const host = (this.proxy.hostname || this.proxy.host).replace(/^\[|\]$/g, '');
|
|
2945
|
-
const port = this.proxy.port ? parseInt(this.proxy.port, 10) : this.proxy.protocol === 'https:' ? 443 : 80;
|
|
2946
|
-
this.connectOpts = {
|
|
2947
|
-
// Attempt to negotiate http/1.1 for proxy servers that support http/2
|
|
2948
|
-
ALPNProtocols: ['http/1.1'],
|
|
2949
|
-
...(opts ? omit(opts, 'headers') : null),
|
|
2950
|
-
host,
|
|
2951
|
-
port,
|
|
2952
|
-
};
|
|
2953
|
-
}
|
|
2954
|
-
|
|
2955
|
-
/**
|
|
2956
|
-
* Called when the node-core HTTP client library is creating a
|
|
2957
|
-
* new HTTP request.
|
|
2958
|
-
*/
|
|
2959
|
-
async connect(req, opts) {
|
|
2960
|
-
const { proxy } = this;
|
|
2961
|
-
|
|
2962
|
-
if (!opts.host) {
|
|
2963
|
-
throw new TypeError('No "host" provided');
|
|
2964
|
-
}
|
|
2965
|
-
|
|
2966
|
-
// Create a socket connection to the proxy server.
|
|
2967
|
-
let socket;
|
|
2968
|
-
if (proxy.protocol === 'https:') {
|
|
2969
|
-
debug('Creating `tls.Socket`: %o', this.connectOpts);
|
|
2970
|
-
const servername = this.connectOpts.servername || this.connectOpts.host;
|
|
2971
|
-
socket = tls.connect({
|
|
2972
|
-
...this.connectOpts,
|
|
2973
|
-
servername: servername && net.isIP(servername) ? undefined : servername,
|
|
2974
|
-
});
|
|
2975
|
-
} else {
|
|
2976
|
-
debug('Creating `net.Socket`: %o', this.connectOpts);
|
|
2977
|
-
socket = net.connect(this.connectOpts);
|
|
2978
|
-
}
|
|
2979
|
-
|
|
2980
|
-
const headers =
|
|
2981
|
-
typeof this.proxyHeaders === 'function' ? this.proxyHeaders() : { ...this.proxyHeaders };
|
|
2982
|
-
const host = net.isIPv6(opts.host) ? `[${opts.host}]` : opts.host;
|
|
2983
|
-
let payload = `CONNECT ${host}:${opts.port} HTTP/1.1\r\n`;
|
|
2984
|
-
|
|
2985
|
-
// Inject the `Proxy-Authorization` header if necessary.
|
|
2986
|
-
if (proxy.username || proxy.password) {
|
|
2987
|
-
const auth = `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`;
|
|
2988
|
-
headers['Proxy-Authorization'] = `Basic ${Buffer.from(auth).toString('base64')}`;
|
|
2989
|
-
}
|
|
2990
|
-
|
|
2991
|
-
headers.Host = `${host}:${opts.port}`;
|
|
2992
|
-
|
|
2993
|
-
if (!headers['Proxy-Connection']) {
|
|
2994
|
-
headers['Proxy-Connection'] = this.keepAlive ? 'Keep-Alive' : 'close';
|
|
2995
|
-
}
|
|
2996
|
-
for (const name of Object.keys(headers)) {
|
|
2997
|
-
payload += `${name}: ${headers[name]}\r\n`;
|
|
2998
|
-
}
|
|
2999
|
-
|
|
3000
|
-
const proxyResponsePromise = parseProxyResponse(socket);
|
|
3001
|
-
|
|
3002
|
-
socket.write(`${payload}\r\n`);
|
|
3003
|
-
|
|
3004
|
-
const { connect, buffered } = await proxyResponsePromise;
|
|
3005
|
-
req.emit('proxyConnect', connect);
|
|
3006
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
3007
|
-
// @ts-ignore Not EventEmitter in Node types
|
|
3008
|
-
this.emit('proxyConnect', connect, req);
|
|
3009
|
-
|
|
3010
|
-
if (connect.statusCode === 200) {
|
|
3011
|
-
req.once('socket', resume);
|
|
3012
|
-
|
|
3013
|
-
if (opts.secureEndpoint) {
|
|
3014
|
-
// The proxy is connecting to a TLS server, so upgrade
|
|
3015
|
-
// this socket connection to a TLS connection.
|
|
3016
|
-
debug('Upgrading socket connection to TLS');
|
|
3017
|
-
const servername = opts.servername || opts.host;
|
|
3018
|
-
return tls.connect({
|
|
3019
|
-
...omit(opts, 'host', 'path', 'port'),
|
|
3020
|
-
socket,
|
|
3021
|
-
servername: net.isIP(servername) ? undefined : servername,
|
|
3022
|
-
});
|
|
3023
|
-
}
|
|
3024
|
-
|
|
3025
|
-
return socket;
|
|
3026
|
-
}
|
|
3027
|
-
|
|
3028
|
-
// Some other status code that's not 200... need to re-play the HTTP
|
|
3029
|
-
// header "data" events onto the socket once the HTTP machinery is
|
|
3030
|
-
// attached so that the node core `http` can parse and handle the
|
|
3031
|
-
// error status code.
|
|
3032
|
-
|
|
3033
|
-
// Close the original socket, and a new "fake" socket is returned
|
|
3034
|
-
// instead, so that the proxy doesn't get the HTTP request
|
|
3035
|
-
// written to it (which may contain `Authorization` headers or other
|
|
3036
|
-
// sensitive data).
|
|
3037
|
-
//
|
|
3038
|
-
// See: https://hackerone.com/reports/541502
|
|
3039
|
-
socket.destroy();
|
|
3040
|
-
|
|
3041
|
-
const fakeSocket = new net.Socket({ writable: false });
|
|
3042
|
-
fakeSocket.readable = true;
|
|
3043
|
-
|
|
3044
|
-
// Need to wait for the "socket" event to re-play the "data" events.
|
|
3045
|
-
req.once('socket', (s) => {
|
|
3046
|
-
debug('Replaying proxy buffer for failed request');
|
|
3047
|
-
// Replay the "buffered" Buffer onto the fake `socket`, since at
|
|
3048
|
-
// this point the HTTP module machinery has been hooked up for
|
|
3049
|
-
// the user.
|
|
3050
|
-
s.push(buffered);
|
|
3051
|
-
s.push(null);
|
|
3052
|
-
});
|
|
3053
|
-
|
|
3054
|
-
return fakeSocket;
|
|
3055
|
-
}
|
|
3056
|
-
}HttpsProxyAgent.__initStatic();
|
|
3057
|
-
|
|
3058
|
-
function resume(socket) {
|
|
3059
|
-
socket.resume();
|
|
3060
|
-
}
|
|
3061
|
-
|
|
3062
|
-
function omit(
|
|
3063
|
-
obj,
|
|
3064
|
-
...keys
|
|
3065
|
-
)
|
|
3066
|
-
|
|
3067
|
-
{
|
|
3068
|
-
const ret = {}
|
|
3069
|
-
|
|
3070
|
-
;
|
|
3071
|
-
let key;
|
|
3072
|
-
for (key in obj) {
|
|
3073
|
-
if (!keys.includes(key)) {
|
|
3074
|
-
ret[key] = obj[key];
|
|
3075
|
-
}
|
|
3076
|
-
}
|
|
3077
|
-
return ret;
|
|
3078
|
-
}
|
|
3079
|
-
|
|
3080
|
-
// Estimated maximum size for reasonable standalone event
|
|
3081
|
-
const GZIP_THRESHOLD = 1024 * 32;
|
|
3082
|
-
|
|
3083
|
-
/**
|
|
3084
|
-
* Gets a stream from a Uint8Array or string
|
|
3085
|
-
* Readable.from is ideal but was added in node.js v12.3.0 and v10.17.0
|
|
3086
|
-
*/
|
|
3087
|
-
function streamFromBody(body) {
|
|
3088
|
-
return new Readable({
|
|
3089
|
-
read() {
|
|
3090
|
-
this.push(body);
|
|
3091
|
-
this.push(null);
|
|
3092
|
-
},
|
|
3093
|
-
});
|
|
3094
|
-
}
|
|
3095
|
-
|
|
3096
|
-
/**
|
|
3097
|
-
* Creates a Transport that uses native the native 'http' and 'https' modules to send events to Sentry.
|
|
3098
|
-
*/
|
|
3099
|
-
function makeNodeTransport(options) {
|
|
3100
|
-
let urlSegments;
|
|
3101
|
-
|
|
3102
|
-
try {
|
|
3103
|
-
urlSegments = new URL(options.url);
|
|
3104
|
-
} catch (e) {
|
|
3105
|
-
consoleSandbox(() => {
|
|
3106
|
-
// eslint-disable-next-line no-console
|
|
3107
|
-
console.warn(
|
|
3108
|
-
'[@sentry/node]: Invalid dsn or tunnel option, will not send any events. The tunnel option must be a full URL when used.',
|
|
3109
|
-
);
|
|
3110
|
-
});
|
|
3111
|
-
return createTransport(options, () => Promise.resolve({}));
|
|
3112
|
-
}
|
|
3113
|
-
|
|
3114
|
-
const isHttps = urlSegments.protocol === 'https:';
|
|
3115
|
-
|
|
3116
|
-
// Proxy prioritization: http => `options.proxy` | `process.env.http_proxy`
|
|
3117
|
-
// Proxy prioritization: https => `options.proxy` | `process.env.https_proxy` | `process.env.http_proxy`
|
|
3118
|
-
const proxy = applyNoProxyOption(
|
|
3119
|
-
urlSegments,
|
|
3120
|
-
options.proxy || (isHttps ? process.env.https_proxy : undefined) || process.env.http_proxy,
|
|
3121
|
-
);
|
|
3122
|
-
|
|
3123
|
-
const nativeHttpModule = isHttps ? https : http$1;
|
|
3124
|
-
const keepAlive = options.keepAlive === undefined ? false : options.keepAlive;
|
|
3125
|
-
|
|
3126
|
-
// TODO(v7): Evaluate if we can set keepAlive to true. This would involve testing for memory leaks in older node
|
|
3127
|
-
// versions(>= 8) as they had memory leaks when using it: #2555
|
|
3128
|
-
const agent = proxy
|
|
3129
|
-
? (new HttpsProxyAgent(proxy) )
|
|
3130
|
-
: new nativeHttpModule.Agent({ keepAlive, maxSockets: 30, timeout: 2000 });
|
|
3131
|
-
|
|
3132
|
-
// This ensures we do not generate any spans in OpenTelemetry for the transport
|
|
3133
|
-
return suppressTracing(() => {
|
|
3134
|
-
const requestExecutor = createRequestExecutor(options, _nullishCoalesce(options.httpModule, () => ( nativeHttpModule)), agent);
|
|
3135
|
-
return createTransport(options, requestExecutor);
|
|
3136
|
-
});
|
|
3137
|
-
}
|
|
3138
|
-
|
|
3139
|
-
/**
|
|
3140
|
-
* Honors the `no_proxy` env variable with the highest priority to allow for hosts exclusion.
|
|
3141
|
-
*
|
|
3142
|
-
* @param transportUrl The URL the transport intends to send events to.
|
|
3143
|
-
* @param proxy The client configured proxy.
|
|
3144
|
-
* @returns A proxy the transport should use.
|
|
3145
|
-
*/
|
|
3146
|
-
function applyNoProxyOption(transportUrlSegments, proxy) {
|
|
3147
|
-
const { no_proxy } = process.env;
|
|
3148
|
-
|
|
3149
|
-
const urlIsExemptFromProxy =
|
|
3150
|
-
no_proxy &&
|
|
3151
|
-
no_proxy
|
|
3152
|
-
.split(',')
|
|
3153
|
-
.some(
|
|
3154
|
-
exemption => transportUrlSegments.host.endsWith(exemption) || transportUrlSegments.hostname.endsWith(exemption),
|
|
3155
|
-
);
|
|
3156
|
-
|
|
3157
|
-
if (urlIsExemptFromProxy) {
|
|
3158
|
-
return undefined;
|
|
3159
|
-
} else {
|
|
3160
|
-
return proxy;
|
|
3161
|
-
}
|
|
3162
|
-
}
|
|
3163
|
-
|
|
3164
|
-
/**
|
|
3165
|
-
* Creates a RequestExecutor to be used with `createTransport`.
|
|
3166
|
-
*/
|
|
3167
|
-
function createRequestExecutor(
|
|
3168
|
-
options,
|
|
3169
|
-
httpModule,
|
|
3170
|
-
agent,
|
|
3171
|
-
) {
|
|
3172
|
-
const { hostname, pathname, port, protocol, search } = new URL(options.url);
|
|
3173
|
-
return function makeRequest(request) {
|
|
3174
|
-
return new Promise((resolve, reject) => {
|
|
3175
|
-
let body = streamFromBody(request.body);
|
|
3176
|
-
|
|
3177
|
-
const headers = { ...options.headers };
|
|
3178
|
-
|
|
3179
|
-
if (request.body.length > GZIP_THRESHOLD) {
|
|
3180
|
-
headers['content-encoding'] = 'gzip';
|
|
3181
|
-
body = body.pipe(createGzip());
|
|
3182
|
-
}
|
|
3183
|
-
|
|
3184
|
-
const req = httpModule.request(
|
|
3185
|
-
{
|
|
3186
|
-
method: 'POST',
|
|
3187
|
-
agent,
|
|
3188
|
-
headers,
|
|
3189
|
-
hostname,
|
|
3190
|
-
path: `${pathname}${search}`,
|
|
3191
|
-
port,
|
|
3192
|
-
protocol,
|
|
3193
|
-
ca: options.caCerts,
|
|
3194
|
-
},
|
|
3195
|
-
res => {
|
|
3196
|
-
res.on('data', () => {
|
|
3197
|
-
// Drain socket
|
|
3198
|
-
});
|
|
3199
|
-
|
|
3200
|
-
res.on('end', () => {
|
|
3201
|
-
// Drain socket
|
|
3202
|
-
});
|
|
3203
|
-
|
|
3204
|
-
res.setEncoding('utf8');
|
|
3205
|
-
|
|
3206
|
-
// "Key-value pairs of header names and values. Header names are lower-cased."
|
|
3207
|
-
// https://nodejs.org/api/http.html#http_message_headers
|
|
3208
|
-
const retryAfterHeader = _nullishCoalesce(res.headers['retry-after'], () => ( null));
|
|
3209
|
-
const rateLimitsHeader = _nullishCoalesce(res.headers['x-sentry-rate-limits'], () => ( null));
|
|
3210
|
-
|
|
3211
|
-
resolve({
|
|
3212
|
-
statusCode: res.statusCode,
|
|
3213
|
-
headers: {
|
|
3214
|
-
'retry-after': retryAfterHeader,
|
|
3215
|
-
'x-sentry-rate-limits': Array.isArray(rateLimitsHeader) ? rateLimitsHeader[0] : rateLimitsHeader,
|
|
3216
|
-
},
|
|
3217
|
-
});
|
|
3218
|
-
},
|
|
3219
|
-
);
|
|
3220
|
-
|
|
3221
|
-
req.on('error', reject);
|
|
3222
|
-
body.pipe(req);
|
|
3223
|
-
});
|
|
3224
|
-
};
|
|
3225
|
-
}
|
|
3226
|
-
|
|
3227
|
-
/** normalizes Windows paths */
|
|
3228
|
-
function normalizeWindowsPath(path) {
|
|
3229
|
-
return path
|
|
3230
|
-
.replace(/^[A-Z]:/, '') // remove Windows-style prefix
|
|
3231
|
-
.replace(/\\/g, '/'); // replace all `\` instances with `/`
|
|
3232
|
-
}
|
|
3233
|
-
|
|
3234
|
-
/** Creates a function that gets the module name from a filename */
|
|
3235
|
-
function createGetModuleFromFilename(
|
|
3236
|
-
basePath = process.argv[1] ? dirname$1(process.argv[1]) : process.cwd(),
|
|
3237
|
-
isWindows = sep === '\\',
|
|
3238
|
-
) {
|
|
3239
|
-
const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
|
|
3240
|
-
|
|
3241
|
-
return (filename) => {
|
|
3242
|
-
if (!filename) {
|
|
3243
|
-
return;
|
|
3244
|
-
}
|
|
3245
|
-
|
|
3246
|
-
const normalizedFilename = isWindows ? normalizeWindowsPath(filename) : filename;
|
|
3247
|
-
|
|
3248
|
-
// eslint-disable-next-line prefer-const
|
|
3249
|
-
let { dir, base: file, ext } = posix.parse(normalizedFilename);
|
|
3250
|
-
|
|
3251
|
-
if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
|
|
3252
|
-
file = file.slice(0, ext.length * -1);
|
|
3253
|
-
}
|
|
3254
|
-
|
|
3255
|
-
if (!dir) {
|
|
3256
|
-
// No dirname whatsoever
|
|
3257
|
-
dir = '.';
|
|
3258
|
-
}
|
|
3259
|
-
|
|
3260
|
-
const n = dir.lastIndexOf('/node_modules');
|
|
3261
|
-
if (n > -1) {
|
|
3262
|
-
return `${dir.slice(n + 14).replace(/\//g, '.')}:${file}`;
|
|
3263
|
-
}
|
|
3264
|
-
|
|
3265
|
-
// Let's see if it's a part of the main module
|
|
3266
|
-
// To be a part of main module, it has to share the same base
|
|
3267
|
-
if (dir.startsWith(normalizedBase)) {
|
|
3268
|
-
let moduleName = dir.slice(normalizedBase.length + 1).replace(/\//g, '.');
|
|
3269
|
-
|
|
3270
|
-
if (moduleName) {
|
|
3271
|
-
moduleName += ':';
|
|
3272
|
-
}
|
|
3273
|
-
moduleName += file;
|
|
3274
|
-
|
|
3275
|
-
return moduleName;
|
|
3276
|
-
}
|
|
3277
|
-
|
|
3278
|
-
return file;
|
|
3279
|
-
};
|
|
3280
|
-
}
|
|
3281
|
-
|
|
3282
|
-
/**
|
|
3283
|
-
* Returns a release dynamically from environment variables.
|
|
3284
|
-
*/
|
|
3285
|
-
function getSentryRelease(fallback) {
|
|
3286
|
-
// Always read first as Sentry takes this as precedence
|
|
3287
|
-
if (process.env.SENTRY_RELEASE) {
|
|
3288
|
-
return process.env.SENTRY_RELEASE;
|
|
3289
|
-
}
|
|
3290
|
-
|
|
3291
|
-
// This supports the variable that sentry-webpack-plugin injects
|
|
3292
|
-
if (GLOBAL_OBJ.SENTRY_RELEASE && GLOBAL_OBJ.SENTRY_RELEASE.id) {
|
|
3293
|
-
return GLOBAL_OBJ.SENTRY_RELEASE.id;
|
|
3294
|
-
}
|
|
3295
|
-
|
|
3296
|
-
return (
|
|
3297
|
-
// GitHub Actions - https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables
|
|
3298
|
-
process.env.GITHUB_SHA ||
|
|
3299
|
-
// Netlify - https://docs.netlify.com/configure-builds/environment-variables/#build-metadata
|
|
3300
|
-
process.env.COMMIT_REF ||
|
|
3301
|
-
// Vercel - https://vercel.com/docs/v2/build-step#system-environment-variables
|
|
3302
|
-
process.env.VERCEL_GIT_COMMIT_SHA ||
|
|
3303
|
-
process.env.VERCEL_GITHUB_COMMIT_SHA ||
|
|
3304
|
-
process.env.VERCEL_GITLAB_COMMIT_SHA ||
|
|
3305
|
-
process.env.VERCEL_BITBUCKET_COMMIT_SHA ||
|
|
3306
|
-
// Zeit (now known as Vercel)
|
|
3307
|
-
process.env.ZEIT_GITHUB_COMMIT_SHA ||
|
|
3308
|
-
process.env.ZEIT_GITLAB_COMMIT_SHA ||
|
|
3309
|
-
process.env.ZEIT_BITBUCKET_COMMIT_SHA ||
|
|
3310
|
-
// Cloudflare Pages - https://developers.cloudflare.com/pages/platform/build-configuration/#environment-variables
|
|
3311
|
-
process.env.CF_PAGES_COMMIT_SHA ||
|
|
3312
|
-
fallback
|
|
3313
|
-
);
|
|
3314
|
-
}
|
|
3315
|
-
|
|
3316
|
-
/** Node.js stack parser */
|
|
3317
|
-
const defaultStackParser = createStackParser(nodeStackLineParser(createGetModuleFromFilename()));
|
|
3318
|
-
|
|
3319
|
-
function _optionalChain$2(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
3320
|
-
/** A client for using Sentry with Node & OpenTelemetry. */
|
|
3321
|
-
class NodeClient extends ServerRuntimeClient {
|
|
3322
|
-
|
|
3323
|
-
constructor(options) {
|
|
3324
|
-
const clientOptions = {
|
|
3325
|
-
...options,
|
|
3326
|
-
platform: 'node',
|
|
3327
|
-
runtime: { name: 'node', version: global.process.version },
|
|
3328
|
-
serverName: options.serverName || global.process.env.SENTRY_NAME || os.hostname(),
|
|
3329
|
-
};
|
|
3330
|
-
|
|
3331
|
-
applySdkMetadata(clientOptions, 'node');
|
|
3332
|
-
|
|
3333
|
-
logger.log(
|
|
3334
|
-
`Initializing Sentry: process: ${process.pid}, thread: ${isMainThread ? 'main' : `worker-${threadId}`}.`,
|
|
3335
|
-
);
|
|
3336
|
-
|
|
3337
|
-
super(clientOptions);
|
|
3338
|
-
}
|
|
3339
|
-
|
|
3340
|
-
/** Get the OTEL tracer. */
|
|
3341
|
-
get tracer() {
|
|
3342
|
-
if (this._tracer) {
|
|
3343
|
-
return this._tracer;
|
|
3344
|
-
}
|
|
3345
|
-
|
|
3346
|
-
const name = '@sentry/node';
|
|
3347
|
-
const version = SDK_VERSION;
|
|
3348
|
-
const tracer = trace.getTracer(name, version);
|
|
3349
|
-
this._tracer = tracer;
|
|
3350
|
-
|
|
3351
|
-
return tracer;
|
|
3352
|
-
}
|
|
3353
|
-
|
|
3354
|
-
/**
|
|
3355
|
-
* @inheritDoc
|
|
3356
|
-
*/
|
|
3357
|
-
async flush(timeout) {
|
|
3358
|
-
const provider = this.traceProvider;
|
|
3359
|
-
const spanProcessor = _optionalChain$2([provider, 'optionalAccess', _ => _.activeSpanProcessor]);
|
|
3360
|
-
|
|
3361
|
-
if (spanProcessor) {
|
|
3362
|
-
await spanProcessor.forceFlush();
|
|
3363
|
-
}
|
|
3364
|
-
|
|
3365
|
-
return super.flush(timeout);
|
|
3366
|
-
}
|
|
3367
|
-
}
|
|
3368
|
-
|
|
3369
|
-
/**
|
|
3370
|
-
* This is a custom ContextManager for OpenTelemetry, which extends the default AsyncLocalStorageContextManager.
|
|
3371
|
-
* It ensures that we create a new hub per context, so that the OTEL Context & the Sentry Hub are always in sync.
|
|
3372
|
-
*
|
|
3373
|
-
* Note that we currently only support AsyncHooks with this,
|
|
3374
|
-
* but since this should work for Node 14+ anyhow that should be good enough.
|
|
3375
|
-
*/
|
|
3376
|
-
const SentryContextManager = wrapContextManagerClass(AsyncLocalStorageContextManager);
|
|
3377
|
-
|
|
3378
|
-
/**
|
|
3379
|
-
* Initialize OpenTelemetry for Node.
|
|
3380
|
-
*/
|
|
3381
|
-
function initOpenTelemetry(client) {
|
|
3382
|
-
if (client.getOptions().debug) {
|
|
3383
|
-
const otelLogger = new Proxy(logger , {
|
|
3384
|
-
get(target, prop, receiver) {
|
|
3385
|
-
const actualProp = prop === 'verbose' ? 'debug' : prop;
|
|
3386
|
-
return Reflect.get(target, actualProp, receiver);
|
|
3387
|
-
},
|
|
3388
|
-
});
|
|
3389
|
-
|
|
3390
|
-
diag.setLogger(otelLogger, DiagLogLevel.DEBUG);
|
|
3391
|
-
}
|
|
3392
|
-
|
|
3393
|
-
setupEventContextTrace(client);
|
|
3394
|
-
|
|
3395
|
-
const provider = setupOtel(client);
|
|
3396
|
-
client.traceProvider = provider;
|
|
3397
|
-
}
|
|
3398
|
-
|
|
3399
|
-
/** Just exported for tests. */
|
|
3400
|
-
function setupOtel(client) {
|
|
3401
|
-
// Create and configure NodeTracerProvider
|
|
3402
|
-
const provider = new BasicTracerProvider({
|
|
3403
|
-
sampler: new SentrySampler(client),
|
|
3404
|
-
resource: new Resource({
|
|
3405
|
-
[SEMRESATTRS_SERVICE_NAME]: 'node',
|
|
3406
|
-
[SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry',
|
|
3407
|
-
[SEMRESATTRS_SERVICE_VERSION]: SDK_VERSION,
|
|
3408
|
-
}),
|
|
3409
|
-
forceFlushTimeoutMillis: 500,
|
|
3410
|
-
});
|
|
3411
|
-
provider.addSpanProcessor(new SentrySpanProcessor());
|
|
3412
|
-
|
|
3413
|
-
// Initialize the provider
|
|
3414
|
-
provider.register({
|
|
3415
|
-
propagator: new SentryPropagator(),
|
|
3416
|
-
contextManager: new SentryContextManager(),
|
|
3417
|
-
});
|
|
3418
|
-
|
|
3419
|
-
return provider;
|
|
3420
|
-
}
|
|
3421
|
-
|
|
3422
|
-
function isCjs() {
|
|
3423
|
-
return typeof require !== 'undefined';
|
|
3424
|
-
}
|
|
3425
|
-
|
|
3426
|
-
function getCjsOnlyIntegrations() {
|
|
3427
|
-
return isCjs() ? [modulesIntegration()] : [];
|
|
3428
|
-
}
|
|
3429
|
-
|
|
3430
|
-
/** Get the default integrations for the Node Experimental SDK. */
|
|
3431
|
-
function getDefaultIntegrations(options) {
|
|
3432
|
-
return [
|
|
3433
|
-
// Common
|
|
3434
|
-
inboundFiltersIntegration(),
|
|
3435
|
-
functionToStringIntegration(),
|
|
3436
|
-
linkedErrorsIntegration(),
|
|
3437
|
-
requestDataIntegration(),
|
|
3438
|
-
// Native Wrappers
|
|
3439
|
-
consoleIntegration(),
|
|
3440
|
-
httpIntegration(),
|
|
3441
|
-
nativeNodeFetchIntegration(),
|
|
3442
|
-
// Global Handlers
|
|
3443
|
-
onUncaughtExceptionIntegration(),
|
|
3444
|
-
onUnhandledRejectionIntegration(),
|
|
3445
|
-
// Event Info
|
|
3446
|
-
contextLinesIntegration(),
|
|
3447
|
-
localVariablesIntegration(),
|
|
3448
|
-
nodeContextIntegration(),
|
|
3449
|
-
...getCjsOnlyIntegrations(),
|
|
3450
|
-
...(hasTracingEnabled(options) ? getAutoPerformanceIntegrations() : []),
|
|
3451
|
-
];
|
|
3452
|
-
}
|
|
3453
|
-
|
|
3454
|
-
/**
|
|
3455
|
-
* Initialize Sentry for Node.
|
|
3456
|
-
*/
|
|
3457
|
-
function init(options = {}) {
|
|
3458
|
-
const clientOptions = getClientOptions(options);
|
|
3459
|
-
|
|
3460
|
-
if (clientOptions.debug === true) {
|
|
3461
|
-
if (DEBUG_BUILD) {
|
|
3462
|
-
logger.enable();
|
|
3463
|
-
} else {
|
|
3464
|
-
// use `console.warn` rather than `logger.warn` since by non-debug bundles have all `logger.x` statements stripped
|
|
3465
|
-
consoleSandbox(() => {
|
|
3466
|
-
// eslint-disable-next-line no-console
|
|
3467
|
-
console.warn('[Sentry] Cannot initialize SDK with `debug` option using a non-debug bundle.');
|
|
3468
|
-
});
|
|
3469
|
-
}
|
|
3470
|
-
}
|
|
3471
|
-
|
|
3472
|
-
if (!isCjs()) {
|
|
3473
|
-
const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(Number);
|
|
3474
|
-
|
|
3475
|
-
// Register hook was added in v20.6.0 and v18.19.0
|
|
3476
|
-
if (nodeMajor >= 22 || (nodeMajor === 20 && nodeMinor >= 6) || (nodeMajor === 18 && nodeMinor >= 19)) {
|
|
3477
|
-
// We need to work around using import.meta.url directly because jest complains about it.
|
|
3478
|
-
const importMetaUrl =
|
|
3479
|
-
typeof import.meta.url !== 'undefined' ? import.meta.url : undefined;
|
|
3480
|
-
|
|
3481
|
-
if (!GLOBAL_OBJ._sentryEsmLoaderHookRegistered && importMetaUrl) {
|
|
3482
|
-
// @ts-expect-error register is available in these versions
|
|
3483
|
-
moduleModule.register('@opentelemetry/instrumentation/hook.mjs', importMetaUrl);
|
|
3484
|
-
GLOBAL_OBJ._sentryEsmLoaderHookRegistered = true;
|
|
3485
|
-
}
|
|
3486
|
-
} else {
|
|
3487
|
-
consoleSandbox(() => {
|
|
3488
|
-
// eslint-disable-next-line no-console
|
|
3489
|
-
console.warn(
|
|
3490
|
-
'[Sentry] You are using Node.js in ESM mode ("import syntax"). The Sentry Node.js SDK is not compatible with ESM in Node.js versions before 18.19.0 or before 20.6.0. Please either build your application with CommonJS ("require() syntax"), or use version 7.x of the Sentry Node.js SDK.',
|
|
3491
|
-
);
|
|
3492
|
-
});
|
|
3493
|
-
}
|
|
3494
|
-
}
|
|
3495
|
-
|
|
3496
|
-
setOpenTelemetryContextAsyncContextStrategy();
|
|
3497
|
-
|
|
3498
|
-
const scope = getCurrentScope();
|
|
3499
|
-
scope.update(options.initialScope);
|
|
3500
|
-
|
|
3501
|
-
const client = new NodeClient(clientOptions);
|
|
3502
|
-
// The client is on the current scope, from where it generally is inherited
|
|
3503
|
-
getCurrentScope().setClient(client);
|
|
3504
|
-
|
|
3505
|
-
if (isEnabled(client)) {
|
|
3506
|
-
client.init();
|
|
3507
|
-
}
|
|
3508
|
-
|
|
3509
|
-
if (options.autoSessionTracking) {
|
|
3510
|
-
startSessionTracking();
|
|
3511
|
-
}
|
|
3512
|
-
|
|
3513
|
-
updateScopeFromEnvVariables();
|
|
3514
|
-
|
|
3515
|
-
if (options.spotlight) {
|
|
3516
|
-
// force integrations to be setup even if no DSN was set
|
|
3517
|
-
// If they have already been added before, they will be ignored anyhow
|
|
3518
|
-
const integrations = client.getOptions().integrations;
|
|
3519
|
-
for (const integration of integrations) {
|
|
3520
|
-
client.addIntegration(integration);
|
|
3521
|
-
}
|
|
3522
|
-
client.addIntegration(
|
|
3523
|
-
spotlightIntegration({
|
|
3524
|
-
sidecarUrl: typeof options.spotlight === 'string' ? options.spotlight : undefined,
|
|
3525
|
-
}),
|
|
3526
|
-
);
|
|
3527
|
-
}
|
|
3528
|
-
|
|
3529
|
-
// If users opt-out of this, they _have_ to set up OpenTelemetry themselves
|
|
3530
|
-
// There is no way to use this SDK without OpenTelemetry!
|
|
3531
|
-
if (!options.skipOpenTelemetrySetup) {
|
|
3532
|
-
initOpenTelemetry(client);
|
|
3533
|
-
}
|
|
3534
|
-
|
|
3535
|
-
validateOpenTelemetrySetup();
|
|
3536
|
-
}
|
|
3537
|
-
|
|
3538
|
-
function validateOpenTelemetrySetup() {
|
|
3539
|
-
if (!DEBUG_BUILD) {
|
|
3540
|
-
return;
|
|
3541
|
-
}
|
|
3542
|
-
|
|
3543
|
-
const setup = openTelemetrySetupCheck();
|
|
3544
|
-
|
|
3545
|
-
const required = ['SentrySpanProcessor', 'SentryContextManager', 'SentryPropagator'] ;
|
|
3546
|
-
for (const k of required) {
|
|
3547
|
-
if (!setup.includes(k)) {
|
|
3548
|
-
logger.error(
|
|
3549
|
-
`You have to set up the ${k}. Without this, the OpenTelemetry & Sentry integration will not work properly.`,
|
|
3550
|
-
);
|
|
3551
|
-
}
|
|
3552
|
-
}
|
|
3553
|
-
|
|
3554
|
-
if (!setup.includes('SentrySampler')) {
|
|
3555
|
-
logger.warn(
|
|
3556
|
-
'You have to set up the SentrySampler. Without this, the OpenTelemetry & Sentry integration may still work, but sample rates set for the Sentry SDK will not be respected.',
|
|
3557
|
-
);
|
|
3558
|
-
}
|
|
3559
|
-
}
|
|
3560
|
-
|
|
3561
|
-
function getClientOptions(options) {
|
|
3562
|
-
if (options.defaultIntegrations === undefined) {
|
|
3563
|
-
options.defaultIntegrations = getDefaultIntegrations(options);
|
|
3564
|
-
}
|
|
3565
|
-
|
|
3566
|
-
const release = getRelease(options.release);
|
|
3567
|
-
|
|
3568
|
-
const autoSessionTracking =
|
|
3569
|
-
typeof release !== 'string'
|
|
3570
|
-
? false
|
|
3571
|
-
: options.autoSessionTracking === undefined
|
|
3572
|
-
? true
|
|
3573
|
-
: options.autoSessionTracking;
|
|
3574
|
-
|
|
3575
|
-
const tracesSampleRate = getTracesSampleRate(options.tracesSampleRate);
|
|
3576
|
-
|
|
3577
|
-
const baseOptions = dropUndefinedKeys({
|
|
3578
|
-
transport: makeNodeTransport,
|
|
3579
|
-
dsn: process.env.SENTRY_DSN,
|
|
3580
|
-
environment: process.env.SENTRY_ENVIRONMENT,
|
|
3581
|
-
});
|
|
3582
|
-
|
|
3583
|
-
const overwriteOptions = dropUndefinedKeys({
|
|
3584
|
-
release,
|
|
3585
|
-
autoSessionTracking,
|
|
3586
|
-
tracesSampleRate,
|
|
3587
|
-
});
|
|
3588
|
-
|
|
3589
|
-
const clientOptions = {
|
|
3590
|
-
...baseOptions,
|
|
3591
|
-
...options,
|
|
3592
|
-
...overwriteOptions,
|
|
3593
|
-
stackParser: stackParserFromStackParserOptions(options.stackParser || defaultStackParser),
|
|
3594
|
-
integrations: getIntegrationsToSetup({
|
|
3595
|
-
defaultIntegrations: options.defaultIntegrations,
|
|
3596
|
-
integrations: options.integrations,
|
|
3597
|
-
}),
|
|
3598
|
-
};
|
|
3599
|
-
|
|
3600
|
-
return clientOptions;
|
|
3601
|
-
}
|
|
3602
|
-
|
|
3603
|
-
function getRelease(release) {
|
|
3604
|
-
if (release !== undefined) {
|
|
3605
|
-
return release;
|
|
3606
|
-
}
|
|
3607
|
-
|
|
3608
|
-
const detectedRelease = getSentryRelease();
|
|
3609
|
-
if (detectedRelease !== undefined) {
|
|
3610
|
-
return detectedRelease;
|
|
3611
|
-
}
|
|
3612
|
-
|
|
3613
|
-
return undefined;
|
|
3614
|
-
}
|
|
3615
|
-
|
|
3616
|
-
function getTracesSampleRate(tracesSampleRate) {
|
|
3617
|
-
if (tracesSampleRate !== undefined) {
|
|
3618
|
-
return tracesSampleRate;
|
|
3619
|
-
}
|
|
3620
|
-
|
|
3621
|
-
const sampleRateFromEnv = process.env.SENTRY_TRACES_SAMPLE_RATE;
|
|
3622
|
-
if (!sampleRateFromEnv) {
|
|
3623
|
-
return undefined;
|
|
3624
|
-
}
|
|
3625
|
-
|
|
3626
|
-
const parsed = parseFloat(sampleRateFromEnv);
|
|
3627
|
-
return isFinite(parsed) ? parsed : undefined;
|
|
3628
|
-
}
|
|
3629
|
-
|
|
3630
|
-
/**
|
|
3631
|
-
* Update scope and propagation context based on environmental variables.
|
|
3632
|
-
*
|
|
3633
|
-
* See https://github.com/getsentry/rfcs/blob/main/text/0071-continue-trace-over-process-boundaries.md
|
|
3634
|
-
* for more details.
|
|
3635
|
-
*/
|
|
3636
|
-
function updateScopeFromEnvVariables() {
|
|
3637
|
-
const sentryUseEnvironment = (process.env.SENTRY_USE_ENVIRONMENT || '').toLowerCase();
|
|
3638
|
-
if (!['false', 'n', 'no', 'off', '0'].includes(sentryUseEnvironment)) {
|
|
3639
|
-
const sentryTraceEnv = process.env.SENTRY_TRACE;
|
|
3640
|
-
const baggageEnv = process.env.SENTRY_BAGGAGE;
|
|
3641
|
-
const propagationContext = propagationContextFromHeaders(sentryTraceEnv, baggageEnv);
|
|
3642
|
-
getCurrentScope().setPropagationContext(propagationContext);
|
|
3643
|
-
}
|
|
3644
|
-
}
|
|
3645
|
-
|
|
3646
|
-
/**
|
|
3647
|
-
* Enable automatic Session Tracking for the node process.
|
|
3648
|
-
*/
|
|
3649
|
-
function startSessionTracking() {
|
|
3650
|
-
const client = getClient$1();
|
|
3651
|
-
if (client && client.getOptions().autoSessionTracking) {
|
|
3652
|
-
client.initSessionFlusher();
|
|
3653
|
-
}
|
|
3654
|
-
|
|
3655
|
-
startSession();
|
|
3656
|
-
|
|
3657
|
-
// Emitted in the case of healthy sessions, error of `mechanism.handled: true` and unhandledrejections because
|
|
3658
|
-
// The 'beforeExit' event is not emitted for conditions causing explicit termination,
|
|
3659
|
-
// such as calling process.exit() or uncaught exceptions.
|
|
3660
|
-
// Ref: https://nodejs.org/api/process.html#process_event_beforeexit
|
|
3661
|
-
process.on('beforeExit', () => {
|
|
3662
|
-
const session = getIsolationScope().getSession();
|
|
3663
|
-
|
|
3664
|
-
// Only call endSession, if the Session exists on Scope and SessionStatus is not a
|
|
3665
|
-
// Terminal Status i.e. Exited or Crashed because
|
|
3666
|
-
// "When a session is moved away from ok it must not be updated anymore."
|
|
3667
|
-
// Ref: https://develop.sentry.dev/sdk/sessions/
|
|
3668
|
-
if (session && session.status !== 'ok') {
|
|
3669
|
-
endSession();
|
|
3670
|
-
}
|
|
3671
|
-
});
|
|
3672
|
-
}
|
|
3673
|
-
|
|
3674
|
-
function isEnabled(client) {
|
|
3675
|
-
return client.getOptions().enabled !== false && client.getTransport() !== undefined;
|
|
3676
|
-
}
|
|
3677
|
-
|
|
3678
|
-
const replacements = [
|
|
3679
|
-
['january', '1'],
|
|
3680
|
-
['february', '2'],
|
|
3681
|
-
['march', '3'],
|
|
3682
|
-
['april', '4'],
|
|
3683
|
-
['may', '5'],
|
|
3684
|
-
['june', '6'],
|
|
3685
|
-
['july', '7'],
|
|
3686
|
-
['august', '8'],
|
|
3687
|
-
['september', '9'],
|
|
3688
|
-
['october', '10'],
|
|
3689
|
-
['november', '11'],
|
|
3690
|
-
['december', '12'],
|
|
3691
|
-
['jan', '1'],
|
|
3692
|
-
['feb', '2'],
|
|
3693
|
-
['mar', '3'],
|
|
3694
|
-
['apr', '4'],
|
|
3695
|
-
['may', '5'],
|
|
3696
|
-
['jun', '6'],
|
|
3697
|
-
['jul', '7'],
|
|
3698
|
-
['aug', '8'],
|
|
3699
|
-
['sep', '9'],
|
|
3700
|
-
['oct', '10'],
|
|
3701
|
-
['nov', '11'],
|
|
3702
|
-
['dec', '12'],
|
|
3703
|
-
['sunday', '0'],
|
|
3704
|
-
['monday', '1'],
|
|
3705
|
-
['tuesday', '2'],
|
|
3706
|
-
['wednesday', '3'],
|
|
3707
|
-
['thursday', '4'],
|
|
3708
|
-
['friday', '5'],
|
|
3709
|
-
['saturday', '6'],
|
|
3710
|
-
['sun', '0'],
|
|
3711
|
-
['mon', '1'],
|
|
3712
|
-
['tue', '2'],
|
|
3713
|
-
['wed', '3'],
|
|
3714
|
-
['thu', '4'],
|
|
3715
|
-
['fri', '5'],
|
|
3716
|
-
['sat', '6'],
|
|
3717
|
-
];
|
|
3718
|
-
|
|
3719
|
-
/**
|
|
3720
|
-
* Replaces names in cron expressions
|
|
3721
|
-
*/
|
|
3722
|
-
function replaceCronNames(cronExpression) {
|
|
3723
|
-
return replacements.reduce(
|
|
3724
|
-
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor
|
|
3725
|
-
(acc, [name, replacement]) => acc.replace(new RegExp(name, 'gi'), replacement),
|
|
3726
|
-
cronExpression,
|
|
3727
|
-
);
|
|
3728
|
-
}
|
|
3729
|
-
|
|
3730
|
-
const ERROR_TEXT = 'Automatic instrumentation of CronJob only supports crontab string';
|
|
3731
|
-
|
|
3732
|
-
/**
|
|
3733
|
-
* Instruments the `cron` library to send a check-in event to Sentry for each job execution.
|
|
3734
|
-
*
|
|
3735
|
-
* ```ts
|
|
3736
|
-
* import * as Sentry from '@sentry/node';
|
|
3737
|
-
* import { CronJob } from 'cron';
|
|
3738
|
-
*
|
|
3739
|
-
* const CronJobWithCheckIn = Sentry.cron.instrumentCron(CronJob, 'my-cron-job');
|
|
3740
|
-
*
|
|
3741
|
-
* // use the constructor
|
|
3742
|
-
* const job = new CronJobWithCheckIn('* * * * *', () => {
|
|
3743
|
-
* console.log('You will see this message every minute');
|
|
3744
|
-
* });
|
|
3745
|
-
*
|
|
3746
|
-
* // or from
|
|
3747
|
-
* const job = CronJobWithCheckIn.from({ cronTime: '* * * * *', onTick: () => {
|
|
3748
|
-
* console.log('You will see this message every minute');
|
|
3749
|
-
* });
|
|
3750
|
-
* ```
|
|
3751
|
-
*/
|
|
3752
|
-
function instrumentCron(lib, monitorSlug) {
|
|
3753
|
-
let jobScheduled = false;
|
|
3754
|
-
|
|
3755
|
-
return new Proxy(lib, {
|
|
3756
|
-
construct(target, args) {
|
|
3757
|
-
const [cronTime, onTick, onComplete, start, timeZone, ...rest] = args;
|
|
3758
|
-
|
|
3759
|
-
if (typeof cronTime !== 'string') {
|
|
3760
|
-
throw new Error(ERROR_TEXT);
|
|
3761
|
-
}
|
|
3762
|
-
|
|
3763
|
-
if (jobScheduled) {
|
|
3764
|
-
throw new Error(`A job named '${monitorSlug}' has already been scheduled`);
|
|
3765
|
-
}
|
|
3766
|
-
|
|
3767
|
-
jobScheduled = true;
|
|
3768
|
-
|
|
3769
|
-
const cronString = replaceCronNames(cronTime);
|
|
3770
|
-
|
|
3771
|
-
async function monitoredTick(context, onComplete) {
|
|
3772
|
-
return withMonitor(
|
|
3773
|
-
monitorSlug,
|
|
3774
|
-
async () => {
|
|
3775
|
-
try {
|
|
3776
|
-
await onTick(context, onComplete);
|
|
3777
|
-
} catch (e) {
|
|
3778
|
-
captureException(e);
|
|
3779
|
-
throw e;
|
|
3780
|
-
}
|
|
3781
|
-
},
|
|
3782
|
-
{
|
|
3783
|
-
schedule: { type: 'crontab', value: cronString },
|
|
3784
|
-
timezone: timeZone || undefined,
|
|
3785
|
-
},
|
|
3786
|
-
);
|
|
3787
|
-
}
|
|
3788
|
-
|
|
3789
|
-
return new target(cronTime, monitoredTick, onComplete, start, timeZone, ...rest);
|
|
3790
|
-
},
|
|
3791
|
-
get(target, prop) {
|
|
3792
|
-
if (prop === 'from') {
|
|
3793
|
-
return (param) => {
|
|
3794
|
-
const { cronTime, onTick, timeZone } = param;
|
|
3795
|
-
|
|
3796
|
-
if (typeof cronTime !== 'string') {
|
|
3797
|
-
throw new Error(ERROR_TEXT);
|
|
3798
|
-
}
|
|
3799
|
-
|
|
3800
|
-
if (jobScheduled) {
|
|
3801
|
-
throw new Error(`A job named '${monitorSlug}' has already been scheduled`);
|
|
3802
|
-
}
|
|
3803
|
-
|
|
3804
|
-
jobScheduled = true;
|
|
3805
|
-
|
|
3806
|
-
const cronString = replaceCronNames(cronTime);
|
|
3807
|
-
|
|
3808
|
-
param.onTick = async (context, onComplete) => {
|
|
3809
|
-
return withMonitor(
|
|
3810
|
-
monitorSlug,
|
|
3811
|
-
async () => {
|
|
3812
|
-
try {
|
|
3813
|
-
await onTick(context, onComplete);
|
|
3814
|
-
} catch (e) {
|
|
3815
|
-
captureException(e);
|
|
3816
|
-
throw e;
|
|
3817
|
-
}
|
|
3818
|
-
},
|
|
3819
|
-
{
|
|
3820
|
-
schedule: { type: 'crontab', value: cronString },
|
|
3821
|
-
timezone: timeZone || undefined,
|
|
3822
|
-
},
|
|
3823
|
-
);
|
|
3824
|
-
};
|
|
3825
|
-
|
|
3826
|
-
return target.from(param);
|
|
3827
|
-
};
|
|
3828
|
-
} else {
|
|
3829
|
-
return target[prop];
|
|
3830
|
-
}
|
|
3831
|
-
},
|
|
3832
|
-
});
|
|
3833
|
-
}
|
|
3834
|
-
|
|
3835
|
-
function _optionalChain$1(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
3836
|
-
/**
|
|
3837
|
-
* Wraps the `node-cron` library with check-in monitoring.
|
|
3838
|
-
*
|
|
3839
|
-
* ```ts
|
|
3840
|
-
* import * as Sentry from "@sentry/node";
|
|
3841
|
-
* import * as cron from "node-cron";
|
|
3842
|
-
*
|
|
3843
|
-
* const cronWithCheckIn = Sentry.cron.instrumentNodeCron(cron);
|
|
3844
|
-
*
|
|
3845
|
-
* cronWithCheckIn.schedule(
|
|
3846
|
-
* "* * * * *",
|
|
3847
|
-
* () => {
|
|
3848
|
-
* console.log("running a task every minute");
|
|
3849
|
-
* },
|
|
3850
|
-
* { name: "my-cron-job" },
|
|
3851
|
-
* );
|
|
3852
|
-
* ```
|
|
3853
|
-
*/
|
|
3854
|
-
function instrumentNodeCron(lib) {
|
|
3855
|
-
return new Proxy(lib, {
|
|
3856
|
-
get(target, prop) {
|
|
3857
|
-
if (prop === 'schedule' && target.schedule) {
|
|
3858
|
-
// When 'get' is called for schedule, return a proxied version of the schedule function
|
|
3859
|
-
return new Proxy(target.schedule, {
|
|
3860
|
-
apply(target, thisArg, argArray) {
|
|
3861
|
-
const [expression, callback, options] = argArray;
|
|
3862
|
-
|
|
3863
|
-
if (!_optionalChain$1([options, 'optionalAccess', _ => _.name])) {
|
|
3864
|
-
throw new Error('Missing "name" for scheduled job. A name is required for Sentry check-in monitoring.');
|
|
3865
|
-
}
|
|
3866
|
-
|
|
3867
|
-
async function monitoredCallback() {
|
|
3868
|
-
return withMonitor(
|
|
3869
|
-
options.name,
|
|
3870
|
-
async () => {
|
|
3871
|
-
// We have to manually catch here and capture the exception because node-cron swallows errors
|
|
3872
|
-
// https://github.com/node-cron/node-cron/issues/399
|
|
3873
|
-
try {
|
|
3874
|
-
return await callback();
|
|
3875
|
-
} catch (e) {
|
|
3876
|
-
captureException(e);
|
|
3877
|
-
throw e;
|
|
3878
|
-
}
|
|
3879
|
-
},
|
|
3880
|
-
{
|
|
3881
|
-
schedule: { type: 'crontab', value: replaceCronNames(expression) },
|
|
3882
|
-
timezone: _optionalChain$1([options, 'optionalAccess', _2 => _2.timezone]),
|
|
3883
|
-
},
|
|
3884
|
-
);
|
|
3885
|
-
}
|
|
3886
|
-
|
|
3887
|
-
return target.apply(thisArg, [expression, monitoredCallback, options]);
|
|
3888
|
-
},
|
|
3889
|
-
});
|
|
3890
|
-
} else {
|
|
3891
|
-
return target[prop];
|
|
3892
|
-
}
|
|
3893
|
-
},
|
|
3894
|
-
});
|
|
3895
|
-
}
|
|
3896
|
-
|
|
3897
|
-
/**
|
|
3898
|
-
* Instruments the `node-schedule` library to send a check-in event to Sentry for each job execution.
|
|
3899
|
-
*
|
|
3900
|
-
* ```ts
|
|
3901
|
-
* import * as Sentry from '@sentry/node';
|
|
3902
|
-
* import * as schedule from 'node-schedule';
|
|
3903
|
-
*
|
|
3904
|
-
* const scheduleWithCheckIn = Sentry.cron.instrumentNodeSchedule(schedule);
|
|
3905
|
-
*
|
|
3906
|
-
* const job = scheduleWithCheckIn.scheduleJob('my-cron-job', '* * * * *', () => {
|
|
3907
|
-
* console.log('You will see this message every minute');
|
|
3908
|
-
* });
|
|
3909
|
-
* ```
|
|
3910
|
-
*/
|
|
3911
|
-
function instrumentNodeSchedule(lib) {
|
|
3912
|
-
return new Proxy(lib, {
|
|
3913
|
-
get(target, prop) {
|
|
3914
|
-
if (prop === 'scheduleJob') {
|
|
3915
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
3916
|
-
return new Proxy(target.scheduleJob, {
|
|
3917
|
-
apply(target, thisArg, argArray) {
|
|
3918
|
-
const [nameOrExpression, expressionOrCallback, callback] = argArray;
|
|
3919
|
-
|
|
3920
|
-
if (
|
|
3921
|
-
typeof nameOrExpression !== 'string' ||
|
|
3922
|
-
typeof expressionOrCallback !== 'string' ||
|
|
3923
|
-
typeof callback !== 'function'
|
|
3924
|
-
) {
|
|
3925
|
-
throw new Error(
|
|
3926
|
-
"Automatic instrumentation of 'node-schedule' requires the first parameter of 'scheduleJob' to be a job name string and the second parameter to be a crontab string",
|
|
3927
|
-
);
|
|
3928
|
-
}
|
|
3929
|
-
|
|
3930
|
-
const monitorSlug = nameOrExpression;
|
|
3931
|
-
const expression = expressionOrCallback;
|
|
3932
|
-
|
|
3933
|
-
async function monitoredCallback() {
|
|
3934
|
-
return withMonitor(
|
|
3935
|
-
monitorSlug,
|
|
3936
|
-
async () => {
|
|
3937
|
-
await _optionalChain([callback, 'optionalCall', _ => _()]);
|
|
3938
|
-
},
|
|
3939
|
-
{
|
|
3940
|
-
schedule: { type: 'crontab', value: replaceCronNames(expression) },
|
|
3941
|
-
},
|
|
3942
|
-
);
|
|
3943
|
-
}
|
|
3944
|
-
|
|
3945
|
-
return target.apply(thisArg, [monitorSlug, expression, monitoredCallback]);
|
|
3946
|
-
},
|
|
3947
|
-
});
|
|
3948
|
-
}
|
|
3949
|
-
|
|
3950
|
-
return target[prop];
|
|
3951
|
-
},
|
|
3952
|
-
});
|
|
3953
|
-
}
|
|
3954
|
-
|
|
3955
|
-
/** Methods to instrument cron libraries for Sentry check-ins */
|
|
3956
|
-
const cron = {
|
|
3957
|
-
instrumentCron,
|
|
3958
|
-
instrumentNodeCron,
|
|
3959
|
-
instrumentNodeSchedule,
|
|
3960
|
-
};
|
|
3961
|
-
|
|
3962
|
-
export { NodeClient, anrIntegration, connectIntegration, consoleIntegration, contextLinesIntegration, createGetModuleFromFilename, cron, defaultStackParser, expressErrorHandler, expressIntegration, fastifyIntegration, getAutoPerformanceIntegrations, getDefaultIntegrations, getSentryRelease, graphqlIntegration, hapiIntegration, httpIntegration, init, initOpenTelemetry, koaIntegration, localVariablesIntegration, makeNodeTransport, modulesIntegration, mongoIntegration, mongooseIntegration, mysql2Integration, mysqlIntegration, nativeNodeFetchIntegration, nestIntegration, nodeContextIntegration, onUncaughtExceptionIntegration, onUnhandledRejectionIntegration, postgresIntegration, prismaIntegration, redisIntegration, setupConnectErrorHandler, setupExpressErrorHandler, setupFastifyErrorHandler, setupHapiErrorHandler, setupKoaErrorHandler, setupNestErrorHandler, spotlightIntegration };
|
|
35
|
+
export { addOpenTelemetryInstrumentation, continueTrace, setOpenTelemetryContextAsyncContextStrategy as setNodeAsyncContextStrategy } from '@sentry/opentelemetry';
|
|
36
|
+
export { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, Scope, addBreadcrumb, addEventProcessor, addIntegration, captureCheckIn, captureConsoleIntegration, captureEvent, captureException, captureFeedback, captureMessage, captureSession, close, createTransport, debugIntegration, dedupeIntegration, endSession, extraErrorDataIntegration, flush, functionToStringIntegration, getActiveSpan, getClient, getCurrentHub, getCurrentScope, getGlobalScope, getIsolationScope, getRootSpan, getSpanDescendants, getSpanStatusFromHttpCode, inboundFiltersIntegration, isInitialized, lastEventId, linkedErrorsIntegration, metricsDefault as metrics, parameterize, requestDataIntegration, rewriteFramesIntegration, sessionTimingIntegration, setContext, setCurrentClient, setExtra, setExtras, setHttpStatus, setMeasurement, setTag, setTags, setUser, spanToBaggageHeader, spanToJSON, spanToTraceHeader, startInactiveSpan, startSession, startSpan, startSpanManual, trpcMiddleware, withActiveSpan, withIsolationScope, withMonitor, withScope, zodErrorsIntegration } from '@sentry/core';
|
|
3963
37
|
//# sourceMappingURL=index.js.map
|