Skip to content

Commit 4e06a81

Browse files
committed
feat(error-tracking): record exception steps synchronously
1 parent 3b9fdb4 commit 4e06a81

4 files changed

Lines changed: 12 additions & 20 deletions

File tree

posthog/src/main/java/com/posthog/PostHog.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -710,16 +710,16 @@ public class PostHog private constructor(
710710
return
711711
}
712712

713-
// Capture the timestamp on the calling thread, before any dispatch.
714713
val timestamp = Date()
715714
val buffer = exceptionStepsBuffer ?: return
716715

717716
try {
718-
queueExecutor.execute {
719-
buffer.add(message, timestamp, properties)
720-
}
717+
// Record synchronously on the calling thread (no background dispatch): a step
718+
// recorded immediately before a crash must already be buffered when the
719+
// uncaught-exception handler captures it. The work is bounded and cheap.
720+
buffer.add(message, timestamp, properties)
721721
} catch (e: Throwable) {
722-
// recording must never throw into the host app (e.g. executor rejected after close)
722+
// recording must never throw into the host app
723723
config?.logger?.log("addExceptionStep has thrown an exception: $e.")
724724
}
725725
}

posthog/src/main/java/com/posthog/PostHogInterface.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,10 @@ public interface PostHogInterface : PostHogCoreInterface {
108108
* Recording never throws into the host app: an empty message is ignored with a
109109
* warning, and internal failures silently skip the step.
110110
*
111-
* Recording is asynchronous so it never blocks the caller: the step is buffered
112-
* on a background queue (the `$timestamp` is still captured at call time). A step
113-
* recorded immediately before a programmatic [captureException] in the same call
114-
* stack may therefore not yet be buffered when that exception is captured, and so
115-
* may be absent from its `$exception_steps`. Steps recorded earlier in the
116-
* session are unaffected.
111+
* Recording is synchronous: the `$timestamp` is captured at call time and the step
112+
* is normalized, byte-budget enforced, and buffered before this method returns, so a
113+
* step recorded immediately before an exception or crash is present when it is
114+
* captured. The work is bounded and cheap, adding negligible latency to the caller.
117115
*
118116
* @param message a non-empty description of the step
119117
* @param properties optional user-supplied properties (the reserved keys

posthog/src/main/java/com/posthog/internal/errortracking/PostHogExceptionStepsBuffer.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ internal class PostHogExceptionStepsBuffer(
2929
private class Entry(val step: Map<String, Any>, val bytes: Int)
3030

3131
/**
32-
* Normalizes and appends a step. [timestamp] is captured by the caller (on the
33-
* calling thread); this method is expected to run on the SDK's background queue.
32+
* Normalizes and appends a step synchronously on the calling thread, so a step
33+
* recorded just before a crash is buffered when the crash is captured. [timestamp]
34+
* is captured by the caller at call time. Thread-safe via [lock].
3435
*/
3536
fun add(
3637
message: String,

posthog/src/test/java/com/posthog/PostHogTest.kt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3394,9 +3394,6 @@ internal class PostHogTest {
33943394
sut.addExceptionStep("B")
33953395
sut.addExceptionStep("C")
33963396

3397-
// drain the queue so the buffered steps are recorded before capture reads them
3398-
queueExecutor.submit { }.get()
3399-
34003397
sut.captureException(RuntimeException("boom"))
34013398

34023399
queueExecutor.shutdownAndAwaitTermination()
@@ -3420,7 +3417,6 @@ internal class PostHogTest {
34203417
val sut = getSut(url.toString(), preloadFeatureFlags = false, reloadFeatureFlags = false)
34213418

34223419
sut.addExceptionStep("buffered")
3423-
queueExecutor.submit { }.get()
34243420

34253421
sut.captureException(
34263422
RuntimeException("boom"),
@@ -3447,13 +3443,11 @@ internal class PostHogTest {
34473443

34483444
sut.addExceptionStep("A")
34493445
sut.addExceptionStep("B")
3450-
queueExecutor.submit { }.get()
34513446
sut.captureException(RuntimeException("first"))
34523447

34533448
sut.reset()
34543449

34553450
sut.addExceptionStep("C")
3456-
queueExecutor.submit { }.get()
34573451
sut.captureException(RuntimeException("second"))
34583452

34593453
// flushAt is high so neither capture triggers a flush on its own; flush explicitly
@@ -3495,7 +3489,6 @@ internal class PostHogTest {
34953489
)
34963490

34973491
sut.addExceptionStep("A")
3498-
queueExecutor.submit { }.get()
34993492
sut.captureException(RuntimeException("boom"))
35003493

35013494
queueExecutor.shutdownAndAwaitTermination()

0 commit comments

Comments
 (0)