Telemetry baseline: durable analytics outbox, session tracking, UI-framework marker#987
Telemetry baseline: durable analytics outbox, session tracking, UI-framework marker#987johnml1135 wants to merge 4 commits into
Conversation
This comment has been minimized.
This comment has been minimized.
NUnit Tests 1 files ± 0 1 suites ±0 11m 19s ⏱️ +40s For more details on these failures, see this check. Results for commit 3a59a1c. ± Comparison against base commit 4509530. ♻️ This comment has been updated with latest results. |
d0c2288 to
c2b97ca
Compare
* durable analytics outbox * session baseline * forward-compatible UI-framework marker Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
c2b97ca to
f5b9386
Compare
Code review (verified against the code at the PR head)This is a large, unusually well-documented change ( HIGH — orphan-recovery path skips the network-availability check, so a stale
|
|
Fixed the HIGH issue in b98e841: added the same While verifying that fix, stress-testing (10 runs) turned up a second, real bug in the same file: Added Also verified the minor point re: |
This comment has been minimized.
This comment has been minimized.
|
(Follow-up: trimmed the two comments from the fix above down to one line each, per repo convention.) |
ProcessInflightFile (orphan recovery) skipped the NetworkAvailable check that ProcessOutboxFile relies on, so a stale .inflight file recovered while offline would be claimed, "delivered" via the fire-and-forget Analytics.Track (a silent no-op with no network), and deleted as if sent - permanently losing it. Add the same check before claiming an orphan. Also found, while verifying the above, that Flush_ConcurrentFlushCalls_DeliverEachEventExactlyOnce is genuinely flaky (~2/3 failure rate) on unmodified code: DeliverClaimedFile released the FileShare.None exclusive lock immediately after reading the file, before delivery and delete. A second racer whose RobustFile.Move spuriously "succeeds" against the same file (the .NET File.Move quirk documented in design.md D14) can win its own exclusive open in that window and deliver the same file again. Fix: hold the lock through delivery and release it only immediately before the terminal delete/restore. Stress-tested 10/10 green after the fix (was ~1/3 pass rate before). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
9eb16d3 to
3a59a1c
Compare
Summary
Establishes a FieldWorks-only telemetry baseline (openspec change:
telemetry-migration-baseline) ahead of the planned Avalonia UI migration, so there's a "legacy WinForms" usage/crash/session baseline to compare against later.AnalyticsOutbox(Src/Common/FwUtils/AnalyticsOutbox.cs): a durable local queue in front ofDesktopAnalytics.Analytics.Track/ReportException. Today, an event generated while offline is silently dropped (DesktopAnalyticsis fire-and-forget with no retry). The outbox persists each event as its own small JSON file, flushes on enqueue/startup/shutdown, and re-checks consent at flush time (not just enqueue time) so revoking consent stops even already-queued data from going out.Analytics.Track/ReportExceptioncall sites are migrated to the new facade (FieldWorks.cs,AreaListener.cs,TrackingHelper.cs,ConcordanceContainer.cs,ConfigureInterlinDialog.cs,ObtainProjectMethod.cs).Analytics.SetApplicationProperty("UiFramework", "WinForms")set once at startup (forward-compatible — a future Avalonia surface sets a different value on the same property), plusduration(dwell time) added to the existing throttledSwitchToToolevent.Bug found and fixed during implementation
While writing the concurrency test for the outbox's file-claim logic, I found that
System.IO.File.Movedoes not reliably report failure to the "losing" thread when two callers race an identical rename on .NET Framework — both can return without throwing, even though the OS performs exactly one physical rename. Verified with an isolated repro outside this codebase (~99% reproducible across hundreds of trials; confirmed via on-disk state that only one physical rename ever occurs, so the underlying filesystem operation is correct — the wrapper's exception surfacing for the loser is not). This invalidated the original "atomic rename = exclusive claim" design. Fixed by following the rename with aFileShare.Noneexclusive open as the real, OS-enforced single-owner check (documented as design.md D14). This was caught byAnalyticsOutboxTests.Flush_ConcurrentFlushCalls_DeliverEachEventExactlyOnce, which failed intermittently (11-20 deliveries instead of 10) before the fix, and now passes deterministically.Test plan
.\build.ps1— full managed build succeeds, no native rebuild triggered..\test.ps1onFwUtilsTests— 390/390 pass, including 15 newAnalyticsOutboxTests(consent gating, FIFO delivery, cap eviction by count/age, claim-race exactly-once delivery, orphan recovery with staleness gating, delivery-failure rollback)..\test.ps1onLexTextDllTests,FieldWorksTests,ITextDllTests— zero regressions from the call-site migration.FieldWorks.cs) orSwitchToTooldwell-time computation (AreaListener.cs) — both are blocked on cross-assembly access toAnalyticsOutbox's test seams (currentlyInternalsVisibleToonly coversFwUtilsTests). Documented as a follow-up intasks.md§3.6/§4.6.UiFrameworkapplication property actually attaches to outgoing events — that's internal to theDesktopAnalytics/Mixpanel client.tasks.md§4.2.%LocalAppData%\SIL\FieldWorks\Analytics\Outbox\, reconnect, relaunch, confirm the directory empties).tasks.md§5.3.OkToPingBasicUsageData) still gates all telemetry, including already-queued-but-unflushed events.tasks.md§5.4.🤖 Generated with Claude Code
This change is