Skip to content

analytics: troubleshooting fields captured at auction end in V2 prebid analytics - #377

Merged
mosherBT merged 3 commits into
masterfrom
mo/analytics-v2-troubleshooting-fields
Oct 28, 2026
Merged

mosherBT merged 3 commits into
masterfrom
mo/analytics-v2-troubleshooting-fields

Conversation

@mosherBT

Copy link
Copy Markdown
Contributor

Why

V1 sends slotSeq, t0, consent, cacheAge/idsMs and the per-bidder om mask; V2 sends none of them. V2 builds its payload bidWinTimeout after auction end, so the page-dependent values can't be read in toWitness.

What Changed

  • analytics: slotSeq, t0, consent and cacheAge/idsMs are captured in trackAuctionEnd and merged into the payload on both the timer and the visibilitychange beacon paths.
  • analytics: cacheAge reads this SDK's own targeting key (OPTABLE_TARGETING_*) and its :ts sibling, instead of V1's configurable cacheKey.
  • analytics: consent bit 16 comes from a tcf2Enforcement hook, live and on replay.
  • analytics: each bidder request carries om when it didn't receive every auction-level Optable source.

How to Test

  • Tested
  • Docs updated — if this adds, changes or removes anything a consumer calls, update the module's .md alongside the code (for example lib/addons/<addon>.md) and the matching README section. New addons ship with a new .md.

Notes

  • Nothing downstream reads these fields yet: the DCN fact job and bid_analytics ignore them.

  • Breaking change

  • Requires release

@mosherBT mosherBT self-assigned this Oct 27, 2026
@mosherBT
mosherBT marked this pull request as ready for review October 27, 2026 17:00
@mosherBT
mosherBT requested review from a team as code owners October 27, 2026 17:00
Comment thread lib/addons/prebid/analytics.ts Outdated
*/
private cacheState(): Record<string, number> {
const { dcn } = this.optableInstance;
if (!dcn) return {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cacheState() reads window.localStorage directly. The SDK's own storage goes through LocalStorageProxy (lib/core/regs/storage.ts), which returns null when consent.deviceAccess is false. So analytics reads device storage that the SDK treats as off-limits, for example a targeting cache left from an earlier visit with consent. Suggest checking consent the same way:

if (!dcn || !dcn.consent?.deviceAccess) return {};

}
const origin = typeof performance !== "undefined" ? performance.timeOrigin : undefined;
if (typeof timestamp === "number" && origin) capture.t0 = Math.round(timestamp - origin);
capture.consent = this.consentFlags(bidderRequests[0]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: consent comes from bidderRequests[0] only. An auction with no bidder requests reports 0, which looks the same as "nothing applies". That's fine for troubleshooting, but worth a line in analytics.md.

const { timestamp, bidderRequests = [] } = event;
const capture: Record<string, number> = {};

const slotKey =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: slotSeq keys on the first ad unit of the auction only. A multi-unit auction, like the usual page-load auction, increments only that unit's count. This matches V1, but the .md wording "Earlier auctions of the same ad unit" overstates it. Maybe "earlier auctions whose first ad unit is this one", or count every ad unit in the auction.

// Per-bidder delivery as a bitmask over optableSources (bit i = optableSources[i]).
// Omitted when the bidder received every source, so the usual case adds nothing.
const auctionSources = witnessData.optableSources as string[];
const fullMask = auctionSources.length && auctionSources.length <= 30 ? 2 ** auctionSources.length - 1 : 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: om is silently skipped when there are more than 30 sources. That's unreachable in practice, but a short comment saying why the cap exists (e.g. keeping the mask in 32-bit int range for downstream bitwise ops) would help the next reader.

@jplaroche jplaroche left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mostly nitpicking stuff, can release without it

@mosherBT
mosherBT force-pushed the mo/analytics-v2-troubleshooting-fields branch from 7f24d83 to 50a7ae4 Compare October 28, 2026 15:36
@mosherBT
mosherBT merged commit 892c7ad into master Oct 28, 2026
7 checks passed
@mosherBT
mosherBT deleted the mo/analytics-v2-troubleshooting-fields branch October 28, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants