diff --git a/.changeset/trueforge-sentry-p1.md b/.changeset/trueforge-sentry-p1.md new file mode 100644 index 000000000..402cafcc9 --- /dev/null +++ b/.changeset/trueforge-sentry-p1.md @@ -0,0 +1,5 @@ +--- +"@truefoundry/trueforge": patch +--- + +Add Sentry for critical-flow error reporting (TrueFoundry auth-server or SENTRY_DSN init) with configurable additional tags. diff --git a/packages/trueforge/.env.example b/packages/trueforge/.env.example index 43161d6ab..431ae5823 100644 --- a/packages/trueforge/.env.example +++ b/packages/trueforge/.env.example @@ -248,6 +248,18 @@ POSTGRES_PORT=5432 # TRUEFOUNDRY_SERVICEFOUNDRY_HTTP_TIMEOUT_MS=10000 ## Max ms for ServiceFoundry agent create/update/delete calls. Default 3000. # TRUEFOUNDRY_SERVICEFOUNDRY_HTTP_AGENT_TIMEOUT_MS=3000 +## Auth server for Sentry DSN lookup when SENTRY_ENABLED=true in TrueFoundry mode. +# TRUEFOUNDRY_AUTH_SERVER_URL= +## Optional tenantName query param for the auth-server Sentry DSN lookup. +# TRUEFOUNDRY_TENANT_NAME= + +## Sentry error reporting (off by default). No-op when NODE_ENV is development/test/local. +# SENTRY_ENABLED=false +## Required when SENTRY_ENABLED=true and not in TrueFoundry mode. +# SENTRY_DSN= +## Extra tags applied on Sentry init (JSON object of strings), e.g. {"priority":"p1","team":"agent-team"}. +# SENTRY_ADDITIONAL_TAGS= + ## Optional per-tenant allowlist of model provider account names (JSON object). ## Tenants omitted are unfiltered. Example: {"internal":["openai-main","anthropic-main"]} # TRUEFOUNDRY_TENANT_ID_TO_ALLOWED_MODEL_PROVIDER_ACCOUNTS={"internal":["openai-main"]} diff --git a/packages/trueforge/package.json b/packages/trueforge/package.json index 15896617d..a2c464801 100644 --- a/packages/trueforge/package.json +++ b/packages/trueforge/package.json @@ -66,6 +66,7 @@ "@hono/swagger-ui": "^0.2.2", "@hono/zod-openapi": "^1.6.1", "@modelcontextprotocol/sdk": "^1.30.0", + "@sentry/node": "^10.74.0", "@truefoundry/trueforge-core": "workspace:*", "@truefoundry/trueforge-sdk": "workspace:*", "better-sqlite3": "^13.0.3", diff --git a/packages/trueforge/src/config.ts b/packages/trueforge/src/config.ts index 6c89a6fea..1aa4b3b1d 100644 --- a/packages/trueforge/src/config.ts +++ b/packages/trueforge/src/config.ts @@ -297,6 +297,30 @@ function parseTrueFoundrySandboxProvider(raw: string | undefined): 'daytona' | ' ); } +/** Parses `SENTRY_ADDITIONAL_TAGS` as a JSON object of string values. Unset/blank → `{}`. */ +function parseSentryAdditionalTags(raw: string | undefined): Record { + if (raw === undefined || raw.trim() === '') { + return {}; + } + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch (error) { + throw new Error('SENTRY_ADDITIONAL_TAGS must be a JSON object of string values', { cause: error }); + } + if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { + throw new Error('SENTRY_ADDITIONAL_TAGS must be a JSON object of string values'); + } + const tags: Record = {}; + for (const [key, value] of Object.entries(parsed)) { + if (typeof value !== 'string') { + throw new Error(`SENTRY_ADDITIONAL_TAGS.${key} must be a string`); + } + tags[key] = value; + } + return tags; +} + /** Parses `POSTGRES_SSL_MODE`. Unset/blank → `''`. Unknown values throw. */ function validatePostgresSslMode(raw: string | undefined): PostgresSslMode | '' { const mode = raw?.trim() ?? ''; @@ -807,6 +831,9 @@ export interface SharedServerConfiguration { OUTBOUND_URL_ALLOWED_HOSTS: string[]; /** Hosts always blocked. Env: `OUTBOUND_URL_BLOCKED_HOSTS` (JSON string array). Empty = none. */ OUTBOUND_URL_BLOCKED_HOSTS: string[]; + SENTRY_ENABLED: boolean; + SENTRY_DSN: string | undefined; + SENTRY_ADDITIONAL_TAGS: Record; } export type StandaloneServerConfiguration = SharedServerConfiguration & { @@ -970,6 +997,8 @@ export type DistributedServerConfiguration = SharedServerConfiguration & { * Unset / empty → web search tools are not registered. Env: `TRUEFOUNDRY_WEB_SEARCH_PROVIDER`. */ TRUEFOUNDRY_WEB_SEARCH_PROVIDER: TrueFoundryWebSearchProviderEnv | undefined; + TRUEFOUNDRY_AUTH_SERVER_URL: string | undefined; + TRUEFOUNDRY_TENANT_NAME: string | undefined; }; export type ServerConfiguration = StandaloneServerConfiguration | DistributedServerConfiguration; @@ -1103,6 +1132,13 @@ const shared: SharedServerConfiguration = { envKey: 'OUTBOUND_URL_BLOCKED_HOSTS', raw: getEnv('OUTBOUND_URL_BLOCKED_HOSTS'), }), + SENTRY_ENABLED: parseBoolean({ + envKey: 'SENTRY_ENABLED', + raw: getEnv('SENTRY_ENABLED'), + defaultValue: false, + }), + SENTRY_DSN: getEnv('SENTRY_DSN', { required: false }), + SENTRY_ADDITIONAL_TAGS: parseSentryAdditionalTags(getEnv('SENTRY_ADDITIONAL_TAGS', { required: false })), }; const configuration: ServerConfiguration = standalone @@ -1202,6 +1238,8 @@ const configuration: ServerConfiguration = standalone TRUEFOUNDRY_WEB_SEARCH_PROVIDER: parseTrueFoundryWebSearchProvider( getEnv('TRUEFOUNDRY_WEB_SEARCH_PROVIDER', { required: false }), ), + TRUEFOUNDRY_AUTH_SERVER_URL: getEnv('TRUEFOUNDRY_AUTH_SERVER_URL', { required: false }), + TRUEFOUNDRY_TENANT_NAME: getEnv('TRUEFOUNDRY_TENANT_NAME', { required: false }), }; export function isOidcConfigured( diff --git a/packages/trueforge/src/controller-main.ts b/packages/trueforge/src/controller-main.ts index 1e4e7b26a..9abe045a4 100644 --- a/packages/trueforge/src/controller-main.ts +++ b/packages/trueforge/src/controller-main.ts @@ -18,6 +18,7 @@ import { createDb } from './db/postgres/client'; import { PostgresScheduleStore } from './db/postgres/schedule-store/PostgresScheduleStore'; import { createControllerLogger } from './logger'; import { PACKAGE_VERSION } from './packageVersion'; +import { initSentry } from './sentry'; try { const logger = createControllerLogger({ @@ -26,6 +27,8 @@ try { version: PACKAGE_VERSION, }); + await initSentry(configuration, logger, { tags: { component: 'controller' } }); + if (configuration.STANDALONE) { // Not an error: in standalone the server process owns the controller in-process, so a // dedicated controller has nothing to do. Exit cleanly (e.g. `pnpm standalone:dev` also diff --git a/packages/trueforge/src/controller/scheduleDispatch.ts b/packages/trueforge/src/controller/scheduleDispatch.ts index 178c0886d..3a8c11d83 100644 --- a/packages/trueforge/src/controller/scheduleDispatch.ts +++ b/packages/trueforge/src/controller/scheduleDispatch.ts @@ -13,6 +13,7 @@ import type { WithTransaction } from '../db/transaction'; import { createTlsFetch, normalizeTlsUrl } from '../http/tls'; import { nextTriggerAfter } from '../runtime/cron'; import { InvalidCronError, type ScheduleRunStatus } from '../schemas/schedule'; +import { captureCriticalException } from '../sentry'; import type { ControlLoop } from './Controller'; /** @@ -303,6 +304,14 @@ export async function dispatchScheduledRuns(params: { run_id: run.id, error, }); + captureCriticalException(error, { + tags: { module: 'scheduleDispatch', operation: 'handoff' }, + extra: { + tenant_id: run.tenant_id, + schedule_id: schedule.id, + run_id: run.id, + }, + }); await finishScheduledRun({ store, run, @@ -329,6 +338,14 @@ export async function dispatchScheduledRuns(params: { run_id: run.id, error, }); + captureCriticalException(error, { + tags: { module: 'scheduleDispatch', operation: 'processRun' }, + extra: { + tenant_id: run.tenant_id, + schedule_id: run.schedule_id, + run_id: run.id, + }, + }); } } diff --git a/packages/trueforge/src/main.ts b/packages/trueforge/src/main.ts index 50a85f796..8c8847fd4 100644 --- a/packages/trueforge/src/main.ts +++ b/packages/trueforge/src/main.ts @@ -93,6 +93,7 @@ import { PACKAGE_VERSION } from './packageVersion'; import { ActiveTurnRegistry } from './runtime/activeTurns'; import { EventSubscriptionRegistry } from './runtime/event-subscription'; import type { ConnectedRedis } from './runtime/redis'; +import { initSentry } from './sentry'; import { printStandaloneStartupBanner } from './startupBanner'; import { InlineMcpServerStore } from './truefoundry/InlineMcpServerStore'; import { parseInlineMcpServers, parseInlineSkills, X_TFG_MCP, X_TFG_SKILLS } from './truefoundry/inlineResources'; @@ -675,6 +676,8 @@ try { version: PACKAGE_VERSION, }); + await initSentry(configuration, logger, { tags: { component: 'server' } }); + if (configuration.STANDALONE) { printStandaloneStartupBanner({ version: PACKAGE_VERSION, color: shouldColorize() }); await prepareCodeModeSocketParent({ path: configuration.CODE_MODE_SOCKET_PARENT, logger }); diff --git a/packages/trueforge/src/sentry/captureCriticalException.ts b/packages/trueforge/src/sentry/captureCriticalException.ts new file mode 100644 index 000000000..062ae3397 --- /dev/null +++ b/packages/trueforge/src/sentry/captureCriticalException.ts @@ -0,0 +1,16 @@ +import * as Sentry from '@sentry/node'; + +export function captureCriticalException( + err: unknown, + options?: { tags?: Record; extra?: Record }, +): void { + Sentry.withScope(scope => { + if (options?.tags) { + scope.setTags(options.tags); + } + if (options?.extra) { + scope.setExtras(options.extra); + } + Sentry.captureException(err); + }); +} diff --git a/packages/trueforge/src/sentry/index.ts b/packages/trueforge/src/sentry/index.ts new file mode 100644 index 000000000..c2a715c19 --- /dev/null +++ b/packages/trueforge/src/sentry/index.ts @@ -0,0 +1,2 @@ +export { captureCriticalException } from './captureCriticalException'; +export { initSentry, type InitSentryOptions } from './initSentry'; diff --git a/packages/trueforge/src/sentry/initSentry.ts b/packages/trueforge/src/sentry/initSentry.ts new file mode 100644 index 000000000..0fd48a838 --- /dev/null +++ b/packages/trueforge/src/sentry/initSentry.ts @@ -0,0 +1,58 @@ +import * as Sentry from '@sentry/node'; +import type { Logger } from 'winston'; + +import { isTrueFoundryModeEnabled, type ServerConfiguration } from '../config'; +import { PACKAGE_VERSION } from '../packageVersion'; +import { initTrueFoundrySentry } from '../truefoundry/initTrueFoundrySentry'; + +function isLocalLikeEnv(nodeEnv: string | undefined): boolean { + return nodeEnv === 'development' || nodeEnv === 'test' || nodeEnv === 'local'; +} + +function applyGlobalTags(tags: Record): void { + const scope = Sentry.getGlobalScope(); + for (const [key, value] of Object.entries(tags)) { + scope.setTag(key, value); + } +} + +export interface InitSentryOptions { + tags?: Record; +} + +export async function initSentry( + config: ServerConfiguration, + logger: Pick, + options?: InitSentryOptions, +): Promise { + if (!config.SENTRY_ENABLED || isLocalLikeEnv(config.NODE_ENV)) { + logger.info('Sentry is not enabled (SENTRY_ENABLED=false or local-like NODE_ENV)'); + return; + } + + const globalTags: Record = { + service: 'trueforge', + TRUEFORGE_VERSION: PACKAGE_VERSION, + ...config.SENTRY_ADDITIONAL_TAGS, + ...options?.tags, + }; + + if (isTrueFoundryModeEnabled(config)) { + await initTrueFoundrySentry({ logger, tags: globalTags }); + return; + } + + const dsn = config.SENTRY_DSN; + if (dsn === undefined || dsn.trim() === '') { + logger.error('SENTRY_DSN is required when SENTRY_ENABLED=true'); + return; + } + Sentry.init({ + dsn, + includeLocalVariables: false, + defaultIntegrations: false, + integrations: [], + }); + applyGlobalTags(globalTags); + logger.info('Sentry initialised'); +} diff --git a/packages/trueforge/src/truefoundry/TrueFoundryAgentStore.ts b/packages/trueforge/src/truefoundry/TrueFoundryAgentStore.ts index 2b96df378..f34b0d730 100644 --- a/packages/trueforge/src/truefoundry/TrueFoundryAgentStore.ts +++ b/packages/trueforge/src/truefoundry/TrueFoundryAgentStore.ts @@ -16,6 +16,7 @@ import { import { PostgresAgentStore } from '../db/postgres/agent-store/PostgresAgentStore'; import type { Database } from '../db/postgres/types'; import { AGENT_DESCRIPTION_MAX_LENGTH } from '../schemas/agent'; +import { captureCriticalException } from '../sentry'; import { callerAccessToken, type ResolveAccessToken } from './accessToken'; import { TrueFoundryServiceFoundryServerClient, @@ -174,7 +175,19 @@ export class TrueFoundryAgentStore implements IAgentStore> failures.push(asError(cleanupError)); } if (failures.length > 1) { - throw new AggregateError(failures, 'createAgent failed and cleanup also failed', { cause: error }); + const aggregate = new AggregateError(failures, 'createAgent failed and cleanup also failed', { + cause: error, + }); + captureCriticalException(aggregate, { + tags: { module: 'TrueFoundryAgentStore', operation: 'dualWrite' }, + extra: { + agent_id: created.id, + agent_name: created.name, + tenant_id: input.tenant_id, + external_id: externalId, + }, + }); + throw aggregate; } throw error; } @@ -226,11 +239,21 @@ export class TrueFoundryAgentStore implements IAgentStore> }), }); } catch (restoreError) { - throw new AggregateError( + const aggregate = new AggregateError( [asError(error), asError(restoreError)], 'updateAgent failed and ServiceFoundry restore also failed', { cause: restoreError }, ); + captureCriticalException(aggregate, { + tags: { module: 'TrueFoundryAgentStore', operation: 'dualWrite' }, + extra: { + agent_id: input.id, + agent_name: previous.name, + tenant_id: input.tenant_id, + external_id: previous.external_id, + }, + }); + throw aggregate; } throw error; } diff --git a/packages/trueforge/src/truefoundry/errors.ts b/packages/trueforge/src/truefoundry/errors.ts index 4a4e5fdde..b0233b148 100644 --- a/packages/trueforge/src/truefoundry/errors.ts +++ b/packages/trueforge/src/truefoundry/errors.ts @@ -1,14 +1,26 @@ import { HTTPException } from 'hono/http-exception'; import type { AgentRecord } from '../db/agentStore'; +import { captureCriticalException } from '../sentry'; export const AGENT_EXTERNAL_ID_REQUIRED = 'Agent is missing a TrueFoundry external id'; export const TRUEFOUNDRY_MANAGED_STATUS = 424 as const; export const TRUEFOUNDRY_MANAGED_MESSAGE = 'This resource is managed by TrueFoundry'; /** Require the remote identity needed for TrueFoundry agent operations. */ -export function requireTrueFoundryAgentExternalId(agent: Pick): string { +export function requireTrueFoundryAgentExternalId( + agent: Pick, +): string { if (agent.external_id === null) { - throw new HTTPException(500, { message: AGENT_EXTERNAL_ID_REQUIRED }); + const error = new HTTPException(500, { message: AGENT_EXTERNAL_ID_REQUIRED }); + captureCriticalException(error, { + tags: { module: 'truefoundry', operation: 'requireExternalId' }, + extra: { + agent_id: agent.id, + tenant_id: agent.tenant_id, + agent_name: agent.name, + }, + }); + throw error; } return agent.external_id; } diff --git a/packages/trueforge/src/truefoundry/initTrueFoundrySentry.ts b/packages/trueforge/src/truefoundry/initTrueFoundrySentry.ts new file mode 100644 index 000000000..e160ac0c3 --- /dev/null +++ b/packages/trueforge/src/truefoundry/initTrueFoundrySentry.ts @@ -0,0 +1,94 @@ +import type { NodeOptions } from '@sentry/node'; +import * as Sentry from '@sentry/node'; +import { fetch as undiciFetch } from 'undici'; +import type { Logger } from 'winston'; + +import configuration, { isTrueFoundryModeEnabled } from '../config'; +import { PACKAGE_VERSION } from '../packageVersion'; + +export const SENTRY_SERVICE_NAME = 'trueforge'; + +async function fetchSentryAuth(input: { + authServerUrl: string; + apiKey: string; + tenantName: string | undefined; + timeoutMs: number; +}): Promise { + const url = `${input.authServerUrl}/api/v1/tenants/sentry-auth-data`; + const urlParamsObj: Record = { + serviceName: SENTRY_SERVICE_NAME, + }; + if (input.tenantName !== undefined && input.tenantName !== '') { + urlParamsObj['tenantName'] = input.tenantName; + } + const params = new URLSearchParams(urlParamsObj); + + try { + const res = await undiciFetch(`${url}?${params.toString()}`, { + headers: { Authorization: `Bearer ${input.apiKey}` }, + signal: AbortSignal.timeout(input.timeoutMs), + }); + if (res.status !== 200) { + return undefined; + } + return await res.json(); + } catch (err) { + if (err instanceof Error && (err.name === 'TimeoutError' || err.name === 'AbortError')) { + throw new Error(`Timed out fetching sentry auth data after ${String(input.timeoutMs)}ms`, { cause: err }); + } + throw err; + } +} + +export async function initTrueFoundrySentry(input: { + logger: Pick; + tags?: Record | undefined; +}): Promise { + const { logger, tags } = input; + if (!isTrueFoundryModeEnabled(configuration)) { + logger.error('Sentry TrueFoundry init requires TrueFoundry mode; skipping'); + return false; + } + const authServerUrl = configuration.TRUEFOUNDRY_AUTH_SERVER_URL; + const apiKey = configuration.TRUEFOUNDRY_API_KEY; + if (authServerUrl === undefined || authServerUrl.trim() === '') { + logger.error('TRUEFOUNDRY_AUTH_SERVER_URL is required when SENTRY_ENABLED in TrueFoundry mode'); + return false; + } + if (apiKey === undefined || apiKey.trim() === '') { + logger.error('TRUEFOUNDRY_API_KEY is required when SENTRY_ENABLED in TrueFoundry mode'); + return false; + } + + try { + const authData = await fetchSentryAuth({ + authServerUrl, + apiKey, + tenantName: configuration.TRUEFOUNDRY_TENANT_NAME, + timeoutMs: configuration.TRUEFOUNDRY_SERVICEFOUNDRY_HTTP_TIMEOUT_MS, + }); + if (authData === undefined || typeof authData !== 'object' || authData === null) { + logger.error('Failed to fetch sentry config. Skipping initialization'); + return false; + } + const sentryInitOptions: NodeOptions = {}; + Object.assign(sentryInitOptions, authData); + sentryInitOptions.includeLocalVariables = false; + sentryInitOptions.defaultIntegrations = false; + sentryInitOptions.integrations = []; + Sentry.init(sentryInitOptions); + Sentry.getGlobalScope().setTag('TRUEFORGE_VERSION', PACKAGE_VERSION); + if (tags) { + for (const [key, value] of Object.entries(tags)) { + Sentry.getGlobalScope().setTag(key, value); + } + } + logger.info('Sentry initialised (TrueFoundry auth server)'); + return true; + } catch (error) { + logger.error('Error in fetching sentry auth data; skipping Sentry initialization', { + error: error instanceof Error ? error.message : error, + }); + return false; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fcc27af86..b3b0a135a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -178,6 +178,9 @@ importers: '@modelcontextprotocol/sdk': specifier: ^1.30.0 version: 1.30.0(supports-color@8.1.1)(zod@4.4.3) + '@sentry/node': + specifier: ^10.74.0 + version: 10.74.0(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.221.0(@opentelemetry/api@1.9.1))(supports-color@8.1.1) '@truefoundry/trueforge-core': specifier: workspace:* version: link:../trueforge-core @@ -2041,6 +2044,10 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@opentelemetry/api-logs@0.220.0': + resolution: {integrity: sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api-logs@0.221.0': resolution: {integrity: sha512-OlanaW1vv7ufTqQ3/fPLI4arGt5ZoM+P8abOMki6uEYnpRazepSWDwDnnw+la7kE26SHVC18//SMccrDvLKOXQ==} engines: {node: '>=8.0.0'} @@ -2139,6 +2146,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation@0.220.0': + resolution: {integrity: sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation@0.221.0': resolution: {integrity: sha512-cCk80Z/iRDf/5gfsKMB4f74LqVA5yKETB/9ojPzVW/6/f70iu89nJvGxsFCxx4XfSohaOofkU19kiYm84AiAlw==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3257,6 +3270,51 @@ packages: cpu: [x64] os: [win32] + '@sentry/conventions@0.16.0': + resolution: {integrity: sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==} + engines: {node: '>=14'} + + '@sentry/core@10.74.0': + resolution: {integrity: sha512-u9rY8vcZfktccwm6LznfCZlqP5C9A+p76r4/pFS1grqpuTO0m21Cl8rosnlESrDGP/Xd9tfr91rWYk0jPH8jeQ==} + engines: {node: '>=18'} + + '@sentry/node-core@10.74.0': + resolution: {integrity: sha512-btgZXcGmkOGgojbxHo/gfGiyqYpzC9E8zVR78Q3MtM6Xnemk9gwJiQlhNmEX/FM+C36WBRPZrdcZcnMaJhbfJw==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 + '@opentelemetry/exporter-trace-otlp-http': '>=0.57.0 <1' + '@opentelemetry/instrumentation': '>=0.57.1 <1' + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@opentelemetry/core': + optional: true + '@opentelemetry/exporter-trace-otlp-http': + optional: true + '@opentelemetry/instrumentation': + optional: true + '@opentelemetry/sdk-trace-base': + optional: true + + '@sentry/node@10.74.0': + resolution: {integrity: sha512-u1wsarTOWHn9CCev81Da5T4IQHZgdcosXRfX2+4DMII/lVJMBYesKixTQuMwXexjVG2+pkf16zTmgjZDa+75jA==} + engines: {node: '>=18'} + + '@sentry/opentelemetry@10.74.0': + resolution: {integrity: sha512-ua5mt0NDBfye+/ACKjAw9Ad2i+y42lOLvhxXZepZXjszblMz80MEsIZflMB7uLZUCTNH7MbZN8tlzCy8KsJKmQ==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + + '@sentry/server-utils@10.74.0': + resolution: {integrity: sha512-AHmPIGE8yVRyywpZRhAkm/H0cHTgsQSPhFZFbaeQLcYHFE/eOP9bAMh8Nj/Apr7WMagFXYHd04tR/63BxtLgKw==} + engines: {node: '>=18'} + '@sinclair/typebox@0.27.12': resolution: {integrity: sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==} @@ -9329,6 +9387,10 @@ snapshots: '@open-draft/until@2.1.0': {} + '@opentelemetry/api-logs@0.220.0': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs@0.221.0': dependencies: '@opentelemetry/api': 1.9.1 @@ -9443,6 +9505,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.220.0 + import-in-the-middle: 3.3.3 + require-in-the-middle: 8.0.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation@0.221.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -10568,6 +10639,54 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.62.2': optional: true + '@sentry/conventions@0.16.0': {} + + '@sentry/core@10.74.0': + dependencies: + '@sentry/conventions': 0.16.0 + + '@sentry/node-core@10.74.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.221.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1))': + dependencies: + '@sentry/conventions': 0.16.0 + '@sentry/core': 10.74.0 + '@sentry/opentelemetry': 10.74.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1)) + import-in-the-middle: 3.3.3 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-http': 0.221.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.220.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) + '@opentelemetry/sdk-trace-base': 2.10.0(@opentelemetry/api@1.9.1) + + '@sentry/node@10.74.0(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.221.0(@opentelemetry/api@1.9.1))(supports-color@8.1.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.220.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) + '@opentelemetry/sdk-trace-base': 2.10.0(@opentelemetry/api@1.9.1) + '@sentry/conventions': 0.16.0 + '@sentry/core': 10.74.0 + '@sentry/node-core': 10.74.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.221.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1)) + '@sentry/opentelemetry': 10.74.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1)) + '@sentry/server-utils': 10.74.0 + import-in-the-middle: 3.3.3 + transitivePeerDependencies: + - '@opentelemetry/core' + - '@opentelemetry/exporter-trace-otlp-http' + - supports-color + + '@sentry/opentelemetry@10.74.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1))': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.10.0(@opentelemetry/api@1.9.1) + '@sentry/conventions': 0.16.0 + '@sentry/core': 10.74.0 + + '@sentry/server-utils@10.74.0': + dependencies: + '@sentry/conventions': 0.16.0 + '@sentry/core': 10.74.0 + '@sinclair/typebox@0.27.12': {} '@sinclair/typebox@0.34.52': {}