expo-observe 58.0.0 → 58.0.3

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 (320) hide show
  1. package/.npmignore +5 -0
  2. package/CHANGELOG.md +14 -0
  3. package/android/src/main/java/expo/modules/observe/Event.kt +10 -0
  4. package/android/src/main/java/expo/modules/observe/EventDispatcher.kt +26 -0
  5. package/android/src/main/java/expo/modules/observe/ObservabilityBackgroundWorker.kt +5 -4
  6. package/android/src/main/java/expo/modules/observe/ObservabilityManager.kt +97 -0
  7. package/android/src/main/java/expo/modules/observe/ObserveModule.kt +1 -0
  8. package/android/src/main/java/expo/modules/observe/OpenTelemetry.kt +161 -1
  9. package/android/src/main/java/expo/modules/observe/SpanDispatchOutcome.kt +43 -0
  10. package/build/module.web.d.ts +1 -1
  11. package/build/types.d.ts +2 -1
  12. package/build/types.js.map +1 -1
  13. package/expo-module.config.json +1 -1
  14. package/ios/DispatchSerializer.swift +40 -0
  15. package/ios/Observability.swift +133 -20
  16. package/ios/OpenTelemetry.swift +150 -6
  17. package/ios/SpanDispatchLoop.swift +112 -0
  18. package/ios/Tests/DispatchLoopTests.swift +6 -1
  19. package/ios/Tests/DispatchSerializerTests.swift +100 -0
  20. package/ios/Tests/DispatchUtilsNextCursorTests.swift +6 -1
  21. package/ios/Tests/DispatchUtilsRetryGateTests.swift +6 -1
  22. package/ios/Tests/OTSpanTests.swift +283 -0
  23. package/ios/Tests/ObservabilityClassifyResponseTests.swift +3 -0
  24. package/ios/Tests/SpanDispatchLoopTests.swift +201 -0
  25. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3-sources.jar +0 -0
  26. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3-sources.jar.md5 +1 -0
  27. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3-sources.jar.sha1 +1 -0
  28. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3-sources.jar.sha256 +1 -0
  29. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3-sources.jar.sha512 +1 -0
  30. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.aar +0 -0
  31. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.aar.md5 +1 -0
  32. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.aar.sha1 +1 -0
  33. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.aar.sha256 +1 -0
  34. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.aar.sha512 +1 -0
  35. package/{android/build/publications/release/module.json → local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.module} +23 -23
  36. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.module.md5 +1 -0
  37. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.module.sha1 +1 -0
  38. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.module.sha256 +1 -0
  39. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.module.sha512 +1 -0
  40. package/{android/build/publications/release/pom-default.xml → local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.pom} +2 -2
  41. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.pom.md5 +1 -0
  42. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.pom.sha1 +1 -0
  43. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.pom.sha256 +1 -0
  44. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.3/expo.modules.observe-58.0.3.pom.sha512 +1 -0
  45. package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml +4 -4
  46. package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.md5 +1 -1
  47. package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.sha1 +1 -1
  48. package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.sha256 +1 -1
  49. package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.sha512 +1 -1
  50. package/package.json +4 -4
  51. package/src/types.ts +2 -1
  52. package/android/build/generated/source/buildConfig/release/expo/modules/observe/BuildConfig.java +0 -12
  53. package/android/build/intermediates/aapt_friendly_merged_manifests/release/processReleaseManifest/aapt/AndroidManifest.xml +0 -7
  54. package/android/build/intermediates/aapt_friendly_merged_manifests/release/processReleaseManifest/aapt/output-metadata.json +0 -18
  55. package/android/build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar +0 -0
  56. package/android/build/intermediates/aar_metadata/release/writeReleaseAarMetadata/aar-metadata.properties +0 -7
  57. package/android/build/intermediates/annotation_processor_list/release/javaPreCompileRelease/annotationProcessors.json +0 -1
  58. package/android/build/intermediates/annotations_typedef_file/release/extractReleaseAnnotations/typedefs.txt +0 -0
  59. package/android/build/intermediates/compile_r_class_jar/release/generateReleaseRFile/R.jar +0 -0
  60. package/android/build/intermediates/compile_symbol_list/release/generateReleaseRFile/R.txt +0 -0
  61. package/android/build/intermediates/incremental/mergeReleaseAssets/merger.xml +0 -2
  62. package/android/build/intermediates/incremental/mergeReleaseJniLibFolders/merger.xml +0 -2
  63. package/android/build/intermediates/incremental/release/packageReleaseResources/compile-file-map.properties +0 -1
  64. package/android/build/intermediates/incremental/release/packageReleaseResources/merger.xml +0 -2
  65. package/android/build/intermediates/incremental/release-mergeJavaRes/merge-state +0 -0
  66. package/android/build/intermediates/java_res/release/processReleaseJavaRes/out/META-INF/expo-observe_release.kotlin_module +0 -0
  67. package/android/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/expo/modules/observe/BuildConfig.class +0 -0
  68. package/android/build/intermediates/local_only_symbol_list/release/parseReleaseLocalResources/R-def.txt +0 -2
  69. package/android/build/intermediates/manifest_merge_blame_file/release/processReleaseManifest/manifest-merger-blame-release-report.txt +0 -9
  70. package/android/build/intermediates/merged_java_res/release/mergeReleaseJavaResource/feature-expo-observe.jar +0 -0
  71. package/android/build/intermediates/merged_manifest/release/processReleaseManifest/AndroidManifest.xml +0 -7
  72. package/android/build/intermediates/nested_resources_validation_report/release/generateReleaseResources/nestedResourcesValidationReport.txt +0 -1
  73. package/android/build/intermediates/source_jar/release/release-sources.jar +0 -0
  74. package/android/build/intermediates/symbol_list_with_package_name/release/generateReleaseRFile/package-aware-r.txt +0 -1
  75. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  76. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
  77. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
  78. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
  79. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  80. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
  81. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +0 -0
  82. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
  83. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  84. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
  85. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
  86. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
  87. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  88. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +0 -0
  89. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  90. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  91. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
  92. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
  93. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
  94. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
  95. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +0 -0
  96. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab +0 -0
  97. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream +0 -0
  98. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream.len +0 -0
  99. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.len +0 -0
  100. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.values.at +0 -0
  101. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i +0 -0
  102. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i.len +0 -0
  103. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  104. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  105. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
  106. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
  107. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
  108. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  109. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +0 -0
  110. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab +0 -0
  111. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream +0 -0
  112. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len +0 -0
  113. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.len +0 -0
  114. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.values.at +0 -0
  115. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i +0 -0
  116. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i.len +0 -0
  117. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
  118. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  119. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
  120. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
  121. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values +0 -0
  122. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  123. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.s +0 -1
  124. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  125. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +0 -0
  126. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  127. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
  128. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
  129. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
  130. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  131. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
  132. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +0 -0
  133. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
  134. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +0 -0
  135. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +0 -0
  136. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +0 -0
  137. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
  138. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +0 -0
  139. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +0 -0
  140. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
  141. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
  142. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +0 -0
  143. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +0 -0
  144. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
  145. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
  146. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +0 -0
  147. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/counters.tab +0 -2
  148. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
  149. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
  150. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
  151. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
  152. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
  153. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
  154. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +0 -0
  155. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
  156. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
  157. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
  158. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
  159. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
  160. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
  161. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +0 -0
  162. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  163. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  164. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  165. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
  166. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  167. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  168. package/android/build/kotlin/compileReleaseKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len +0 -0
  169. package/android/build/kotlin/compileReleaseKotlin/cacheable/last-build.bin +0 -0
  170. package/android/build/kotlin/compileReleaseKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
  171. package/android/build/outputs/aar/expo-observe-release.aar +0 -0
  172. package/android/build/outputs/logs/manifest-merger-release-report.txt +0 -14
  173. package/android/build/tmp/compileReleaseJavaWithJavac/previous-compilation-data.bin +0 -0
  174. package/android/build/tmp/kotlin-classes/release/META-INF/expo-observe_release.kotlin_module +0 -0
  175. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/BaseObservabilityManager$Companion.class +0 -0
  176. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/BaseObservabilityManager$cleanup$1.class +0 -0
  177. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/BaseObservabilityManager$dispatchUnsentLogs$1.class +0 -0
  178. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/BaseObservabilityManager$dispatchUnsentMetrics$1.class +0 -0
  179. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/BaseObservabilityManager.class +0 -0
  180. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/BundleDefaults$__Pika.class +0 -0
  181. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/BundleDefaults.class +0 -0
  182. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Config$__Pika.class +0 -0
  183. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Config.class +0 -0
  184. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ConstantsKt.class +0 -0
  185. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/CursorRepairKt$repairCursorIfStale$1.class +0 -0
  186. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/CursorRepairKt$repairLogCursorIfStale$4.class +0 -0
  187. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/CursorRepairKt$repairMetricCursorIfStale$4.class +0 -0
  188. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/CursorRepairKt.class +0 -0
  189. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchResult$NonRetryableFailure.class +0 -0
  190. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchResult$PartialSuccess.class +0 -0
  191. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchResult$PayloadTooLarge.class +0 -0
  192. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchResult$RetryableFailure.class +0 -0
  193. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchResult$Success.class +0 -0
  194. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchResult.class +0 -0
  195. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchUtils$RetryGateState$Companion.class +0 -0
  196. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchUtils$RetryGateState.class +0 -0
  197. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/DispatchUtils.class +0 -0
  198. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/EASMetric$$serializer.class +0 -0
  199. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/EASMetric$Companion.class +0 -0
  200. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/EASMetric.class +0 -0
  201. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Event$$serializer.class +0 -0
  202. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Event$Companion.class +0 -0
  203. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Event.class +0 -0
  204. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/EventDispatcher.class +0 -0
  205. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ExpoManifest.class +0 -0
  206. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ExpoManifestKt.class +0 -0
  207. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/LogEvent$$serializer.class +0 -0
  208. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/LogEvent$Companion.class +0 -0
  209. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/LogEvent.class +0 -0
  210. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Metadata$$serializer.class +0 -0
  211. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Metadata$AppUpdatesInfo$$serializer.class +0 -0
  212. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Metadata$AppUpdatesInfo$Companion.class +0 -0
  213. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Metadata$AppUpdatesInfo.class +0 -0
  214. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Metadata$Companion.class +0 -0
  215. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/Metadata.class +0 -0
  216. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue$Arr.class +0 -0
  217. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue$Bln.class +0 -0
  218. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue$Companion.class +0 -0
  219. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue$Dbl.class +0 -0
  220. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue$Int64.class +0 -0
  221. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue$KvList.class +0 -0
  222. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue$Str.class +0 -0
  223. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValue.class +0 -0
  224. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAnyValueSerializer.class +0 -0
  225. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAttribute$$serializer.class +0 -0
  226. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAttribute$Companion.class +0 -0
  227. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTAttribute.class +0 -0
  228. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTDataPoint$$serializer.class +0 -0
  229. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTDataPoint$Companion.class +0 -0
  230. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTDataPoint.class +0 -0
  231. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTEvent$$serializer.class +0 -0
  232. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTEvent$Companion.class +0 -0
  233. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTEvent.class +0 -0
  234. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTGauge$$serializer.class +0 -0
  235. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTGauge$Companion.class +0 -0
  236. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTGauge.class +0 -0
  237. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTKeyValue$$serializer.class +0 -0
  238. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTKeyValue$Companion.class +0 -0
  239. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTKeyValue.class +0 -0
  240. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTLogRecord$$serializer.class +0 -0
  241. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTLogRecord$Companion.class +0 -0
  242. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTLogRecord.class +0 -0
  243. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTLogsRequestBody$$serializer.class +0 -0
  244. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTLogsRequestBody$Companion.class +0 -0
  245. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTLogsRequestBody.class +0 -0
  246. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTMetadata$$serializer.class +0 -0
  247. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTMetadata$Companion.class +0 -0
  248. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTMetadata.class +0 -0
  249. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTMetric$$serializer.class +0 -0
  250. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTMetric$Companion.class +0 -0
  251. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTMetric.class +0 -0
  252. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTPartialSuccess$$serializer.class +0 -0
  253. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTPartialSuccess$Companion.class +0 -0
  254. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTPartialSuccess.class +0 -0
  255. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTRequestBody$$serializer.class +0 -0
  256. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTRequestBody$Companion.class +0 -0
  257. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTRequestBody.class +0 -0
  258. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTResourceLogs$$serializer.class +0 -0
  259. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTResourceLogs$Companion.class +0 -0
  260. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTResourceLogs.class +0 -0
  261. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScope$$serializer.class +0 -0
  262. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScope$Companion.class +0 -0
  263. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScope.class +0 -0
  264. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScopeLogs$$serializer.class +0 -0
  265. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScopeLogs$Companion.class +0 -0
  266. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScopeLogs.class +0 -0
  267. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScopeMetrics$$serializer.class +0 -0
  268. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScopeMetrics$Companion.class +0 -0
  269. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTScopeMetrics.class +0 -0
  270. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTServiceResponse$$serializer.class +0 -0
  271. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTServiceResponse$Companion.class +0 -0
  272. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTServiceResponse.class +0 -0
  273. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTStringValue$$serializer.class +0 -0
  274. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTStringValue$Companion.class +0 -0
  275. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OTStringValue.class +0 -0
  276. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObservabilityBackgroundWorker$Companion.class +0 -0
  277. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObservabilityBackgroundWorker$doWork$1.class +0 -0
  278. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObservabilityBackgroundWorker.class +0 -0
  279. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObservabilityManager.class +0 -0
  280. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$Constant$1.class +0 -0
  281. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$Coroutine$1.class +0 -0
  282. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$Function$1.class +0 -0
  283. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$Function$2.class +0 -0
  284. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$Function$3.class +0 -0
  285. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$Function$4.class +0 -0
  286. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$FunctionWithoutArgs$1.class +0 -0
  287. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$OnActivityEntersBackground$1.class +0 -0
  288. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule$definition$lambda$9$$inlined$OnCreate$1.class +0 -0
  289. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObserveModule.class +0 -0
  290. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObservePreferences.class +0 -0
  291. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/ObservePreferencesKt.class +0 -0
  292. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/OpenTelemetryKt.class +0 -0
  293. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/PersistedBundleDefaults$$serializer.class +0 -0
  294. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/PersistedBundleDefaults$Companion.class +0 -0
  295. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/PersistedBundleDefaults.class +0 -0
  296. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/PersistedConfig$$serializer.class +0 -0
  297. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/PersistedConfig$Companion.class +0 -0
  298. package/android/build/tmp/kotlin-classes/release/expo/modules/observe/PersistedConfig.class +0 -0
  299. package/android/build/tmp/publishReleasePublicationToNPMPackageRepository/module-maven-metadata.xml +0 -13
  300. package/android/build/tmp/sourceReleaseJar/MANIFEST.MF +0 -2
  301. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0-sources.jar +0 -0
  302. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0-sources.jar.md5 +0 -1
  303. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0-sources.jar.sha1 +0 -1
  304. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0-sources.jar.sha256 +0 -1
  305. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0-sources.jar.sha512 +0 -1
  306. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.aar +0 -0
  307. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.aar.md5 +0 -1
  308. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.aar.sha1 +0 -1
  309. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.aar.sha256 +0 -1
  310. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.aar.sha512 +0 -1
  311. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.module +0 -154
  312. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.module.md5 +0 -1
  313. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.module.sha1 +0 -1
  314. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.module.sha256 +0 -1
  315. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.module.sha512 +0 -1
  316. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.pom +0 -106
  317. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.pom.md5 +0 -1
  318. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.pom.sha1 +0 -1
  319. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.pom.sha256 +0 -1
  320. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.0/expo.modules.observe-58.0.0.pom.sha512 +0 -1
package/.npmignore CHANGED
@@ -20,3 +20,8 @@ __rsc_tests__
20
20
  # Ensure native dirs aren't excluded by root .gitignore
21
21
  !/ios
22
22
  !/android
23
+
24
+ # Exclude native build output (pnpm pack ignores nested .gitignore files)
25
+ .cxx/
26
+ .gradle/
27
+ /android/build/
package/CHANGELOG.md CHANGED
@@ -10,6 +10,18 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 58.0.3 — 2026-09-14
14
+
15
+ _This version does not introduce any user-facing changes._
16
+
17
+ ## 58.0.2 — 2026-09-14
18
+
19
+ _This version does not introduce any user-facing changes._
20
+
21
+ ## 58.0.1 — 2026-09-14
22
+
23
+ _This version does not introduce any user-facing changes._
24
+
13
25
  ## 58.0.0 — 2026-09-10
14
26
 
15
27
  ### 🎉 New features
@@ -19,10 +31,12 @@
19
31
  - Add `reportError` to report caught, non-fatal errors from your own `try`/`catch` blocks. ([#47871](https://github.com/expo/expo/pull/47871) by [@tsapeta](https://github.com/tsapeta))
20
32
  - Add an `errorHandlingEnabled` option to `configure` to opt out of recording unhandled JavaScript errors. ([#48506](https://github.com/expo/expo/pull/48506) by [@tsapeta](https://github.com/tsapeta))
21
33
  - Add `Observe.clientId`, the EAS client id recorded on every event, so apps can correlate Observe data with other services. ([#49599](https://github.com/expo/expo/pull/49599) by [@kadikraman](https://github.com/kadikraman))
34
+ - Export network requests as OTLP traces. ([#48883](https://github.com/expo/expo/pull/48883) by [@tsapeta](https://github.com/tsapeta))
22
35
 
23
36
  ### 🐛 Bug fixes
24
37
 
25
38
  - Fix OTel date conversion ([#48161](https://github.com/expo/expo/pull/48161) by [@Ubax](https://github.com/Ubax))
39
+ - [iOS] Stop overlapping dispatches from sending the same pending rows twice. `dispatchEvents()` now waits for a dispatch already in progress instead of overlapping with it. ([#48883](https://github.com/expo/expo/pull/48883) by [@tsapeta](https://github.com/tsapeta))
26
40
  - [Android] Explicitly enable `buildFeatures.buildConfig`, required by AGP 9. ([#47729](https://github.com/expo/expo/pull/47729) by [@gabrieldonadel](https://github.com/gabrieldonadel))
27
41
 
28
42
  ### 💡 Others
@@ -145,3 +145,13 @@ data class Event(
145
145
  val metrics: List<EASMetric>,
146
146
  val logs: List<LogEvent> = emptyList()
147
147
  )
148
+
149
+ /**
150
+ * One session's spans paired with the event carrying that session's resource metadata, ready
151
+ * for the traces dispatch. Spans travel separately from `Event` because they aren't part of
152
+ * the payload shape the metrics/logs signals share.
153
+ */
154
+ data class SpanBatch(
155
+ val event: Event,
156
+ val spans: List<OTSpan>
157
+ )
@@ -27,6 +27,8 @@ class EventDispatcher(
27
27
 
28
28
  private fun logsEndpointUrl(): String = "$baseProjectUrl/v1/logs"
29
29
 
30
+ private fun tracesEndpointUrl(): String = "$baseProjectUrl/v1/traces"
31
+
30
32
  /**
31
33
  * POSTs `events` to the metrics endpoint and classifies the response per the OTLP retry
32
34
  * spec. Non-throwing: payload-encoding errors collapse to `NonRetryable` (same bytes will
@@ -76,6 +78,30 @@ class EventDispatcher(
76
78
  executePost(continuation, logsEndpointUrl(), body)
77
79
  }
78
80
 
81
+ /**
82
+ * Dispatches span batches to `{baseUrl}/{projectId}/v1/traces`. Each batch carries one
83
+ * session's spans plus the event holding that session's resource metadata.
84
+ */
85
+ suspend fun dispatchSpans(batches: List<SpanBatch>): DispatchResult =
86
+ suspendCancellableCoroutine { continuation ->
87
+ if (batches.isEmpty()) {
88
+ // Empty input isn't a server response - signal success.
89
+ continuation.resume(DispatchResult.Success)
90
+ return@suspendCancellableCoroutine Unit
91
+ }
92
+ val easId = EASClientID(context).uuid.toString()
93
+ val body = try {
94
+ OTTracesRequestBody(
95
+ resourceSpans = batches.map { it.event.toOTResourceSpans(easId, it.spans) }
96
+ ).toJson(prettyPrint = true)
97
+ } catch (e: Exception) {
98
+ Log.w(OBSERVE_TAG, "Encoding traces request body failed: ${e.message}")
99
+ continuation.resume(DispatchResult.NonRetryableFailure("encoding error: ${e.message}"))
100
+ return@suspendCancellableCoroutine Unit
101
+ }
102
+ executePost(continuation, tracesEndpointUrl(), body)
103
+ }
104
+
79
105
  private fun executePost(
80
106
  continuation: kotlinx.coroutines.CancellableContinuation<DispatchResult>,
81
107
  endpointUrl: String,
@@ -18,7 +18,7 @@ import androidx.work.workDataOf
18
18
  import expo.modules.appmetrics.storage.SessionManager
19
19
 
20
20
  /**
21
- * Background worker that dispatches stored metrics and logs to EAS Observe.
21
+ * Background worker that dispatches stored metrics, logs, and spans to EAS Observe.
22
22
  */
23
23
  class ObservabilityBackgroundWorker(
24
24
  context: Context,
@@ -72,10 +72,11 @@ class ObservabilityBackgroundWorker(
72
72
  observabilityManager.cleanup()
73
73
  observabilityManager.dispatchUnsentMetrics()
74
74
  observabilityManager.dispatchUnsentLogs()
75
- Log.d(OBSERVE_TAG, "Successfully dispatched unsent metrics and logs")
75
+ observabilityManager.dispatchUnsentSpans()
76
+ Log.d(OBSERVE_TAG, "Successfully dispatched unsent metrics, logs, and spans")
76
77
  Result.success()
77
78
  } catch (e: Exception) {
78
- Log.e(OBSERVE_TAG, "Failed to dispatch metrics", e)
79
+ Log.e(OBSERVE_TAG, "Failed to dispatch metrics, logs, and spans", e)
79
80
  // Retry with exponential backoff
80
81
  Result.retry()
81
82
  }
@@ -108,7 +109,7 @@ class ObservabilityBackgroundWorker(
108
109
  .getInstance(context)
109
110
  .enqueueUniqueWork(
110
111
  WORK_NAME,
111
- // Keep an in-flight dispatch; cancelling it can duplicate a request the server received.
112
+ // Keep an in-flight dispatch; canceling it can duplicate a request the server received.
112
113
  ExistingWorkPolicy.KEEP,
113
114
  periodicWork
114
115
  )
@@ -4,6 +4,7 @@ import android.content.Context
4
4
  import android.util.Log
5
5
  import expo.modules.easclient.EASClientID
6
6
  import expo.modules.appmetrics.storage.SessionManager
7
+ import expo.modules.appmetrics.storage.Span
7
8
  import expo.modules.appmetrics.utils.TimeUtils
8
9
  import expo.modules.interfaces.constants.ConstantsInterface
9
10
  import kotlinx.coroutines.currentCoroutineContext
@@ -49,6 +50,10 @@ class ObservabilityManager(
49
50
  baseManager.dispatchUnsentLogs()
50
51
  }
51
52
 
53
+ suspend fun dispatchUnsentSpans() {
54
+ baseManager.dispatchUnsentSpans()
55
+ }
56
+
52
57
  fun scheduleBackgroundDispatch() {
53
58
  ObservabilityBackgroundWorker.scheduleBackgroundDispatch(
54
59
  context = context,
@@ -91,6 +96,7 @@ class BaseObservabilityManager(
91
96
  */
92
97
  private var metricsRetryGate: DispatchUtils.RetryGateState = DispatchUtils.RetryGateState.initial
93
98
  private var logsRetryGate: DispatchUtils.RetryGateState = DispatchUtils.RetryGateState.initial
99
+ private var spansRetryGate: DispatchUtils.RetryGateState = DispatchUtils.RetryGateState.initial
94
100
 
95
101
  /**
96
102
  * Returns true and logs when an active retry gate suppresses this dispatch round. Called
@@ -276,6 +282,90 @@ class BaseObservabilityManager(
276
282
  }
277
283
  }
278
284
 
285
+ /**
286
+ * Dispatches persisted spans to `/v1/traces`.
287
+ *
288
+ * Unlike metrics and logs there is no persisted cursor yet: a consumed (or deliberately
289
+ * dropped) batch is deleted outright and the table itself acts as the queue. Rows survive
290
+ * on a retryable failure and go out on the next dispatch. Batches are chunked at the
291
+ * server's per-request span limit.
292
+ */
293
+ suspend fun dispatchUnsentSpans(): Unit = spansDispatchMutex.withLock {
294
+ if (retryGateBlocks(spansRetryGate, "spans")) {
295
+ return
296
+ }
297
+
298
+ if (!shouldDispatch()) {
299
+ // Mirrors metrics/logs advancing the cursor past rows they won't send.
300
+ sessionManager.getMaxSpanId()?.let { sessionManager.deleteSpansUpTo(it) }
301
+ return
302
+ }
303
+
304
+ // Rows are read in id order one server-sized chunk at a time; a chunk that fails retryably
305
+ // stops the loop and leaves its rows (and everything after them) for the next dispatch. An
306
+ // oversized chunk is split in half and retried, so one huge span doesn't drop its whole
307
+ // chunk.
308
+ var readCursor = -1L
309
+ val chunks = ArrayDeque<List<Span>>()
310
+ while (currentCoroutineContext().isActive) {
311
+ if (chunks.isEmpty()) {
312
+ val nextChunk = sessionManager.getSpans(readCursor, MAX_SPANS_PER_REQUEST)
313
+ if (nextChunk.isEmpty()) {
314
+ return
315
+ }
316
+ readCursor = nextChunk.last().id
317
+ chunks.addLast(nextChunk)
318
+ }
319
+ val chunk = chunks.removeFirst()
320
+ val spansBySessionId = chunk.groupBy { it.sessionId }
321
+ // Batched because an offline backlog spans many sessions, and each halving retry would
322
+ // otherwise re-run every lookup.
323
+ val sessions = sessionManager.getSessions(spansBySessionId.keys).associateBy { it.id }
324
+ val batches = spansBySessionId.mapNotNull { (sessionId, spans) ->
325
+ // Spans whose session row no longer exists are skipped, because their rows are about to be
326
+ // deleted below anyway.
327
+ val session = sessions[sessionId] ?: return@mapNotNull null
328
+ SpanBatch(
329
+ event = Event(metadata = Metadata.fromSessionMetadata(session), metrics = emptyList()),
330
+ spans = spans.map { it.toOTSpan() }
331
+ )
332
+ }
333
+ val highestId = chunk.last().id
334
+ if (batches.isEmpty()) {
335
+ sessionManager.deleteSpansUpTo(highestId)
336
+ continue
337
+ }
338
+ val result = eventDispatcher.dispatchSpans(batches)
339
+ spansRetryGate = nextGate(spansRetryGate, result)
340
+ when (result) {
341
+ is DispatchResult.PartialSuccess ->
342
+ Log.w(
343
+ OBSERVE_TAG,
344
+ "Partial success on batch of ${chunk.size} span(s): " +
345
+ "server rejected ${result.partial.rejectedCount} " +
346
+ "(${result.partial.errorMessage ?: "no error message"})"
347
+ )
348
+ is DispatchResult.NonRetryableFailure ->
349
+ Log.w(OBSERVE_TAG, "Dropping batch of ${chunk.size} span(s): ${result.reason}")
350
+ DispatchResult.PayloadTooLarge -> if (chunk.size == 1) {
351
+ Log.w(OBSERVE_TAG, "Dropping span that exceeds the server's payload limit")
352
+ }
353
+ DispatchResult.Success, is DispatchResult.RetryableFailure -> Unit
354
+ }
355
+ when (spanDispatchDisposition(result, chunk.size)) {
356
+ SpanDispatchDisposition.HALVE_AND_RETRY -> {
357
+ // `toList()` rather than the `subList` views: nested views would keep the whole
358
+ // original chunk (up to 512 rows with their JSON blobs) reachable until every
359
+ // descendant had been processed. Matches iOS, which materializes with `Array`.
360
+ chunks.addFirst(chunk.subList(chunk.size / 2, chunk.size).toList())
361
+ chunks.addFirst(chunk.subList(0, chunk.size / 2).toList())
362
+ }
363
+ SpanDispatchDisposition.DELETE_AND_CONTINUE -> sessionManager.deleteSpansUpTo(highestId)
364
+ SpanDispatchDisposition.KEEP_AND_STOP -> return
365
+ }
366
+ }
367
+ }
368
+
279
369
  private fun isInSample(): Boolean {
280
370
  val rate = ObservePreferences.getConfig(context)?.sampleRate ?: return true
281
371
  val clamped = rate.coerceIn(0.0, 1.0)
@@ -303,8 +393,15 @@ class BaseObservabilityManager(
303
393
  }
304
394
 
305
395
  companion object {
396
+ /**
397
+ * Maximum spans per request accepted by the ingestion endpoint; it rejects everything past
398
+ * this count within one POST, so larger backlogs are sent as sequential chunks.
399
+ */
400
+ const val MAX_SPANS_PER_REQUEST = 512
401
+
306
402
  // Serialize foreground and background dispatches without blocking the other signal.
307
403
  private val metricsDispatchMutex = Mutex()
308
404
  private val logsDispatchMutex = Mutex()
405
+ private val spansDispatchMutex = Mutex()
309
406
  }
310
407
  }
@@ -66,6 +66,7 @@ class ObserveModule : Module() {
66
66
  AsyncFunction("dispatchEvents") Coroutine { ->
67
67
  observabilityManager.dispatchUnsentMetrics()
68
68
  observabilityManager.dispatchUnsentLogs()
69
+ observabilityManager.dispatchUnsentSpans()
69
70
  }
70
71
 
71
72
  Function("configure") { config: Config ->
@@ -6,6 +6,7 @@ import expo.modules.appmetrics.logevents.Severity
6
6
  import expo.modules.appmetrics.utils.TimeUtils.timestampToDateNS
7
7
  import kotlinx.serialization.Serializable
8
8
  import kotlinx.serialization.json.Json
9
+ import kotlinx.serialization.json.JsonArray
9
10
  import kotlinx.serialization.json.JsonElement
10
11
  import kotlinx.serialization.json.JsonObject
11
12
  import kotlinx.serialization.json.JsonPrimitive
@@ -134,6 +135,59 @@ data class OTResourceLogs(
134
135
  val schemaUrl: String
135
136
  )
136
137
 
138
+ /**
139
+ * One OTLP span, per https://opentelemetry.io/docs/concepts/signals/traces/#spans.
140
+ *
141
+ * Nullable fields with defaults (`parentSpanId`, `status`) rely on kotlinx-serialization omitting
142
+ * defaults: the ingestion endpoint rejects a span whose present `parentSpanId` isn't valid hex, so
143
+ * a root span must omit the key entirely rather than send null or "".
144
+ */
145
+ @Serializable
146
+ data class OTSpan(
147
+ val traceId: String,
148
+ val spanId: String,
149
+ val parentSpanId: String? = null,
150
+ val name: String,
151
+ val kind: Int,
152
+ val startTimeUnixNano: Long,
153
+ val endTimeUnixNano: Long,
154
+ val attributes: List<OTAttribute>,
155
+ val events: List<OTSpanEvent>,
156
+ val droppedEventsCount: Int = 0,
157
+ val status: OTSpanStatus? = null
158
+ )
159
+
160
+ /** `Span.Event` per the OTLP proto, used for redirect hops. */
161
+ @Serializable
162
+ data class OTSpanEvent(
163
+ val timeUnixNano: Long,
164
+ val name: String,
165
+ val attributes: List<OTAttribute>
166
+ )
167
+
168
+ /**
169
+ * `Status` per the OTLP proto. Only attached when the span represents a failure; a successful
170
+ * client span stays UNSET by omitting the status entirely, per the semantic conventions.
171
+ */
172
+ @Serializable
173
+ data class OTSpanStatus(
174
+ val code: Int,
175
+ val message: String? = null
176
+ )
177
+
178
+ @Serializable
179
+ data class OTScopeSpans(
180
+ val scope: OTScope,
181
+ val spans: List<OTSpan>
182
+ )
183
+
184
+ @Serializable
185
+ data class OTResourceSpans(
186
+ val resource: OTMetadata,
187
+ val scopeSpans: List<OTScopeSpans>,
188
+ val schemaUrl: String
189
+ )
190
+
137
191
  // MARK: -- Request body for Open Telemetry events
138
192
 
139
193
  @Serializable
@@ -160,6 +214,18 @@ data class OTLogsRequestBody(
160
214
  }
161
215
  }
162
216
 
217
+ @Serializable
218
+ data class OTTracesRequestBody(
219
+ val resourceSpans: List<OTResourceSpans>
220
+ ) {
221
+ fun toJson(prettyPrint: Boolean = false): String {
222
+ val json = Json {
223
+ this.prettyPrint = prettyPrint
224
+ }
225
+ return json.encodeToString(serializer(), this)
226
+ }
227
+ }
228
+
163
229
  // MARK: -- Response shapes
164
230
 
165
231
  /**
@@ -177,10 +243,11 @@ data class OTLogsRequestBody(
177
243
  data class OTPartialSuccess(
178
244
  val rejectedDataPoints: Int? = null,
179
245
  val rejectedLogRecords: Int? = null,
246
+ val rejectedSpans: Int? = null,
180
247
  val errorMessage: String? = null
181
248
  ) {
182
249
  val rejectedCount: Int
183
- get() = (rejectedDataPoints ?: 0) + (rejectedLogRecords ?: 0)
250
+ get() = (rejectedDataPoints ?: 0) + (rejectedLogRecords ?: 0) + (rejectedSpans ?: 0)
184
251
  }
185
252
 
186
253
  @Serializable
@@ -399,6 +466,99 @@ fun Event.toOTResourceLogs(easClientId: String): OTResourceLogs {
399
466
  )
400
467
  }
401
468
 
469
+ /**
470
+ * Wraps already-mapped spans in this event's resource envelope. Spans are passed in rather than
471
+ * derived from the event because they come from `spans` rows, which are not part of the `Event`
472
+ * payload shape the metrics/logs signals share.
473
+ */
474
+ fun Event.toOTResourceSpans(easClientId: String, spans: List<OTSpan>): OTResourceSpans {
475
+ return OTResourceSpans(
476
+ resource = toOTMetadata(easClientId),
477
+ scopeSpans = listOf(
478
+ OTScopeSpans(
479
+ scope = OTScope(name = "expo-observe", version = BuildConfig.EXPO_OBSERVE_VERSION),
480
+ spans = spans
481
+ )
482
+ ),
483
+ schemaUrl = SEMCONV_SCHEMA_URL
484
+ )
485
+ }
486
+
487
+ /**
488
+ * Maximum span events the ingestion endpoint keeps per span; anything past it is counted as
489
+ * dropped server-side, so the SDK truncates locally and reports the loss itself instead of
490
+ * shipping payload that is guaranteed to be discarded.
491
+ */
492
+ private const val MAX_SPAN_EVENT_COUNT = 32
493
+
494
+ /**
495
+ * Converts a persisted unix-epoch millisecond timestamp to nanoseconds, saturating instead of
496
+ * overflowing: a corrupt row or a device clock set far into the future must never wrap into a
497
+ * negative timestamp.
498
+ */
499
+ private fun spanNanosecondsFromMilliseconds(milliseconds: Long): Long {
500
+ val maxRepresentableMs = Long.MAX_VALUE / 1_000_000
501
+ return milliseconds.coerceIn(0, maxRepresentableMs) * 1_000_000
502
+ }
503
+
504
+ /**
505
+ * Maps one persisted span row onto the OTLP wire shape. The mapping is producer-agnostic:
506
+ * attributes and events were shaped by whichever producer recorded the row (per its own
507
+ * semantic conventions), and this conversion only handles the generic concerns: timestamps,
508
+ * the session attribute, the event cap, and the JSON-to-`AnyValue` typing. Mirrors the iOS
509
+ * `SpanRow.toOTSpan`.
510
+ */
511
+ fun expo.modules.appmetrics.storage.Span.toOTSpan(): OTSpan {
512
+ // Millisecond precision matches the backend's DateTime64(3) storage. A clock adjustment
513
+ // mid-span can invert the two wall-clock timestamps, and the server rejects a span whose end
514
+ // precedes its start, so the end clamps to the start.
515
+ val startNs = spanNanosecondsFromMilliseconds(startTimestampMs)
516
+ val endNs = maxOf(startNs, spanNanosecondsFromMilliseconds(endTimestampMs))
517
+
518
+ val otAttributes = mutableListOf(
519
+ OTAttribute.of(key = "session.id", rawValue = sessionId)
520
+ )
521
+ attributes
522
+ ?.let { json -> runCatching { Json.decodeFromString<JsonObject>(json) }.getOrNull() }
523
+ ?.let { obj -> otAttributes.addAll(otAttributesFromJsonObject(obj).first) }
524
+
525
+ // The ingestion endpoint keeps at most `MAX_SPAN_EVENT_COUNT` events per span and counts the
526
+ // rest as dropped; truncating locally reports the same loss without shipping payload that is
527
+ // guaranteed to be discarded.
528
+ val allEvents = events
529
+ ?.let { json -> runCatching { Json.decodeFromString<JsonArray>(json) }.getOrNull() }
530
+ ?: JsonArray(emptyList())
531
+ val otEvents = allEvents.take(MAX_SPAN_EVENT_COUNT).mapNotNull { element ->
532
+ val obj = element as? JsonObject ?: return@mapNotNull null
533
+ // The server drops nameless events anyway; skipping them locally keeps the payload honest.
534
+ val name = (obj["name"] as? JsonPrimitive)?.takeIf { it.isString }?.content
535
+ ?: return@mapNotNull null
536
+ val eventAttributes = (obj["attributes"] as? JsonObject)
537
+ ?.let { otAttributesFromJsonObject(it).first }
538
+ ?: emptyList()
539
+ // Producers may record a per-event timestamp; without one the event anchors to the span
540
+ // start, which keeps it inside the span window.
541
+ val timeNs = (obj["timeMs"] as? JsonPrimitive)?.longOrNull
542
+ ?.let { spanNanosecondsFromMilliseconds(it) }
543
+ ?: startNs
544
+ OTSpanEvent(timeUnixNano = timeNs, name = name, attributes = eventAttributes)
545
+ }
546
+
547
+ return OTSpan(
548
+ traceId = traceId,
549
+ spanId = spanId,
550
+ parentSpanId = parentSpanId,
551
+ name = name,
552
+ kind = kind,
553
+ startTimeUnixNano = startNs,
554
+ endTimeUnixNano = endNs,
555
+ attributes = otAttributes,
556
+ events = otEvents,
557
+ droppedEventsCount = maxOf(0, allEvents.size - MAX_SPAN_EVENT_COUNT),
558
+ status = statusCode?.let { OTSpanStatus(code = it, message = statusMessage) }
559
+ )
560
+ }
561
+
402
562
  internal fun otAnyValueFromJsonElement(element: JsonElement): OTAnyValue? {
403
563
  if (element is kotlinx.serialization.json.JsonNull) {
404
564
  return null
@@ -0,0 +1,43 @@
1
+ // Copyright 2025-present 650 Industries. All rights reserved.
2
+
3
+ package expo.modules.observe
4
+
5
+ /**
6
+ * What the span dispatch loop does with a chunk once the server has answered. Mirrors the iOS
7
+ * `SpanDispatchLoop.Disposition`.
8
+ *
9
+ * Spans deliberately do not reuse the metrics and logs loop: that one owns a persisted cursor and
10
+ * stops after a batch it cannot advance past, whereas spans have no cursor (the table is the
11
+ * queue) and delete a dropped chunk before continuing with the next one.
12
+ */
13
+ internal enum class SpanDispatchDisposition {
14
+ /** Delete the chunk's rows and continue with the next chunk. */
15
+ DELETE_AND_CONTINUE,
16
+
17
+ /** Leave every remaining row in place and stop, so the next dispatch retries them. */
18
+ KEEP_AND_STOP,
19
+
20
+ /** Too large to send but still splittable: retry the halves before anything after them. */
21
+ HALVE_AND_RETRY
22
+ }
23
+
24
+ /**
25
+ * Maps a send outcome onto what the loop does next. Pure, so every branch is assertable without
26
+ * a database, a network, or a coroutine scope.
27
+ *
28
+ * @param chunkCount how many spans were in the chunk that produced [result].
29
+ */
30
+ internal fun spanDispatchDisposition(result: DispatchResult, chunkCount: Int): SpanDispatchDisposition =
31
+ when (result) {
32
+ DispatchResult.Success, is DispatchResult.PartialSuccess ->
33
+ SpanDispatchDisposition.DELETE_AND_CONTINUE
34
+ is DispatchResult.RetryableFailure -> SpanDispatchDisposition.KEEP_AND_STOP
35
+ is DispatchResult.NonRetryableFailure -> SpanDispatchDisposition.DELETE_AND_CONTINUE
36
+ // A single row cannot be split, so it is dropped: keeping it would block every span behind
37
+ // it on every future dispatch.
38
+ DispatchResult.PayloadTooLarge -> if (chunkCount > 1) {
39
+ SpanDispatchDisposition.HALVE_AND_RETRY
40
+ } else {
41
+ SpanDispatchDisposition.DELETE_AND_CONTINUE
42
+ }
43
+ }
@@ -17,5 +17,5 @@ declare class ExpoObserveModule extends NativeModule<ObserveModuleEvents> implem
17
17
  isJsDev: boolean;
18
18
  }): void;
19
19
  }
20
- declare const _default: typeof ExpoObserveModule;
20
+ declare const _default: ExpoObserveModule;
21
21
  export default _default;
package/build/types.d.ts CHANGED
@@ -162,7 +162,8 @@ export declare class ObserveModule extends NativeModule<ObserveModuleEvents> {
162
162
  * this method to flush events manually, for example, during testing or to ensure events
163
163
  * are sent before a specific point.
164
164
  *
165
- * @returns A promise that resolves when the pending events have been dispatched.
165
+ * @returns A promise that resolves once the events have been dispatched. A call made while
166
+ * another dispatch is running waits for it to finish first.
166
167
  *
167
168
  * @example
168
169
  * ```ts
@@ -1 +1 @@
1
- {"version":3,"sources":["types.ts"],"sourcesContent":[null],"names":[],"mappings":"AAqIA,WAwJC"}
1
+ {"version":3,"sources":["types.ts"],"sourcesContent":[null],"names":[],"mappings":"AAqIA,WAyJC"}
@@ -9,7 +9,7 @@
9
9
  "publication": {
10
10
  "groupId": "expo.modules.observe",
11
11
  "artifactId": "expo.modules.observe",
12
- "version": "58.0.0",
12
+ "version": "58.0.3",
13
13
  "repository": "local-maven-repo"
14
14
  }
15
15
  }
@@ -0,0 +1,40 @@
1
+ // Copyright 2025-present 650 Industries. All rights reserved.
2
+
3
+ import ExpoAppMetrics
4
+
5
+ /// Runs dispatch passes one at a time, so a later caller waits for the pass ahead of it instead
6
+ /// of overlapping with it.
7
+ ///
8
+ /// `ObservabilityManager` is actor-isolated but reentrant at every network `await`, so two
9
+ /// overlapping passes would read and send the same pending rows before either advanced its cursor
10
+ /// or deleted its batch. Serializing also lets a JS `dispatchEvents()` promise mean the queue was
11
+ /// drained, matching Android's per-signal mutexes.
12
+ @AppMetricsActor
13
+ internal final class DispatchSerializer {
14
+ private var inFlight: Task<Void, Never>?
15
+ private var generation = 0
16
+ private var inFlightGeneration = 0
17
+
18
+ /// Waits for any pass already running, then runs `work` as the new in-flight pass.
19
+ internal func run(_ work: @escaping () async -> Void) async {
20
+ // The predecessor is captured and awaited *inside* the new task, not before creating it.
21
+ // Awaiting first would suspend, letting two callers both observe a finished pass and start
22
+ // work concurrently. Chaining publishes this task synchronously, so the next caller links
23
+ // behind it.
24
+ let predecessor = inFlight
25
+ generation += 1
26
+ let ownGeneration = generation
27
+ let task = Task {
28
+ await predecessor?.value
29
+ await work()
30
+ }
31
+ inFlight = task
32
+ inFlightGeneration = ownGeneration
33
+ await task.value
34
+ // Cleared only when still the current pass, so a caller resuming late cannot orphan the pass
35
+ // that replaced it.
36
+ if inFlightGeneration == ownGeneration {
37
+ inFlight = nil
38
+ }
39
+ }
40
+ }