@od-oneapp/observability 2026.1.1301

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.
Files changed (107) hide show
  1. package/README.md +523 -0
  2. package/dist/client-next.d.mts +20 -0
  3. package/dist/client-next.d.mts.map +1 -0
  4. package/dist/client-next.mjs +64 -0
  5. package/dist/client-next.mjs.map +1 -0
  6. package/dist/client.d.mts +11 -0
  7. package/dist/client.d.mts.map +1 -0
  8. package/dist/client.mjs +47 -0
  9. package/dist/client.mjs.map +1 -0
  10. package/dist/env.d.mts +15 -0
  11. package/dist/env.d.mts.map +1 -0
  12. package/dist/env.mjs +45 -0
  13. package/dist/env.mjs.map +1 -0
  14. package/dist/factory-DkY353r8.mjs +380 -0
  15. package/dist/factory-DkY353r8.mjs.map +1 -0
  16. package/dist/hooks-useObservability.d.mts +11 -0
  17. package/dist/hooks-useObservability.d.mts.map +1 -0
  18. package/dist/hooks-useObservability.mjs +174 -0
  19. package/dist/hooks-useObservability.mjs.map +1 -0
  20. package/dist/index-CpcdzWrF.d.mts +24 -0
  21. package/dist/index-CpcdzWrF.d.mts.map +1 -0
  22. package/dist/index.d.mts +88 -0
  23. package/dist/index.d.mts.map +1 -0
  24. package/dist/index.mjs +97 -0
  25. package/dist/index.mjs.map +1 -0
  26. package/dist/manager-BxQqOPEg.d.mts +33 -0
  27. package/dist/manager-BxQqOPEg.d.mts.map +1 -0
  28. package/dist/plugin-Bfq-o3nr.d.mts +60 -0
  29. package/dist/plugin-Bfq-o3nr.d.mts.map +1 -0
  30. package/dist/plugin-Bt-ygG1m.d.mts +254 -0
  31. package/dist/plugin-Bt-ygG1m.d.mts.map +1 -0
  32. package/dist/plugin-CLFwRERa.mjs +593 -0
  33. package/dist/plugin-CLFwRERa.mjs.map +1 -0
  34. package/dist/plugin-CP895lBx.mjs +534 -0
  35. package/dist/plugin-CP895lBx.mjs.map +1 -0
  36. package/dist/plugin-CaQxviDs.d.mts +61 -0
  37. package/dist/plugin-CaQxviDs.d.mts.map +1 -0
  38. package/dist/plugin-lPdJirTY.mjs +234 -0
  39. package/dist/plugin-lPdJirTY.mjs.map +1 -0
  40. package/dist/plugins-betterstack-env.d.mts +29 -0
  41. package/dist/plugins-betterstack-env.d.mts.map +1 -0
  42. package/dist/plugins-betterstack-env.mjs +75 -0
  43. package/dist/plugins-betterstack-env.mjs.map +1 -0
  44. package/dist/plugins-betterstack.d.mts +4 -0
  45. package/dist/plugins-betterstack.mjs +4 -0
  46. package/dist/plugins-console.d.mts +37 -0
  47. package/dist/plugins-console.d.mts.map +1 -0
  48. package/dist/plugins-console.mjs +196 -0
  49. package/dist/plugins-console.mjs.map +1 -0
  50. package/dist/plugins-sentry-env.d.mts +37 -0
  51. package/dist/plugins-sentry-env.d.mts.map +1 -0
  52. package/dist/plugins-sentry-env.mjs +79 -0
  53. package/dist/plugins-sentry-env.mjs.map +1 -0
  54. package/dist/plugins-sentry-microfrontend-env.d.mts +49 -0
  55. package/dist/plugins-sentry-microfrontend-env.d.mts.map +1 -0
  56. package/dist/plugins-sentry-microfrontend-env.mjs +80 -0
  57. package/dist/plugins-sentry-microfrontend-env.mjs.map +1 -0
  58. package/dist/plugins-sentry-microfrontend.d.mts +2 -0
  59. package/dist/plugins-sentry-microfrontend.mjs +3 -0
  60. package/dist/plugins-sentry.d.mts +5 -0
  61. package/dist/plugins-sentry.mjs +6 -0
  62. package/dist/server-edge.d.mts +15 -0
  63. package/dist/server-edge.d.mts.map +1 -0
  64. package/dist/server-edge.mjs +53 -0
  65. package/dist/server-edge.mjs.map +1 -0
  66. package/dist/server-next.d.mts +17 -0
  67. package/dist/server-next.d.mts.map +1 -0
  68. package/dist/server-next.mjs +64 -0
  69. package/dist/server-next.mjs.map +1 -0
  70. package/dist/server.d.mts +11 -0
  71. package/dist/server.d.mts.map +1 -0
  72. package/dist/server.mjs +48 -0
  73. package/dist/server.mjs.map +1 -0
  74. package/dist/utils-CuGrTcD6.d.mts +77 -0
  75. package/dist/utils-CuGrTcD6.d.mts.map +1 -0
  76. package/env.ts +67 -0
  77. package/package.json +147 -0
  78. package/src/client-next.ts +131 -0
  79. package/src/client.ts +70 -0
  80. package/src/core/index.ts +15 -0
  81. package/src/core/manager.ts +361 -0
  82. package/src/core/plugin.ts +61 -0
  83. package/src/core/types.ts +151 -0
  84. package/src/factory/builder.ts +132 -0
  85. package/src/factory/index.ts +67 -0
  86. package/src/factory/presets.ts +78 -0
  87. package/src/hooks/useObservability.ts +206 -0
  88. package/src/plugins/betterstack/env.ts +101 -0
  89. package/src/plugins/betterstack/index.ts +15 -0
  90. package/src/plugins/betterstack/plugin.ts +373 -0
  91. package/src/plugins/console/index.ts +323 -0
  92. package/src/plugins/sentry/__tests__/plugin-tracing.test.ts +511 -0
  93. package/src/plugins/sentry/env.ts +93 -0
  94. package/src/plugins/sentry/index.ts +28 -0
  95. package/src/plugins/sentry/plugin.ts +953 -0
  96. package/src/plugins/sentry/types.ts +252 -0
  97. package/src/plugins/sentry-microfrontend/env.ts +105 -0
  98. package/src/plugins/sentry-microfrontend/index.ts +12 -0
  99. package/src/plugins/sentry-microfrontend/multiplexed-transport.ts +221 -0
  100. package/src/plugins/sentry-microfrontend/plugin.ts +500 -0
  101. package/src/plugins/sentry-microfrontend/sentry-types.ts +140 -0
  102. package/src/plugins/sentry-microfrontend/types.ts +130 -0
  103. package/src/plugins/sentry-microfrontend/utils.ts +326 -0
  104. package/src/server-edge.ts +113 -0
  105. package/src/server-next.ts +114 -0
  106. package/src/server.ts +71 -0
  107. package/src/shared.ts +148 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-CLFwRERa.mjs","names":[],"sources":["../src/plugins/sentry-microfrontend/multiplexed-transport.ts","../src/plugins/sentry-microfrontend/sentry-types.ts","../src/plugins/sentry-microfrontend/utils.ts","../src/plugins/sentry-microfrontend/plugin.ts"],"sourcesContent":["/**\n * @fileoverview Multiplexed transport utilities for Sentry Micro Frontend Plugin\n * Multiplexed transport utilities for Sentry Micro Frontend Plugin\n */\n\nimport { logWarn } from '@repo/shared/logs';\n\nimport type { BackstageAppConfig } from './types';\n\n/**\n * Create a multiplexed transport that routes events to different Sentry projects.\n *\n * Creates a Sentry transport that routes events to different DSNs based on the\n * backstageApp information in the event. Supports edge runtime environments.\n *\n * @param backstage - Array of backstage app configurations with DSNs\n * @param fallbackDsn - Optional fallback DSN if no backstageApp match is found\n * @param sentryClient - Optional Sentry client instance (defaults to global Sentry)\n * @returns Multiplexed transport instance, or undefined if Sentry is not available\n */\n\nexport function createMultiplexedTransport(\n backstage: BackstageAppConfig[],\n fallbackDsn?: string,\n\n sentryClient?: any,\n): any {\n // Import Sentry dynamically or use provided client (edge runtime compatible)\n const Sentry =\n sentryClient !== undefined\n ? sentryClient\n : typeof globalThis !== 'undefined'\n ? (globalThis as any).Sentry\n : null;\n\n if (!Sentry?.makeMultiplexedTransport || !Sentry.makeFetchTransport) {\n logWarn('Sentry multiplexed transport not available');\n return undefined;\n }\n\n return Sentry.makeMultiplexedTransport(Sentry.makeFetchTransport, (args: any) => {\n const event = args.getEvent();\n\n if (!event) {\n return [];\n }\n\n // Extract backstageApp information from various sources\n const backstageApp =\n event.tags?.backstageApp ??\n event.extra?.backstageApp ??\n event.contexts?.microFrontend?.backstageApp;\n\n if (backstageApp) {\n // Find backstageApp configuration\n const backstageAppConfig = backstage.find(z => z.name === backstageApp);\n\n if (backstageAppConfig?.dsn) {\n // Route to backstageApp-specific DSN\n\n const envelope: any[] = [\n {\n dsn: backstageAppConfig.dsn,\n release: backstageAppConfig.release ?? event.release,\n },\n ];\n\n // Add backstageApp-specific tags if configured\n if (backstageAppConfig.tags && event.tags) {\n Object.assign(event.tags, backstageAppConfig.tags);\n }\n\n return envelope;\n }\n }\n\n // Use fallback DSN if provided\n if (fallbackDsn) {\n return [{ dsn: fallbackDsn }];\n }\n\n // Let the default DSN handle it\n return [];\n });\n}\n\n/**\n * Create a transport matcher function for more complex routing logic.\n *\n * Creates a matcher function that can be used with Sentry's multiplexed transport\n * to route events based on custom logic, including stack frame analysis.\n *\n * @param backstage - Array of backstage app configurations\n * @param customMatcher - Optional custom matcher function to extract backstageApp from event\n * @returns Matcher function that returns an array of transport configurations\n */\nexport function createTransportMatcher(\n backstage: BackstageAppConfig[],\n\n customMatcher?: (event: any) => string | undefined,\n): (args: any) => any[] {\n return (args: any) => {\n const event = args.getEvent();\n\n if (!event) {\n return [];\n }\n\n // Use custom matcher if provided\n let backstageApp: string | undefined;\n if (customMatcher) {\n backstageApp = customMatcher(event);\n }\n\n // Fall back to default backstageApp detection\n backstageApp ??=\n event.tags?.backstageApp ??\n event.extra?.backstageApp ??\n event.contexts?.microFrontend?.backstageApp;\n\n // Try to detect backstageApp from stack frames\n if (!backstageApp && event.exception?.values?.[0]?.stacktrace?.frames) {\n const { frames } = event.exception.values[0].stacktrace;\n for (const frame of frames) {\n if (frame.filename) {\n // Check if filename contains backstageApp hints\n if (frame.filename.includes('/cms/')) {\n backstageApp = 'cms';\n break;\n } else if (frame.filename.includes('/workflows/')) {\n backstageApp = 'workflows';\n break;\n } else if (frame.filename.includes('/authmgmt/')) {\n backstageApp = 'authmgmt';\n break;\n }\n }\n }\n }\n\n if (backstageApp) {\n const backstageAppConfig = backstage.find(z => z.name === backstageApp);\n if (backstageAppConfig?.dsn) {\n return [\n {\n dsn: backstageAppConfig.dsn,\n release: backstageAppConfig.release,\n },\n ];\n }\n }\n\n return [];\n };\n}\n\n/**\n * Enhance an event with Backstage app information before sending.\n *\n * Adds backstageApp tags, context, and extra data to a Sentry event.\n * This ensures events are properly tagged for micro frontend routing.\n *\n * @param event - Sentry event to enhance\n * @param backstageApp - Name of the backstage app\n * @param additionalContext - Optional additional context to add\n * @returns Enhanced event with backstageApp information\n */\n\nexport function enhanceEventWithBackstageApp(\n event: any,\n backstageApp: string,\n\n additionalContext?: Record<string, any>,\n): any {\n // Add backstageApp tag\n event.tags ??= {};\n event.tags.backstageApp = backstageApp;\n event.tags.microFrontend = true;\n\n // Add backstageApp context\n event.contexts ??= {};\n event.contexts.microFrontend = {\n backstageApp,\n timestamp: new Date().toISOString(),\n ...additionalContext,\n };\n\n // Add backstageApp to extra for backward compatibility\n event.extra ??= {};\n event.extra.backstageApp = backstageApp;\n\n return event;\n}\n\n/**\n * Create a beforeSend hook that adds backstageApp information.\n *\n * Creates a Sentry beforeSend hook that enhances events with backstageApp information\n * before they are sent. Can wrap an existing beforeSend hook.\n *\n * @param backstageApp - Name of the backstage app\n * @param originalBeforeSend - Optional original beforeSend hook to wrap\n * @returns beforeSend hook function\n */\nexport function createBackstageBeforeSend(\n backstageApp: string,\n\n originalBeforeSend?: (event: any, hint: any) => any,\n): (event: any, hint: any) => any {\n return (event: any, hint: any) => {\n // Enhance with backstageApp information\n enhanceEventWithBackstageApp(event, backstageApp);\n\n // Call original beforeSend if provided\n if (originalBeforeSend) {\n return originalBeforeSend(event, hint);\n }\n\n return event;\n };\n}\n","/**\n * @fileoverview Type definitions for Sentry SDK\n * Type definitions for Sentry SDK\n * These are minimal types to avoid using 'any' throughout the plugin\n */\n\nimport type { LogLevel } from '../../core/types';\n\n/**\n * Sentry Scope interface\n */\nexport interface SentryScope {\n setTag(key: string, value: string | number | boolean | null): void;\n setContext(key: string, context: Record<string, any> | null): void;\n setUser(user: Record<string, any> | null): void;\n setLevel(level: SentrySeverityLevel): void;\n setFingerprint(fingerprint: string[]): void;\n clear(): void;\n}\n\n/**\n * Sentry Hub interface\n */\nexport interface SentryHub {\n getScope(): SentryScope;\n pushScope(): SentryScope;\n popScope(): boolean;\n withScope(callback: (scope: SentryScope) => void): void;\n}\n\n/**\n * Sentry Client interface\n */\nexport interface SentryClient {\n captureException(exception: any, hint?: any): string;\n captureMessage(message: string, level?: SentrySeverityLevel): string;\n captureEvent(event: any): string;\n flush(timeout?: number): Promise<boolean>;\n close(timeout?: number): Promise<boolean>;\n getOptions(): SentryOptions;\n getCurrentHub?(): SentryHub;\n}\n\n/**\n * Sentry SDK interface\n */\nexport interface SentrySDK {\n init(options: SentryOptions): void;\n captureException(exception: any, captureContext?: any): string;\n captureMessage(message: string, captureContext?: any): string;\n captureEvent(event: any, hint?: any): string;\n withScope(callback: (scope: SentryScope) => void): void;\n addBreadcrumb(breadcrumb: any): void;\n setUser(user: any): void;\n setTag(key: string, value: string): void;\n setContext(key: string, context: any): void;\n setExtra(key: string, extra: any): void;\n getCurrentHub(): SentryHub;\n getClient<T extends SentryClient>(): T | undefined;\n flush(timeout?: number): Promise<boolean>;\n close(timeout?: number): Promise<boolean>;\n lastEventId(): string | undefined;\n\n // Integration methods\n addEventProcessor(processor: (event: any) => any | null): void;\n\n // Browser specific\n Scope?: new () => SentryScope;\n\n // Transport creation\n makeMultiplexedTransport?(\n createTransport: (options: any) => any,\n matcher: (options: any) => any[],\n ): any;\n makeFetchTransport?(options: any): any;\n\n // Integrations\n browserTracingIntegration?(): any;\n browserProfilingIntegration?(): any;\n replayIntegration?(options?: any): any;\n captureConsoleIntegration?(options?: any): any;\n feedbackIntegration?(options?: any): any;\n vercelAIIntegration?(): any;\n}\n\n/**\n * Sentry Options interface\n */\nexport interface SentryOptions {\n dsn?: string;\n release?: string;\n environment?: string;\n debug?: boolean;\n sampleRate?: number;\n tracesSampleRate?: number;\n profilesSampleRate?: number;\n replaysSessionSampleRate?: number;\n replaysOnErrorSampleRate?: number;\n transport?: any;\n integrations?: any[];\n beforeSend?: (event: any, hint: any) => any | null;\n beforeSendTransaction?: (event: any, hint: any) => any | null;\n beforeBreadcrumb?: (breadcrumb: any, hint?: any) => any | null;\n tracePropagationTargets?: (string | RegExp)[];\n initialScope?: any;\n maxBreadcrumbs?: number;\n attachStacktrace?: boolean;\n autoSessionTracking?: boolean;\n sendDefaultPii?: boolean;\n\n _experiments?: Record<string, any>;\n}\n\n/**\n * Sentry Severity Level\n */\nexport type SentrySeverityLevel = 'fatal' | 'error' | 'warning' | 'log' | 'info' | 'debug';\n\n/**\n * Map LogLevel to Sentry Severity.\n *\n * Converts the observability package's LogLevel to Sentry's SeverityLevel format.\n *\n * @param level - Log level from observability package\n * @returns Corresponding Sentry severity level\n */\nexport function mapLogLevelToSentrySeverity(level: LogLevel): SentrySeverityLevel {\n switch (level) {\n case 'error':\n return 'error';\n case 'warning':\n return 'warning';\n case 'info':\n return 'info';\n case 'debug':\n return 'debug';\n default:\n return 'info';\n }\n}\n","/**\n * @fileoverview Utility functions for Sentry Micro Frontend Plugin\n * Utility functions for Sentry Micro Frontend Plugin\n */\n\nimport { logError } from '@repo/shared/logs';\n\nimport type { BackstageAppConfig } from './types';\n\n/**\n * Detect the current micro frontend backstageApp based on URL path.\n *\n * Analyzes the current URL pathname to determine which micro frontend application\n * is active. Supports custom path patterns and falls back to default patterns.\n *\n * @param customPatterns - Optional array of custom backstage app configurations with path patterns\n * @returns Detected backstage app name, or undefined if not detected\n *\n * @example\n * ```typescript\n * const app = detectCurrentBackstageApp([\n * { name: 'admin', pathPatterns: ['/admin', '/manage'] },\n * { name: 'dashboard', pathPatterns: [/^\\/dashboard/] }\n * ]);\n * // Returns: 'admin' if pathname starts with '/admin' or '/manage'\n * ```\n */\nexport function detectCurrentBackstageApp(\n customPatterns?: BackstageAppConfig[],\n): string | undefined {\n // Edge runtime compatible check\n if (\n typeof globalThis === 'undefined' ||\n !(globalThis as { location?: { pathname?: string; href?: string } }).location\n ) {\n // Server-side or edge: check Next.js basePath or environment variables\n // Note: process.env is not available in edge runtime\n if (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_BASE_PATH) {\n return process.env.NEXT_PUBLIC_BASE_PATH.replace('/', '');\n }\n return undefined;\n }\n\n const path = (globalThis as { location?: { pathname?: string } }).location?.pathname;\n if (!path) {\n return undefined;\n }\n\n // Check custom patterns first\n if (customPatterns) {\n for (const config of customPatterns) {\n if (config.pathPatterns) {\n for (const pattern of config.pathPatterns) {\n if (typeof pattern === 'string' && path.startsWith(pattern)) {\n return config.name;\n } else if (pattern instanceof RegExp && pattern.test(path)) {\n return config.name;\n }\n }\n }\n }\n }\n\n // Default patterns for common microfrontend zones\n if (path.startsWith('/admin')) return 'admin';\n if (path.startsWith('/dashboard')) return 'dashboard';\n if (path.startsWith('/settings')) return 'settings';\n\n // Check for backstageApp in global object (set by host)\n\n if ((globalThis as any).__SENTRY_MICRO_FRONTEND_APP__) {\n return (globalThis as any).__SENTRY_MICRO_FRONTEND_APP__;\n }\n\n if ((globalThis as any).__SENTRY_MICRO_FRONTEND_ZONE__) {\n // Backwards compatibility with legacy zone naming\n\n return (globalThis as any).__SENTRY_MICRO_FRONTEND_ZONE__;\n }\n\n return 'main';\n}\n\n/**\n * Check if running in a host environment.\n *\n * Determines whether the current application is acting as a host for micro frontends\n * or as a child micro frontend. Checks for explicit host markers and Sentry initialization state.\n *\n * @returns True if running as host, false if child or standalone\n */\nexport function isHostEnvironment(): boolean {\n if (typeof globalThis === 'undefined') {\n return false;\n }\n\n // Check if explicitly marked as host\n\n if ((globalThis as any).__SENTRY_MICRO_FRONTEND_HOST__) {\n return true;\n }\n\n // Check if Sentry is already initialized (likely means we're a child)\n if (\n (globalThis as any).Sentry &&\n typeof (globalThis as any).Sentry.getCurrentHub === 'function'\n ) {\n return false;\n }\n\n // Default to false (safer to assume child mode)\n return false;\n}\n\n/**\n * Create a Sentry scope with backstageApp-specific context.\n *\n * Creates a new Sentry scope configured with micro frontend tags and context.\n * Includes backstageApp tag, microFrontend context, and any additional tags provided.\n *\n * @param backstageApp - Name of the backstage app\n * @param additionalTags - Optional additional tags to set on the scope\n * @returns Sentry scope instance, or null if Sentry is not available\n */\n\nexport function createBackstageScope(\n backstageApp: string,\n additionalTags?: Record<string, string>,\n): unknown {\n if (typeof globalThis === 'undefined' || !(globalThis as any).Sentry) {\n return null;\n }\n\n const { Sentry } = globalThis as any;\n\n // Check if Scope constructor is available\n if (!Sentry.Scope || typeof Sentry.Scope !== 'function') {\n return null;\n }\n\n try {\n const scope = new Sentry.Scope();\n\n // Set backstageApp tag\n scope.setTag('backstageApp', backstageApp);\n scope.setTag('microFrontend', true);\n\n // Set backstageApp context\n scope.setContext('microFrontend', {\n backstageApp,\n\n isHost: (globalThis as any).__SENTRY_MICRO_FRONTEND_HOST__ ?? false,\n url: (globalThis as { location?: { href?: string } }).location?.href ?? undefined,\n });\n\n // Add additional tags if provided\n if (additionalTags) {\n Object.entries(additionalTags).forEach(([key, value]) => {\n scope.setTag(key, value);\n });\n }\n\n return scope;\n } catch (error) {\n logError('[SentryMicroFrontendPlugin] Failed to create Backstage scope', { error });\n return null;\n }\n}\n\n/**\n * Check if Sentry is already initialized by a parent application.\n *\n * Verifies whether a parent Sentry instance exists in the global scope.\n * Used to determine if the current micro frontend should use the parent's Sentry\n * or initialize its own instance.\n *\n * @returns True if parent Sentry is available, false otherwise\n */\nexport function hasParentSentry(): boolean {\n if (typeof globalThis === 'undefined') {\n return false;\n }\n\n // Check for Sentry global\n\n const { Sentry } = globalThis as any;\n if (!Sentry) {\n return false;\n }\n\n // Verify it's a real Sentry instance with expected methods\n const requiredMethods = ['captureException', 'captureMessage', 'withScope'];\n return requiredMethods.every(method => typeof Sentry[method] === 'function');\n}\n\n/**\n * Get the parent Sentry instance if available.\n *\n * Returns the global Sentry instance if it exists and has been initialized\n * by a parent application. Returns null if no parent Sentry is available.\n *\n * @returns Parent Sentry SDK instance, or null if not available\n */\n\nexport function getParentSentry(): any {\n if (hasParentSentry()) {\n return (globalThis as any).Sentry;\n }\n return null;\n}\n\n/**\n * Mark the current environment as a host.\n *\n * Sets global flags indicating that this application is acting as a host\n * for micro frontends. This affects how Sentry events are routed and tagged.\n *\n * @param backstageApp - Optional backstage app name to set\n */\nexport function markAsHost(backstageApp?: string): void {\n if (typeof globalThis !== 'undefined') {\n (globalThis as any).__SENTRY_MICRO_FRONTEND_HOST__ = true;\n if (backstageApp) {\n (globalThis as any).__SENTRY_MICRO_FRONTEND_APP__ = backstageApp;\n\n (globalThis as any).__SENTRY_MICRO_FRONTEND_ZONE__ = backstageApp;\n }\n }\n}\n\n/**\n * Get backstageApp-specific configuration.\n *\n * Finds the configuration for a specific backstage app from the provided array.\n *\n * @param backstageApp - Name of the backstage app to find\n * @param backstage - Array of backstage app configurations\n * @returns Configuration for the specified app, or undefined if not found\n */\nexport function getBackstageAppConfig(\n backstageApp: string,\n backstage: BackstageAppConfig[],\n): BackstageAppConfig | undefined {\n return backstage.find(app => app.name === backstageApp);\n}\n\n/**\n * Ensure only one Sentry initialization happens (thread-safe).\n *\n * Prevents multiple Sentry initializations using global state tracking.\n * Uses timestamps and unique IDs to detect concurrent initialization attempts.\n * Throws an error if Sentry is already initialized or if concurrent initialization is detected.\n *\n * @throws Error if Sentry is already initialized or concurrent initialization detected\n */\nexport function ensureSingleInit(): void {\n // Use a more robust initialization check with timestamps\n const initKey = '__SENTRY_INIT_STATE__';\n const now = Date.now();\n\n if (typeof globalThis !== 'undefined') {\n const globalState = (globalThis as any)[initKey];\n\n // Check if already initialized or currently initializing\n if (globalState) {\n const { status, timestamp, id } = globalState;\n\n // If initialized, throw error\n if (status === 'initialized') {\n throw new Error(\n 'Sentry has already been initialized! Use hasParentSentry() to check before initializing.',\n );\n }\n\n // If initializing and it's been less than 5 seconds, assume another process is initializing\n if (status === 'initializing' && now - timestamp < 5000) {\n throw new Error(\n `Sentry initialization already in progress (started ${now - timestamp}ms ago by ${id})`,\n );\n }\n }\n\n // Mark as initializing with timestamp and unique ID\n const initId = `${now}-${Math.random().toString(36).substr(2, 9)}`;\n\n (globalThis as any)[initKey] = {\n status: 'initializing',\n timestamp: now,\n id: initId,\n };\n\n // Small delay to allow for race condition detection\n // In a real thread-safe implementation, this would use proper locking\n\n const checkState = (globalThis as any)[initKey];\n if (checkState.id !== initId) {\n throw new Error('Concurrent Sentry initialization detected');\n }\n\n // Mark as initialized\n\n (globalThis as any)[initKey] = {\n status: 'initialized',\n timestamp: now,\n id: initId,\n };\n\n // Also set legacy flag for backward compatibility\n\n (globalThis as any).__SENTRY_INITIALIZED__ = true;\n }\n}\n\n/**\n * Reset initialization flag (mainly for testing).\n *\n * Clears the global Sentry initialization state. Useful for testing scenarios\n * where you need to test initialization multiple times.\n */\nexport function resetInitFlag(): void {\n if (typeof globalThis !== 'undefined') {\n delete (globalThis as any).__SENTRY_INITIALIZED__;\n\n delete (globalThis as any).__SENTRY_INIT_STATE__;\n }\n}\n","/**\n * @fileoverview Sentry Micro Frontend Plugin\n * Sentry Micro Frontend Plugin\n *\n * Extends the base Sentry plugin with micro frontend-specific functionality\n */\n\nimport { SentryPlugin } from '@integrations/sentry/observability-plugin/plugin';\nimport { logDebug, logError, logWarn } from '@repo/shared/logs';\n\nimport {\n createBackstageBeforeSend,\n createMultiplexedTransport,\n enhanceEventWithBackstageApp,\n} from './multiplexed-transport';\nimport { mapLogLevelToSentrySeverity } from './sentry-types';\nimport {\n createBackstageScope,\n detectCurrentBackstageApp,\n ensureSingleInit,\n getParentSentry,\n hasParentSentry,\n markAsHost,\n} from './utils';\n\nimport type { SentryScope, SentrySDK } from './sentry-types';\nimport type { MicroFrontendMode, SentryMicroFrontendConfig } from './types';\nimport type { LogLevel, ObservabilityContext } from '../../core/types';\n\n/**\n * Sentry plugin optimized for micro frontend architectures\n */\n/**\n * Sentry plugin optimized for micro frontend architectures.\n *\n * Extends SentryPlugin with micro frontend-specific features:\n * - Host/child/standalone mode detection\n * - Multiplexed transport for routing events to different Sentry projects\n * - Backstage app context management\n * - Parent Sentry instance detection and reuse\n */\nexport class SentryMicroFrontendPlugin extends SentryPlugin {\n private mode: MicroFrontendMode;\n private backstageApp: string | undefined;\n private parentSentry: SentrySDK | undefined;\n private backstageScope: SentryScope | undefined;\n private microFrontendConfig: SentryMicroFrontendConfig;\n private eventProcessorCleanup?: (() => void) | undefined;\n\n /**\n * Create a new SentryMicroFrontendPlugin instance.\n *\n * @param config - Sentry micro frontend plugin configuration\n */\n constructor(config: SentryMicroFrontendConfig = {}) {\n // Determine operation mode\n const mode = SentryMicroFrontendPlugin.determineMode(config);\n\n // Prepare base config based on mode\n const baseConfig = SentryMicroFrontendPlugin.prepareConfig(config, mode);\n\n // Initialize parent class\n super(baseConfig);\n\n this.mode = mode;\n this.microFrontendConfig = config;\n this.backstageApp = config.backstageApp ?? detectCurrentBackstageApp(config.backstageApps);\n\n // Handle parent Sentry if in child mode\n if (this.mode === 'child') {\n const parent = getParentSentry();\n if (parent) {\n this.parentSentry = parent as SentrySDK;\n this.enabled = true; // Re-enable since we'll use parent\n\n // Create backstageApp-specific scope\n if (this.backstageApp && this.parentSentry.Scope) {\n this.backstageScope = createBackstageScope(\n this.backstageApp,\n config.globalTags,\n ) as SentryScope;\n }\n }\n }\n\n // Mark as host if applicable\n if (this.mode === 'host') {\n markAsHost(this.backstageApp);\n }\n }\n\n /**\n * Determine the operation mode based on configuration and environment.\n *\n * @param config - Plugin configuration\n * @returns Operation mode ('host', 'child', or 'standalone')\n */\n private static determineMode(config: SentryMicroFrontendConfig): MicroFrontendMode {\n // Explicit host mode\n if (config.isHost) {\n return 'host';\n }\n\n // Check for parent Sentry\n if (config.detectParent !== false && hasParentSentry()) {\n return 'child';\n }\n\n // Default to standalone\n return 'standalone';\n }\n\n /**\n * Prepare configuration based on operation mode.\n *\n * Adjusts configuration settings based on whether running as host, child, or standalone.\n *\n * @param config - Original plugin configuration\n * @param mode - Determined operation mode\n * @returns Prepared configuration object\n */\n private static prepareConfig(\n config: SentryMicroFrontendConfig,\n mode: MicroFrontendMode,\n ): SentryMicroFrontendConfig {\n const preparedConfig = { ...config };\n\n if (mode === 'child') {\n // Disable initialization in child mode\n preparedConfig.enabled = false;\n } else if (\n mode === 'host' &&\n config.backstageApps &&\n config.useMultiplexedTransport !== false\n ) {\n // Host mode with multiplexed transport is handled in initialize()\n // Don't set transport here as it needs Sentry to be loaded first\n }\n\n return preparedConfig;\n }\n\n /**\n * Initialize the plugin\n */\n async initialize(config?: SentryMicroFrontendConfig): Promise<void> {\n const mergedConfig = { ...this.microFrontendConfig, ...config };\n\n if (this.mode === 'child') {\n // In child mode, we don't initialize Sentry but set up our client reference\n if (this.parentSentry) {\n this.client = this.parentSentry;\n this.initialized = true;\n\n // Configure parent Sentry to include backstageApp information\n if (this.backstageApp && mergedConfig.addBackstageContext !== false) {\n this.configureParentSentry();\n }\n }\n return;\n }\n\n // For host and standalone modes\n if (mergedConfig.preventDuplicateInit !== false) {\n try {\n ensureSingleInit();\n } catch (error) {\n logError('Sentry initialization prevented', { error });\n this.enabled = false;\n return;\n }\n }\n\n // Initialize parent class\n await super.initialize(mergedConfig);\n\n // Additional setup for host mode\n if (this.mode === 'host' && this.client && mergedConfig.backstageApps) {\n this.setupHostMode(mergedConfig);\n }\n }\n\n /**\n * Configure parent Sentry instance for child mode\n */\n private configureParentSentry(): void {\n if (!this.parentSentry || !this.backstageApp) {\n logWarn(\n '[SentryMicroFrontendPlugin] Cannot configure parent Sentry: missing parentSentry or backstageApp',\n );\n return;\n }\n\n try {\n // Create event processor\n\n const processor = (event: any) => {\n try {\n if (this.shouldProcessEvent(event)) {\n enhanceEventWithBackstageApp(event, this.backstageApp ?? '', {\n mode: this.mode,\n plugin: 'SentryMicroFrontendPlugin',\n });\n }\n } catch (error) {\n logError('[SentryMicroFrontendPlugin] Error processing event', { error, event });\n }\n return event;\n };\n\n // Add global event processor to include backstageApp information\n if (typeof this.parentSentry.addEventProcessor === 'function') {\n this.parentSentry.addEventProcessor(processor);\n\n // Store cleanup function\n this.eventProcessorCleanup = () => {\n // Note: Sentry SDK doesn't provide a way to remove event processors\n // We'll mark events as processed to avoid double processing\n };\n } else {\n logWarn('[SentryMicroFrontendPlugin] Parent Sentry does not support addEventProcessor');\n }\n } catch (error) {\n logError('[SentryMicroFrontendPlugin] Failed to configure parent Sentry', { error });\n }\n }\n\n /**\n * Set up host mode with multiplexed transport\n */\n private setupHostMode(config: SentryMicroFrontendConfig): void {\n if (!config.backstageApps || !this.client) {\n logWarn(\n '[SentryMicroFrontendPlugin] Cannot setup host mode: missing backstageApps or client',\n );\n return;\n }\n\n try {\n // Create multiplexed transport\n const transport = createMultiplexedTransport(\n config.backstageApps,\n config.fallbackDsn ?? config.dsn,\n this.client,\n );\n\n if (transport && typeof (this.client as any).init === 'function') {\n // Re-initialize with multiplexed transport\n const currentOptions =\n typeof (this.client as any).getOptions === 'function'\n ? (this.client as any).getOptions()\n : {};\n\n (this.client as any).init({\n ...currentOptions,\n transport,\n beforeSend: createBackstageBeforeSend(this.backstageApp ?? 'main', config.beforeSend),\n });\n } else {\n logWarn(\n '[SentryMicroFrontendPlugin] Client does not support init method or transport creation failed',\n );\n }\n } catch (error) {\n logError('[SentryMicroFrontendPlugin] Failed to setup host mode', { error });\n }\n }\n\n /**\n * Check if we should process this event\n */\n\n private shouldProcessEvent(event: any): boolean {\n // Don't double-process events\n if (event.tags?.microFrontendProcessed) {\n return false;\n }\n\n // Check if event is from our backstageApp\n if (\n this.backstageApp &&\n event.tags?.backstageApp &&\n event.tags.backstageApp !== this.backstageApp\n ) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Capture an exception with backstageApp context\n */\n captureException(error: Error | unknown, context?: ObservabilityContext): void {\n if (!this.enabled) return;\n\n try {\n if (this.mode === 'child' && this.parentSentry && this.backstageScope) {\n // Use parent Sentry with backstageApp scope\n if (typeof this.parentSentry.withScope === 'function') {\n this.parentSentry.withScope((scope: any) => {\n try {\n // Copy backstageApp scope properties\n if (this.backstageScope && typeof scope.setTag === 'function') {\n scope.setTag('backstageApp', this.backstageApp ?? 'unknown');\n scope.setTag('microFrontend', true);\n scope.setContext('microFrontend', {\n backstageApp: this.backstageApp,\n mode: this.mode,\n });\n }\n\n // Add additional context\n if (context && typeof scope.setContext === 'function') {\n scope.setContext('additional', context);\n }\n\n if (this.parentSentry && typeof this.parentSentry.captureException === 'function') {\n this.parentSentry.captureException(error);\n }\n } catch (scopeError) {\n logError('[SentryMicroFrontendPlugin] Error in scope callback', {\n error: scopeError,\n });\n }\n });\n } else {\n logWarn('[SentryMicroFrontendPlugin] Parent Sentry does not support withScope');\n }\n } else {\n // Use standard capture with backstageApp enhancement\n const enhancedContext = {\n ...context,\n microFrontend: {\n backstageApp: this.backstageApp,\n mode: this.mode,\n },\n };\n super.captureException(error, enhancedContext);\n }\n } catch (captureError) {\n logError('[SentryMicroFrontendPlugin] Failed to capture exception', { error: captureError });\n }\n }\n\n /**\n * Capture a message with backstageApp context\n */\n captureMessage(message: string, level: LogLevel = 'info', context?: ObservabilityContext): void {\n if (!this.enabled) return;\n\n try {\n const sentryLevel = mapLogLevelToSentrySeverity(level);\n\n if (this.mode === 'child' && this.parentSentry && this.backstageScope) {\n // Use parent Sentry with backstageApp scope\n if (typeof this.parentSentry.withScope === 'function') {\n this.parentSentry.withScope((scope: SentryScope) => {\n try {\n // Copy backstageApp scope properties\n if (this.backstageScope && typeof scope.setTag === 'function') {\n scope.setTag('backstageApp', this.backstageApp ?? 'unknown');\n scope.setTag('microFrontend', true);\n if (typeof scope.setContext === 'function') {\n scope.setContext('microFrontend', {\n backstageApp: this.backstageApp,\n mode: this.mode,\n });\n }\n }\n\n // Add additional context\n if (context && typeof scope.setContext === 'function') {\n scope.setContext('additional', context);\n }\n\n if (this.parentSentry && typeof this.parentSentry.captureMessage === 'function') {\n this.parentSentry.captureMessage(message, sentryLevel);\n }\n } catch (scopeError) {\n logError('[SentryMicroFrontendPlugin] Error in scope callback', {\n error: scopeError,\n });\n }\n });\n } else {\n logWarn('[SentryMicroFrontendPlugin] Parent Sentry does not support withScope');\n }\n } else {\n // Use standard capture\n super.captureMessage(message, level, context);\n }\n } catch (captureError) {\n logError('[SentryMicroFrontendPlugin] Failed to capture message', { error: captureError });\n }\n }\n\n /**\n * Get the current operation mode\n */\n getMode(): MicroFrontendMode {\n return this.mode;\n }\n\n /**\n * Get the current Backstage app identifier\n */\n getBackstageApp(): string | undefined {\n return this.backstageApp;\n }\n\n /**\n * @deprecated Use getBackstageApp instead.\n */\n getZone(): string | undefined {\n return this.getBackstageApp();\n }\n\n /**\n * Get debug information about the plugin state\n */\n\n getDebugInfo(): Record<string, any> {\n return {\n mode: this.mode,\n backstageApp: this.backstageApp,\n enabled: this.enabled,\n initialized: this.initialized,\n hasParentSentry: this.mode === 'child' && Boolean(this.parentSentry),\n clientType: this.client ? this.client.constructor.name : 'none',\n };\n }\n\n /**\n * Clean up the plugin and release resources\n */\n async cleanup(): Promise<void> {\n try {\n // Clean up event processor if it exists\n if (this.eventProcessorCleanup) {\n this.eventProcessorCleanup();\n this.eventProcessorCleanup = undefined;\n }\n\n // Clear backstageApp scope\n\n if (this.backstageScope && typeof (this.backstageScope as any).clear === 'function') {\n (this.backstageScope as any).clear();\n }\n this.backstageScope = undefined;\n\n // Clear parent Sentry reference\n this.parentSentry = undefined;\n\n // Call parent cleanup\n await super.cleanup();\n\n // Mark as not initialized\n this.initialized = false;\n this.enabled = false;\n\n logDebug('[SentryMicroFrontendPlugin] Cleanup completed');\n } catch (error) {\n logError('[SentryMicroFrontendPlugin] Error during cleanup', { error });\n }\n }\n\n /**\n * Destroy the plugin (alias for cleanup)\n */\n async destroy(): Promise<void> {\n await this.cleanup();\n }\n}\n\n/**\n * Factory function to create a Sentry Micro Frontend plugin.\n *\n * Creates a configured SentryMicroFrontendPlugin instance with micro frontend-specific\n * features like multiplexed transport and backstage app routing.\n *\n * @param config - Optional Sentry micro frontend plugin configuration\n * @returns SentryMicroFrontendPlugin instance\n *\n * @example\n * ```typescript\n * const sentry = createSentryMicroFrontendPlugin({\n * dsn: 'https://...',\n * backstage: [\n * { name: 'admin', dsn: 'https://admin-dsn...' },\n * { name: 'dashboard', dsn: 'https://dashboard-dsn...' },\n * ],\n * });\n * ```\n */\nexport const createSentryMicroFrontendPlugin = (\n config?: SentryMicroFrontendConfig,\n): SentryMicroFrontendPlugin => {\n return new SentryMicroFrontendPlugin(config);\n};\n"],"mappings":"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"}
@@ -0,0 +1,534 @@
1
+ import { isBrowser } from "./index.mjs";
2
+ import { safeEnv } from "./plugins-sentry-env.mjs";
3
+ import { logDebug, logError, logWarn } from "@od-oneapp/shared/logger";
4
+
5
+ //#region src/plugins/sentry/plugin.ts
6
+ /**
7
+ * @fileoverview Sentry plugin implementation
8
+ * Sentry plugin implementation
9
+ */
10
+ /**
11
+ * Default log filter function that filters out noisy development logs.
12
+ * Filters out:
13
+ * - Next.js Fast Refresh messages
14
+ * - HMR (Hot Module Replacement) messages
15
+ * - Prisma query logs (server-only)
16
+ * - Prisma engine/info logs
17
+ * - SQL queries matching Prisma's format
18
+ *
19
+ * Handles ANSI color codes, console formatting (%c, %s), and CSS styling.
20
+ *
21
+ * @param log - The log entry from Sentry
22
+ * @param isServer - Whether this is running on the server (defaults to true)
23
+ * @returns The log entry if it should be sent, or null to filter it out
24
+ */
25
+ function defaultBeforeSendLog(log, isServer = true) {
26
+ const messageToCheck = log.message || log.formatted || "";
27
+ if (!messageToCheck) return log;
28
+ const messageWithoutAnsi = String(messageToCheck).replace(/\u001b\[[0-9;]*m/g, "").replace(/%[csdfiO]/g, "").replace(/background:[^;]+;?/g, "").replace(/color:[^;]+;?/g, "").replace(/border-radius:[^;]+;?/g, "").replace(/light-dark\([^)]+\)/g, "").replace(/rgba?\([^)]+\)/g, "").replace(/#[0-9a-fA-F]{3,8}/g, "").toLowerCase();
29
+ if (messageWithoutAnsi.includes("[fast refresh]") || messageWithoutAnsi.includes("[hmr]")) return null;
30
+ if (isServer) {
31
+ if (messageWithoutAnsi.includes("prisma:query") || messageWithoutAnsi.includes("prisma:engine") || messageWithoutAnsi.includes("prisma:info") || messageWithoutAnsi.includes("select") && messageWithoutAnsi.includes("from") && (messageWithoutAnsi.includes("\"public\".") || messageWithoutAnsi.includes("public."))) return null;
32
+ }
33
+ return log;
34
+ }
35
+ /**
36
+ * Sentry plugin implementation
37
+ */
38
+ /**
39
+ * Sentry plugin implementation.
40
+ *
41
+ * Integrates Sentry error tracking and performance monitoring into the observability system.
42
+ * Auto-detects the appropriate Sentry package (@sentry/node, @sentry/browser, @sentry/nextjs)
43
+ * based on the runtime environment.
44
+ */
45
+ var SentryPlugin = class {
46
+ name = "sentry";
47
+ enabled;
48
+ client;
49
+ initialized = false;
50
+ sentryPackage;
51
+ config;
52
+ /**
53
+ * Create a new SentryPlugin instance.
54
+ *
55
+ * @param config - Sentry plugin configuration
56
+ */
57
+ constructor(config = {}) {
58
+ this.config = config;
59
+ const env = safeEnv();
60
+ const hasDSN = config.dsn ?? env.SENTRY_DSN ?? env.NEXT_PUBLIC_SENTRY_DSN;
61
+ this.enabled = config.enabled ?? Boolean(hasDSN);
62
+ this.sentryPackage = config.sentryPackage ?? this.detectSentryPackage();
63
+ }
64
+ /**
65
+ * Detect which Sentry package to use based on the runtime environment.
66
+ *
67
+ * @returns Package name ('@sentry/node', '@sentry/browser', or '@sentry/nextjs')
68
+ */
69
+ detectSentryPackage() {
70
+ if (isBrowser()) return "@sentry/browser";
71
+ else if (process.env.NEXT_RUNTIME === "edge") return "@sentry/nextjs";
72
+ else if (process.env.NEXT_RUNTIME) return "@sentry/nextjs";
73
+ else return "@sentry/node";
74
+ }
75
+ getClient() {
76
+ return this.client;
77
+ }
78
+ /**
79
+ * Get safe environment access (for testing/mocking).
80
+ *
81
+ * @returns Environment configuration object
82
+ */
83
+ getSafeEnv() {
84
+ return safeEnv();
85
+ }
86
+ async initialize(config) {
87
+ if (this.initialized || !this.enabled) return;
88
+ const env = safeEnv();
89
+ const mergedConfig = {
90
+ ...this.config,
91
+ ...config
92
+ };
93
+ const dsn = mergedConfig.dsn ?? env.SENTRY_DSN ?? env.NEXT_PUBLIC_SENTRY_DSN;
94
+ if (!dsn) {
95
+ logWarn("Sentry plugin: No DSN provided, skipping initialization");
96
+ this.enabled = false;
97
+ return;
98
+ }
99
+ try {
100
+ this.client = await import(
101
+ /* webpackIgnore: true */
102
+ this.sentryPackage
103
+ );
104
+ if (this.client && this.enabled) {
105
+ let { integrations } = mergedConfig;
106
+ if (!integrations && this.client.browserTracingIntegration) {
107
+ integrations = [];
108
+ if (mergedConfig.tracesSampleRate !== void 0 || env.SENTRY_TRACES_SAMPLE_RATE !== void 0) integrations.push(this.client.browserTracingIntegration());
109
+ if (this.client.replayIntegration && (mergedConfig.replaysSessionSampleRate !== void 0 || env.SENTRY_REPLAYS_SESSION_SAMPLE_RATE !== void 0)) integrations.push(this.client.replayIntegration());
110
+ if (this.client.profilesIntegration && (mergedConfig.profilesSampleRate !== void 0 || env.SENTRY_PROFILES_SAMPLE_RATE !== void 0)) integrations.push(this.client.profilesIntegration());
111
+ }
112
+ const enableLogs = mergedConfig.enableLogs ?? true;
113
+ if (enableLogs && this.client.consoleLoggingIntegration) {
114
+ const consoleLoggingConfig = mergedConfig.consoleLoggingIntegration || { levels: [
115
+ "log",
116
+ "warn",
117
+ "error"
118
+ ] };
119
+ if (!integrations) integrations = [];
120
+ integrations.push(this.client.consoleLoggingIntegration(consoleLoggingConfig));
121
+ }
122
+ this.client.init({
123
+ dsn,
124
+ environment: mergedConfig.environment ?? env.SENTRY_ENVIRONMENT ?? "development",
125
+ release: mergedConfig.release ?? env.SENTRY_RELEASE,
126
+ debug: mergedConfig.debug ?? env.SENTRY_DEBUG,
127
+ enableLogs,
128
+ tracesSampleRate: mergedConfig.tracesSampleRate ?? env.SENTRY_TRACES_SAMPLE_RATE,
129
+ profilesSampleRate: mergedConfig.profilesSampleRate ?? env.SENTRY_PROFILES_SAMPLE_RATE,
130
+ replaysSessionSampleRate: mergedConfig.replaysSessionSampleRate ?? env.SENTRY_REPLAYS_SESSION_SAMPLE_RATE,
131
+ replaysOnErrorSampleRate: mergedConfig.replaysOnErrorSampleRate ?? env.SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE,
132
+ integrations,
133
+ beforeSend: mergedConfig.beforeSend,
134
+ beforeSendTransaction: mergedConfig.beforeSendTransaction,
135
+ beforeSendLog: mergedConfig.beforeSendLog || (enableLogs ? (log) => defaultBeforeSendLog(log, !isBrowser()) : void 0),
136
+ tracePropagationTargets: mergedConfig.tracePropagationTargets,
137
+ initialScope: mergedConfig.initialScope,
138
+ maxBreadcrumbs: mergedConfig.maxBreadcrumbs,
139
+ attachStacktrace: mergedConfig.attachStacktrace,
140
+ autoSessionTracking: mergedConfig.autoSessionTracking
141
+ });
142
+ this.initialized = true;
143
+ }
144
+ } catch (error) {
145
+ logError(`Failed to import Sentry package '${this.sentryPackage}'`, { error });
146
+ this.enabled = false;
147
+ }
148
+ }
149
+ async shutdown() {
150
+ if (this.client && this.initialized) {
151
+ if (this.client.close) await this.client.close();
152
+ else if (this.client.flush) await this.client.flush();
153
+ this.initialized = false;
154
+ }
155
+ }
156
+ /**
157
+ * Clean up the plugin (alias for shutdown)
158
+ */
159
+ async cleanup() {
160
+ await this.shutdown();
161
+ }
162
+ captureException(error, context) {
163
+ if (!this.enabled || !this.client) return;
164
+ if (typeof this.client.captureException !== "function") {
165
+ logError("[Sentry Fallback] Exception", { error });
166
+ return;
167
+ }
168
+ try {
169
+ this.client.captureException(error, context);
170
+ } catch (captureError) {
171
+ logWarn("[Sentry Plugin] Failed to capture exception", { error: captureError });
172
+ logError("[Sentry Fallback] Exception", { error });
173
+ }
174
+ }
175
+ captureMessage(message, level = "info", context) {
176
+ if (!this.enabled || !this.client) return;
177
+ if (this.client.logger) try {
178
+ const attributes = context ? context.extra ?? context : void 0;
179
+ switch (level) {
180
+ case "debug":
181
+ this.client.logger.debug(message, attributes);
182
+ return;
183
+ case "info":
184
+ this.client.logger.info(message, attributes);
185
+ return;
186
+ case "warning":
187
+ this.client.logger.warn(message, attributes);
188
+ return;
189
+ case "error":
190
+ this.client.logger.error(message, attributes);
191
+ return;
192
+ }
193
+ } catch (error) {
194
+ logWarn("[Sentry Plugin] Failed to log via logger", { error });
195
+ }
196
+ if (typeof this.client.captureMessage !== "function") {
197
+ if (level === "error") logError(`[Sentry Fallback] ${message}`);
198
+ else if (level === "warning") logWarn(`[Sentry Fallback] ${message}`);
199
+ else logDebug(`[Sentry Fallback] ${message}`);
200
+ return;
201
+ }
202
+ const sentryLevel = level === "warning" ? "warning" : level === "error" ? "error" : level === "debug" ? "debug" : "info";
203
+ try {
204
+ const captureContext = context ? {
205
+ level: sentryLevel,
206
+ extra: context.extra ?? context,
207
+ tags: context.tags
208
+ } : sentryLevel;
209
+ this.client.captureMessage(message, captureContext);
210
+ } catch (error) {
211
+ logWarn("[Sentry Plugin] Failed to capture message", { error });
212
+ if (level === "error") logError(`[Sentry Fallback] ${message}`);
213
+ else if (level === "warning") logWarn(`[Sentry Fallback] ${message}`);
214
+ else logDebug(`[Sentry Fallback] ${message}`);
215
+ }
216
+ }
217
+ setUser(user) {
218
+ if (!this.enabled || !this.client) return;
219
+ this.client.setUser(user);
220
+ }
221
+ addBreadcrumb(breadcrumb) {
222
+ if (!this.enabled || !this.client) return;
223
+ this.client.addBreadcrumb({
224
+ ...breadcrumb,
225
+ timestamp: breadcrumb.timestamp ?? Date.now() / 1e3
226
+ });
227
+ }
228
+ withScope(callback) {
229
+ if (!this.enabled || !this.client) return;
230
+ this.client.withScope(callback);
231
+ }
232
+ /**
233
+ * Start a new transaction
234
+ */
235
+ startTransaction(context, customSamplingContext) {
236
+ if (!this.enabled || !this.client) return void 0;
237
+ if (this.client.startTransaction) return this.client.startTransaction(context, customSamplingContext);
238
+ logWarn("startTransaction not available in this Sentry version");
239
+ }
240
+ /**
241
+ * Start a new span
242
+ */
243
+ startSpan(context) {
244
+ if (!this.enabled || !this.client) return void 0;
245
+ if (this.client.startSpan) return this.client.startSpan(context);
246
+ const transaction = this.getActiveTransaction();
247
+ if (transaction?.startChild) return transaction.startChild(context);
248
+ }
249
+ /**
250
+ * Get the currently active transaction
251
+ */
252
+ getActiveTransaction() {
253
+ if (!this.enabled || !this.client) return void 0;
254
+ if (this.client.getActiveTransaction) return this.client.getActiveTransaction();
255
+ if (this.client.getCurrentScope) {
256
+ const scope = this.client.getCurrentScope();
257
+ if (scope?.getTransaction) return scope.getTransaction();
258
+ }
259
+ }
260
+ /**
261
+ * Configure the current scope
262
+ */
263
+ configureScope(callback) {
264
+ if (!this.enabled || !this.client) return;
265
+ if (this.client.configureScope) this.client.configureScope(callback);
266
+ else if (this.client.withScope) this.client.withScope(callback);
267
+ }
268
+ /**
269
+ * Get the current hub instance
270
+ */
271
+ getCurrentHub() {
272
+ if (!this.enabled || !this.client) return void 0;
273
+ if (this.client.getCurrentHub) return this.client.getCurrentHub();
274
+ }
275
+ /**
276
+ * Set a measurement on the active transaction
277
+ */
278
+ setMeasurement(name, value, unit) {
279
+ if (!this.enabled || !this.client) return;
280
+ const transaction = this.getActiveTransaction();
281
+ if (transaction?.setMeasurement) transaction.setMeasurement(name, value, unit);
282
+ }
283
+ /**
284
+ * Add performance entries as breadcrumbs and measurements
285
+ */
286
+ addPerformanceEntries(entries) {
287
+ if (!this.enabled || !this.client) return;
288
+ const transaction = this.getActiveTransaction();
289
+ entries.forEach((entry) => {
290
+ this.addBreadcrumb({
291
+ category: "performance",
292
+ message: `${entry.entryType}: ${entry.name}`,
293
+ data: {
294
+ name: entry.name,
295
+ duration: entry.duration,
296
+ startTime: entry.startTime,
297
+ ...entry.entryType === "navigation" && {
298
+ transferSize: entry.transferSize,
299
+ encodedBodySize: entry.encodedBodySize,
300
+ decodedBodySize: entry.decodedBodySize
301
+ },
302
+ ...entry.entryType === "resource" && {
303
+ initiatorType: entry.initiatorType,
304
+ transferSize: entry.transferSize
305
+ }
306
+ }
307
+ });
308
+ if (transaction && entry.entryType === "navigation") {
309
+ const navEntry = entry;
310
+ const fetchStart = navEntry.fetchStart ?? 0;
311
+ if (navEntry.responseStart !== void 0) this.setMeasurement("fcp", navEntry.responseStart - fetchStart, "millisecond");
312
+ if (navEntry.domInteractive !== void 0) this.setMeasurement("dom_interactive", navEntry.domInteractive - fetchStart, "millisecond");
313
+ if (navEntry.domComplete !== void 0) this.setMeasurement("dom_complete", navEntry.domComplete - fetchStart, "millisecond");
314
+ if (navEntry.loadEventEnd !== void 0) this.setMeasurement("load_event_end", navEntry.loadEventEnd - fetchStart, "millisecond");
315
+ }
316
+ });
317
+ }
318
+ /**
319
+ * Record a Web Vital measurement
320
+ */
321
+ recordWebVital(name, value, options) {
322
+ if (!this.enabled || !this.client) return;
323
+ const { unit = "millisecond", rating } = options ?? {};
324
+ const transaction = this.getActiveTransaction();
325
+ if (transaction) {
326
+ transaction.setMeasurement(name.toLowerCase(), value, unit);
327
+ if (rating) transaction.setTag(`webvital.${name.toLowerCase()}.rating`, rating);
328
+ this.client.captureMessage(`Web Vital: ${name}`, {
329
+ level: "info",
330
+ tags: {
331
+ "webvital.name": name,
332
+ "webvital.value": value,
333
+ "webvital.unit": unit,
334
+ ...rating && { "webvital.rating": rating }
335
+ },
336
+ contexts: { trace: {
337
+ trace_id: transaction.traceId,
338
+ span_id: transaction.spanId
339
+ } }
340
+ });
341
+ }
342
+ }
343
+ /**
344
+ * Create a custom performance mark
345
+ */
346
+ mark(name, options) {
347
+ if (!this.enabled) return;
348
+ if (typeof performance !== "undefined" && performance.mark) performance.mark(name, options);
349
+ this.addBreadcrumb({
350
+ category: "performance.mark",
351
+ message: name,
352
+ data: options?.detail,
353
+ timestamp: Date.now() / 1e3
354
+ });
355
+ }
356
+ /**
357
+ * Create a custom performance measure
358
+ */
359
+ measure(name, startMarkOrOptions, endMark) {
360
+ if (!this.enabled) return;
361
+ if (typeof performance !== "undefined" && performance.measure) {
362
+ if (typeof startMarkOrOptions === "string") performance.measure(name, startMarkOrOptions, endMark);
363
+ else if (startMarkOrOptions) performance.measure(name, startMarkOrOptions);
364
+ else performance.measure(name);
365
+ const measures = performance.getEntriesByName(name, "measure");
366
+ const measure = measures[measures.length - 1];
367
+ if (measure) {
368
+ this.setMeasurement(name, measure.duration, "millisecond");
369
+ this.addBreadcrumb({
370
+ category: "performance.measure",
371
+ message: name,
372
+ data: {
373
+ duration: measure.duration,
374
+ startTime: measure.startTime
375
+ },
376
+ timestamp: measure.startTime / 1e3
377
+ });
378
+ }
379
+ }
380
+ }
381
+ /**
382
+ * Log a trace message using Sentry.logger (if available).
383
+ * Falls back to captureMessage if logger is not available.
384
+ *
385
+ * @param message - Trace message to log
386
+ * @param attributes - Optional attributes to include with the log
387
+ */
388
+ logTrace(message, attributes) {
389
+ if (!this.enabled || !this.client) return;
390
+ if (this.client.logger?.trace) try {
391
+ this.client.logger.trace(message, attributes);
392
+ return;
393
+ } catch (error) {
394
+ logWarn("[Sentry Plugin] Failed to log trace", { error });
395
+ }
396
+ this.captureMessage(message, "debug", attributes);
397
+ }
398
+ /**
399
+ * Log a debug message using Sentry.logger (if available).
400
+ * Falls back to captureMessage if logger is not available.
401
+ *
402
+ * @param message - Debug message to log
403
+ * @param attributes - Optional attributes to include with the log
404
+ */
405
+ logDebug(message, attributes) {
406
+ if (!this.enabled || !this.client) return;
407
+ if (this.client.logger?.debug) try {
408
+ this.client.logger.debug(message, attributes);
409
+ return;
410
+ } catch (error) {
411
+ logWarn("[Sentry Plugin] Failed to log debug", { error });
412
+ }
413
+ this.captureMessage(message, "debug", attributes);
414
+ }
415
+ /**
416
+ * Log an info message using Sentry.logger (if available).
417
+ * Falls back to captureMessage if logger is not available.
418
+ *
419
+ * @param message - Info message to log
420
+ * @param attributes - Optional attributes to include with the log
421
+ */
422
+ logInfo(message, attributes) {
423
+ if (!this.enabled || !this.client) return;
424
+ if (this.client.logger?.info) try {
425
+ this.client.logger.info(message, attributes);
426
+ return;
427
+ } catch (error) {
428
+ logWarn("[Sentry Plugin] Failed to log info", { error });
429
+ }
430
+ this.captureMessage(message, "info", attributes);
431
+ }
432
+ /**
433
+ * Log a warning message using Sentry.logger (if available).
434
+ * Falls back to captureMessage if logger is not available.
435
+ *
436
+ * @param message - Warning message to log
437
+ * @param attributes - Optional attributes to include with the log
438
+ */
439
+ logWarn(message, attributes) {
440
+ if (!this.enabled || !this.client) return;
441
+ if (this.client.logger?.warn) try {
442
+ this.client.logger.warn(message, attributes);
443
+ return;
444
+ } catch (error) {
445
+ logWarn("[Sentry Plugin] Failed to log warn", { error });
446
+ }
447
+ this.captureMessage(message, "warning", attributes);
448
+ }
449
+ /**
450
+ * Log an error message using Sentry.logger (if available).
451
+ * Falls back to captureMessage if logger is not available.
452
+ *
453
+ * @param message - Error message to log
454
+ * @param attributes - Optional attributes to include with the log
455
+ */
456
+ logError(message, attributes) {
457
+ if (!this.enabled || !this.client) return;
458
+ if (this.client.logger?.error) try {
459
+ this.client.logger.error(message, attributes);
460
+ return;
461
+ } catch (error) {
462
+ logWarn("[Sentry Plugin] Failed to log error", { error });
463
+ }
464
+ this.captureMessage(message, "error", attributes);
465
+ }
466
+ /**
467
+ * Log a fatal message using Sentry.logger (if available).
468
+ * Falls back to captureMessage if logger is not available.
469
+ *
470
+ * @param message - Fatal message to log
471
+ * @param attributes - Optional attributes to include with the log
472
+ */
473
+ logFatal(message, attributes) {
474
+ if (!this.enabled || !this.client) return;
475
+ if (this.client.logger?.fatal) try {
476
+ this.client.logger.fatal(message, attributes);
477
+ return;
478
+ } catch (error) {
479
+ logWarn("[Sentry Plugin] Failed to log fatal", { error });
480
+ }
481
+ this.captureMessage(message, "error", attributes);
482
+ }
483
+ /**
484
+ * Get the Sentry.logger instance if available.
485
+ * This allows direct access to Sentry.logger APIs including fmt.
486
+ *
487
+ * @returns Sentry.logger instance or undefined if not available
488
+ *
489
+ * @example
490
+ * ```typescript
491
+ * const logger = sentryPlugin.getLogger();
492
+ * if (logger) {
493
+ * logger.info(logger.fmt`User ${user.id} logged in`);
494
+ * }
495
+ * ```
496
+ */
497
+ getLogger() {
498
+ if (!this.enabled || !this.client) return void 0;
499
+ return this.client.logger;
500
+ }
501
+ async flush(timeout) {
502
+ if (!this.enabled || !this.client || !this.client.flush) return true;
503
+ try {
504
+ return await this.client.flush(timeout);
505
+ } catch (_error) {
506
+ return false;
507
+ }
508
+ }
509
+ };
510
+ /**
511
+ * Factory function to create a Sentry plugin.
512
+ *
513
+ * Creates a configured SentryPlugin instance with optional configuration.
514
+ * Auto-detects the appropriate Sentry package based on the environment.
515
+ *
516
+ * @param config - Optional Sentry plugin configuration
517
+ * @returns SentryPlugin instance
518
+ *
519
+ * @example
520
+ * ```typescript
521
+ * const sentry = createSentryPlugin({
522
+ * dsn: 'https://...',
523
+ * environment: 'production',
524
+ * tracesSampleRate: 0.1,
525
+ * });
526
+ * ```
527
+ */
528
+ const createSentryPlugin = (config) => {
529
+ return new SentryPlugin(config);
530
+ };
531
+
532
+ //#endregion
533
+ export { createSentryPlugin as n, defaultBeforeSendLog as r, SentryPlugin as t };
534
+ //# sourceMappingURL=plugin-CP895lBx.mjs.map