diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c5d0fc68..b354edec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ and this project adheres to - ✨(backend) expose the attachment max size in the config endpoint #2577 - ✨(frontend) warn before uploading an attachment over the size limit #2577 - ✨(frontend) add keyboard shortcut to open presentation mode #2697 +- ✨(collaboration) report yhub errors to sentry, configured through + `SENTRY_*` - ✨(backend) add opt-in prometheus metrics on `/metrics`, protected by a bearer token - ✨(helm) add a dedicated ingress for the prometheus metrics diff --git a/documentation/env.md b/documentation/env.md index 008384900..758ea29d3 100644 --- a/documentation/env.md +++ b/documentation/env.md @@ -194,6 +194,11 @@ documents what each of them changes. | YHUB_TASK_CONCURRENCY | Tasks one worker process claims at once | 5 | | YHUB_TASK_DEBOUNCE_MS | How long an update waits on the redis stream before a worker persists it | 10000 | | YHUB_MIN_MESSAGE_LIFETIME_MS | How long persisted updates stay replayable from redis rather than read back from postgres | 60000 | +| SENTRY_DSN | Sentry DSN, unset disables it: the SDK is then not even loaded. Can be read from a file with SENTRY_DSN_FILE | | +| SENTRY_ENVIRONMENT | Environment the events are reported under | production | +| SENTRY_RELEASE | Release the events are reported under. Detected by the SDK when unset | | +| SENTRY_TRACES_SAMPLE_RATE | Share of the traces sent to Sentry, between `0` and `1` | 0 | +| SENTRY_PROFILES_SAMPLE_RATE | Share of the sampled traces that are also profiled, between `0` and `1`. The native profiler is only loaded above `0` | 0 | | SOFT_MIGRATION | Set to "true" to seed a room from the legacy Django/S3 document store the first time it is opened | false | | LEGACY_S3_ENDPOINT_URL | Required by SOFT_MIGRATION, endpoint of the legacy media bucket, without a path | | | LEGACY_S3_ACCESS_KEY_ID | Required by SOFT_MIGRATION, read access to that bucket (or LEGACY_S3_ACCESS_KEY_ID_FILE) | | diff --git a/src/yhub-server/Dockerfile b/src/yhub-server/Dockerfile index 405418992..7ceda9e4c 100644 --- a/src/yhub-server/Dockerfile +++ b/src/yhub-server/Dockerfile @@ -70,4 +70,6 @@ USER ${DOCKER_USER} ENTRYPOINT [ "/usr/local/bin/entrypoint" ] -CMD ["node", "dist/server.js"] +# sentry.js is preloaded so that error reporting is in place before anything else +# is imported; it does nothing without a SENTRY_DSN +CMD ["node", "--import", "./dist/sentry.js", "dist/server.js"] diff --git a/src/yhub-server/README.md b/src/yhub-server/README.md index 5e3727a7d..e88ecae24 100644 --- a/src/yhub-server/README.md +++ b/src/yhub-server/README.md @@ -391,6 +391,43 @@ made *versioned*, so what is exercised is what a deployment runs rather than a simpler case. Watch it with `mc ls --versions --recursive impress/yhub-storage` from an `mc` container on the stack's network. +## Error reporting (Sentry) + +`src/sentry.ts` is preloaded by the start command +(`node --import ./dist/sentry.js dist/server.js`), the way y-provider does it: +the SDK hooks the modules it instruments as they are loaded, so it has to be +there before `server.ts` imports anything. A deployment that overrides the +command of the container has to keep the `--import`. + +| Variable | Default | What it changes | +| -------- | ------- | --------------- | +| `SENTRY_DSN` | — | Where to report. Unset: nothing is reported and the SDK is not loaded (or `…_FILE`) | +| `SENTRY_ENVIRONMENT` | `production` | Environment the events are reported under | +| `SENTRY_RELEASE` | detected | Release the events are reported under | +| `SENTRY_TRACES_SAMPLE_RATE` | `0` | Share of the traces that are sent, `0` to `1` | +| `SENTRY_PROFILES_SAMPLE_RATE` | `0` | Share of the sampled traces that are profiled, `0` to `1` | + +A rate that is not a number between 0 and 1 is a startup error. + +What is reported: + +- uncaught exceptions and unhandled rejections. The process still ends on an + unhandled rejection, as it does without Sentry: the SDK's default would + report it and carry on; +- every `error` and `fatal` line of the logger, with its fields as context. + yhub and this wrapper share one pino logger, and that is where a failed + upgrade, compaction or migration ends up — none of them is thrown to anything + that could report it. `warn` lines (a backend that did not answer, a lost + `content-updated` notification) are not sent. + +Every event carries the tags `application: yhub-server` and `role` (the +`YHUB_ROLE` of the process), the server and the worker being one image. + +Traces are of limited use here: the websocket server is uWebSockets.js, which +the SDK does not instrument, so there is no span for an upgrade or a message. +What is traced is the outgoing side — the calls to the backend, postgres, +redis — when something starts a trace around them. + ## Tests `yarn test` runs the **vitest** suite under `__tests__/*.spec.ts` diff --git a/src/yhub-server/__tests__/sentry.spec.ts b/src/yhub-server/__tests__/sentry.spec.ts new file mode 100644 index 000000000..cb1cb54b0 --- /dev/null +++ b/src/yhub-server/__tests__/sentry.spec.ts @@ -0,0 +1,163 @@ +// sentry.ts — the module preloaded with `node --import`. Tested with both SDK +// packages mocked: the environment goes in, and what is handed to `Sentry.init` +// (or the fact that the SDK was never touched) comes out. + +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const sentry = vi.hoisted(() => ({ + loaded: vi.fn(), + init: vi.fn(), + setTag: vi.fn(), + pinoIntegration: vi.fn((options: unknown) => ({ name: 'Pino', options })), + onUnhandledRejectionIntegration: vi.fn((options: unknown) => ({ + name: 'OnUnhandledRejection', + options, + })), +})); +const profiling = vi.hoisted(() => ({ + loaded: vi.fn(), + nodeProfilingIntegration: vi.fn(() => ({ name: 'ProfilingIntegration' })), +})); + +vi.mock('@sentry/node', () => { + sentry.loaded(); + return sentry; +}); +vi.mock('@sentry/profiling-node', () => { + profiling.loaded(); + return profiling; +}); + +const load = () => import('../src/sentry.js'); + +beforeEach(() => { + vi.unstubAllEnvs(); + vi.resetModules(); + vi.clearAllMocks(); + for (const key of [ + 'SENTRY_DSN', + 'SENTRY_DSN_FILE', + 'SENTRY_ENVIRONMENT', + 'SENTRY_RELEASE', + 'SENTRY_TRACES_SAMPLE_RATE', + 'SENTRY_PROFILES_SAMPLE_RATE', + 'YHUB_ROLE', + ]) { + vi.stubEnv(key, ''); + } +}); + +describe('without a SENTRY_DSN', () => { + it('does not even import the SDK', async () => { + await load(); + + expect(sentry.loaded).not.toHaveBeenCalled(); + expect(profiling.loaded).not.toHaveBeenCalled(); + expect(sentry.init).not.toHaveBeenCalled(); + }); + + it('ignores the other settings', async () => { + vi.stubEnv('SENTRY_TRACES_SAMPLE_RATE', '1'); + vi.stubEnv('SENTRY_PROFILES_SAMPLE_RATE', '1'); + + await load(); + + expect(sentry.loaded).not.toHaveBeenCalled(); + expect(profiling.loaded).not.toHaveBeenCalled(); + }); +}); + +describe('with a SENTRY_DSN', () => { + beforeEach(() => { + vi.stubEnv('SENTRY_DSN', 'https://key@sentry.example.com/1'); + }); + + it('reports errors only, by default', async () => { + await load(); + + expect(sentry.init).toHaveBeenCalledTimes(1); + expect(sentry.init).toHaveBeenCalledWith( + expect.objectContaining({ + dsn: 'https://key@sentry.example.com/1', + environment: undefined, + release: undefined, + tracesSampleRate: 0, + profilesSampleRate: 0, + }), + ); + // the native profiler is not loaded by a deployment that does not profile + expect(profiling.loaded).not.toHaveBeenCalled(); + }); + + it('reports the error lines of the logger and keeps node crashing on unhandled rejections', async () => { + await load(); + + expect(sentry.pinoIntegration).toHaveBeenCalledWith({ + error: { levels: ['error', 'fatal'] }, + }); + expect(sentry.onUnhandledRejectionIntegration).toHaveBeenCalledWith({ + mode: 'strict', + }); + const { integrations } = sentry.init.mock.calls[0][0] as { + integrations: Array<{ name: string }>; + }; + expect(integrations.map(({ name }) => name)).toEqual([ + 'Pino', + 'OnUnhandledRejection', + ]); + }); + + it('takes the environment, the release and the sampling rates from the environment', async () => { + vi.stubEnv('SENTRY_ENVIRONMENT', 'preprod'); + vi.stubEnv('SENTRY_RELEASE', 'docs@5.0.0'); + vi.stubEnv('SENTRY_TRACES_SAMPLE_RATE', '0.25'); + vi.stubEnv('SENTRY_PROFILES_SAMPLE_RATE', '0.5'); + + await load(); + + expect(sentry.init).toHaveBeenCalledWith( + expect.objectContaining({ + environment: 'preprod', + release: 'docs@5.0.0', + tracesSampleRate: 0.25, + profilesSampleRate: 0.5, + }), + ); + }); + + it('loads the profiler only when profiles are sampled', async () => { + vi.stubEnv('SENTRY_PROFILES_SAMPLE_RATE', '0.5'); + + await load(); + + expect(profiling.nodeProfilingIntegration).toHaveBeenCalledTimes(1); + const { integrations } = sentry.init.mock.calls[0][0] as { + integrations: Array<{ name: string }>; + }; + expect(integrations.map(({ name }) => name)).toContain( + 'ProfilingIntegration', + ); + }); + + it('tags the events with the application and the role of the process', async () => { + vi.stubEnv('YHUB_ROLE', 'worker'); + + await load(); + + expect(sentry.setTag).toHaveBeenCalledWith('application', 'yhub-server'); + expect(sentry.setTag).toHaveBeenCalledWith('role', 'worker'); + }); + + it.each([ + ['SENTRY_TRACES_SAMPLE_RATE', 'abc'], + ['SENTRY_TRACES_SAMPLE_RATE', '1.5'], + ['SENTRY_PROFILES_SAMPLE_RATE', '-0.1'], + ])('refuses to start when %s is "%s"', async (name, value) => { + vi.stubEnv(name, value); + + await expect(load()).rejects.toThrow( + `${name} must be a number between 0 and 1 (got "${value}")`, + ); + expect(sentry.init).not.toHaveBeenCalled(); + }); +}); diff --git a/src/yhub-server/package.json b/src/yhub-server/package.json index 221111c14..ae7b306bb 100644 --- a/src/yhub-server/package.json +++ b/src/yhub-server/package.json @@ -5,7 +5,7 @@ "scripts": { "build": "tsc -p tsconfig.build.json", "typecheck": "tsc -p tsconfig.json --noEmit", - "start": "node dist/server.js", + "start": "node --import ./dist/sentry.js dist/server.js", "dev": "nodemon --config nodemon.json", "init-db": "node node_modules/@y/hub/bin/init-db.js", "lint": "eslint", @@ -14,6 +14,8 @@ }, "dependencies": { "@aws-sdk/client-s3": "3.1110.0", + "@sentry/node": "10.70.0", + "@sentry/profiling-node": "10.70.0", "@y/hub": "0.9.0", "@y/y": "14.0.0-rc.25", "jose": "6.2.8" diff --git a/src/yhub-server/src/config.ts b/src/yhub-server/src/config.ts index e88d81c37..737e8915b 100644 --- a/src/yhub-server/src/config.ts +++ b/src/yhub-server/src/config.ts @@ -24,6 +24,17 @@ const intEnv = (name: string, dflt: number, min = 1): number => { return value; }; +// A sampling rate, refused rather than guessed when it is not a number between +// 0 and 1: Sentry reads anything else as "drop everything" and says so nowhere. +const rateEnv = (name: string, dflt: number): number => { + const raw = process.env[name]; + const value = raw == null || raw === '' ? dflt : Number(raw); + if (!Number.isFinite(value) || value < 0 || value > 1) { + throw new Error(`${name} must be a number between 0 and 1 (got "${raw}")`); + } + return value; +}; + export const PORT = Number(process.env.PORT || 3002); export const REDIS = process.env.REDIS; export const POSTGRES = process.env.POSTGRES; @@ -155,3 +166,20 @@ export const BACKEND_TOKEN_MARGIN_MS = 10000; // signs to call us: no long-lived shared secret, only our private key here and // its public half published on the JWKS endpoint. export const YHUB_JWT_PRIVATE_KEY = secret('YHUB_JWT_PRIVATE_KEY', ''); + +// Error reporting, set up by `sentry.ts` before anything else is loaded. No DSN, +// no Sentry: the SDK is not even imported. +export const SENTRY_DSN = secret('SENTRY_DSN', ''); +// Left to the SDK when unset: it reports "production", and reads the release +// from what the platform exposes. +export const SENTRY_ENVIRONMENT = process.env.SENTRY_ENVIRONMENT || undefined; +export const SENTRY_RELEASE = process.env.SENTRY_RELEASE || undefined; +// Both off by default, like the backend. The websocket server is not an http +// server the SDK knows how to trace, so a trace here is the outgoing calls (the +// backend, postgres, redis) rather than a request from end to end. +export const SENTRY_TRACES_SAMPLE_RATE = rateEnv('SENTRY_TRACES_SAMPLE_RATE', 0); +// Relative to the traces: the share of sampled traces that are also profiled. +export const SENTRY_PROFILES_SAMPLE_RATE = rateEnv( + 'SENTRY_PROFILES_SAMPLE_RATE', + 0, +); diff --git a/src/yhub-server/src/sentry.ts b/src/yhub-server/src/sentry.ts new file mode 100644 index 000000000..e0cb79905 --- /dev/null +++ b/src/yhub-server/src/sentry.ts @@ -0,0 +1,53 @@ +/** + * Error reporting. Preloaded with `node --import ./dist/sentry.js`, the way + * y-provider does it: the SDK hooks the modules it instruments as they are + * loaded, so it has to be initialised before `server.ts` imports any of them. + * + * Everything is read from the environment (`config.ts`), and without a + * `SENTRY_DSN` nothing happens at all — the SDK is imported on demand, so a + * deployment that does not use Sentry does not pay for it. + * + * What reaches Sentry: + * - uncaught exceptions and unhandled rejections; + * - every `error` and `fatal` line of the pino logger, ours and yhub's — it is + * one logger, and it is where a failed upgrade, compaction or migration ends + * up, none of them being thrown to anything that could report it; + * - traces and profiles, when their sampling rates ask for them. + */ +import { + ROLE, + SENTRY_DSN, + SENTRY_ENVIRONMENT, + SENTRY_PROFILES_SAMPLE_RATE, + SENTRY_RELEASE, + SENTRY_TRACES_SAMPLE_RATE, +} from './config.js'; + +if (SENTRY_DSN) { + const Sentry = await import('@sentry/node'); + + const integrations = [ + Sentry.pinoIntegration({ error: { levels: ['error', 'fatal'] } }), + // Node ends the process on an unhandled rejection. The SDK's default would + // report it and carry on, which makes a server behave differently with and + // without error reporting: report it, then end as Node would have. + Sentry.onUnhandledRejectionIntegration({ mode: 'strict' }), + ]; + if (SENTRY_PROFILES_SAMPLE_RATE > 0) { + // a native module: only loaded by the deployments that profile + const { nodeProfilingIntegration } = await import('@sentry/profiling-node'); + integrations.push(nodeProfilingIntegration()); + } + + Sentry.init({ + dsn: SENTRY_DSN, + environment: SENTRY_ENVIRONMENT, + release: SENTRY_RELEASE, + integrations, + tracesSampleRate: SENTRY_TRACES_SAMPLE_RATE, + profilesSampleRate: SENTRY_PROFILES_SAMPLE_RATE, + }); + Sentry.setTag('application', 'yhub-server'); + // the server and the worker are the same image: tell them apart + Sentry.setTag('role', ROLE); +} diff --git a/src/yhub-server/yarn.lock b/src/yhub-server/yarn.lock index e40371c14..c5363c708 100644 --- a/src/yhub-server/yarn.lock +++ b/src/yhub-server/yarn.lock @@ -2,6 +2,37 @@ # yarn lockfile v1 +"@apm-js-collab/code-transformer-bundler-plugins@^0.7.3": + version "0.7.4" + resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer-bundler-plugins/-/code-transformer-bundler-plugins-0.7.4.tgz#f323d5f723565e34f1409aa96e445ad489020f76" + integrity sha512-nAfOeZPSUAQvJa1iFT/5oCrTm5YQhMMrfCNthNnaXHZiOQhu1KGuLoIx7HtbAi3wfwaBYLaICPIeenIaEwcXIg== + dependencies: + "@apm-js-collab/code-transformer" "^0.18.1" + es-module-lexer "^2.1.0" + magic-string "^0.30.21" + module-details-from-path "^1.0.4" + +"@apm-js-collab/code-transformer@^0.18.0", "@apm-js-collab/code-transformer@^0.18.1": + version "0.18.1" + resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer/-/code-transformer-0.18.1.tgz#66ce01cfe9607779b4abebb4f54c49a46e8b48ca" + integrity sha512-u1Hb6bHjWtkSpiprwVP6YaHC1DTN4RAU3zYkUDUe7WMnJwdyU1pwTL9dFKiSJB9IiLue/EQovmyx6xhU7FFtAQ== + dependencies: + "@types/estree" "^1.0.8" + astring "^1.9.0" + esquery "^1.7.0" + meriyah "^6.1.4" + semifies "^1.0.0" + source-map "^0.6.0" + +"@apm-js-collab/tracing-hooks@^0.13.0": + version "0.13.2" + resolved "https://registry.yarnpkg.com/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.13.2.tgz#c219d04bf115bf7828a8887e437c0d6c888159f0" + integrity sha512-jk3acBAJecK8ctMGlkappcX5CtymTxmd0GGnUqh4ZX3Pk1RbXei/vzGEDTeOHcXJ6wsRkm8PhHsfOba0jxB67A== + dependencies: + "@apm-js-collab/code-transformer" "^0.18.0" + debug "^4.4.1" + module-details-from-path "^1.0.4" + "@aws-sdk/checksums@^3.1000.27": version "3.1000.29" resolved "https://registry.yarnpkg.com/@aws-sdk/checksums/-/checksums-3.1000.29.tgz#f12a6926eb3789057f5f16582fc226fe533c87fa" @@ -450,6 +481,66 @@ resolved "https://registry.yarnpkg.com/@nodable/entities/-/entities-3.0.0.tgz#694703bc864d30eaed55c2e3def00dbd61493670" integrity sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw== +"@opentelemetry/api-logs@0.220.0": + version "0.220.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.220.0.tgz#b2ed235de4b5f3c1769adfa5f3bc247d82cdfbc9" + integrity sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w== + dependencies: + "@opentelemetry/api" "^1.3.0" + +"@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.1.tgz#c1b0346de336ba55af2d5a7970882037baedec05" + integrity sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q== + +"@opentelemetry/core@2.11.0": + version "2.11.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-2.11.0.tgz#97849d3808bba652568b7d53e20c9fc38bcb7c49" + integrity sha512-7YP44XH0tV6+Mb54x2YGf84i7yi+31MBZlE8JwvozkxyTvXbSp10X7cI7YE49ChJ3shMJoBmCJF3+1QFBJctGA== + dependencies: + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/instrumentation@^0.220.0": + version "0.220.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.220.0.tgz#542b36bf4871dd83dc84e1373ac4bbcd35a8bfb8" + integrity sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw== + dependencies: + "@opentelemetry/api-logs" "0.220.0" + import-in-the-middle "^3.0.0" + require-in-the-middle "^8.0.0" + +"@opentelemetry/resources@2.11.0": + version "2.11.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-2.11.0.tgz#b4e60ba92a4f122dc979f3402aea9713a98eac5b" + integrity sha512-Ie7+8q8MDF4FAEQCKVMTx3ReUvxiIAgIiiW3c9JdmP8+HMcDy20puT+AHjexnExgnbvBxjQ9fjkFDWrikJ2jQA== + dependencies: + "@opentelemetry/core" "2.11.0" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/sdk-trace-base@^2.9.0": + version "2.11.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.11.0.tgz#3f725401fcfb16e5b2335c103cdc9488829632d1" + integrity sha512-H19x/TX/LZdqiYOjM7fqtSxwlplC5pgelavqbQdHbhdq0q/AI/TGkM2dfGuuynTXmJPeF2HoZVoPDu+TGoW78A== + dependencies: + "@opentelemetry/core" "2.11.0" + "@opentelemetry/resources" "2.11.0" + "@opentelemetry/sdk-trace" "2.11.0" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/sdk-trace@2.11.0": + version "2.11.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace/-/sdk-trace-2.11.0.tgz#46bbba9d366755df7d6faded0f7cd25af7f265c2" + integrity sha512-fFnTqGm8/G73GQVnxYi7LXa1ZVYEUvgL6XI1LpvV0bPC7WQ/ZGgKxCSl8FnlZBKto9JHHEFTO6s6CUpvvtwFrA== + dependencies: + "@opentelemetry/core" "2.11.0" + "@opentelemetry/resources" "2.11.0" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/semantic-conventions@^1.29.0": + version "1.43.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz#f3f467e36c27332f0e735ec86cdcd78dd6f27865" + integrity sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg== + "@pinojs/redact@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@pinojs/redact/-/redact-0.4.0.tgz#c3de060dd12640dcc838516aa2a6803cc7b2e9d6" @@ -607,6 +698,79 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz#cd19d691330cbd52ebb13620acb6cf7140b95e80" integrity sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w== +"@sentry/conventions@^0.16.0": + version "0.16.0" + resolved "https://registry.yarnpkg.com/@sentry/conventions/-/conventions-0.16.0.tgz#3b58d15714cf44dca1518496c00749eec5525009" + integrity sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ== + +"@sentry/core@10.70.0": + version "10.70.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-10.70.0.tgz#96eb9f2beab7d4b3b10c1b59ec7833e4dfe5fb73" + integrity sha512-ozhCTDqg89oB4XmWfAwuHshABpvT7AkRpaPnogopPfMAaI61G1t8EKCJ4W7aum8JSBonlfyjPCyW5oYZFm0KvA== + dependencies: + "@sentry/conventions" "^0.16.0" + +"@sentry/node-core@10.70.0": + version "10.70.0" + resolved "https://registry.yarnpkg.com/@sentry/node-core/-/node-core-10.70.0.tgz#d7998eaf9d0efa06d05ba6d9c001afcb8e68dcb5" + integrity sha512-oPOEVVNxv5WHtckx2i06Wi9FLWyvOg/1DUeX732jZ4iqT2nupINaMH4nF4f4kSvUThFnxkFSRQxwqOxgzMKhKA== + dependencies: + "@sentry/conventions" "^0.16.0" + "@sentry/core" "10.70.0" + "@sentry/opentelemetry" "10.70.0" + import-in-the-middle "^3.0.0" + +"@sentry/node-cpu-profiler@^2.4.3": + version "2.4.4" + resolved "https://registry.yarnpkg.com/@sentry/node-cpu-profiler/-/node-cpu-profiler-2.4.4.tgz#b275d249bd6e0c98c1e4f2ebc87acf702e6ae04d" + integrity sha512-FaYbFWxEyTTVqXLJccokJCkwx2cTuvTiRaM9kA+O4HbZVT49xYXqSV9cNLI5UqJhgdHiZDhelBd5LB4wj7IIRA== + dependencies: + detect-libc "^2.0.3" + node-abi "^3.73.0" + +"@sentry/node@10.70.0": + version "10.70.0" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-10.70.0.tgz#99fa23d90147c92e8291b8da9d706d3759468bc1" + integrity sha512-SPOOVxmKTVIEtqvOKkQT163e/pOwucjS7OPsCHyRs8sFR4nfBNu0EThplyqnvqd5BWBMTPH6WTBQfo+QWHV+HA== + dependencies: + "@opentelemetry/api" "^1.9.1" + "@opentelemetry/instrumentation" "^0.220.0" + "@opentelemetry/sdk-trace-base" "^2.9.0" + "@sentry/conventions" "^0.16.0" + "@sentry/core" "10.70.0" + "@sentry/node-core" "10.70.0" + "@sentry/opentelemetry" "10.70.0" + "@sentry/server-utils" "10.70.0" + import-in-the-middle "^3.0.0" + +"@sentry/opentelemetry@10.70.0": + version "10.70.0" + resolved "https://registry.yarnpkg.com/@sentry/opentelemetry/-/opentelemetry-10.70.0.tgz#40823a9d16b5d271a52de65244a3e2faeba43fbb" + integrity sha512-UNV/2tqypcUK6FDzerAsFJn1Km/c4VZCYkUZDNbnV5S0cwAq2BYKMo4M5vovaLDBQlxA+Wk9ovbxi5wYjjl9fw== + dependencies: + "@sentry/conventions" "^0.16.0" + "@sentry/core" "10.70.0" + +"@sentry/profiling-node@10.70.0": + version "10.70.0" + resolved "https://registry.yarnpkg.com/@sentry/profiling-node/-/profiling-node-10.70.0.tgz#ca7b288d4db741923b5920c78a64a38e5d8cfdc1" + integrity sha512-bOL1nd+t6H4dHM2BB768Thm26UXBxIqBORIMr7nM+QLID+6DjHRcknRZbyklGNUVzAggsprzQ3f61i2xdRmTEA== + dependencies: + "@sentry/core" "10.70.0" + "@sentry/node" "10.70.0" + "@sentry/node-cpu-profiler" "^2.4.3" + +"@sentry/server-utils@10.70.0": + version "10.70.0" + resolved "https://registry.yarnpkg.com/@sentry/server-utils/-/server-utils-10.70.0.tgz#df7ab00caae3f7398338062ba91d7a355ca27489" + integrity sha512-rzegZjMFFgCp3o+N8+XU13rfSvz4B+f8rU0ijBGrQcHdMNyfsFDTu1UTm262JofmrV2u+s+D0u0vFTnqtOGkbA== + dependencies: + "@apm-js-collab/code-transformer-bundler-plugins" "^0.7.3" + "@apm-js-collab/tracing-hooks" "^0.13.0" + "@sentry/conventions" "^0.16.0" + "@sentry/core" "10.70.0" + meriyah "^6.1.4" + "@smithy/core@^3.31.1", "@smithy/core@^3.33.2", "@smithy/core@^3.33.3": version "3.33.3" resolved "https://registry.yarnpkg.com/@smithy/core/-/core-3.33.3.tgz#c1e801fe17160bcbf6c714cf16e832057e6bf79e" @@ -922,6 +1086,11 @@ assertion-error@^2.0.1: resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== +astring@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/astring/-/astring-1.9.0.tgz#cc73e6062a7eb03e7d19c22d8b0b3451fd9bfeef" + integrity sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg== + async@^3.2.4: version "3.2.6" resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" @@ -1014,6 +1183,11 @@ chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" +cjs-module-lexer@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-2.2.1.tgz#ab35b03c56ade05fe170c70e67ae89f60666847c" + integrity sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q== + cluster-key-slot@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" @@ -1028,7 +1202,7 @@ cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" -debug@^4, debug@^4.3.1, debug@^4.3.2, debug@^4.4.1, debug@^4.4.3: +debug@^4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.5, debug@^4.4.1, debug@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== @@ -1050,11 +1224,26 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +detect-libc@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + es-module-lexer@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== +es-module-lexer@^2.1.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.3.2.tgz#311fa4f40168c1975c505477c51b23234d41ad55" + integrity sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw== + +es-module-lexer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-3.0.2.tgz#8cc09097dd80a9cbb79437fd405b935cac76cda3" + integrity sha512-BuIB67FngDSyQ/dpQNOZybwdEBDUGJQvOqwWr4ha/ufYiqzuEwPkKO2zLhRAgay28tStRIHUeWmszZAJo3GCOg== + "esbuild@^0.27.0 || ^0.28.0": version "0.28.2" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.28.2.tgz#0f43bd1bad955b72d24e2261e3abe5957ccf0816" @@ -1322,6 +1511,15 @@ ignore@^7.0.5: resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.8.tgz#84d8466899958458ee30b4190c839ee1446cc88d" integrity sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q== +import-in-the-middle@^3.0.0: + version "3.5.1" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-3.5.1.tgz#74fc490851751005dc2f776a87bc8a075904b474" + integrity sha512-mPKuL8bPQzecui2KK6Gb+M8JvJoHnhS1FeYGa22QopBmlevF5F0FE6ued/B5EgHDeIoMTONIpDWWFKUPOG0DBQ== + dependencies: + cjs-module-lexer "^2.2.0" + es-module-lexer "^3.0.2" + module-details-from-path "^1.0.4" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -1433,13 +1631,18 @@ loupe@^3.1.0, loupe@^3.1.4: resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.2.1.tgz#0095cf56dc5b7a9a7c08ff5b1a8796ec8ad17e76" integrity sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ== -magic-string@^0.30.17: +magic-string@^0.30.17, magic-string@^0.30.21: version "0.30.21" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== dependencies: "@jridgewell/sourcemap-codec" "^1.5.5" +meriyah@^6.1.4: + version "6.1.4" + resolved "https://registry.yarnpkg.com/meriyah/-/meriyah-6.1.4.tgz#2d49a8934fbcd9205c20564579c3560d9b1e077b" + integrity sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ== + mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" @@ -1478,6 +1681,11 @@ minio@^8.0.6: through2 "^4.0.2" xml2js "^0.5.0 || ^0.6.2" +module-details-from-path@^1.0.3, module-details-from-path@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.4.tgz#b662fdcd93f6c83d3f25289da0ce81c8d9685b94" + integrity sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w== + ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -1493,6 +1701,13 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +node-abi@^3.73.0: + version "3.96.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.96.0.tgz#8b038bc6cca97d6cec2e9aafdb81b900da1ff80d" + integrity sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg== + dependencies: + semver "^7.3.5" + nodemon@3.1.14: version "3.1.14" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.14.tgz#8487ca379c515301d221ec007f27f24ecafa2b51" @@ -1700,6 +1915,14 @@ redis@^5.10.0: "@redis/search" "5.12.1" "@redis/time-series" "5.12.1" +require-in-the-middle@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz#dbde2587f669398626d56b20c868ab87bf01cce4" + integrity sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ== + dependencies: + debug "^4.3.5" + module-details-from-path "^1.0.3" + rollup@^4.43.0: version "4.63.1" resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.63.1.tgz#a9b96d5b2558d034babb12ad8b67a043bc870ac4" @@ -1750,7 +1973,12 @@ sax@>=0.6.0: resolved "https://registry.yarnpkg.com/sax/-/sax-1.6.1.tgz#4c23cf608c0b693ab54b4b5888e92cfe977b9843" integrity sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q== -semver@^7.5.3, semver@^7.7.3: +semifies@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semifies/-/semifies-1.0.0.tgz#b69569f32c2ba2ac04f705ea82831364289b2ae2" + integrity sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw== + +semver@^7.3.5, semver@^7.5.3, semver@^7.7.3: version "7.8.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69" integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== @@ -1791,6 +2019,11 @@ source-map-js@^1.2.1: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + split-on-first@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"