react-native-nitro-tracing 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/NitroTracing.podspec +32 -0
  4. package/README.md +75 -0
  5. package/android/CMakeLists.txt +49 -0
  6. package/android/build.gradle +133 -0
  7. package/android/fix-prefab.gradle +51 -0
  8. package/android/gradle.properties +5 -0
  9. package/android/proguard-rules.pro +5 -0
  10. package/android/src/main/AndroidManifest.xml +2 -0
  11. package/android/src/main/cpp/cpp-adapter.cpp +47 -0
  12. package/android/src/main/java/com/margelo/nitro/nitrotracing/HermesSamplingGuard.kt +43 -0
  13. package/android/src/main/java/com/margelo/nitro/nitrotracing/NitroTracingFrameSource.kt +36 -0
  14. package/android/src/main/java/com/margelo/nitro/nitrotracing/NitroTracingPackage.kt +31 -0
  15. package/client.ts +15 -0
  16. package/cpp/HermesSampling.cpp +26 -0
  17. package/cpp/HermesSampling.hpp +10 -0
  18. package/cpp/HybridRecording.cpp +66 -0
  19. package/cpp/HybridRecording.hpp +30 -0
  20. package/cpp/HybridTraceSpan.cpp +10 -0
  21. package/cpp/HybridTraceSpan.hpp +17 -0
  22. package/cpp/HybridTracing.cpp +10 -0
  23. package/cpp/HybridTracing.hpp +10 -0
  24. package/cpp/NativeConversions.cpp +77 -0
  25. package/cpp/NativeConversions.hpp +15 -0
  26. package/cpp/core/NativeSampler.cpp +152 -0
  27. package/cpp/core/NativeSampler.hpp +56 -0
  28. package/cpp/core/Recorder.cpp +218 -0
  29. package/cpp/core/Recorder.hpp +60 -0
  30. package/cpp/core/Retention.cpp +26 -0
  31. package/cpp/core/Retention.hpp +7 -0
  32. package/cpp/core/TraceJson.cpp +214 -0
  33. package/cpp/core/TraceJson.hpp +7 -0
  34. package/cpp/core/TraceTypes.hpp +62 -0
  35. package/expo.ts +2 -0
  36. package/ios/NitroTracingFrameSource.mm +35 -0
  37. package/lib/commonjs/client.cjs +767 -0
  38. package/lib/commonjs/client.cjs.map +7 -0
  39. package/lib/commonjs/expo.cjs +88 -0
  40. package/lib/commonjs/expo.cjs.map +7 -0
  41. package/lib/commonjs/index.cjs +28 -0
  42. package/lib/commonjs/index.cjs.map +7 -0
  43. package/lib/commonjs/performance.cjs +112 -0
  44. package/lib/commonjs/performance.cjs.map +7 -0
  45. package/lib/commonjs/plugins.cjs +477 -0
  46. package/lib/commonjs/plugins.cjs.map +7 -0
  47. package/lib/commonjs/react.cjs +3833 -0
  48. package/lib/commonjs/react.cjs.map +7 -0
  49. package/lib/commonjs/release-profiler.cjs +142 -0
  50. package/lib/commonjs/release-profiler.cjs.map +7 -0
  51. package/lib/commonjs/sentry.cjs +188 -0
  52. package/lib/commonjs/sentry.cjs.map +7 -0
  53. package/lib/module/client.mjs +768 -0
  54. package/lib/module/client.mjs.map +7 -0
  55. package/lib/module/expo.mjs +62 -0
  56. package/lib/module/expo.mjs.map +7 -0
  57. package/lib/module/index.mjs +7 -0
  58. package/lib/module/index.mjs.map +7 -0
  59. package/lib/module/performance.mjs +89 -0
  60. package/lib/module/performance.mjs.map +7 -0
  61. package/lib/module/plugins.mjs +461 -0
  62. package/lib/module/plugins.mjs.map +7 -0
  63. package/lib/module/react.mjs +3851 -0
  64. package/lib/module/react.mjs.map +7 -0
  65. package/lib/module/release-profiler.mjs +137 -0
  66. package/lib/module/release-profiler.mjs.map +7 -0
  67. package/lib/module/sentry.mjs +165 -0
  68. package/lib/module/sentry.mjs.map +7 -0
  69. package/lib/typescript/client.d.ts +4 -0
  70. package/lib/typescript/expo.d.ts +2 -0
  71. package/lib/typescript/performance.d.ts +2 -0
  72. package/lib/typescript/plugins.d.ts +12 -0
  73. package/lib/typescript/react.d.ts +5 -0
  74. package/lib/typescript/release-profiler.d.ts +2 -0
  75. package/lib/typescript/sentry.d.ts +2 -0
  76. package/lib/typescript/src/client/TracingSink.d.ts +10 -0
  77. package/lib/typescript/src/client/createTraceClient.d.ts +82 -0
  78. package/lib/typescript/src/client/playground.d.ts +12 -0
  79. package/lib/typescript/src/expo/ExpoAdapter.d.ts +15 -0
  80. package/lib/typescript/src/expo/index.d.ts +2 -0
  81. package/lib/typescript/src/index.d.ts +6 -0
  82. package/lib/typescript/src/plugins/TracePlugin.d.ts +22 -0
  83. package/lib/typescript/src/plugins/errors.d.ts +15 -0
  84. package/lib/typescript/src/plugins/nativeMetrics.d.ts +13 -0
  85. package/lib/typescript/src/plugins/navigation.d.ts +29 -0
  86. package/lib/typescript/src/plugins/network.d.ts +23 -0
  87. package/lib/typescript/src/plugins/orderSpans.d.ts +3 -0
  88. package/lib/typescript/src/plugins/performance.d.ts +13 -0
  89. package/lib/typescript/src/plugins/releaseProfiler.d.ts +25 -0
  90. package/lib/typescript/src/plugins/runtimeMetrics.d.ts +17 -0
  91. package/lib/typescript/src/plugins/sentry.d.ts +15 -0
  92. package/lib/typescript/src/plugins/startPlugins.d.ts +4 -0
  93. package/lib/typescript/src/react/DetailView.d.ts +11 -0
  94. package/lib/typescript/src/react/ExplorerFilters.d.ts +9 -0
  95. package/lib/typescript/src/react/ExplorerView.d.ts +9 -0
  96. package/lib/typescript/src/react/InspectorControls.d.ts +229 -0
  97. package/lib/typescript/src/react/InspectorHeader.d.ts +8 -0
  98. package/lib/typescript/src/react/InspectorSheet.d.ts +16 -0
  99. package/lib/typescript/src/react/MetricsView.d.ts +7 -0
  100. package/lib/typescript/src/react/SummaryView.d.ts +11 -0
  101. package/lib/typescript/src/react/TimelineView.d.ts +8 -0
  102. package/lib/typescript/src/react/TraceInspector.d.ts +11 -0
  103. package/lib/typescript/src/react/TraceOverlay.d.ts +17 -0
  104. package/lib/typescript/src/react/explorerModel.d.ts +38 -0
  105. package/lib/typescript/src/react/labels.d.ts +203 -0
  106. package/lib/typescript/src/react/native.d.ts +42 -0
  107. package/lib/typescript/src/react/readSnapshot.d.ts +4 -0
  108. package/lib/typescript/src/react/store.d.ts +9 -0
  109. package/lib/typescript/src/react/topics.d.ts +80 -0
  110. package/lib/typescript/src/react/useAppReadyMetric.d.ts +3 -0
  111. package/lib/typescript/src/react/useLiveRecording.d.ts +4 -0
  112. package/lib/typescript/src/react/useTraceViewer.d.ts +140 -0
  113. package/lib/typescript/src/react/viewerModel.d.ts +59 -0
  114. package/lib/typescript/src/specs/Recording.nitro.d.ts +31 -0
  115. package/lib/typescript/src/specs/TraceSpan.nitro.d.ts +14 -0
  116. package/lib/typescript/src/specs/Tracing.nitro.d.ts +17 -0
  117. package/lib/typescript/src/types/CompletedSpanOptions.d.ts +15 -0
  118. package/lib/typescript/src/types/EventContext.d.ts +8 -0
  119. package/lib/typescript/src/types/MarkEvent.d.ts +4 -0
  120. package/lib/typescript/src/types/MarkOptions.d.ts +6 -0
  121. package/lib/typescript/src/types/MetricEvent.d.ts +8 -0
  122. package/lib/typescript/src/types/MetricOptions.d.ts +10 -0
  123. package/lib/typescript/src/types/NativeSamplingOptions.d.ts +7 -0
  124. package/lib/typescript/src/types/ReadOptions.d.ts +7 -0
  125. package/lib/typescript/src/types/RecordingOptions.d.ts +11 -0
  126. package/lib/typescript/src/types/RecordingStats.d.ts +25 -0
  127. package/lib/typescript/src/types/SpanEvent.d.ts +13 -0
  128. package/lib/typescript/src/types/SpanOptions.d.ts +6 -0
  129. package/lib/typescript/src/types/SpanOutcome.d.ts +2 -0
  130. package/lib/typescript/src/types/TraceAttribute.d.ts +7 -0
  131. package/lib/typescript/src/types/TraceAttributes.d.ts +3 -0
  132. package/lib/typescript/src/types/TraceContext.d.ts +10 -0
  133. package/lib/typescript/src/types/TracePage.d.ts +18 -0
  134. package/lib/typescript/src/types.d.ts +17 -0
  135. package/nitro.json +20 -0
  136. package/nitrogen/generated/.gitattributes +1 -0
  137. package/nitrogen/generated/android/NitroTracing+autolinking.cmake +83 -0
  138. package/nitrogen/generated/android/NitroTracing+autolinking.gradle +27 -0
  139. package/nitrogen/generated/android/NitroTracingOnLoad.cpp +49 -0
  140. package/nitrogen/generated/android/NitroTracingOnLoad.hpp +34 -0
  141. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotracing/NitroTracingOnLoad.kt +35 -0
  142. package/nitrogen/generated/ios/NitroTracing+autolinking.rb +62 -0
  143. package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Bridge.cpp +17 -0
  144. package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Bridge.hpp +27 -0
  145. package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Umbrella.hpp +38 -0
  146. package/nitrogen/generated/ios/NitroTracingAutolinking.mm +35 -0
  147. package/nitrogen/generated/ios/NitroTracingAutolinking.swift +16 -0
  148. package/nitrogen/generated/shared/c++/CompletedSpanOptions.hpp +122 -0
  149. package/nitrogen/generated/shared/c++/HybridRecordingSpec.cpp +31 -0
  150. package/nitrogen/generated/shared/c++/HybridRecordingSpec.hpp +100 -0
  151. package/nitrogen/generated/shared/c++/HybridTraceSpanSpec.cpp +23 -0
  152. package/nitrogen/generated/shared/c++/HybridTraceSpanSpec.hpp +65 -0
  153. package/nitrogen/generated/shared/c++/HybridTracingSpec.cpp +22 -0
  154. package/nitrogen/generated/shared/c++/HybridTracingSpec.hpp +68 -0
  155. package/nitrogen/generated/shared/c++/MarkEvent.hpp +102 -0
  156. package/nitrogen/generated/shared/c++/MarkOptions.hpp +99 -0
  157. package/nitrogen/generated/shared/c++/MetricEvent.hpp +110 -0
  158. package/nitrogen/generated/shared/c++/MetricOptions.hpp +107 -0
  159. package/nitrogen/generated/shared/c++/NativeSamplingOptions.hpp +87 -0
  160. package/nitrogen/generated/shared/c++/ReadOptions.hpp +87 -0
  161. package/nitrogen/generated/shared/c++/RecordingOptions.hpp +95 -0
  162. package/nitrogen/generated/shared/c++/RecordingStats.hpp +123 -0
  163. package/nitrogen/generated/shared/c++/SpanEvent.hpp +121 -0
  164. package/nitrogen/generated/shared/c++/SpanOptions.hpp +99 -0
  165. package/nitrogen/generated/shared/c++/SpanOutcome.hpp +84 -0
  166. package/nitrogen/generated/shared/c++/TraceAttribute.hpp +87 -0
  167. package/nitrogen/generated/shared/c++/TracePage.hpp +111 -0
  168. package/package.json +236 -0
  169. package/performance.ts +2 -0
  170. package/plugins.ts +24 -0
  171. package/react-native.config.js +16 -0
  172. package/react.ts +10 -0
  173. package/release-profiler.ts +6 -0
  174. package/sentry.ts +2 -0
  175. package/src/client/TracingSink.ts +25 -0
  176. package/src/client/createTraceClient.ts +439 -0
  177. package/src/client/playground.ts +143 -0
  178. package/src/expo/ExpoAdapter.ts +71 -0
  179. package/src/expo/index.ts +2 -0
  180. package/src/index.ts +8 -0
  181. package/src/plugins/TracePlugin.ts +22 -0
  182. package/src/plugins/errors.ts +69 -0
  183. package/src/plugins/nativeMetrics.ts +26 -0
  184. package/src/plugins/navigation.ts +111 -0
  185. package/src/plugins/network.ts +178 -0
  186. package/src/plugins/orderSpans.ts +22 -0
  187. package/src/plugins/performance.ts +120 -0
  188. package/src/plugins/releaseProfiler.ts +123 -0
  189. package/src/plugins/runtimeMetrics.ts +131 -0
  190. package/src/plugins/sentry.ts +176 -0
  191. package/src/plugins/startPlugins.ts +58 -0
  192. package/src/react/DetailView.tsx +514 -0
  193. package/src/react/ExplorerFilters.tsx +151 -0
  194. package/src/react/ExplorerView.tsx +243 -0
  195. package/src/react/InspectorControls.tsx +651 -0
  196. package/src/react/InspectorHeader.tsx +171 -0
  197. package/src/react/InspectorSheet.tsx +89 -0
  198. package/src/react/MetricsView.tsx +182 -0
  199. package/src/react/SummaryView.tsx +121 -0
  200. package/src/react/TimelineView.tsx +87 -0
  201. package/src/react/TraceInspector.tsx +196 -0
  202. package/src/react/TraceOverlay.tsx +585 -0
  203. package/src/react/explorerModel.ts +285 -0
  204. package/src/react/labels.ts +234 -0
  205. package/src/react/native.tsx +159 -0
  206. package/src/react/readSnapshot.ts +58 -0
  207. package/src/react/store.ts +31 -0
  208. package/src/react/topics.ts +427 -0
  209. package/src/react/useAppReadyMetric.ts +10 -0
  210. package/src/react/useLiveRecording.ts +43 -0
  211. package/src/react/useTraceViewer.ts +333 -0
  212. package/src/react/viewerModel.ts +172 -0
  213. package/src/specs/Recording.nitro.ts +40 -0
  214. package/src/specs/TraceSpan.nitro.ts +14 -0
  215. package/src/specs/Tracing.nitro.ts +14 -0
  216. package/src/types/CompletedSpanOptions.ts +15 -0
  217. package/src/types/EventContext.ts +8 -0
  218. package/src/types/MarkEvent.ts +3 -0
  219. package/src/types/MarkOptions.ts +6 -0
  220. package/src/types/MetricEvent.ts +8 -0
  221. package/src/types/MetricOptions.ts +10 -0
  222. package/src/types/NativeSamplingOptions.ts +7 -0
  223. package/src/types/ReadOptions.ts +7 -0
  224. package/src/types/RecordingOptions.ts +11 -0
  225. package/src/types/RecordingStats.ts +25 -0
  226. package/src/types/SpanEvent.ts +13 -0
  227. package/src/types/SpanOptions.ts +6 -0
  228. package/src/types/SpanOutcome.ts +2 -0
  229. package/src/types/TraceAttribute.ts +7 -0
  230. package/src/types/TraceAttributes.ts +4 -0
  231. package/src/types/TraceContext.ts +10 -0
  232. package/src/types/TracePage.ts +18 -0
  233. package/src/types.ts +17 -0
@@ -0,0 +1,131 @@
1
+ import type { TracePlugin } from './TracePlugin'
2
+ export interface RuntimeMetricsOptions {
3
+ /** Reporting interval in ms, minimum 250; default 1000. */
4
+ intervalMs?: number
5
+ /** Observe JS requestAnimationFrame callbacks (not native UI frames). Default true. */
6
+ frames?: boolean
7
+ }
8
+ /** Injectable clock/lifecycle surface for deterministic tests and alternate hosts. */
9
+ export interface RuntimeMetricsHost {
10
+ now(): number
11
+ isActive(): boolean
12
+ subscribe(listener: (active: boolean) => void): () => void
13
+ requestFrame(callback: () => void): number
14
+ cancelFrame(id: number): void
15
+ }
16
+ const nativeHost = (): RuntimeMetricsHost => {
17
+ const { AppState } = require('react-native') as typeof import('react-native')
18
+ return {
19
+ now: () => globalThis.performance.now(),
20
+ isActive: () => AppState.currentState === 'active',
21
+ subscribe: (listener) => {
22
+ const subscription = AppState.addEventListener('change', (state) =>
23
+ listener(state === 'active')
24
+ )
25
+ return () => subscription.remove()
26
+ },
27
+ requestFrame: (callback) => requestAnimationFrame(callback),
28
+ cancelFrame: (id) => cancelAnimationFrame(id),
29
+ }
30
+ }
31
+ /** Low-volume foreground JS health samples. No per-frame native calls or UI FPS claims. */
32
+ export function createRuntimeMetricsPlugin(
33
+ options: RuntimeMetricsOptions = {},
34
+ host?: RuntimeMetricsHost
35
+ ): TracePlugin {
36
+ const interval = options.intervalMs ?? 1000
37
+ if (!Number.isFinite(interval) || interval < 250)
38
+ throw new Error('Runtime metrics interval must be at least 250ms')
39
+ return {
40
+ id: 'runtime-metrics',
41
+ start({ recording, reportError }) {
42
+ const api = host ?? nativeHost()
43
+ let stopped = false
44
+ let active = false
45
+ let timer: ReturnType<typeof setTimeout> | undefined
46
+ let frame: number | undefined
47
+ let started = 0
48
+ let previousFrame: number | undefined
49
+ let frames = 0
50
+ let maxGap = 0
51
+ let longGaps = 0
52
+ const reset = () => {
53
+ started = api.now()
54
+ frames = 0
55
+ maxGap = 0
56
+ longGaps = 0
57
+ }
58
+ const emit = (name: string, value: number, unit: string) => {
59
+ try {
60
+ recording.recordMetric({
61
+ name,
62
+ value,
63
+ unit,
64
+ correlationId: '',
65
+ attributes: [
66
+ { key: 'source', value: 'js-runtime' },
67
+ { key: 'scope', value: 'foreground' },
68
+ { key: 'windowMs', value: String(api.now() - started) },
69
+ ],
70
+ })
71
+ } catch (error) {
72
+ reportError(error)
73
+ }
74
+ }
75
+ const onFrame = () => {
76
+ if (stopped || !active) return
77
+ const now = api.now()
78
+ if (previousFrame !== undefined) {
79
+ const gap = Math.max(0, now - previousFrame)
80
+ maxGap = Math.max(maxGap, gap)
81
+ if (gap > 50) longGaps++
82
+ }
83
+ previousFrame = now
84
+ frames++
85
+ frame = api.requestFrame(onFrame)
86
+ }
87
+ const tick = () => {
88
+ if (stopped || !active) return
89
+ const elapsed = api.now() - started
90
+ emit('js.event_loop.delay', Math.max(0, elapsed - interval), 'ms')
91
+ if (options.frames !== false && elapsed > 0) {
92
+ emit(
93
+ 'js.frame_callback.rate',
94
+ (frames * 1000) / elapsed,
95
+ 'callbacks/s'
96
+ )
97
+ if (frames > 1) emit('js.frame_gap.max', maxGap, 'ms')
98
+ emit('js.frame_gap.over_50ms', longGaps, 'count')
99
+ }
100
+ reset()
101
+ timer = setTimeout(tick, interval)
102
+ }
103
+ const suspend = () => {
104
+ clearTimeout(timer)
105
+ if (frame !== undefined) api.cancelFrame(frame)
106
+ frame = undefined
107
+ }
108
+ const change = (next: boolean) => {
109
+ if (stopped || next === active) return
110
+ active = next
111
+ previousFrame = undefined
112
+ suspend()
113
+ reset()
114
+ if (active) {
115
+ timer = setTimeout(tick, interval)
116
+ if (options.frames !== false) frame = api.requestFrame(onFrame)
117
+ }
118
+ }
119
+ const unsubscribe = api.subscribe(change)
120
+ change(api.isActive())
121
+ return {
122
+ stop() {
123
+ stopped = true
124
+ active = false
125
+ suspend()
126
+ unsubscribe()
127
+ },
128
+ }
129
+ },
130
+ }
131
+ }
@@ -0,0 +1,176 @@
1
+ import type * as Sentry from '@sentry/react-native'
2
+ import type { SpanEvent } from '../types'
3
+ import type { TracePlugin } from './TracePlugin'
4
+ import { orderSpans } from './orderSpans'
5
+ /** Optional Sentry capabilities; SDK initialization and consent remain app-owned. */
6
+ export interface SentryPluginOptions {
7
+ /** Inject an installed SDK. This module has no runtime SDK dependency. */
8
+ sentry: Pick<typeof Sentry, 'getClient' | 'spanToJSON' | 'startInactiveSpan'>
9
+ /** Capture observable SDK spans locally. Defaults to false; sampling applies. */
10
+ captureSpans?: boolean
11
+ /** Explicitly opt into sending retained span names/timing/status to Sentry on stop. Defaults to false. */
12
+ exportSpans?: boolean
13
+ /** Only these sanitized custom attribute keys may leave the device. Defaults to none. */
14
+ exportedAttributeKeys?: readonly string[]
15
+ }
16
+ /** Optional SDK measurements and opt-in export; neither direction is enabled implicitly. */
17
+ export function createSentryPlugin(options: SentryPluginOptions): TracePlugin {
18
+ return {
19
+ id: 'sentry',
20
+ start({ recording, reportError }) {
21
+ const { sentry } = options
22
+ const stats = recording.getStats()
23
+ const anchor = stats.startedAtUnixMs
24
+ const client = sentry.getClient()
25
+ if (options.captureSpans && !client)
26
+ throw new Error('Initialize Sentry before enabling local capture')
27
+ /** Sentry as the measurement layer: its frame and app-start data use the native metric names. */
28
+ const recordMeasurements = (
29
+ data: ReturnType<typeof sentry.spanToJSON>,
30
+ start: number,
31
+ anchorMs: number
32
+ ) => {
33
+ const end = (data.timestamp ?? data.start_timestamp) * 1000 - anchorMs
34
+ const metric = (name: string, value: unknown, unit: string) => {
35
+ if (typeof value !== 'number' || !Number.isFinite(value)) return
36
+ recording.recordMetric({
37
+ name,
38
+ value,
39
+ unit,
40
+ timestampMs: Math.max(0, end),
41
+ correlationId: data.trace_id,
42
+ attributes: [
43
+ { key: 'source', value: 'sentry' },
44
+ {
45
+ key: 'span',
46
+ value: (data.description || data.op || '').slice(0, 256),
47
+ },
48
+ ],
49
+ })
50
+ }
51
+ // Set by Sentry's nativeFrames integration on spans it measured.
52
+ metric('ui.frames.total', data.data?.['frames.total'], 'count')
53
+ metric('ui.frames.slow', data.data?.['frames.slow'], 'count')
54
+ metric('ui.frames.frozen', data.data?.['frames.frozen'], 'count')
55
+ // appStart integration spans: app.start.cold / app.start.warm.
56
+ if (data.op === 'app.start.cold' || data.op === 'app.start.warm')
57
+ metric(data.op, end - start, 'ms')
58
+ }
59
+ const unsubscribe = options.captureSpans
60
+ ? client!.on('spanEnd', (span) => {
61
+ try {
62
+ const data = sentry.spanToJSON(span)
63
+ if (data.data?.['nitro.exported'] || data.timestamp === undefined)
64
+ return
65
+ const start = data.start_timestamp * 1000 - anchor
66
+ if (start < 0) return
67
+ recording.recordSpan({
68
+ name: (data.description || data.op || 'sentry.span').slice(
69
+ 0,
70
+ 128
71
+ ),
72
+ correlationId: data.trace_id,
73
+ timestampMs: start,
74
+ sourceSpanId: `sentry:${data.trace_id}:${data.span_id}`,
75
+ sourceParentSpanId: data.parent_span_id
76
+ ? `sentry:${data.trace_id}:${data.parent_span_id}`
77
+ : undefined,
78
+ durationMs: Math.max(
79
+ 0,
80
+ (data.timestamp - data.start_timestamp) * 1000
81
+ ),
82
+ outcome:
83
+ data.status && data.status !== 'ok' ? 'error' : 'success',
84
+ attributes: [
85
+ { key: 'source', value: 'sentry' },
86
+ { key: 'sentry.span_id', value: data.span_id },
87
+ {
88
+ key: 'sentry.parent_span_id',
89
+ value: data.parent_span_id || '',
90
+ },
91
+ ],
92
+ })
93
+ recordMeasurements(data, start, anchor)
94
+ } catch (error) {
95
+ reportError(error)
96
+ }
97
+ })
98
+ : undefined
99
+ return {
100
+ stop() {
101
+ unsubscribe?.()
102
+ },
103
+ async flush() {
104
+ if (options.exportSpans !== true) return
105
+ if (!sentry.getClient())
106
+ throw new Error('Initialize Sentry before exporting traces')
107
+ const spans: SpanEvent[] = []
108
+ let cursor = 0
109
+ while (true) {
110
+ const page = await recording.readEvents({
111
+ afterSequence: cursor,
112
+ limit: 1000,
113
+ })
114
+ spans.push(
115
+ ...page.spans.filter(
116
+ (s) =>
117
+ !s.attributes.some(
118
+ (a) => a.key === 'source' && a.value === 'sentry'
119
+ )
120
+ )
121
+ )
122
+ if (page.nextSequence === cursor) break
123
+ cursor = page.nextSequence
124
+ }
125
+ const ordered = orderSpans(spans)
126
+ const handles = new Map<
127
+ string,
128
+ ReturnType<typeof sentry.startInactiveSpan>
129
+ >()
130
+ const keys = new Set(options.exportedAttributeKeys ?? [])
131
+ for (let index = 0; index < ordered.length; index++) {
132
+ if (index > 0 && index % 250 === 0)
133
+ await new Promise<void>((resolve) => setTimeout(resolve, 0))
134
+ const span = ordered[index]!
135
+ const id = span.spanId
136
+ const parentSpan = handles.get(span.parentSpanId)
137
+ const exported = sentry.startInactiveSpan({
138
+ name: span.name,
139
+ op: 'nitro.trace',
140
+ startTime: (anchor + span.timestampMs) / 1000,
141
+ parentSpan: parentSpan ?? null,
142
+ forceTransaction: !parentSpan,
143
+ attributes: {
144
+ ...Object.fromEntries(
145
+ span.attributes
146
+ .filter((a) => keys.has(a.key))
147
+ .map((a) => [a.key, a.value])
148
+ ),
149
+ 'nitro.exported': true,
150
+ 'nitro.span_id': id,
151
+ 'nitro.parent_missing': Boolean(
152
+ span.parentSpanId && !parentSpan
153
+ ),
154
+ 'nitro.outcome': span.outcome,
155
+ },
156
+ })
157
+ exported.setStatus(
158
+ span.outcome === 'success'
159
+ ? { code: 1 }
160
+ : { code: 2, message: span.outcome }
161
+ )
162
+ handles.set(id, exported)
163
+ }
164
+ for (let index = ordered.length - 1; index >= 0; index--) {
165
+ const span = ordered[index]!
166
+ handles
167
+ .get(span.spanId)!
168
+ .end((anchor + span.timestampMs + span.durationMs) / 1000)
169
+ if (index > 0 && index % 250 === 0)
170
+ await new Promise<void>((resolve) => setTimeout(resolve, 0))
171
+ }
172
+ },
173
+ }
174
+ },
175
+ }
176
+ }
@@ -0,0 +1,58 @@
1
+ import type { Recording } from '../specs/Recording.nitro'
2
+ import type { PluginHandle, TracePlugin } from './TracePlugin'
3
+ /** Start optional integrations. Failures are isolated; stop waits for all cleanup. */
4
+ export function startPlugins(
5
+ recording: Recording,
6
+ plugins: readonly TracePlugin[],
7
+ onError: (error: unknown) => void
8
+ ): PluginHandle {
9
+ const handles: PluginHandle[] = []
10
+ const ids = new Set<string>()
11
+ for (const plugin of plugins) {
12
+ if (ids.has(plugin.id))
13
+ throw new Error(`Duplicate tracing plugin: ${plugin.id}`)
14
+ ids.add(plugin.id)
15
+ }
16
+ const reportError = (error: unknown) => {
17
+ try {
18
+ onError(error)
19
+ } catch {
20
+ /* Error reporters cannot break capture. */
21
+ }
22
+ }
23
+ for (const plugin of plugins) {
24
+ try {
25
+ handles.push(plugin.start({ recording, reportError }))
26
+ } catch (error) {
27
+ reportError(error)
28
+ }
29
+ }
30
+ let stopping: Promise<void> | undefined
31
+ return {
32
+ stop() {
33
+ if (!stopping)
34
+ stopping = (async () => {
35
+ for (const handle of [...handles].reverse()) {
36
+ try {
37
+ await handle.stop()
38
+ } catch (error) {
39
+ reportError(error)
40
+ }
41
+ }
42
+ try {
43
+ recording.stop()
44
+ } catch (error) {
45
+ reportError(error)
46
+ }
47
+ for (const handle of handles) {
48
+ try {
49
+ await handle.flush?.()
50
+ } catch (error) {
51
+ reportError(error)
52
+ }
53
+ }
54
+ })()
55
+ return stopping
56
+ },
57
+ }
58
+ }