expo-observe 56.0.5 → 56.0.6
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.
- package/android/src/main/java/expo/modules/observe/Event.kt +37 -1
- package/android/src/main/java/expo/modules/observe/EventDispatcher.kt +62 -27
- package/android/src/main/java/expo/modules/observe/OTAnyValueSerializer.kt +111 -0
- package/android/src/main/java/expo/modules/observe/ObservabilityBackgroundWorker.kt +5 -1
- package/android/src/main/java/expo/modules/observe/ObservabilityManager.kt +56 -0
- package/android/src/main/java/expo/modules/observe/ObserveModule.kt +4 -1
- package/android/src/main/java/expo/modules/observe/OpenTelemetry.kt +215 -14
- package/android/src/main/java/expo/modules/observe/storage/ObserveDatabase.kt +25 -1
- package/android/src/main/java/expo/modules/observe/storage/PendingLogsManager.kt +38 -0
- package/build/ObserveProvider.d.ts +3 -0
- package/build/ObserveProvider.d.ts.map +1 -0
- package/build/ObserveProvider.js +6 -0
- package/build/ObserveProvider.js.map +1 -0
- package/build/ObserveRoot.d.ts +11 -0
- package/build/ObserveRoot.d.ts.map +1 -0
- package/build/ObserveRoot.js +12 -0
- package/build/ObserveRoot.js.map +1 -0
- package/build/index.d.ts +3 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -1
- package/build/index.js.map +1 -1
- package/build/integrations/expo-router/index.d.ts +4 -0
- package/build/integrations/expo-router/index.d.ts.map +1 -0
- package/build/integrations/expo-router/index.js +4 -0
- package/build/integrations/expo-router/index.js.map +1 -0
- package/build/integrations/expo-router/init.d.ts +3 -0
- package/build/integrations/expo-router/init.d.ts.map +1 -0
- package/build/integrations/expo-router/init.js +6 -0
- package/build/integrations/expo-router/init.js.map +1 -0
- package/build/integrations/expo-router/router.d.ts +4 -0
- package/build/integrations/expo-router/router.d.ts.map +1 -0
- package/build/integrations/expo-router/router.js +12 -0
- package/build/integrations/expo-router/router.js.map +1 -0
- package/build/integrations/expo-router/useObserveForRouter.d.ts +5 -0
- package/build/integrations/expo-router/useObserveForRouter.d.ts.map +1 -0
- package/build/integrations/expo-router/useObserveForRouter.js +48 -0
- package/build/integrations/expo-router/useObserveForRouter.js.map +1 -0
- package/build/module.d.ts +2 -2
- package/build/module.d.ts.map +1 -1
- package/build/module.js +18 -1
- package/build/module.js.map +1 -1
- package/build/types.d.ts +8 -0
- package/build/types.d.ts.map +1 -1
- package/build/types.js.map +1 -1
- package/build/useObserve.d.ts +4 -0
- package/build/useObserve.d.ts.map +1 -0
- package/build/useObserve.js +9 -0
- package/build/useObserve.js.map +1 -0
- package/expo-module.config.json +1 -1
- package/jest.config.js +2 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6-sources.jar +0 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6-sources.jar.md5 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6-sources.jar.sha1 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6-sources.jar.sha256 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6-sources.jar.sha512 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.aar +0 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.aar.md5 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.aar.sha1 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.aar.sha256 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.aar.sha512 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/{56.0.5/expo.modules.observe-56.0.5.module → 56.0.6/expo.modules.observe-56.0.6.module} +23 -23
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.module.md5 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.module.sha1 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.module.sha256 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.module.sha512 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/{56.0.5/expo.modules.observe-56.0.5.pom → 56.0.6/expo.modules.observe-56.0.6.pom} +2 -2
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.pom.md5 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.pom.sha1 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.pom.sha256 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.pom.sha512 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml +4 -4
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.sha512 +1 -1
- package/package.json +14 -5
- package/src/ObserveProvider.tsx +5 -0
- package/src/ObserveRoot.tsx +26 -0
- package/src/index.ts +3 -1
- package/src/integrations/expo-router/index.ts +3 -0
- package/src/integrations/expo-router/init.ts +7 -0
- package/src/integrations/expo-router/router.ts +11 -0
- package/src/integrations/expo-router/useObserveForRouter.ts +65 -0
- package/src/module.ts +21 -2
- package/src/types.ts +8 -0
- package/src/useObserve.ts +10 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5-sources.jar +0 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5-sources.jar.md5 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5-sources.jar.sha1 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5-sources.jar.sha256 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5-sources.jar.sha512 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5.aar +0 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5.aar.md5 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5.aar.sha1 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5.aar.sha256 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5.aar.sha512 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5.module.md5 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5.module.sha1 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5.module.sha256 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5.module.sha512 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5.pom.md5 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5.pom.sha1 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5.pom.sha256 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.5/expo.modules.observe-56.0.5.pom.sha512 +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package expo.modules.observe
|
|
2
2
|
|
|
3
|
+
import expo.modules.appmetrics.storage.LogRecord
|
|
3
4
|
import expo.modules.appmetrics.storage.Metric
|
|
4
5
|
import expo.modules.appmetrics.storage.Session
|
|
5
6
|
import kotlinx.serialization.Serializable
|
|
@@ -104,8 +105,43 @@ data class EASMetric(
|
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Wire shape of a log event ready for dispatch. Distinct from the storage-side
|
|
110
|
+
* `LogRecord`: this form has the JSON `attributes` blob already parsed back
|
|
111
|
+
* into a structured object (so the OTel encoder can map values to typed
|
|
112
|
+
* `OTAnyValue`s) and drops storage-only columns like `logId`.
|
|
113
|
+
*/
|
|
114
|
+
@Serializable
|
|
115
|
+
data class LogEvent(
|
|
116
|
+
val sessionId: String,
|
|
117
|
+
val timestamp: String,
|
|
118
|
+
val name: String,
|
|
119
|
+
val body: String? = null,
|
|
120
|
+
val severity: String,
|
|
121
|
+
val attributes: JsonObject? = null,
|
|
122
|
+
val droppedAttributesCount: Int = 0
|
|
123
|
+
) {
|
|
124
|
+
companion object {
|
|
125
|
+
fun fromLogRecord(log: LogRecord): LogEvent =
|
|
126
|
+
LogEvent(
|
|
127
|
+
sessionId = log.sessionId,
|
|
128
|
+
timestamp = log.timestamp,
|
|
129
|
+
name = log.name,
|
|
130
|
+
body = log.body,
|
|
131
|
+
severity = log.severity,
|
|
132
|
+
// Stored as a JSON string; parse defensively, falling back to no
|
|
133
|
+
// attributes if the blob is somehow malformed.
|
|
134
|
+
attributes = log.attributes?.let {
|
|
135
|
+
runCatching { Json.decodeFromString<JsonObject>(it) }.getOrNull()
|
|
136
|
+
},
|
|
137
|
+
droppedAttributesCount = log.droppedAttributesCount
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
107
142
|
@Serializable
|
|
108
143
|
data class Event(
|
|
109
144
|
val metadata: Metadata,
|
|
110
|
-
val metrics: List<EASMetric
|
|
145
|
+
val metrics: List<EASMetric>,
|
|
146
|
+
val logs: List<LogEvent> = emptyList()
|
|
111
147
|
)
|
|
@@ -21,15 +21,18 @@ class EventDispatcher(
|
|
|
21
21
|
private val useOpenTelemetry: Boolean = false,
|
|
22
22
|
private val httpClient: OkHttpClient = OkHttpClient()
|
|
23
23
|
) {
|
|
24
|
-
private
|
|
25
|
-
|
|
24
|
+
private val baseProjectUrl: String
|
|
25
|
+
get() = when (baseUrl.endsWith("/")) {
|
|
26
26
|
true -> "${baseUrl}$projectId"
|
|
27
27
|
else -> "$baseUrl/$projectId"
|
|
28
28
|
}
|
|
29
|
-
return if (useOpenTelemetry) "$base/v1/metrics" else base
|
|
30
|
-
}
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
private fun metricsEndpointUrl(): String =
|
|
31
|
+
if (useOpenTelemetry) "$baseProjectUrl/v1/metrics" else baseProjectUrl
|
|
32
|
+
|
|
33
|
+
private fun logsEndpointUrl(): String = "$baseProjectUrl/v1/logs"
|
|
34
|
+
|
|
35
|
+
suspend fun dispatch(events: List<Event>): Boolean =
|
|
33
36
|
suspendCancellableCoroutine { continuation ->
|
|
34
37
|
if (events.isEmpty()) {
|
|
35
38
|
continuation.resume(false)
|
|
@@ -54,28 +57,7 @@ class EventDispatcher(
|
|
|
54
57
|
json.encodeToString(Payload.serializer(), payload)
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Log.d(TAG, "Sending events to $endpointUrl")
|
|
60
|
-
|
|
61
|
-
val request = Request
|
|
62
|
-
.Builder()
|
|
63
|
-
.url(endpointUrl)
|
|
64
|
-
.post(body.toRequestBody("application/json".toMediaType()))
|
|
65
|
-
.build()
|
|
66
|
-
|
|
67
|
-
Log.d(TAG, body)
|
|
68
|
-
|
|
69
|
-
val call = httpClient.newCall(request)
|
|
70
|
-
|
|
71
|
-
continuation.invokeOnCancellation {
|
|
72
|
-
call.cancel()
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
val response = call.execute()
|
|
76
|
-
Log.d(TAG, "Server responded with: ${response.body?.string()}")
|
|
77
|
-
|
|
78
|
-
continuation.resume(response.code in 200..299)
|
|
60
|
+
executePost(continuation, metricsEndpointUrl(), body)
|
|
79
61
|
} catch (e: Exception) {
|
|
80
62
|
Log.w(
|
|
81
63
|
TAG,
|
|
@@ -85,6 +67,59 @@ class EventDispatcher(
|
|
|
85
67
|
}
|
|
86
68
|
}
|
|
87
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Dispatches log records to `{baseUrl}/{projectId}/v1/logs`. Always uses the
|
|
72
|
+
* OTLP wire shape — there is no legacy logs endpoint.
|
|
73
|
+
*/
|
|
74
|
+
suspend fun dispatchLogs(events: List<Event>): Boolean =
|
|
75
|
+
suspendCancellableCoroutine { continuation ->
|
|
76
|
+
val easId = EASClientID(context).uuid.toString()
|
|
77
|
+
val resourceLogs = events
|
|
78
|
+
.filter { it.logs.isNotEmpty() }
|
|
79
|
+
.map { it.toOTResourceLogs(easId) }
|
|
80
|
+
if (resourceLogs.isEmpty()) {
|
|
81
|
+
continuation.resume(false)
|
|
82
|
+
return@suspendCancellableCoroutine Unit
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
val body = OTLogsRequestBody(resourceLogs = resourceLogs).toJson(prettyPrint = true)
|
|
86
|
+
executePost(continuation, logsEndpointUrl(), body)
|
|
87
|
+
} catch (e: Exception) {
|
|
88
|
+
Log.w(
|
|
89
|
+
TAG,
|
|
90
|
+
"Dispatching the logs has thrown an error: ${e.message}"
|
|
91
|
+
)
|
|
92
|
+
continuation.resumeWithException(e)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private fun executePost(
|
|
97
|
+
continuation: kotlinx.coroutines.CancellableContinuation<Boolean>,
|
|
98
|
+
endpointUrl: String,
|
|
99
|
+
body: String
|
|
100
|
+
) {
|
|
101
|
+
Log.d(TAG, "Sending events to $endpointUrl")
|
|
102
|
+
|
|
103
|
+
val request = Request
|
|
104
|
+
.Builder()
|
|
105
|
+
.url(endpointUrl)
|
|
106
|
+
.post(body.toRequestBody("application/json".toMediaType()))
|
|
107
|
+
.build()
|
|
108
|
+
|
|
109
|
+
Log.d(TAG, body)
|
|
110
|
+
|
|
111
|
+
val call = httpClient.newCall(request)
|
|
112
|
+
|
|
113
|
+
continuation.invokeOnCancellation {
|
|
114
|
+
call.cancel()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
val response = call.execute()
|
|
118
|
+
Log.d(TAG, "Server responded with: ${response.body?.string()}")
|
|
119
|
+
|
|
120
|
+
continuation.resume(response.code in 200..299)
|
|
121
|
+
}
|
|
122
|
+
|
|
88
123
|
companion object {
|
|
89
124
|
private const val TAG = "EasObserve"
|
|
90
125
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
package expo.modules.observe
|
|
2
|
+
|
|
3
|
+
import kotlinx.serialization.KSerializer
|
|
4
|
+
import kotlinx.serialization.builtins.ListSerializer
|
|
5
|
+
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
6
|
+
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
|
7
|
+
import kotlinx.serialization.encoding.Decoder
|
|
8
|
+
import kotlinx.serialization.encoding.Encoder
|
|
9
|
+
import kotlinx.serialization.json.JsonDecoder
|
|
10
|
+
import kotlinx.serialization.json.JsonElement
|
|
11
|
+
import kotlinx.serialization.json.JsonEncoder
|
|
12
|
+
import kotlinx.serialization.json.JsonPrimitive
|
|
13
|
+
import kotlinx.serialization.json.boolean
|
|
14
|
+
import kotlinx.serialization.json.booleanOrNull
|
|
15
|
+
import kotlinx.serialization.json.buildJsonObject
|
|
16
|
+
import kotlinx.serialization.json.contentOrNull
|
|
17
|
+
import kotlinx.serialization.json.doubleOrNull
|
|
18
|
+
import kotlinx.serialization.json.jsonArray
|
|
19
|
+
import kotlinx.serialization.json.jsonObject
|
|
20
|
+
import kotlinx.serialization.json.jsonPrimitive
|
|
21
|
+
import kotlinx.serialization.json.longOrNull
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Custom serializer that emits the OTLP-correct wire shape for `OTAnyValue`.
|
|
25
|
+
* Each variant becomes a single-key JSON object:
|
|
26
|
+
* - Str -> {"stringValue": "..."}
|
|
27
|
+
* - Int64 -> {"intValue": "42"} (string per OTLP int64 mapping)
|
|
28
|
+
* - Dbl -> {"doubleValue": 3.14}
|
|
29
|
+
* - Bln -> {"boolValue": true}
|
|
30
|
+
* - Arr -> {"arrayValue": {"values": [...]}}
|
|
31
|
+
* - KvList -> {"kvlistValue": {"values": [{"key": "...", "value": ...}]}}
|
|
32
|
+
*/
|
|
33
|
+
internal object OTAnyValueSerializer : KSerializer<OTAnyValue> {
|
|
34
|
+
override val descriptor: SerialDescriptor =
|
|
35
|
+
buildClassSerialDescriptor("expo.modules.observe.OTAnyValue")
|
|
36
|
+
|
|
37
|
+
override fun serialize(encoder: Encoder, value: OTAnyValue) {
|
|
38
|
+
val jsonEncoder = encoder as? JsonEncoder
|
|
39
|
+
?: error("OTAnyValue is only serializable to JSON (got ${encoder::class}).")
|
|
40
|
+
|
|
41
|
+
val element: JsonElement = when (value) {
|
|
42
|
+
is OTAnyValue.Str -> buildJsonObject { put("stringValue", JsonPrimitive(value.value)) }
|
|
43
|
+
is OTAnyValue.Int64 -> buildJsonObject { put("intValue", JsonPrimitive(value.value.toString())) }
|
|
44
|
+
is OTAnyValue.Dbl -> buildJsonObject { put("doubleValue", JsonPrimitive(value.value)) }
|
|
45
|
+
is OTAnyValue.Bln -> buildJsonObject { put("boolValue", JsonPrimitive(value.value)) }
|
|
46
|
+
is OTAnyValue.Arr -> buildJsonObject {
|
|
47
|
+
put(
|
|
48
|
+
"arrayValue",
|
|
49
|
+
buildJsonObject {
|
|
50
|
+
put(
|
|
51
|
+
"values",
|
|
52
|
+
jsonEncoder.json.encodeToJsonElement(
|
|
53
|
+
ListSerializer(OTAnyValueSerializer),
|
|
54
|
+
value.values
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
is OTAnyValue.KvList -> buildJsonObject {
|
|
61
|
+
put(
|
|
62
|
+
"kvlistValue",
|
|
63
|
+
buildJsonObject {
|
|
64
|
+
put(
|
|
65
|
+
"values",
|
|
66
|
+
jsonEncoder.json.encodeToJsonElement(
|
|
67
|
+
ListSerializer(OTKeyValue.serializer()),
|
|
68
|
+
value.values
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
jsonEncoder.encodeJsonElement(element)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override fun deserialize(decoder: Decoder): OTAnyValue {
|
|
80
|
+
val jsonDecoder = decoder as? JsonDecoder
|
|
81
|
+
?: error("OTAnyValue is only deserializable from JSON (got ${decoder::class}).")
|
|
82
|
+
val obj = jsonDecoder.decodeJsonElement().jsonObject
|
|
83
|
+
|
|
84
|
+
obj["stringValue"]?.let {
|
|
85
|
+
return OTAnyValue.Str(it.jsonPrimitive.contentOrNull ?: "")
|
|
86
|
+
}
|
|
87
|
+
obj["intValue"]?.let {
|
|
88
|
+
val parsed = it.jsonPrimitive.contentOrNull?.toLongOrNull()
|
|
89
|
+
?: it.jsonPrimitive.longOrNull
|
|
90
|
+
?: error("OTAnyValue.intValue could not be parsed as Long: $it")
|
|
91
|
+
return OTAnyValue.Int64(parsed)
|
|
92
|
+
}
|
|
93
|
+
obj["doubleValue"]?.let {
|
|
94
|
+
return OTAnyValue.Dbl(it.jsonPrimitive.doubleOrNull ?: error("OTAnyValue.doubleValue not a number: $it"))
|
|
95
|
+
}
|
|
96
|
+
obj["boolValue"]?.let {
|
|
97
|
+
return OTAnyValue.Bln(it.jsonPrimitive.booleanOrNull ?: it.jsonPrimitive.boolean)
|
|
98
|
+
}
|
|
99
|
+
obj["arrayValue"]?.let { arr ->
|
|
100
|
+
val values = arr.jsonObject["values"]?.jsonArray
|
|
101
|
+
?: error("OTAnyValue.arrayValue is missing `values`")
|
|
102
|
+
return OTAnyValue.Arr(values.map { jsonDecoder.json.decodeFromJsonElement(OTAnyValueSerializer, it) })
|
|
103
|
+
}
|
|
104
|
+
obj["kvlistValue"]?.let { kv ->
|
|
105
|
+
val values = kv.jsonObject["values"]?.jsonArray
|
|
106
|
+
?: error("OTAnyValue.kvlistValue is missing `values`")
|
|
107
|
+
return OTAnyValue.KvList(values.map { jsonDecoder.json.decodeFromJsonElement(OTKeyValue.serializer(), it) })
|
|
108
|
+
}
|
|
109
|
+
error("OTAnyValue has no recognized variant: $obj")
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -15,6 +15,7 @@ import androidx.work.OneTimeWorkRequestBuilder
|
|
|
15
15
|
import androidx.work.WorkManager
|
|
16
16
|
import androidx.work.WorkerParameters
|
|
17
17
|
import androidx.work.workDataOf
|
|
18
|
+
import expo.modules.observe.storage.PendingLogsManager
|
|
18
19
|
import expo.modules.observe.storage.PendingMetricsManager
|
|
19
20
|
import expo.modules.appmetrics.storage.SessionManager
|
|
20
21
|
|
|
@@ -43,12 +44,14 @@ class ObservabilityBackgroundWorker(
|
|
|
43
44
|
)
|
|
44
45
|
|
|
45
46
|
val pendingMetricsManager = PendingMetricsManager(context)
|
|
47
|
+
val pendingLogsManager = PendingLogsManager(context)
|
|
46
48
|
|
|
47
49
|
BaseObservabilityManager(
|
|
48
50
|
context = context,
|
|
49
51
|
projectId = projectId,
|
|
50
52
|
sessionManager = sessionManager,
|
|
51
53
|
pendingMetricsManager = pendingMetricsManager,
|
|
54
|
+
pendingLogsManager = pendingLogsManager,
|
|
52
55
|
baseUrl = baseUrl,
|
|
53
56
|
isDebugBuild = BuildConfig.DEBUG,
|
|
54
57
|
useOpenTelemetry = useOpenTelemetry
|
|
@@ -81,7 +84,8 @@ class ObservabilityBackgroundWorker(
|
|
|
81
84
|
// This also adds a side benefit of cleaning up even if dispatch fails
|
|
82
85
|
observabilityManager.cleanup()
|
|
83
86
|
observabilityManager.dispatchUnsentMetrics()
|
|
84
|
-
|
|
87
|
+
observabilityManager.dispatchUnsentLogs()
|
|
88
|
+
Log.d(OBSERVE_TAG, "Successfully dispatched unsent metrics and logs")
|
|
85
89
|
Result.success()
|
|
86
90
|
} catch (e: Exception) {
|
|
87
91
|
Log.e(OBSERVE_TAG, "Failed to dispatch metrics", e)
|
|
@@ -2,6 +2,7 @@ package expo.modules.observe
|
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import expo.modules.easclient.EASClientID
|
|
5
|
+
import expo.modules.observe.storage.PendingLogsManager
|
|
5
6
|
import expo.modules.observe.storage.PendingMetricsManager
|
|
6
7
|
import expo.modules.appmetrics.storage.SessionManager
|
|
7
8
|
import expo.modules.interfaces.constants.ConstantsInterface
|
|
@@ -30,11 +31,13 @@ class ObservabilityManager(
|
|
|
30
31
|
useOpenTelemetry = manifest.useOpenTelemetry
|
|
31
32
|
|
|
32
33
|
val pendingMetricsManager = PendingMetricsManager(context)
|
|
34
|
+
val pendingLogsManager = PendingLogsManager(context)
|
|
33
35
|
|
|
34
36
|
baseManager = BaseObservabilityManager(
|
|
35
37
|
context = context,
|
|
36
38
|
sessionManager = sessionManager,
|
|
37
39
|
pendingMetricsManager = pendingMetricsManager,
|
|
40
|
+
pendingLogsManager = pendingLogsManager,
|
|
38
41
|
projectId = projectId,
|
|
39
42
|
baseUrl = baseUrl,
|
|
40
43
|
isDebugBuild = BuildConfig.DEBUG,
|
|
@@ -44,12 +47,19 @@ class ObservabilityManager(
|
|
|
44
47
|
sessionManager.addMetricsInsertListener { metricIds ->
|
|
45
48
|
pendingMetricsManager.addPendingMetrics(metricIds)
|
|
46
49
|
}
|
|
50
|
+
sessionManager.addLogsInsertListener { logIds ->
|
|
51
|
+
pendingLogsManager.addPendingLogs(logIds)
|
|
52
|
+
}
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
suspend fun dispatchUnsentMetrics() {
|
|
50
56
|
baseManager.dispatchUnsentMetrics()
|
|
51
57
|
}
|
|
52
58
|
|
|
59
|
+
suspend fun dispatchUnsentLogs() {
|
|
60
|
+
baseManager.dispatchUnsentLogs()
|
|
61
|
+
}
|
|
62
|
+
|
|
53
63
|
fun scheduleBackgroundDispatch() {
|
|
54
64
|
ObservabilityBackgroundWorker.scheduleBackgroundDispatch(
|
|
55
65
|
context = context,
|
|
@@ -64,6 +74,7 @@ class BaseObservabilityManager(
|
|
|
64
74
|
private val context: Context,
|
|
65
75
|
private val sessionManager: SessionManager,
|
|
66
76
|
private val pendingMetricsManager: PendingMetricsManager,
|
|
77
|
+
private val pendingLogsManager: PendingLogsManager,
|
|
67
78
|
val projectId: String,
|
|
68
79
|
val baseUrl: String,
|
|
69
80
|
private val isDebugBuild: Boolean = false,
|
|
@@ -116,6 +127,49 @@ class BaseObservabilityManager(
|
|
|
116
127
|
}
|
|
117
128
|
}
|
|
118
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Dispatches log events to `/v1/logs`. Independent from the metrics path —
|
|
132
|
+
* a logs failure doesn't affect the metrics pending table and vice versa.
|
|
133
|
+
*/
|
|
134
|
+
suspend fun dispatchUnsentLogs() {
|
|
135
|
+
val pendingIds = pendingLogsManager.getAllPendingLogIds()
|
|
136
|
+
if (pendingIds.isEmpty()) {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!shouldDispatch()) {
|
|
141
|
+
pendingLogsManager.removePendingLogs(pendingIds)
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
val sessionsWithPendingLogs = sessionManager.getSessionsWithLogs(pendingIds)
|
|
146
|
+
|
|
147
|
+
// Clean up orphaned pending IDs (logs deleted from the `logs` table but
|
|
148
|
+
// still tracked in `pending_logs`).
|
|
149
|
+
val resolvedLogIds = sessionsWithPendingLogs.flatMap { it.logs }.map { it.logId }.toSet()
|
|
150
|
+
val orphanedIds = pendingIds.filter { it !in resolvedLogIds }
|
|
151
|
+
if (orphanedIds.isNotEmpty()) {
|
|
152
|
+
pendingLogsManager.removePendingLogs(orphanedIds)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (sessionsWithPendingLogs.isEmpty()) {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
val events = sessionsWithPendingLogs.map { sessionWithLogs ->
|
|
160
|
+
Event(
|
|
161
|
+
metadata = Metadata.fromSessionMetadata(sessionWithLogs.session),
|
|
162
|
+
metrics = emptyList(),
|
|
163
|
+
logs = sessionWithLogs.logs.map { LogEvent.fromLogRecord(it) }
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (eventDispatcher.dispatchLogs(events)) {
|
|
168
|
+
val dispatchedLogIds = sessionsWithPendingLogs.flatMap { it.logs }.map { it.logId }
|
|
169
|
+
pendingLogsManager.removePendingLogs(dispatchedLogIds)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
119
173
|
private fun isInSample(): Boolean {
|
|
120
174
|
val rate = ObservePreferences.getConfig(context)?.sampleRate ?: return true
|
|
121
175
|
val clamped = rate.coerceIn(0.0, 1.0)
|
|
@@ -136,7 +190,9 @@ class BaseObservabilityManager(
|
|
|
136
190
|
|
|
137
191
|
suspend fun cleanup() {
|
|
138
192
|
pendingMetricsManager.cleanupOldPendingMetrics()
|
|
193
|
+
pendingLogsManager.cleanupOldPendingLogs()
|
|
139
194
|
// TODO(@ubax): Move sessionManager.cleanupOldSessions out of eas observe
|
|
140
195
|
sessionManager.cleanupOldSessions()
|
|
196
|
+
sessionManager.cleanupOldLogs()
|
|
141
197
|
}
|
|
142
198
|
}
|
|
@@ -50,7 +50,10 @@ class ObserveModule : Module() {
|
|
|
50
50
|
observabilityManager.scheduleBackgroundDispatch()
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
AsyncFunction("dispatchEvents") Coroutine { ->
|
|
53
|
+
AsyncFunction("dispatchEvents") Coroutine { ->
|
|
54
|
+
observabilityManager.dispatchUnsentMetrics()
|
|
55
|
+
observabilityManager.dispatchUnsentLogs()
|
|
56
|
+
}
|
|
54
57
|
|
|
55
58
|
Function("configure") { config: Config ->
|
|
56
59
|
ObservePreferences.setConfig(
|