Repository navigation
Conversation
…n error Adds RumConfiguration.Builder.setSessionOnError and the remote configuration keys rum.sessionOnError / rum.sessionReplayOnError (absent keeps the init value; the console wins over init). A session the session sample rate does not keep is drawn as WITHHELD when the switch is on: it is assembled as usual but its events go to an in-memory WithheldEventWriter instead of the batch. Events are held after the event mappers have run and serialized, so an error dropped by a mapper releases nothing. The buffer keeps the last 60s, at most 64KiB and 200 non-view events plus 50 views (latest update per view, current view never evicted), evicting successful requests and long tasks first and errors last, newest first. The first error of the session releases the buffer after a per-session jitter of 0-3s (String.hashCode based), with the window frozen when the release is scheduled; views go first oldest to newest, then errors, then the rest. A JVM crash releases synchronously within the crash write, an oversized error is written on its own, and setForcedSession releases immediately. When the session expires, is renewed or stopped, an unreleased buffer is dropped and its id remembered (last four) so late events of that session are dropped too; a session that had errored is released instead. Withdrawn consent drops what is held. Withheld sessions report a session sample rate of 0, view events carry session.sampled_for_error, the session listener sees them as discarded and getCurrentSessionId answers null until release. The last view is still written locally, so a native crash or fatal ANR is reported at the next launch with the marker and rate 0. A console reset no longer ends a session kept on error while the rate is 0 and the switch is on; a switch change at rate 0 now resets the running session, and beforeSampling returning 0 turns the switch off.
… session errors Adds RumConfiguration.Builder.setSessionReplayOnError (remote key rum.sessionReplayOnError, latched per session at the draw). RUM now tells Session Replay, on the session bus message, whether the session's events are kept on error, the replay switch it was drawn under, and whether it has reported its error. Session Replay keeps its own replay draw and holds the records of two kinds of session in memory instead of writing them: a collected session whose replay the rate missed while the switch is on, and a session whose events are withheld, whichever way the replay draw went, since its replay has nothing to attach to until the events are released. The error releasing a replay-only session is judged after the event mappers, like the events' own; forcing the session releases too. Held records are cut only at full snapshots, keeping from the newest full snapshot at least a minute old, so the release spans the last minute across views and starts playable. View record counts and has_replay are only updated for records actually written, so nothing held and thrown away is ever claimed. A session that ends unreleased drops its records, and records of that session still queued are dropped too. View events carry session.sampled_for_error_replay for replays kept on error and session.sampled_for_replay, which counts a held replay whose events are held too.
Session Replay resources now go through the record writer, which holds them with the records of a withheld session and writes or drops them together; a resource hash is remembered as sent only once it is written. The held replay is released when the session's events actually are - after the jitter, by an explicit bus message from the event buffer - not when the error is first seen. Released events and the releasing error claim session.has_replay when their view has replay records (sent or held), counted by Session Replay apart from records_count. A release waiting for the jitter goes at once when the app leaves the foreground, and an oversized crash releases the history right after it. The release forwards every held detail, views only setting the order, so an error whose view was never held or was evicted still goes out. The held replay gains a 4MiB bound (images bounded apart), cut at full snapshots, and a release cut at a periodic full snapshot starts with the view's meta and focus records. sampled_for_replay is only reported for sessions in an on-error mode, and Session Replay leaves its feature context untouched when nothing is withheld, so views of customers who did not opt in are unchanged.
…n in a batch The upload filter keeps only the latest version of each view in a batch. It used to keep that version where it was written, so when a released error session's buffer (views first, oldest first, then errors and details) shared a batch with the live updates written right after the release - always the case for a crash, which updates the view's crash count - the views ended up last and out of start order. The intake builds the session out of the first view it sees. The latest version now takes the place of the view's first occurrence in the batch; which versions survive is unchanged.
…ur consent and never evict a crash Session Replay now keeps aside the replay of a session whose events were withheld until RUM says whether that session was released or thrown away, instead of discarding it as soon as the next session announces itself: RUM's word lands on the storage thread and can arrive after the next session has started. RUM sends an explicit discard for such a session, and a stopped session draining beside its successor no longer announces itself as current. Images captured while a replay is withheld are held across sessions and sent only with released records that show them, since the recorder captures an image once per process. Records and images captured while tracking consent is withdrawn are neither held nor sent, and what was held is dropped. The replay byte budget is enforced on every record. A crash is never evicted from the withheld event buffer, even when it does not fit alongside an earlier large error. A session kept on error is only ended by the emergency stop, not by a console rate leaving zero.
…rop buffers on consent withdrawal Session Replay keeps aside every replay whose session ended while held, including a collected session's replay held on error, until RUM says what became of it; RUM now reports the end of such a session too. Several ended sessions can wait at once, since storage may lag more than one session behind. A stopped session draining beside its successor no longer draws a session of its own, which ended the running session's buffer, and a late error of a stopped session no longer unmarks the running session's release. Session Replay handles RUM's announcements and fates under one lock. Both features now observe tracking consent directly: what was held in memory under a consent since withdrawn is dropped even when no event comes by to notice. A replay span over the byte budget is dropped rather than kept, a sent record flushes the held images it shows, and a view's meta and focus survive the consent drop so the resumed replay stays playable.
…withheld session's data A withheld session that ends without an error now also forgets the view it wrote locally for the native crash reporter, so a crash of a later uncollected session is not reported against it. Both writers remember more discarded sessions, since a stopped session keeps draining its requests while the sessions after it come and go. A stopped session keeps its own state for what it still drains instead of expiring. An SDK stop flushes a release still waiting for its jitter. Session Replay notes RUM's word on an ended session the moment it is given, so a released session is never the one thrown away when too many ended sessions wait, and a view's meta and focus survive the drop of a span over the byte budget. A view of a session whose replay was released claims the replay as soon as the records exist, so the final view of a stopped session does not replace the released one without it. The web view consumers leave the replay and the replay claim alone while the native replay of the session is withheld.
…t rate 0 on app-launch vitals The recorder captures each image once per process. An image held for a replay kept on error and then dropped unsent - evicted over the image budget or cleared when consent is withdrawn - was never captured again, so a replay released later showed a placeholder for it. The writer now tells the recorder what it dropped; the recorder forgets those ids and resolves such an image again from its drawable the next time it is shown, instead of answering from its caches. The app-launch vitals of a session kept on error report a session sample rate of 0, like every other event of such a session.
… view's replay markers once The core takes a feature out of its registry before stopping it, so the flush of a release still waiting for its jitter could not look the RUM scope up at stop time: the scope is kept from initialization. A view of a withheld session keeps its Session Replay entry when it completes, so it can still claim the replay held for it when the session is released; Session Replay drops the entry once nothing is sent or held for the view. A view's replay markers stay as resolved once they are true, so a late update written after another session took over the replay context still describes its own session. The replay switch alone no longer restarts a running session on an immediate configuration change: a replay draw is made once per session.
…eport a withheld session's last view however old A session already released holds nothing more once consent is withdrawn: its events go to the batch, where consent decides, instead of through the buffer, where a release granted later would have carried out what was collected while consent was withdrawn. An image first captured while consent is withdrawn is forgotten by the recorder like one dropped from the held store, so it is captured again; the forgotten ids are bounded, and only the view in progress keeps its start across a consent drop. More discarded sessions are remembered, since a request can outlive many sessions. The native crash reporter writes the last view of a session kept on error with the crash however old the view is: that session uploaded nothing before it crashed, so this view is the only one the intake will get.
…hing for a stopped session thrown away The SDK stop ends the session kept on error with it: released if it had reported its error, thrown away if not, along with the view it wrote locally - a crash after the stop must not be reported against a session that never errored. The write scope only queues that work, and the core shuts its executor down without draining it once the features are stopped, so the stop waits for the queue to get there, the way a crash does. A stopped session still draining its requests hands its views nothing to write with while it is withheld: it was thrown away at the stop, and remembering its id for a while could not cover a request that outlives dozens of later sessions. A stopped session that had errored is collected by then and keeps writing.
Settling the withheld session at the stop looked the write scope up synchronously, which waits on the context thread without a bound; a stop asked for from the RUM thread - a session listener, say - found that thread waiting on the RUM thread in turn, and the stop never returned, holding the SDK registry lock with it. The stop now queues the work through the ordinary write scope and waits, with a bound, for the persistence thread to have run it. A stop asked for from inside the SDK's own pipeline waits the bound out and goes on without the work, which is what it did before, minus the hang.
…sion Replay stops The features stop in no particular order. Session Replay only heard that a replay may go out when RUM actually released the events, after the jitter, and a stop in between left it stopping with the replay still held and nobody left to tell it. RUM now says so the moment the releasing error is written, Session Replay notes it, and its own stop writes every replay whose session reported an error, throws the rest away, and waits - with a bound - for that to have run.
… an oversized error after its views, and flush held replay on stop - A JVM crash releases the held events in the crash write, but the replay held in memory dies with the process. Released views and the crash claimed `session.has_replay` from the held records as well, so the session advertised a replay that never reached storage. On a crash, events now claim only the records already written. The same applies to the crash of a collected session whose replay only is withheld. - An error larger than the whole budget was written on its own ahead of the session's views, and alone if the app died during the jitter. The history is now released at once, with the error after its views. - The core unregisters a feature before stopping it, so Session Replay's stop could not find its own feature and never wrote the replay a released session had earned. The record writer now keeps the feature scope it found.
…ew contributes to a session kept on error - The release telemetry counted what the buffer held, not what storage accepted. It now also reports `buffer.write_failures`. - WebView events are not held while a session waits for its error, so an error raised in a WebView does not keep the session. Say so on `setSessionOnError`. - Pin that a fatal ANR of a session kept on error is reported with its last view however old, as an NDK crash already is.
…ge to hold An image over the held-replay budget was dropped without telling the recorder, which captures each image once per process, so no later replay that showed it would ever carry it.
…ts records are held A session that reported its error turns TRACKED at once, but its release waits for the jitter. A view completing in between removed its Session Replay context entry, so the released view and its events could no longer claim the replay written right after them. A completed view whose records are still held is now only marked completed, and Session Replay drops the entry once none of its records is held. This also stops the entries of released views from staying behind for good. Also say on `setSessionOnError` that app launch vitals are not part of a session kept on error.
…top without waiting when nothing is held - A crash release no longer claims the held replay, but still told Session Replay to write it. Written in the crash handler's last moments, it was stored with no event to open it from. A crash now releases the events only. - Stopping the SDK queued a settle step and waited up to two seconds in both RUM and Session Replay, for every customer. It now returns at once when no session is withheld or watched and nothing is held. - Session Replay's release telemetry reports `buffer.write_failures`, like RUM's. - Say on `setSessionReplayOnError` that WebView content is not recorded while the replay is held, and that a replay held at a crash is lost.
… ever engaged, and drop a held replay a crash did not claim - The stop skipped its settle step when no session was withheld, held or watched. That state is set by storage work which may still be queued when the stop is asked, and the core drops queued work once the features are stopped, so a session that had earned its release - or its replay - could be lost. The stop now decides from a flag set on the caller's thread before any such work is queued. A customer who never turned either switch on still pays nothing at the stop. - A crash release claims no held replay, but left it held. If the process survived the crash, as when a wrapper reports a fatal error, the replay was written at the stop with nothing to open it from. Session Replay is now told to drop it. - Say on `setSessionOnError` that an app killed while a release is being written can leave a kept session whose error never reached storage.
…eleased it A crash release told Session Replay to drop the session's replay, which also marks the session as thrown away there. When the process survives, as when a wrapper reports a fatal error and carries on, every later record of that still-collected session was dropped. The replay now waits instead: the session's next event lets it out and the views written from then on claim it. If the process dies, the held replay goes with it.
… claim it A crash told Session Replay to expect the replay's release before the crash release, which lets no replay out, ran. Should the process survive with no later event of that session, Session Replay's stop wrote the held replay with nothing to open it from. A crash no longer announces the release, and a session that ends after outliving its crash drops the replay. Also say that images of a replay released by the SDK's stop may not be written before it shuts down.
codex发现了5个问题[P1] 迟到崩溃会先写 error、后写唯一的 view,并且忽略 error 被 mapper 丢弃的结果 [P1] WebView 错误完全不能触发异常会话补采 [P2] 撤回 consent 会丢掉已经获得释放资格的会话 [P2] 超过 64KiB 的错误绕过了 0–3 秒抖动 [P2] SDK stop 只等待回放 records,没有等待对应图片资源落盘 opus也认同了这5个问题我逐条核对了同事列的 5 个问题,全部成立,引用的行号也和 上一轮我把第 2、3、4 条当作「作者在注释里写明的取舍」,把第 1、5 条漏掉了。按 PRD 来看,这 5 条都应该算缺陷。 1. [P1] 迟到崩溃先写 error,并且忽略 error 被丢弃——成立
2. [P1] WebView 错误不能触发补采——成立
修起来不轻:
3. [P2] 撤回 consent 会丢掉已经有资格放行的会话——成立,而且比描述的更糟除了缓冲被清空,我顺着状态往下走,确认了「回放再也收不到最终结果」这一点:
修法要注意一个限制: 4. [P2] 超过 64 KiB 的错误绕过了抖动——成立
建议的折中:把超大错误放进一个不占预算的单独槽位,照常走 5. [P2] stop 不等图片资源落盘——成立,而且通常是写不进去
修法:
修复优先级先修第 1 条和第 5 条,范围小、方案明确,第 4 条的折中方案也比较直接。第 2 条和第 3 条需要先拍板:WebView 要不要纳入首版,consent 撤回是改 core 还是接受偏差。 |
- Late crash: write the last view ahead of the crash for a session kept on error, and write nothing when the crash cannot be serialized. - WebView: hold the events of a withheld session with the native ones; a page error (not `source: agent`) releases the session, and releases a replay kept on error for a collected session. - Consent: a release scheduled under granted consent runs at once through a write scope taken then when consent is withdrawn, replay and images included; one scheduled while consent was pending is dropped. A release that runs out its jitter writes under the consent in force then. - Oversized errors: an error larger than the 64 KiB budget is written at once and not held; the history keeps its own jitter. - SDK stop: wait for the images of a released replay to be written. - In-process crash: the view update of the crash itself no longer lets the held replay out.
…urvives the mapper, view first For a session kept on error, the last view written locally is the only one the intake will ever get and the crash is the only reason it is kept. The late NDK crash and fatal ANR paths wrote the crash first and ignored its result, so a crash dropped by the error mapper still uploaded the view: a kept session with no error. The crash is now serialized first; the pair is dropped if the mapper drops it, and otherwise the view goes first, as in every release. Collected sessions keep their current behaviour.
…e replay of a released session on consent withdrawal - An error over the buffer budget released the whole history at once, skipping the jitter that spreads correlated releases. It is now kept apart, out of the budget and bounded to a few, and leaves with the history after the jitter, behind its views. A crash still leaves at once. - Withdrawing consent while a released session waited for its jitter dropped what it held but never told Session Replay, which kept holding that session's replay until the SDK stopped. The session goes on collected, so Session Replay is now told its replay is released.
The stop called back as soon as the released records were written, while their images were still queued through the resources feature; and the core unregisters a feature before stopping it, so the images were often skipped altogether. The resources writer now keeps the scope it found while registered, the stop calls back only once the queued images are in storage, and the resources feature lets Session Replay settle before it stops, whichever of the two the core stops first.
|
感谢逐条核对。5 条的处理如下,已推送到 1. 迟到崩溃先写 error、忽略 mapper 丢弃 —— 已修
|
结论:同事的 3 个提交里,1 和 5 解决了;2、3、4 没有按 PRD 解决。其中 2 是明确推迟;3 和 4 是另选了方案,而且这两条都和我们在
1. 迟到崩溃:解决。 判断条件 5. stop 不等图片:解决。 思路和我的不同,但成立:
2. WebView:未解决。 这是范围上的取舍,理由是 iOS 0.7.0 也是这么发布的。技术上能做,我的分支已经实现并在真机验证过。纳不纳入需要你或产品来定。如果维持现状,应该把 PRD 改掉,而不是只在 KDoc 里写一句限制。 3. 撤回 consent:不符合 PRD。 同事的理由是「core 在 NOT_GRANTED 下拒绝一切写入,回调里放行也写不进去」。这只对回调时才去拿的写入 scope 成立。
4. 超大错误:不符合 PRD。 同事采用的「单独存放、不占预算、随历史一起抖动」是我在第一轮 review 里提的折中建议,这一点我得说清楚。后来你决定对齐 PRD 原文:超大错误立即写入,历史照常等抖动。他的版本还有两个问题:
两个分支的关系
可以这样处理:
|
… the stop" This reverts commit d3414b5.
…ettle the replay of a released session on consent withdrawal" This reverts commit 8cfe933.
… if it survives the mapper, view first" This reverts commit e8497a9.
fix(rum): settle the review findings of session on error
…hort A web page in a tracked WebView sets the content of the events held for a withheld session, so nothing it sends may be able to take the app down: - Numbers read from WebView events (date, resource status code) are parsed through a double. Gson's asLong threw NumberFormatException on a literal such as 1e99999999999 on the storage thread, which killed the process. - Held view events get a byte bound of their own (256 KiB). They were only bounded in count, so 50 large views could exhaust the heap. A view over the bound alone is not held. Views left with nothing in the window are pruned before evicting by bytes. - A WebView view dated in the future no longer stays the current view over the native views after it. - Session Replay remembers the meta and focus records of at most 16 views. Settling a withheld session or a held replay at SDK stop now waits at most 500 ms each instead of 2 s: the stop is usually called on the main thread, and the core's own shutdown already waits up to 3 s. Documents that remote configuration also controls the on-error switches, and that with session on error enabled the event mappers run for every session.
…und the held images by count A stopped session that expires while it still drains now becomes EXPIRED again, as it does upstream, so the events it drains are no longer written. Only a session involved in on-error capture keeps its own state while it drains. Before this, a stopped session of an application using neither switch kept writing after its inactivity timeout. The images held for a withheld replay are bounded in count (512) as well as in bytes: every entry also retains its filename and write callback.
…-error capture was used The background callback that flushes a release waiting for its jitter submitted a write-scope task on every Activity stop, also for applications using neither switch. It now does so only once the withheld writer was engaged, like the SDK stop already does.
…is used The handling added for a stopped session that drains beside its successor (no new draw once it expires or times out, no announcement to Session Replay) also applied to applications using neither switch, which changed their behaviour: a stopped session reaching its maximum duration was no longer renewed, and Session Replay no longer heard from stopped sessions. Both now apply only once the withheld writer was engaged, where they keep a draw from taking the running session's buffer. The RUM thread also no longer reads the writer's replay state - and takes its lock, which the storage thread can hold across a file write - on every event of an application that never used on-error capture.
…read the release state without the lock The stopped-session handling (no new draw, no announcement to Session Replay) was gated on the withheld writer having been engaged. An application with session on error switched on whose sessions were all sampled in never engages it, so its stopped sessions went back to redrawing on the next interaction. It now applies wherever on-error capture is switched on, here or by the console, or was already used. The Activity lifecycle callback that flushes a waiting release is registered only where on-error capture can be used: either switch on, or remote configuration enabled. The RUM thread reads whether a session or its replay was released through volatile fields instead of the writer's lock, which the storage thread can hold across a file write.
fix(rum): bound what a withheld session holds and keep the SDK stop short
Summary
Adds error-session capture to RUM and Session Replay.
RumConfiguration.Builder.setSessionOnError(Boolean)Default
false. Remote key:rum.sessionOnError.WITHHELDstate. Their events are assembled and run through the event mappers, then held in memory byWithheldEventWriterinstead of being written.setForcedSession()releases immediately.getCurrentSessionIdreturns null until they are released.setSessionReplayOnError(Boolean)on the Session Replay configurationDefault
false. Remote key:rum.sessionReplayOnError.records_countandhas_replaycount only records that were actually written. On release,session.has_replayis set on released views and events whose view has replay records.Reporting
_dd.configuration.session_sample_rate: 0.session.sampled_for_errorandsession.sampled_for_error_replay.session.sampled_for_replayis reported only when an on-error mode applies to that session.Remote configuration
0with the switch on no longer resets a withheld session.0resets the session.beforeSamplingreturning0also turnssessionOnErroroff for that session.Behaviour change for all users
RumViewEventFilterstill keeps only the latest version of each view in a batch, but now places it at the view's first position instead of the position of its last write. A release followed by view updates in the same batch therefore keeps views ahead of errors and details. The set of uploaded events is unchanged.Known limitations
Testing
publishwith JDK 21; they are a pre-existingTypeface.DEFAULTreflection error.setForcedSession()→ released immediately;has_replayset;