Skip to content

Commit 50a33bd

Browse files
authored
Merge pull request #512 from code-payments/chore/breadcrumbs
chore: instrument breadcrumbs for core flows
2 parents 458fd64 + 35aaf8e commit 50a33bd

15 files changed

Lines changed: 225 additions & 24 deletions

File tree

api/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ dependencies {
7474
implementation(Libs.rxjava)
7575
implementation(Libs.kotlinx_coroutines_core)
7676
implementation(Libs.kotlinx_serialization_json)
77+
implementation(Libs.kotlinx_datetime)
7778
implementation(Libs.inject)
7879

7980
implementation(Libs.grpc_okhttp)

api/src/main/java/com/getcode/model/Kin.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,13 @@ data class Kin(val quarks: Long): Value {
6363
}
6464
}
6565

66-
fun min(a: Kin, b: Kin): Kin {
66+
private fun min(a: Kin, b: Kin): Kin {
6767
if (a.quarks > b.quarks) {
6868
return b
6969
}
7070

7171
return a
72-
}
72+
}
73+
74+
val Kin.description: String
75+
get() = "K ${toKinTruncating().quarks} ${fractionalQuarks()}"

api/src/main/java/com/getcode/network/client/Client_Transaction.kt

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ import com.getcode.solana.keys.base58
3131
import com.getcode.solana.organizer.GiftCardAccount
3232
import com.getcode.solana.organizer.Organizer
3333
import com.getcode.solana.organizer.Relationship
34+
import com.getcode.utils.TraceType
3435
import com.getcode.utils.flowInterval
36+
import com.getcode.utils.trace
3537
import io.reactivex.rxjava3.core.Completable
3638
import io.reactivex.rxjava3.core.Single
3739
import io.reactivex.rxjava3.schedulers.Schedulers
@@ -337,15 +339,20 @@ fun Client.withdrawExternally(
337339
Completable.complete()
338340
}.doOnComplete {
339341
Timber.d(steps.joinToString("\n"))
340-
}
342+
}.concatWith(
341343
// 6. Execute withdrawal
342-
.concatWith(
343-
withdraw(
344-
amount = amount,
345-
organizer = organizer,
346-
destination = destination
347-
)
344+
withdraw(
345+
amount = amount,
346+
organizer = organizer,
347+
destination = destination
348348
)
349+
).doOnComplete {
350+
trace(
351+
tag = "Trx",
352+
message = "Withdraw completed",
353+
type = TraceType.Process
354+
)
355+
}
349356
}
350357

351358
private fun Client.withdraw(
@@ -517,6 +524,13 @@ fun Client.receiveFromPrimaryIfWithinLimits(organizer: Organizer): Completable {
517524
}
518525
}
519526
.ignoreElement()
527+
.andThen {
528+
trace(
529+
tag = "Trx",
530+
message = "Received from primary",
531+
type = TraceType.Process
532+
)
533+
}
520534
.andThen { fetchLimits(true) }
521535
}
522536

api/src/main/java/com/getcode/network/client/TransactionReceiver.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,24 @@ class TransactionReceiver @Inject constructor(
7676
organizer = organizer
7777
).blockingGet()
7878

79+
trace(
80+
tag = "Trx",
81+
message = "Received from relationship",
82+
type = TraceType.Process,
83+
metadata = {
84+
"domain" to relationship.domain.relationshipHost
85+
"kin" to relationship.partialBalance
86+
}
87+
)
88+
7989
receivedTotal += relationship.partialBalance
8090

91+
trace(
92+
tag = "Trx",
93+
message = "Received from incoming",
94+
type = TraceType.Process
95+
)
96+
8197
if (intent is IntentPublicTransfer) {
8298
setTray(organizer, intent.resultTray)
8399
}

api/src/main/java/com/getcode/network/exchange/Exchange.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.getcode.network.core.NetworkOracle
1111
import com.getcode.network.repository.PrefRepository
1212
import com.getcode.utils.ErrorUtils
1313
import com.getcode.utils.TraceType
14+
import com.getcode.utils.format
1415
import com.getcode.utils.trace
1516
import kotlinx.coroutines.CoroutineScope
1617
import kotlinx.coroutines.Dispatchers
@@ -23,6 +24,7 @@ import kotlinx.coroutines.flow.mapNotNull
2324
import kotlinx.coroutines.flow.onEach
2425
import kotlinx.coroutines.launch
2526
import kotlinx.coroutines.suspendCancellableCoroutine
27+
import kotlinx.datetime.Instant
2628
import timber.log.Timber
2729
import java.util.Date
2830
import javax.inject.Inject
@@ -249,6 +251,14 @@ class CodeExchange @Inject constructor(
249251
rates.rateForUsd()!!
250252
}
251253

254+
trace(tag = "Background",
255+
message = "Updated rates",
256+
type = TraceType.Process,
257+
metadata = {
258+
"date" to Instant.fromEpochMilliseconds(rates.dateMillis).format("yyyy-MM-dd HH:mm:ss")
259+
}
260+
)
261+
252262
}
253263

254264
@OptIn(ExperimentalTime::class)

api/src/main/java/com/getcode/network/repository/TransactionRepository.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,17 @@ class TransactionRepository @Inject constructor(
584584
.doOnSuccess {
585585
setLimits(it)
586586
setMaximumDeposit(it.maxDeposit)
587+
trace(
588+
tag = "Trx",
589+
message = "Fetched limits",
590+
type = TraceType.Process,
591+
metadata = {
592+
val sendLimit = it.sendLimitFor(CurrencyCode.USD)
593+
if (sendLimit != null) {
594+
"limitNextTx" to sendLimit
595+
}
596+
}
597+
)
587598
}
588599
.doOnError(ErrorUtils::handleError)
589600
.toFlowable()

api/src/main/java/com/getcode/solana/organizer/Organizer.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import com.getcode.model.Kin
88
import com.getcode.model.unusable
99
import com.getcode.network.repository.getPublicKeyBase58
1010
import com.getcode.solana.keys.*
11+
import com.getcode.utils.TraceType
1112
import com.getcode.utils.timedTrace
13+
import com.getcode.utils.trace
1214
import timber.log.Timber
1315

1416
class Organizer(
@@ -59,6 +61,15 @@ class Organizer(
5961
this.accountInfos = infos
6062
tray.createRelationships(infos)
6163
propagateBalances()
64+
65+
trace(
66+
tag = "Organizer",
67+
message = "Fetched account infos",
68+
type = TraceType.Process,
69+
metadata = {
70+
"tray" to tray.reportableRepresentation()
71+
}
72+
)
6273
}
6374

6475
fun getAccountInfo() = accountInfos

api/src/main/java/com/getcode/solana/organizer/Tray.kt

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
package com.getcode.solana.organizer
22

3-
import android.content.Context
43
import com.getcode.crypt.DerivePath
54
import com.getcode.crypt.DerivedKey
65
import com.getcode.crypt.MnemonicPhrase
76
import com.getcode.model.AccountInfo
87
import com.getcode.model.Domain
98
import com.getcode.model.Kin
109
import com.getcode.model.RelationshipBox
10+
import com.getcode.model.description
1111
import com.getcode.solana.keys.PublicKey
12+
import com.getcode.solana.keys.base58
1213
import com.getcode.utils.TraceType
13-
import com.getcode.utils.timedTrace
14+
import com.getcode.utils.padded
1415
import com.getcode.utils.trace
1516
import kotlin.math.min
1617

@@ -866,6 +867,29 @@ class Tray(
866867
return container
867868
}
868869

870+
fun reportableRepresentation(): List<String> {
871+
return listOf(
872+
string(named = "Primary ", partialAccount = owner),
873+
string(named = "Incoming ", partialAccount = incoming),
874+
string(named = "Outgoing ", partialAccount = outgoing),
875+
string("1 ", slot = slot(SlotType.Bucket1)),
876+
string("10 ", slot = slot(SlotType.Bucket10)),
877+
string("100 ", slot = slot(SlotType.Bucket100)),
878+
string("1k ", slot = slot(SlotType.Bucket1k)),
879+
string("10k ", slot = slot(SlotType.Bucket10k)),
880+
string("100k ", slot = slot(SlotType.Bucket100k)),
881+
string("1m ", slot = slot(SlotType.Bucket1m)),
882+
)
883+
}
884+
885+
private fun string(named: String, partialAccount: PartialAccount): String {
886+
return "$named ${partialAccount.getCluster().vaultPublicKey.base58().padded(44)}) ${partialAccount.partialBalance.description}"
887+
}
888+
889+
private fun string(named: String, slot: Slot): String {
890+
return "$named ${slot.getCluster().vaultPublicKey.base58().padded(44)}) ${slot.partialBalance.description}"
891+
}
892+
869893
override fun equals(other: Any?): Boolean {
870894
if (this === other) return true
871895
if (javaClass != other?.javaClass) return false

app/src/main/java/com/getcode/util/Instant.kt renamed to api/src/main/java/com/getcode/utils/Instant.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.getcode.util
1+
package com.getcode.utils
22

33
import kotlinx.datetime.DatePeriod
44
import kotlinx.datetime.DateTimeUnit
@@ -9,6 +9,9 @@ import kotlinx.datetime.atStartOfDayIn
99
import kotlinx.datetime.minus
1010
import kotlinx.datetime.plus
1111
import kotlinx.datetime.toLocalDateTime
12+
import java.text.SimpleDateFormat
13+
import java.util.Date
14+
import java.util.Locale
1215

1316
fun Instant.toLocalDate(timeZone: TimeZone = TimeZone.currentSystemDefault()) =
1417
toLocalDateTime(timeZone).date
@@ -18,4 +21,13 @@ fun LocalDate.atStartOfDay(tz: TimeZone = TimeZone.currentSystemDefault()) = atS
1821
fun LocalDate.atEndOfDay(tz: TimeZone = TimeZone.currentSystemDefault()): Instant {
1922
val tomorrowAtMidnight = ((this + DatePeriod(days = 1)).atStartOfDayIn(tz))
2023
return tomorrowAtMidnight.minus(value = 1, unit = DateTimeUnit.NANOSECOND)
24+
}
25+
26+
fun Instant.format(format: String = "yyyy-MM-dd"): String {
27+
val epoch = this.toEpochMilliseconds()
28+
29+
val formatter = SimpleDateFormat(format, Locale.getDefault())
30+
val date = Date(epoch)
31+
32+
return formatter.format(date)
2133
}

api/src/main/java/com/getcode/utils/Logging.kt

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,25 +67,28 @@ fun trace(
6767
message: String,
6868
tag: String? = null,
6969
type: TraceType = TraceType.Log,
70+
metadata: MetadataBuilder.() -> Unit = {},
7071
error: Throwable? = null
7172
) {
72-
val tree = if (tag == null) Timber else Timber.tag(tag)
73-
val traceMessage = if (tag == null) message else "trace : $message"
73+
val tagBlock = tag?.let { "[$it] " }
74+
val tree = if (tagBlock == null) Timber else Timber.tag(tagBlock)
7475

75-
tree.d(traceMessage)
76+
tree.d(message)
77+
78+
val metadataMap = metadata { metadata() }
7679

7780
if (Bugsnag.isStarted()) {
7881
val breadcrumb = if (tag != null) {
79-
"$tag | $traceMessage"
82+
"$tagBlock $message"
8083
} else {
81-
traceMessage
84+
message
8285
}
8386

8487
val breadcrumbType = type.toBugsnagBreadcrumbType()
8588
if (breadcrumbType != null) {
8689
Bugsnag.leaveBreadcrumb(
8790
breadcrumb,
88-
emptyMap(),
91+
metadataMap,
8992
breadcrumbType
9093
)
9194
}
@@ -98,6 +101,7 @@ fun <T> timedTrace(
98101
message: String,
99102
tag: String? = null,
100103
type: TraceType = TraceType.Log,
104+
metadata: MetadataBuilder.() -> Unit = {},
101105
error: Throwable? = null,
102106
block: () -> T
103107
): T {
@@ -107,7 +111,23 @@ fun <T> timedTrace(
107111
}
108112

109113
val newMessage = "$message took ${time.inWholeMilliseconds}ms"
110-
trace(newMessage, tag, type, error)
114+
trace(newMessage, tag, type, metadata, error)
111115

112116
return result
117+
}
118+
119+
class MetadataBuilder {
120+
private val map = mutableMapOf<String, Any>()
121+
122+
infix fun String.to(value: Any) {
123+
map[this] = value
124+
}
125+
126+
fun build(): Map<String, Any> = map
127+
}
128+
129+
fun metadata(block: MetadataBuilder.() -> Unit): Map<String, Any> {
130+
val builder = MetadataBuilder()
131+
builder.block()
132+
return builder.build()
113133
}

0 commit comments

Comments
 (0)